Prerequisites
This guide covers the environment setup required before working with Zenith.NET.
System Requirements
Hardware
Zenith.NET supports multiple graphics backends across platforms:
| Platform | DirectX 12 | Metal 4 | Vulkan 1.4 |
|---|---|---|---|
| Windows | Yes | No | Yes |
| Linux | No | No | Yes |
| Android | No | No | Yes |
| macOS | No | Yes | Yes |
| iOS | No | Yes | Yes |
Note
These tutorials are designed for desktop platforms (Windows, Linux, and macOS).
Software
- .NET SDK: 10.0 or later
- IDE: Visual Studio 2026, VS Code, or JetBrains Rider
Building the Tutorials
The example code in these tutorials is designed to be extensible. We'll create a base project structure that all tutorials will share.
Creating the Project
dotnet new console -n ZenithTutorials
cd ZenithTutorials
Required Packages
Install the following NuGet packages:
dotnet add package Zenith.NET.DirectX12
dotnet add package Zenith.NET.Metal
dotnet add package Zenith.NET.Vulkan
dotnet add package Zenith.NET.Extensions.Slang
dotnet add package Silk.NET.Windowing
dotnet add package Silk.NET.Input
Project Configuration
Update your .csproj file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Zenith.NET.DirectX12" Version="*" />
<PackageReference Include="Zenith.NET.Metal" Version="*" />
<PackageReference Include="Zenith.NET.Vulkan" Version="*" />
<PackageReference Include="Zenith.NET.Extensions.Slang" Version="*" />
<PackageReference Include="Silk.NET.Windowing" Version="*" />
<PackageReference Include="Silk.NET.Input" Version="*" />
</ItemGroup>
</Project>
Note
AllowUnsafeBlocks is required because the tutorials use sizeof with custom structs for GPU buffer sizing.
Project Structure
Organize your project with the following directory structure:
ZenithTutorials/
├── Program.cs # Application entry point
├── App.cs # Application framework
├── IRenderer.cs # Renderer interface
├── BindingHelper.cs # Cross-platform resource binding helper
├── CocoaHelper.cs # macOS CAMetalLayer helper
├── Usings.cs # Global using statements
└── Renderers/ # All tutorial renderers
Global Usings
Create Usings.cs for shared using statements across all files:
global using System.Numerics;
global using System.Runtime.InteropServices;
global using Zenith.NET;
global using Zenith.NET.Extensions.Slang;
global using Buffer = Zenith.NET.Buffer;
This eliminates repetitive using statements in each renderer file.
Renderer Interface
All tutorial renderers implement a common interface. Create IRenderer.cs:
namespace ZenithTutorials;
internal interface IRenderer : IDisposable
{
void Update(double deltaTime);
void Render();
void Resize(uint width, uint height);
}
This interface ensures all renderers follow a consistent pattern:
Update- Called each frame for logic updates (animations, input handling)Render- Called each frame to record and submit draw commandsResize- Called when the window size changesDispose- Cleanup GPU resources
Binding Helper
Different graphics backends use different indexing schemes for resource bindings:
| Backend | Index Scheme |
|---|---|
| DirectX 12 | Per-type: CBV, SRV, UAV, Sampler each start at 0 |
| Vulkan | Global: All resources share index space (0, 1, 2, ...) |
| Metal | Per-category: Buffer, Texture, Sampler each start at 0 |
Create BindingHelper.cs to handle these differences automatically:
namespace ZenithTutorials;
internal static class BindingHelper
{
public static ResourceBinding[] Bindings(params ResourceBinding[] bindings)
{
switch (App.Context.Backend)
{
case Backend.DirectX12:
{
uint cbvIndex = 0;
uint srvIndex = 0;
uint uavIndex = 0;
uint samplerIndex = 0;
for (int i = 0; i < bindings.Length; i++)
{
ref ResourceBinding binding = ref bindings[i];
binding = binding with
{
Index = binding.Type switch
{
ResourceType.ConstantBuffer => cbvIndex++,
ResourceType.StructuredBuffer or
ResourceType.Texture or
ResourceType.AccelerationStructure => srvIndex++,
ResourceType.StructuredBufferReadWrite or
ResourceType.TextureReadWrite => uavIndex++,
ResourceType.Sampler => samplerIndex++,
_ => binding.Index
}
};
}
}
break;
case Backend.Vulkan:
{
for (int i = 0; i < bindings.Length; i++)
{
ref ResourceBinding binding = ref bindings[i];
binding = binding with { Index = (uint)i };
}
}
break;
case Backend.Metal:
{
uint bufferIndex = 0;
uint textureIndex = 0;
uint samplerIndex = 0;
for (int i = 0; i < bindings.Length; i++)
{
ref ResourceBinding binding = ref bindings[i];
binding = binding with
{
Index = binding.Type switch
{
ResourceType.ConstantBuffer or
ResourceType.StructuredBuffer or
ResourceType.StructuredBufferReadWrite => bufferIndex++,
ResourceType.Texture or
ResourceType.TextureReadWrite => textureIndex++,
ResourceType.Sampler => samplerIndex++,
_ => binding.Index
}
};
}
}
break;
}
return bindings;
}
}
Usage example:
resourceLayout = App.Context.CreateResourceLayout(new()
{
Bindings = BindingHelper.Bindings
(
new() { Type = ResourceType.Texture, Count = 1, StageFlags = ShaderStageFlags.Pixel },
new() { Type = ResourceType.Sampler, Count = 1, StageFlags = ShaderStageFlags.Pixel }
)
});
The helper automatically assigns the correct Index values based on the current backend, so you don't need to specify them manually.
Cocoa Helper
On macOS, creating a rendering surface requires a CAMetalLayer. Silk.NET.Windowing doesn't expose this directly, so we need a helper to create it using Objective-C runtime interop.
Create CocoaHelper.cs:
namespace ZenithTutorials;
internal static partial class CocoaHelper
{
private const string LibObjC = "/usr/lib/libobjc.A.dylib";
[LibraryImport(LibObjC, EntryPoint = "objc_getClass")]
private static partial nint GetClass([MarshalAs(UnmanagedType.LPUTF8Str)] string name);
[LibraryImport(LibObjC, EntryPoint = "sel_registerName")]
private static partial nint Selector([MarshalAs(UnmanagedType.LPUTF8Str)] string name);
[LibraryImport(LibObjC, EntryPoint = "objc_msgSend")]
private static partial nint Send(nint receiver, nint selector);
[LibraryImport(LibObjC, EntryPoint = "objc_msgSend")]
private static partial nint Send(nint receiver, nint selector, [MarshalAs(UnmanagedType.I1)] bool arg);
[LibraryImport(LibObjC, EntryPoint = "objc_msgSend")]
private static partial nint Send(nint receiver, nint selector, nint arg);
public static nint CreateLayer(nint cocoa)
{
nint layer = Send(GetClass("CAMetalLayer"), Selector("layer"));
Send(layer, Selector("retain"));
nint view = Send(cocoa, Selector("contentView"));
Send(view, Selector("setWantsLayer:"), true);
Send(view, Selector("setLayer:"), layer);
return layer;
}
}
The CAMetalLayer can be used with both Metal and Vulkan backends on macOS.
Application Framework
All tutorials share a common application framework that handles window creation, graphics context initialization, and the main loop.
App.cs
Create App.cs as the reusable application framework:
using Silk.NET.Windowing;
using Zenith.NET.DirectX12;
using Zenith.NET.Metal;
using Zenith.NET.Vulkan;
namespace ZenithTutorials;
internal static class App
{
private static readonly IWindow window;
static App()
{
// Ensure platform is supported
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux() && !OperatingSystem.IsMacOS())
{
throw new PlatformNotSupportedException("This tutorial only supports Windows, Linux, and macOS.");
}
// Create window with no graphics API (we manage rendering ourselves)
window = Window.Create(WindowOptions.Default with
{
API = GraphicsAPI.None,
Title = "Zenith.NET Tutorial",
Size = new(1280, 720)
});
window.Initialize();
// Create graphics context and surface based on platform
Surface surface;
if (OperatingSystem.IsWindows())
{
Context = GraphicsContext.CreateDirectX12(useValidationLayer: true);
surface = Surface.Win32(window.Native!.Win32!.Value.Hwnd, Width, Height);
}
else if (OperatingSystem.IsLinux())
{
Context = GraphicsContext.CreateVulkan(useValidationLayer: true);
surface = Surface.Xlib(window.Native!.X11!.Value.Display, (nint)window.Native.X11.Value.Window, Width, Height);
}
else
{
Context = GraphicsContext.CreateMetal(useValidationLayer: true);
surface = Surface.Apple(CocoaHelper.CreateLayer(window.Native!.Cocoa!.Value), Width, Height);
}
// Log validation messages for debugging
Context.ValidationMessage += (sender, args) =>
{
Console.WriteLine($"[{args.Source} - {args.Severity}] {args.Message}");
};
// Create swap chain for double-buffered rendering
SwapChain = Context.CreateSwapChain(new()
{
Surface = surface,
ColorTargetFormat = PixelFormat.B8G8R8A8UNorm,
DepthStencilTargetFormat = PixelFormat.D32FloatS8UInt
});
}
public static GraphicsContext Context { get; }
public static SwapChain SwapChain { get; }
public static uint Width => (uint)window.Size.X;
public static uint Height => (uint)window.Size.Y;
public static void Run<TRenderer>() where TRenderer : IRenderer, new()
{
using TRenderer renderer = new();
window.Update += renderer.Update;
window.Render += delta =>
{
// Skip rendering when window is minimized
if (Width <= 0 || Height <= 0)
{
return;
}
renderer.Render();
SwapChain.Present();
};
window.Resize += size =>
{
if (Width <= 0 || Height <= 0)
{
return;
}
// Notify renderer first, then resize swap chain
renderer.Resize(Width, Height);
SwapChain.Resize(Width, Height);
};
window.Run();
}
public static void Cleanup()
{
SwapChain.Dispose();
Context.Dispose();
window.Dispose();
}
}
Program.cs
Create Program.cs as the simple entry point:
using ZenithTutorials;
using ZenithTutorials.Renderers;
App.Run<HelloTriangleRenderer>();
App.Cleanup();
Note
HelloTriangleRenderer will be created in the next tutorial.
This framework provides:
- Platform validation - Ensures only supported platforms (Windows, Linux, macOS) are used
- Window creation with Silk.NET (1280×720 default size)
- Cross-platform backend selection (DirectX 12 on Windows, Vulkan on Linux, Metal on macOS)
- SwapChain management for presenting frames
- Resize handling for responsive rendering
- Generic renderer pattern using
App.Run<TRenderer>()for easy tutorial switching - Static access to
App.ContextandApp.SwapChainfrom renderers
Verify Installation
Before continuing, verify your setup compiles correctly:
dotnet build
If the build succeeds, you're ready to start Hello Triangle!
Source Code
Tip
The complete source code for all tutorials is available on GitHub: ZenithTutorials