Skip to content

Instantly share code, notes, and snippets.

@mlarouche
Last active September 14, 2020 21:28
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save mlarouche/e113b9170403b1e07de9d98168fa0bb4 to your computer and use it in GitHub Desktop.
Direct3D12 Sample in Zig
const std = @import("std");
const win32 = @import("win32.zig");
const d3d12 = @import("d3d12.zig");
const dxgi = @import("dxgi1_4.zig");
const d3dcompiler = @import("d3dcompiler.zig");
const windows = std.os.windows;
const L = std.unicode.utf8ToUtf16LeStringLiteral;
const WindowX = 100;
const WindowY = 100;
const WindowWidth = 1280;
const WindowHeight = 720;
const Fullscreen = false;
//BEGIN D3D12 variables
const FrameBufferCount = 3;
var device: ?*d3d12.ID3D12Device = null;
var swap_chain: ?*dxgi.IDXGISwapChain3 = null;
var command_queue: ?*d3d12.ID3D12CommandQueue = null;
var rtv_descriptor_heap: ?*d3d12.ID3D12DescriptorHeap = null;
var render_targets: [FrameBufferCount]?*d3d12.ID3D12Resource = undefined;
var command_allocator: [FrameBufferCount]?*d3d12.ID3D12CommandAllocator = undefined;
var command_list: ?*d3d12.ID3D12GraphicsCommandList = null;
var fence: [FrameBufferCount]?*d3d12.ID3D12Fence = undefined;
var fence_event: win32.HANDLE = null;
var fence_value: [FrameBufferCount]u64 = undefined;
var frame_index: usize = 0;
var rtv_descriptor_size: usize = 0;
var pipeline_state_object: ?*d3d12.ID3D12PipelineState = null;
var root_signature: ?*d3d12.ID3D12RootSignature = null;
var viewport: d3d12.D3D12_VIEWPORT = undefined;
var scissor_rect: d3d12.D3D12_RECT = undefined;
var vertex_buffer: ?*d3d12.ID3D12Resource = null;
var vertex_buffer_view: d3d12.D3D12_VERTEX_BUFFER_VIEW = undefined;
var index_buffer: ?*d3d12.ID3D12Resource = null;
var index_buffer_view: d3d12.D3D12_INDEX_BUFFER_VIEW = undefined;
var depth_stencil_buffer: ?*d3d12.ID3D12Resource = null;
var depth_descriptor_heap: ?*d3d12.ID3D12DescriptorHeap = null;
var main_descriptor_heap: [FrameBufferCount]?*d3d12.ID3D12DescriptorHeap = undefined;
var constant_buffer_upload_heap: [FrameBufferCount]?*d3d12.ID3D12Resource = undefined;
var color_multiplier_data: ConstantBuffer = undefined;
var color_multiplier_gpu_address: [FrameBufferCount]*u8 = undefined;
//END
var hwnd: win32.HWND = undefined;
// TODO: use Color from zigimg
pub const Color = struct {
R: f32,
G: f32,
B: f32,
A: f32,
const Self = @This();
pub fn initRGB(r: f32, g: f32, b: f32) Self {
return Self{
.R = r,
.G = g,
.B = b,
.A = 1.0,
};
}
pub fn initRGBA(r: f32, g: f32, b: f32, a: f32) Self {
return Self{
.R = r,
.G = g,
.B = b,
.A = a,
};
}
pub fn premultipliedAlpha(self: Self) Self {
return Self{
.R = self.R * self.A,
.G = self.G * self.A,
.B = self.B * self.A,
.A = self.A,
};
}
};
const Float3 = struct {
x: f32 = 0.0,
y: f32 = 0.0,
z: f32 = 0.0,
};
const Vertex = struct {
position: Float3,
color: Color,
};
const ConstantBuffer = struct {
color_multiplier: Color
};
const input_layout = [_]d3d12.D3D12_INPUT_ELEMENT_DESC{
.{
.SemanticName = "POSITION",
.SemanticIndex = 0,
.Format = ._R32G32B32_FLOAT,
.InputSlot = 0,
.AlignedByteOffset = 0,
.InputSlotClass = ._PER_VERTEX_DATA,
.InstanceDataStepRate = 0,
},
.{
.SemanticName = "COLOR",
.SemanticIndex = 0,
.Format = ._R32G32B32A32_FLOAT,
.InputSlot = 0,
.AlignedByteOffset = @byteOffsetOf(Vertex, "color"),
.InputSlotClass = ._PER_VERTEX_DATA,
.InstanceDataStepRate = 0,
},
};
const quad_vertices = [_]Vertex{
// First Quad (closer to camera, blue)
// top left
.{
.position = .{ .x = -0.5, .y = 0.5, .z = 0.5 },
.color = Color.initRGB(0.0, 0.0, 1.0),
},
// bottom right
.{
.position = .{ .x = 0.5, .y = -0.5, .z = 0.5 },
.color = Color.initRGB(0.0, 0.0, 1.0),
},
// bottom left
.{
.position = .{ .x = -0.5, .y = -0.5, .z = 0.5 },
.color = Color.initRGB(0.0, 0.0, 1.0),
},
// top right
.{
.position = .{ .x = 0.5, .y = 0.5, .z = 0.5 },
.color = Color.initRGB(0.0, 0.0, 1.0),
},
// // Second Quad (closer to camera, blue)
// // top left
// .{
// .position = .{ .x = -0.75, .y = 0.75, .z = 0.7 },
// .color = Color.initRGB(0.0, 1.0, 0.0),
// },
// // bottom right
// .{
// .position = .{ .x = 0.0, .y = 0.0, .z = 0.7 },
// .color = Color.initRGB(0.0, 1.0, 0.0),
// },
// // bottom left
// .{
// .position = .{ .x = -0.75, .y = 0.0, .z = 0.7 },
// .color = Color.initRGB(0.0, 1.0, 0.0),
// },
// // top right
// .{
// .position = .{ .x = 0.0, .y = 0.75, .z = 0.7 },
// .color = Color.initRGB(0.0, 1.0, 0.0),
// },
};
const quad_indices = [_]u32{
0, 1, 2, // First triangle
0, 3, 1, // Second triangle
};
fn bufferSize(buffer: anytype) usize {
return std.mem.len(buffer) * @sizeOf(std.meta.Child(@TypeOf(buffer)));
}
fn comFindReturnType(comptime vtable_type: type, comptime name: []const u8) type {
@setEvalBranchQuota(4000);
for (@typeInfo(vtable_type).Struct.fields) |field| {
if (std.mem.eql(u8, field.name, name)) {
const func_type = @typeInfo(@typeInfo(field.field_type).Optional.child);
return func_type.Fn.return_type.?;
}
}
return void;
}
fn comFindVtableType(comptime parent_type: type) type {
switch (@typeInfo(parent_type)) {
.Struct => |struct_info| {
for (struct_info.fields) |field| {
if (std.mem.eql(u8, field.name, "lpVtbl")) {
return @typeInfo(field.field_type).Pointer.child;
}
}
},
.Pointer => |pointer_info| {
for (@typeInfo(pointer_info.child).Struct.fields) |field| {
if (std.mem.eql(u8, field.name, "lpVtbl")) {
return @typeInfo(field.field_type).Pointer.child;
}
}
},
.Optional => |optional| {
const pointer_type = std.meta.Child(optional.child);
for (@typeInfo(pointer_type).Struct.fields) |field| {
if (std.mem.eql(u8, field.name, "lpVtbl")) {
return @typeInfo(field.field_type).Pointer.child;
}
}
},
else => {
@compileError("Type not handled in comFindVTableType");
},
}
return void;
}
fn comCall(self: anytype, comptime name: []const u8, args: anytype) comFindReturnType(comFindVtableType(@TypeOf(self)), name) {
if (@typeInfo(@TypeOf(self)) == .Optional) {
if (self) |self_ptr| {
if (@field(self_ptr.lpVtbl[0], name)) |func| {
return @call(.{}, func, .{self_ptr} ++ args);
}
}
} else {
if (@field(self.lpVtbl[0], name)) |func| {
return @call(.{}, func, .{self} ++ args);
}
}
return undefined;
}
inline fn comCast(comptime target: type, instance: anytype) [*c]target {
return @ptrCast([*c]target, instance);
}
inline fn comRelease(instance: anytype) void {
if (@typeInfo(@TypeOf(instance)) == .Optional) {
if (instance) |instance_ptr| {
_ = comCall(instance_ptr, "Release", .{});
}
} else {
_ = comCall(instance, "Release", .{});
}
}
fn getComInteface(self: anytype, interface: anytype, result: anytype) !void {
const hr = comCall(self, "QueryInterface", .{ interface, @ptrCast([*c]?*c_void, result) });
if (hr < 0) {
return error.QueryInterfaceFailed;
}
}
inline fn uuidof(comptime parent_type: type, comptime interface: type) [*c]const @field(parent_type, "IID") {
const interface_full_name = @typeName(interface);
const index = comptime std.mem.lastIndexOf(u8, interface_full_name, "_").? + 1;
const iid_name = "IID_" ++ interface_full_name[index..];
return &@field(parent_type, iid_name);
}
inline fn createDXFactoryObject(instance: anytype) win32.HRESULT {
const instance_type = std.meta.Child(std.meta.Child(std.meta.Child(@TypeOf(instance))));
return dxgi.CreateDXGIFactory2(0, uuidof(dxgi, instance_type), @ptrCast([*c]?*c_void, instance));
}
inline fn memcpySubresource(
dest: *const d3d12.D3D12_MEMCPY_DEST,
source: *const d3d12.D3D12_SUBRESOURCE_DATA,
row_size_in_bytes: usize,
num_rows: u32,
num_slices: u32,
) void {
var slice: usize = 0;
while (slice < num_slices) : (slice += 1) {
const dest_slice = @intToPtr([*]u8, @ptrToInt(dest.pData) + @intCast(usize, dest.SlicePitch) * slice);
const source_slice = @intToPtr([*]const u8, @ptrToInt(source.pData) + @intCast(usize, source.SlicePitch) * slice);
var row: usize = 0;
while (row < num_rows) : (row += 1) {
const dest_row = @intToPtr([*]u8, @ptrToInt(dest_slice) + @intCast(usize, dest.RowPitch) * row);
const source_row = @intToPtr([*]const u8, @ptrToInt(source_slice) + @intCast(usize, source.RowPitch) * row);
std.mem.copy(u8, dest_row[0..row_size_in_bytes], source_row[0..row_size_in_bytes]);
}
}
}
fn updateSubresourcesAlloc(
allocator: *std.mem.Allocator,
commands: ?*d3d12.ID3D12GraphicsCommandList,
destination: ?*d3d12.ID3D12Resource,
intermediate: ?*d3d12.ID3D12Resource,
intermediate_offset: usize,
first_subresource: usize,
num_subresources: usize,
src_data: [*]d3d12.D3D12_SUBRESOURCE_DATA,
) !u64 {
const memory_to_alloc = (@sizeOf(d3d12.D3D12_PLACED_SUBRESOURCE_FOOTPRINT) + @sizeOf(u32) + @sizeOf(u64)) * num_subresources;
const memory = try allocator.alloc(u8, memory_to_alloc);
defer allocator.free(memory);
const layouts = @intToPtr([*]d3d12.D3D12_PLACED_SUBRESOURCE_FOOTPRINT, @ptrToInt(&memory[0]));
const row_sizes_in_bytes = @intToPtr([*]u64, @ptrToInt(layouts) + num_subresources * @sizeOf(@TypeOf(layouts[0])));
const num_rows = @intToPtr([*]u32, @ptrToInt(row_sizes_in_bytes) + num_subresources * @sizeOf(*u64));
var desc: d3d12.D3D12_RESOURCE_DESC = undefined;
_ = comCall(destination, "GetDesc", .{&desc});
var dest_device: ?*d3d12.ID3D12Device = null;
_ = comCall(destination, "GetDevice", .{ uuidof(d3d12, d3d12.ID3D12Device), @ptrCast([*c]?*c_void, &dest_device) });
defer comRelease(dest_device);
var required_size: u64 = 0;
comCall(dest_device, "GetCopyableFootprints", .{ &desc, @intCast(c_uint, first_subresource), @intCast(c_uint, num_subresources), @intCast(c_uint, intermediate_offset), layouts, num_rows, row_sizes_in_bytes, &required_size });
const result: u64 = try updateSubresources(commands, destination, intermediate, intermediate_offset, first_subresource, num_subresources, required_size, layouts, num_rows, row_sizes_in_bytes, src_data);
return result;
}
fn updateSubresources(
commands: ?*d3d12.ID3D12GraphicsCommandList,
destination: ?*d3d12.ID3D12Resource,
intermediate: ?*d3d12.ID3D12Resource,
intermediate_offset: usize,
first_subresource: usize,
num_subresources: usize,
required_size: u64,
layouts: [*]d3d12.D3D12_PLACED_SUBRESOURCE_FOOTPRINT,
num_rows: [*]u32,
row_sizes_in_bytes: [*]u64,
src_data: [*]d3d12.D3D12_SUBRESOURCE_DATA,
) !u64 {
var intermediate_desc: d3d12.D3D12_RESOURCE_DESC = undefined;
comCall(intermediate, "GetDesc", .{&intermediate_desc});
var destination_desc: d3d12.D3D12_RESOURCE_DESC = undefined;
comCall(destination, "GetDesc", .{&destination_desc});
if (intermediate_desc.Dimension != ._BUFFER or intermediate_desc.Width < (required_size + layouts[0].Offset) or required_size > (std.math.maxInt(usize) - 1) or (destination_desc.Dimension == ._BUFFER and (first_subresource != 0 or num_subresources != 1))) {
return error.InvalidIntermediateAndDestination;
}
var data: ?*u8 = null;
var hr = comCall(intermediate, "Map", .{ 0, null, @ptrCast([*c]?*c_void, &data) });
if (hr < 0) {
return error.MapFailed;
}
defer comCall(intermediate, "Unmap", .{ 0, null });
var index: usize = 0;
while (index < num_subresources) : (index += 1) {
if (row_sizes_in_bytes[index] > (std.math.maxInt(u64) - 1)) {
return error.RowSizeTooLarge;
}
var dest_data: d3d12.D3D12_MEMCPY_DEST = .{
.pData = @intToPtr(*c_void, @ptrToInt(data.?) + layouts[index].Offset),
.RowPitch = layouts[index].Footprint.RowPitch,
.SlicePitch = layouts[index].Footprint.RowPitch * num_rows[index],
};
memcpySubresource(&dest_data, &src_data[index], row_sizes_in_bytes[index], num_rows[index], layouts[index].Footprint.Depth);
}
if (destination_desc.Dimension == ._BUFFER) {
comCall(commands, "CopyBufferRegion", .{ destination, 0, intermediate, layouts[0].Offset, layouts[0].Footprint.Width });
} else {
index = 0;
while (index < num_subresources) : (index += 1) {
var dest_texture: d3d12.D3D12_TEXTURE_COPY_LOCATION = .{
.pResource = destination,
.Type = ._SUBRESOURCE_INDEX,
.unnamed_0 = .{
.SubresourceIndex = @intCast(c_uint, first_subresource + index),
},
};
var source_texture: d3d12.D3D12_TEXTURE_COPY_LOCATION = .{
.pResource = intermediate,
.Type = ._PLACED_FOOTPRINT,
.unnamed_0 = .{
.PlacedFootprint = layouts[index],
},
};
comCall(commands, "CopyTextureRegion", .{ &dest_texture, 0, 0, 0, &source_texture, null });
}
}
return required_size;
}
fn initDirect3D() !void {
var hr: win32.HRESULT = undefined;
if (std.builtin.mode == .Debug) {
var debug_control: ?*d3d12.ID3D12Debug1 = null;
hr = d3d12.D3D12GetDebugInterface(uuidof(d3d12, d3d12.ID3D12Debug1), @ptrCast([*c]?*c_void, &debug_control));
if (hr >= 0) {
comCall(debug_control, "EnableDebugLayer", .{});
_ = comCall(debug_control, "Release", .{});
}
}
// Create DXGI factory
var dxgi_factory: ?*dxgi.IDXGIFactory4 = null;
hr = createDXFactoryObject(&dxgi_factory);
if (hr < 0) {
return error.DXGIFactoryCreationFailed;
}
// Find proper adapter for D3D12 device
var adapter: ?*dxgi.IDXGIAdapter1 = null;
var adapter_index: u32 = 0;
var adapter_found = false;
while (comCall(dxgi_factory, "EnumAdapters1", .{ adapter_index, &adapter }) != 0x887A0002) {
var desc: dxgi.DXGI_ADAPTER_DESC1 = undefined;
_ = comCall(adapter, "GetDesc1", .{&desc});
if ((desc.Flags & @as(u32, dxgi.DXGI_ADAPTER_FLAG_SOFTWARE)) == dxgi.DXGI_ADAPTER_FLAG_SOFTWARE) {
adapter_index += 1;
continue;
}
hr = d3d12.D3D12CreateDevice(comCast(d3d12.IUnknown, adapter.?), ._11_0, uuidof(d3d12, d3d12.ID3D12Device), null);
if (hr >= 0) {
adapter_found = true;
break;
}
adapter_index += 1;
}
// Create the actual device
hr = d3d12.D3D12CreateDevice(comCast(d3d12.IUnknown, adapter.?), ._11_0, uuidof(d3d12, d3d12.ID3D12Device), @ptrCast([*c]?*c_void, &device));
if (hr < 0) {
return error.DeviceCreationFailed;
}
// Create a direct command queue
var command_queue_desc = std.mem.zeroes(d3d12.D3D12_COMMAND_QUEUE_DESC);
command_queue_desc.Flags = .D3D12_COMMAND_QUEUE_FLAG_NONE;
command_queue_desc.Type = ._DIRECT;
hr = comCall(device, "CreateCommandQueue", .{ &command_queue_desc, uuidof(d3d12, d3d12.ID3D12CommandQueue), @ptrCast([*c]?*c_void, &command_queue) });
if (hr < 0) {
return error.CommandQueueCreationFailed;
}
// Create the swap chain
var swap_chain_desc: dxgi.DXGI_SWAP_CHAIN_DESC1 = std.mem.zeroes(dxgi.DXGI_SWAP_CHAIN_DESC1);
swap_chain_desc.Format = ._R8G8B8A8_UNORM;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.BufferUsage = dxgi.DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.BufferCount = FrameBufferCount;
swap_chain_desc.Scaling = ._NONE;
swap_chain_desc.SwapEffect = ._FLIP_SEQUENTIAL;
var fullscreen_desc: dxgi.DXGI_SWAP_CHAIN_FULLSCREEN_DESC = std.mem.zeroes(dxgi.DXGI_SWAP_CHAIN_FULLSCREEN_DESC);
fullscreen_desc.RefreshRate.Numerator = 60;
fullscreen_desc.RefreshRate.Denominator = 1;
fullscreen_desc.Windowed = @boolToInt(!Fullscreen);
var temp_swap_chain: ?*dxgi.IDXGISwapChain1 = null;
hr = comCall(dxgi_factory, "CreateSwapChainForHwnd", .{ comCast(dxgi.IUnknown, command_queue.?), @ptrCast(dxgi.HWND, hwnd), &swap_chain_desc, &fullscreen_desc, null, &temp_swap_chain });
if (hr < 0) {
return error.CreateTempSwapChainFailed;
}
defer comRelease(temp_swap_chain);
try getComInteface(temp_swap_chain, uuidof(dxgi, dxgi.IDXGISwapChain3), &swap_chain);
// Create the render target descriptor heap
var rtv_descriptor_desc: d3d12.D3D12_DESCRIPTOR_HEAP_DESC = std.mem.zeroes(d3d12.D3D12_DESCRIPTOR_HEAP_DESC);
rtv_descriptor_desc.Type = ._RTV;
rtv_descriptor_desc.NumDescriptors = FrameBufferCount;
hr = comCall(device, "CreateDescriptorHeap", .{ &rtv_descriptor_desc, uuidof(d3d12, d3d12.ID3D12DescriptorHeap), @ptrCast([*c]?*c_void, &rtv_descriptor_heap) });
if (hr < 0) {
return error.RTVDescriptorHeapCreationFailed;
}
// Get the size of a descriptor in this heap
rtv_descriptor_size = @as(usize, comCall(device, "GetDescriptorHandleIncrementSize", .{d3d12.D3D12_DESCRIPTOR_HEAP_TYPE._RTV}));
// Get handle to the first descritor in the heap.
var rtv_handle: d3d12.D3D12_CPU_DESCRIPTOR_HANDLE = undefined;
comCall(rtv_descriptor_heap, "GetCPUDescriptorHandleForHeapStart", .{&rtv_handle});
// Create a render target view for each buffer
var index: usize = 0;
while (index < FrameBufferCount) : (index += 1) {
hr = comCall(swap_chain, "GetBuffer", .{ @truncate(c_uint, index), @ptrCast([*c]const dxgi.IID, uuidof(d3d12, d3d12.ID3D12Resource)), @ptrCast([*c]?*c_void, &render_targets[index]) });
if (hr < 0) {
return error.CannotGetSwapChainBuffer;
}
comCall(device, "CreateRenderTargetView", .{ render_targets[index], null, rtv_handle });
rtv_handle.ptr += rtv_descriptor_size;
}
// Create the command allocators
index = 0;
while (index < FrameBufferCount) : (index += 1) {
hr = comCall(device, "CreateCommandAllocator", .{ ._DIRECT, uuidof(d3d12, d3d12.ID3D12CommandAllocator), @ptrCast([*c]?*c_void, &command_allocator[index]) });
if (hr < 0) {
return error.CommandAllocatorCreationFailed;
}
}
// Create the command list with the first allocator
hr = comCall(device, "CreateCommandList", .{ 0, ._DIRECT, command_allocator[0], null, uuidof(d3d12, d3d12.ID3D12GraphicsCommandList), @ptrCast([*]?*c_void, &command_list) });
if (hr < 0) {
return error.CommandListCreationFailed;
}
// Create a fence and fence event
index = 0;
while (index < FrameBufferCount) : (index += 1) {
hr = comCall(device, "CreateFence", .{ 0, .D3D12_FENCE_FLAG_NONE, uuidof(d3d12, d3d12.ID3D12Fence), @ptrCast([*c]?*c_void, &fence[index]) });
if (hr < 0) {
return error.CreateFenceFailed;
}
fence_value[index] = 0;
}
// Create a handle to a fence event
fence_event = d3d12.CreateEvent(null, @boolToInt(false), @boolToInt(false), null);
if (fence_event == null) {
return error.FenceEventCreationFailed;
}
// Create root signature
var descriptor_table_ranges: [1]d3d12.D3D12_DESCRIPTOR_RANGE = undefined;
descriptor_table_ranges[0].RangeType = ._CBV;
descriptor_table_ranges[0].NumDescriptors = 1;
descriptor_table_ranges[0].BaseShaderRegister = 0;
descriptor_table_ranges[0].RegisterSpace = 0;
descriptor_table_ranges[0].OffsetInDescriptorsFromTableStart = d3d12.D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
var descriptor_table: d3d12.D3D12_ROOT_DESCRIPTOR_TABLE = std.mem.zeroes(d3d12.D3D12_ROOT_DESCRIPTOR_TABLE);
descriptor_table.NumDescriptorRanges = descriptor_table_ranges.len;
descriptor_table.pDescriptorRanges = &descriptor_table_ranges[0];
// TODO: Use https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-d3d12serializeversionedrootsignature instead
var root_signature_desc: d3d12.D3D12_ROOT_SIGNATURE_DESC = std.mem.zeroes(d3d12.D3D12_ROOT_SIGNATURE_DESC);
root_signature_desc.Flags = .D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
var signature: ?*d3d12.ID3DBlob = null;
hr = d3d12.D3D12SerializeRootSignature(&root_signature_desc, ._1, &signature, null);
if (hr < 0) {
return error.CannotSerializeRootSignature;
}
hr = comCall(device, "CreateRootSignature", .{ 0, comCall(signature, "GetBufferPointer", .{}), comCall(signature, "GetBufferSize", .{}), uuidof(d3d12, d3d12.ID3D12RootSignature), @ptrCast([*c]?*c_void, &root_signature) });
if (hr < 0) {
return error.RootSignatureCreationFailed;
}
// Create vertex and pixel shader
var vertex_shader: ?*d3d12.ID3DBlob = null;
var error_buffer: ?*d3d12.ID3DBlob = null;
hr = d3dcompiler.D3DCompileFromFile(
L("data/shaders/simple_vs.hlsl"),
null,
null,
"main",
"vs_5_0",
d3dcompiler.D3DCOMPILE_DEBUG | d3dcompiler.D3DCOMPILE_SKIP_OPTIMIZATION,
0,
@ptrCast([*c][*c]d3dcompiler.ID3DBlob, &vertex_shader),
@ptrCast([*c][*c]d3dcompiler.ID3DBlob, &error_buffer),
);
if (hr < 0) {
std.log.err("Vertex shader compilation failed:\n{}", .{@ptrCast([*:0]const u8, comCall(error_buffer, "GetBufferPointer", .{}))});
return error.VertexShaderCompilationFailed;
}
var vertex_shader_bytecode: d3d12.D3D12_SHADER_BYTECODE = undefined;
vertex_shader_bytecode.BytecodeLength = comCall(vertex_shader, "GetBufferSize", .{});
vertex_shader_bytecode.pShaderBytecode = comCall(vertex_shader, "GetBufferPointer", .{});
var pixel_shader: ?*d3d12.ID3DBlob = null;
hr = d3dcompiler.D3DCompileFromFile(
L("data/shaders/simple_ps.hlsl"),
null,
null,
"main",
"ps_5_0",
d3dcompiler.D3DCOMPILE_DEBUG | d3dcompiler.D3DCOMPILE_SKIP_OPTIMIZATION,
0,
@ptrCast([*c][*c]d3dcompiler.ID3DBlob, &pixel_shader),
@ptrCast([*c][*c]d3dcompiler.ID3DBlob, &error_buffer),
);
if (hr < 0) {
std.log.err("Pixel shader compilation failed:\n{}", .{@ptrCast([*:0]const u8, comCall(error_buffer, "GetBufferPointer", .{}))});
return error.PixelShaderCompilationFailed;
}
var pixel_shader_bytecode: d3d12.D3D12_SHADER_BYTECODE = undefined;
pixel_shader_bytecode.BytecodeLength = comCall(pixel_shader, "GetBufferSize", .{});
pixel_shader_bytecode.pShaderBytecode = comCall(pixel_shader, "GetBufferPointer", .{});
// Create input layout
var input_layout_desc: d3d12.D3D12_INPUT_LAYOUT_DESC = undefined;
input_layout_desc.NumElements = input_layout.len;
input_layout_desc.pInputElementDescs = &input_layout[0];
// Create the pipeline state object
var pso_desc: d3d12.D3D12_GRAPHICS_PIPELINE_STATE_DESC = std.mem.zeroes(d3d12.D3D12_GRAPHICS_PIPELINE_STATE_DESC);
pso_desc.InputLayout = input_layout_desc;
pso_desc.pRootSignature = root_signature;
pso_desc.VS = vertex_shader_bytecode;
pso_desc.PS = pixel_shader_bytecode;
pso_desc.PrimitiveTopologyType = ._TRIANGLE;
pso_desc.RTVFormats[0] = ._R8G8B8A8_UNORM;
pso_desc.SampleDesc.Count = 1;
pso_desc.SampleMask = 0xffffffff;
pso_desc.RasterizerState = .{
.FillMode = ._SOLID,
.CullMode = ._BACK,
.FrontCounterClockwise = @boolToInt(false),
.DepthBias = d3d12.D3D12_DEFAULT_DEPTH_BIAS,
.DepthBiasClamp = d3d12.D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
.SlopeScaledDepthBias = d3d12.D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
.DepthClipEnable = @boolToInt(true),
.MultisampleEnable = @boolToInt(false),
.AntialiasedLineEnable = @boolToInt(false),
.ForcedSampleCount = 0,
.ConservativeRaster = ._OFF,
};
pso_desc.BlendState = .{
.AlphaToCoverageEnable = @boolToInt(false),
.IndependentBlendEnable = @boolToInt(false),
.RenderTarget = [_]d3d12.D3D12_RENDER_TARGET_BLEND_DESC{.{
.BlendEnable = @boolToInt(false),
.LogicOpEnable = @boolToInt(false),
.SrcBlend = ._ONE,
.DestBlend = ._ZERO,
.BlendOp = ._ADD,
.SrcBlendAlpha = ._ONE,
.DestBlendAlpha = ._ZERO,
.BlendOpAlpha = ._ADD,
.LogicOp = ._NOOP,
.RenderTargetWriteMask = d3d12.D3D12_COLOR_WRITE_ENABLE_ALL,
}} ** 8,
};
const default_stencil_op = d3d12.D3D12_DEPTH_STENCILOP_DESC{
.StencilFailOp = ._KEEP,
.StencilDepthFailOp = ._KEEP,
.StencilPassOp = ._KEEP,
.StencilFunc = ._ALWAYS,
};
pso_desc.DepthStencilState = .{
.DepthEnable = @boolToInt(true),
.DepthWriteMask = ._ALL,
.DepthFunc = ._LESS,
.StencilEnable = @boolToInt(false),
.StencilReadMask = d3d12.D3D12_DEFAULT_STENCIL_READ_MASK,
.StencilWriteMask = d3d12.D3D12_DEFAULT_STENCIL_WRITE_MASK,
.FrontFace = default_stencil_op,
.BackFace = default_stencil_op,
};
pso_desc.NumRenderTargets = 1;
hr = comCall(device, "CreateGraphicsPipelineState", .{ &pso_desc, uuidof(d3d12, d3d12.ID3D12PipelineState), @ptrCast([*c]?*c_void, &pipeline_state_object) });
if (hr < 0) {
return error.PipelineStateCreationFailed;
}
const vertex_buffer_size = bufferSize(quad_vertices);
// Create a vertex buffer
const default_heap_properties: d3d12.D3D12_HEAP_PROPERTIES = .{
.Type = ._DEFAULT,
.CPUPageProperty = ._UNKNOWN,
.MemoryPoolPreference = ._UNKNOWN,
.CreationNodeMask = 1,
.VisibleNodeMask = 1,
};
const upload_heap_properties: d3d12.D3D12_HEAP_PROPERTIES = .{
.Type = ._UPLOAD,
.CPUPageProperty = ._UNKNOWN,
.MemoryPoolPreference = ._UNKNOWN,
.CreationNodeMask = 1,
.VisibleNodeMask = 1,
};
const vertex_resource_desc: d3d12.D3D12_RESOURCE_DESC = .{
.Dimension = ._BUFFER,
.Alignment = 0,
.Width = vertex_buffer_size,
.Height = 1,
.DepthOrArraySize = 1,
.MipLevels = 1,
.Format = ._UNKNOWN,
.SampleDesc = d3d12.DXGI_SAMPLE_DESC{
.Count = 1,
.Quality = 0,
},
.Layout = ._ROW_MAJOR,
.Flags = .D3D12_RESOURCE_FLAG_NONE,
};
hr = comCall(device, "CreateCommittedResource", .{ &default_heap_properties, .D3D12_HEAP_FLAG_NONE, &vertex_resource_desc, .D3D12_RESOURCE_STATE_COPY_DEST, null, uuidof(d3d12, d3d12.ID3D12Resource), @ptrCast([*c]?*c_void, &vertex_buffer) });
if (hr < 0) {
return error.VertexBufferCreationFailed;
}
_ = comCall(vertex_buffer, "SetName", .{L("Vertex Buffer Resource Heap")});
var vertex_buffer_upload_heap: ?*d3d12.ID3D12Resource = null;
hr = comCall(device, "CreateCommittedResource", .{ &upload_heap_properties, .D3D12_HEAP_FLAG_NONE, &vertex_resource_desc, .D3D12_RESOURCE_STATE_GENERIC_READ, null, uuidof(d3d12, d3d12.ID3D12Resource), @ptrCast([*c]?*c_void, &vertex_buffer_upload_heap) });
if (hr < 0) {
return error.VertexBufferUploadHeapCreationFailed;
}
_ = comCall(vertex_buffer_upload_heap, "SetName", .{L("Vertex Buffer Upload Resource Heap")});
var vertex_data: d3d12.D3D12_SUBRESOURCE_DATA = .{
.pData = &quad_vertices[0],
.RowPitch = @intCast(c_longlong, vertex_buffer_size),
.SlicePitch = @intCast(c_longlong, vertex_buffer_size),
};
_ = try updateSubresourcesAlloc(std.heap.c_allocator, command_list, vertex_buffer, vertex_buffer_upload_heap, 0, 0, 1, @ptrCast([*]d3d12.D3D12_SUBRESOURCE_DATA, &vertex_data));
const vertex_barrier = resourceBarrierTransition(vertex_buffer, .D3D12_RESOURCE_STATE_COPY_DEST, .D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
comCall(command_list, "ResourceBarrier", .{ 1, &vertex_barrier });
// Create index buffer
const index_buffer_size = bufferSize(quad_indices);
const index_resource_desc: d3d12.D3D12_RESOURCE_DESC = .{
.Dimension = ._BUFFER,
.Alignment = 0,
.Width = index_buffer_size,
.Height = 1,
.DepthOrArraySize = 1,
.MipLevels = 1,
.Format = ._UNKNOWN,
.SampleDesc = d3d12.DXGI_SAMPLE_DESC{
.Count = 1,
.Quality = 0,
},
.Layout = ._ROW_MAJOR,
.Flags = .D3D12_RESOURCE_FLAG_NONE,
};
hr = comCall(device, "CreateCommittedResource", .{ &default_heap_properties, .D3D12_HEAP_FLAG_NONE, &index_resource_desc, .D3D12_RESOURCE_STATE_COPY_DEST, null, uuidof(d3d12, d3d12.ID3D12Resource), @ptrCast([*c]?*c_void, &index_buffer) });
if (hr < 0) {
return error.IndexBufferCreationFailed;
}
_ = comCall(index_buffer, "SetName", .{L("Index Buffer Resource Heap")});
var index_buffer_upload_heap: ?*d3d12.ID3D12Resource = null;
hr = comCall(device, "CreateCommittedResource", .{ &upload_heap_properties, .D3D12_HEAP_FLAG_NONE, &index_resource_desc, .D3D12_RESOURCE_STATE_GENERIC_READ, null, uuidof(d3d12, d3d12.ID3D12Resource), @ptrCast([*c]?*c_void, &index_buffer_upload_heap) });
if (hr < 0) {
return error.IndexBufferUploadHeapCreationFailed;
}
_ = comCall(index_buffer_upload_heap, "SetName", .{L("Index Buffer Upload Resource Heap")});
var indices_data: d3d12.D3D12_SUBRESOURCE_DATA = .{
.pData = &quad_indices[0],
.RowPitch = @intCast(c_longlong, index_buffer_size),
.SlicePitch = @intCast(c_longlong, index_buffer_size),
};
_ = try updateSubresourcesAlloc(std.heap.c_allocator, command_list, index_buffer, index_buffer_upload_heap, 0, 0, 1, @ptrCast([*]d3d12.D3D12_SUBRESOURCE_DATA, &indices_data));
const indices_barrier = resourceBarrierTransition(index_buffer, .D3D12_RESOURCE_STATE_COPY_DEST, .D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
comCall(command_list, "ResourceBarrier", .{ 1, &indices_barrier });
index_buffer_view.BufferLocation = comCall(index_buffer, "GetGPUVirtualAddress", .{});
index_buffer_view.Format = ._R32_UINT;
index_buffer_view.SizeInBytes = @intCast(c_uint, index_buffer_size);
_ = comCall(command_list, "Close", .{});
var command_lists: ?*d3d12.ID3D12CommandList = null;
getComInteface(command_list, uuidof(d3d12, d3d12.ID3D12CommandList), &command_lists) catch unreachable;
comCall(command_queue, "ExecuteCommandLists", .{ 1, @ptrCast([*c]const [*c]d3d12.ID3D12CommandList, &command_lists) });
fence_value[frame_index] += 1;
hr = comCall(command_queue, "Signal", .{ fence[frame_index], fence_value[frame_index] });
if (hr < 0) {
quitApplication();
}
// Create Depth/Stencil buffer and view
var dsv_heap_desc = d3d12.D3D12_DESCRIPTOR_HEAP_DESC{
.NumDescriptors = 1,
.Type = ._DSV,
.Flags = .D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
.NodeMask = 0,
};
hr = comCall(device, "CreateDescriptorHeap", .{ &dsv_heap_desc, uuidof(d3d12, d3d12.ID3D12DescriptorHeap), @ptrCast([*c]?*c_void, &depth_descriptor_heap) });
if (hr < 0) {
return error.DepthStencilDescriptorHeapCreationFailed;
}
var depth_stencil_desc: d3d12.D3D12_DEPTH_STENCIL_VIEW_DESC = std.mem.zeroes(d3d12.D3D12_DEPTH_STENCIL_VIEW_DESC);
depth_stencil_desc.Format = ._D32_FLOAT;
depth_stencil_desc.ViewDimension = ._TEXTURE2D;
depth_stencil_desc.Flags = .D3D12_DSV_FLAG_NONE;
var depth_stencil_clear_value = d3d12.D3D12_CLEAR_VALUE{
.Format = ._D32_FLOAT,
.unnamed_0 = .{
.DepthStencil = .{
.Depth = 1.0,
.Stencil = 0,
},
},
};
const depth_resource_desc: d3d12.D3D12_RESOURCE_DESC = .{
.Dimension = ._TEXTURE2D,
.Alignment = 0,
.Width = WindowWidth,
.Height = WindowHeight,
.DepthOrArraySize = 1,
.MipLevels = 0,
.Format = ._D32_FLOAT,
.SampleDesc = d3d12.DXGI_SAMPLE_DESC{
.Count = 1,
.Quality = 0,
},
.Layout = ._UNKNOWN,
.Flags = .D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
};
hr = comCall(device, "CreateCommittedResource", .{ &default_heap_properties, .D3D12_HEAP_FLAG_NONE, &depth_resource_desc, .D3D12_RESOURCE_STATE_DEPTH_WRITE, &depth_stencil_clear_value, uuidof(d3d12, d3d12.ID3D12Resource), @ptrCast([*c]?*c_void, &depth_stencil_buffer) });
if (hr < 0) {
return error.DepthStencilBufferCreationFailed;
}
_ = comCall(depth_stencil_buffer, "SetName", .{L("Depth/Stencil Resource Heap")});
var depth_stencil_handle: d3d12.D3D12_CPU_DESCRIPTOR_HANDLE = undefined;
comCall(depth_descriptor_heap, "GetCPUDescriptorHandleForHeapStart", .{&depth_stencil_handle});
comCall(device, "CreateDepthStencilView", .{ depth_stencil_buffer, &depth_stencil_desc, depth_stencil_handle });
// Create a vertex buffer view for the triangle.
vertex_buffer_view.BufferLocation = comCall(vertex_buffer, "GetGPUVirtualAddress", .{});
vertex_buffer_view.StrideInBytes = @sizeOf(Vertex);
vertex_buffer_view.SizeInBytes = @intCast(c_uint, vertex_buffer_size);
// Fill viewport
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = WindowWidth;
viewport.Height = WindowHeight;
viewport.MinDepth = 0.0;
viewport.MaxDepth = 1.0;
// Fill scissor rect
scissor_rect.left = 0;
scissor_rect.top = 0;
scissor_rect.right = WindowWidth;
scissor_rect.bottom = WindowHeight;
}
fn resourceBarrierTransition(resource: ?*d3d12.ID3D12Resource, state_before: d3d12.D3D12_RESOURCE_STATES, state_after: d3d12.D3D12_RESOURCE_STATES) d3d12.D3D12_RESOURCE_BARRIER {
var result: d3d12.D3D12_RESOURCE_BARRIER = std.mem.zeroes(d3d12.D3D12_RESOURCE_BARRIER);
result.Type = ._TRANSITION;
result.Flags = .D3D12_RESOURCE_BARRIER_FLAG_NONE;
result.unnamed_0.Transition.pResource = resource.?;
result.unnamed_0.Transition.StateBefore = state_before;
result.unnamed_0.Transition.StateAfter = state_after;
result.unnamed_0.Transition.Subresource = d3d12.D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
return result;
}
fn quitApplication() void {
_ = win32.PostMessageA(hwnd, win32.WM_CLOSE, 0, 0);
}
fn update() void {}
fn updatePipeline() void {
var hr: win32.HRESULT = undefined;
waitForPreviousFrame();
hr = comCall(command_allocator[frame_index], "Reset", .{});
if (hr < 0) {
quitApplication();
}
hr = comCall(command_list, "Reset", .{ command_allocator[frame_index], pipeline_state_object });
if (hr < 0) {
quitApplication();
}
const first_barrier = resourceBarrierTransition(render_targets[frame_index], .D3D12_RESOURCE_STATE_PRESENT, .D3D12_RESOURCE_STATE_RENDER_TARGET);
const second_barrier = resourceBarrierTransition(render_targets[frame_index], .D3D12_RESOURCE_STATE_RENDER_TARGET, .D3D12_RESOURCE_STATE_PRESENT);
comCall(command_list, "ResourceBarrier", .{ 1, &first_barrier });
var rtv_handle: d3d12.D3D12_CPU_DESCRIPTOR_HANDLE = undefined;
comCall(rtv_descriptor_heap, "GetCPUDescriptorHandleForHeapStart", .{&rtv_handle});
rtv_handle.ptr += frame_index * rtv_descriptor_size;
var depth_stencil_handle: d3d12.D3D12_CPU_DESCRIPTOR_HANDLE = undefined;
comCall(depth_descriptor_heap, "GetCPUDescriptorHandleForHeapStart", .{&depth_stencil_handle});
comCall(command_list, "OMSetRenderTargets", .{ 1, &rtv_handle, @boolToInt(false), &depth_stencil_handle });
const clear_color = [_]f32{ 0.0, 0.2, 0.4, 1.0 };
comCall(command_list, "ClearRenderTargetView", .{ rtv_handle, &clear_color, 0, null });
comCall(command_list, "ClearDepthStencilView", .{ depth_stencil_handle, .D3D12_CLEAR_FLAG_DEPTH, 1.0, 0, 0, null });
comCall(command_list, "SetGraphicsRootSignature", .{root_signature});
comCall(command_list, "RSSetViewports", .{ 1, &viewport });
comCall(command_list, "RSSetScissorRects", .{ 1, &scissor_rect });
comCall(command_list, "IASetPrimitiveTopology", .{._TRIANGLELIST});
comCall(command_list, "IASetVertexBuffers", .{ 0, 1, &vertex_buffer_view });
comCall(command_list, "IASetIndexBuffer", .{&index_buffer_view});
comCall(command_list, "DrawIndexedInstanced", .{ 6, 1, 0, 0, 0 });
comCall(command_list, "DrawIndexedInstanced", .{ 6, 1, 0, 4, 0 });
comCall(command_list, "ResourceBarrier", .{ 1, &second_barrier });
hr = comCall(command_list, "Close", .{});
if (hr < 0) {
quitApplication();
}
}
fn render() void {
var hr: win32.HRESULT = undefined;
updatePipeline();
var command_lists: ?*d3d12.ID3D12CommandList = null;
getComInteface(command_list, uuidof(d3d12, d3d12.ID3D12CommandList), &command_lists) catch unreachable;
comCall(command_queue, "ExecuteCommandLists", .{ 1, @ptrCast([*c]const [*c]d3d12.ID3D12CommandList, &command_lists) });
hr = comCall(command_queue, "Signal", .{ fence[frame_index], fence_value[frame_index] });
if (hr < 0) {
quitApplication();
}
hr = comCall(swap_chain, "Present", .{ 0, 0 });
if (hr < 0) {
quitApplication();
}
}
fn cleanup() void {
var index: usize = 0;
while (index < FrameBufferCount) : (index += 1) {
frame_index = index;
waitForPreviousFrame();
}
var fs: c_int = 0;
if (comCall(swap_chain, "GetFullscreenState", .{ &fs, null }) >= 0) {
_ = comCall(swap_chain, "SetFullscreenState", .{ @boolToInt(false), null });
}
comRelease(device);
comRelease(swap_chain);
comRelease(command_queue);
comRelease(rtv_descriptor_heap);
comRelease(command_list);
index = 0;
while (index < FrameBufferCount) : (index += 1) {
comRelease(render_targets[index]);
comRelease(command_allocator[index]);
comRelease(fence[index]);
}
comRelease(pipeline_state_object);
comRelease(root_signature);
comRelease(vertex_buffer);
comRelease(index_buffer);
comRelease(depth_stencil_buffer);
comRelease(depth_descriptor_heap);
}
fn waitForPreviousFrame() void {
var hr: win32.HRESULT = undefined;
frame_index = comCall(swap_chain, "GetCurrentBackBufferIndex", .{});
const completed_value = comCall(fence[frame_index], "GetCompletedValue", .{});
if (completed_value < fence_value[frame_index]) {
hr = comCall(fence[frame_index], "SetEventOnCompletion", .{ fence_value[frame_index], fence_event });
if (hr < 0) {
// Workaround
render_targets[frame_index] = null;
command_allocator[frame_index] = null;
fence[frame_index] = null;
return;
}
_ = win32.WaitForSingleObject(fence_event, win32.INFINITE);
}
fence_value[frame_index] += 1;
}
fn WndProc(hWnd: win32.HWND, msg: win32.UINT, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(.C) win32.LRESULT {
switch (msg) {
win32.WM_SYSCOMMAND => {
if ((wParam & 0xfff0) == win32.SC_KEYMENU) {
return 0;
}
},
win32.WM_DESTROY => {
_ = win32.PostQuitMessage(0);
return 0;
},
win32.WM_KEYDOWN => {
if (wParam == win32.VK_ESCAPE) {
if (win32.MessageBox(null, "Are you sure you want to exit ?", "Really?", win32.MB_YESNO | win32.MB_ICONQUESTION) == win32.IDYES) {
_ = win32.DestroyWindow(hWnd);
}
}
},
else => {},
}
return win32.DefWindowProc(hWnd, msg, wParam, lParam);
}
pub fn init() !void {
const winClass: win32.WNDCLASSEX = .{
.cbSize = @sizeOf(win32.WNDCLASSEX),
.style = win32.CS_HREDRAW | win32.CS_VREDRAW,
.lpfnWndProc = WndProc,
.cbClsExtra = 0,
.cbWndExtra = 0,
.hInstance = win32.GetModuleHandle(null),
.hIcon = null,
.hCursor = null,
.hbrBackground = null,
.lpszMenuName = null,
.lpszClassName = "DefaultHochelagWindow",
.hIconSm = null,
};
var x: i32 = WindowX;
var y: i32 = WindowY;
var width: i32 = WindowWidth;
var height: i32 = WindowHeight;
if (Fullscreen) {
const hMon = win32.MonitorFromWindow(null, win32.MONITOR_DEFAULTTONEAREST);
var monitorInfo = std.mem.zeroes(win32.MONITORINFO);
_ = win32.GetMonitorInfo(hMon, &monitorInfo);
x = 0;
y = 0;
width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
}
_ = win32.RegisterClassEx(&winClass);
defer _ = win32.UnregisterClass(winClass.lpszClassName, winClass.hInstance);
hwnd = win32.CreateWindowEx(0, winClass.lpszClassName, "Hochelag Direct3D 12 Playground", win32.WS_OVERLAPPEDWINDOW, x, y, width, height, null, null, winClass.hInstance, null);
if (hwnd == null) {
std.debug.warn("Error creating Win32 Window = 0x{x}\n", .{@enumToInt(windows.kernel32.GetLastError())});
return error.InvalidWin32Window;
}
defer _ = win32.DestroyWindow(hwnd);
if (Fullscreen) {
_ = win32.SetWindowLong(hwnd, win32.GWL_STYLE, 0);
}
_ = win32.ShowWindow(hwnd, win32.SW_SHOWDEFAULT);
_ = win32.UpdateWindow(hwnd);
try initDirect3D();
var msg: win32.MSG = std.mem.zeroes(win32.MSG);
while (msg.message != win32.WM_QUIT) {
if (win32.PeekMessage(&msg, null, 0, 0, win32.PM_REMOVE) != 0) {
_ = win32.TranslateMessage(&msg);
_ = win32.DispatchMessage(&msg);
continue;
}
update();
render();
}
waitForPreviousFrame();
windows.CloseHandle(fence_event.?);
cleanup();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment