mach/src/sysgpu/d3d12.zig

4155 lines
152 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const sysgpu = @import("sysgpu/main.zig");
const limits = @import("limits.zig");
const shader = @import("shader.zig");
const utils = @import("utils.zig");
const c = @import("d3d12/c.zig");
const conv = @import("d3d12/conv.zig");
const gpu_allocator = @import("gpu_allocator.zig");
const log = std.log.scoped(.d3d12);
// TODO - need to tweak all these sizes and make a better allocator
const general_heap_size = 1024;
const general_block_size = 16;
const sampler_heap_size = 1024;
const sampler_block_size = 16;
const rtv_heap_size = 1024;
const rtv_block_size = 16;
const dsv_heap_size = 1024;
const dsv_block_size = 1;
const upload_page_size = 64 * 1024 * 1024; // TODO - split writes and/or support large uploads
const max_back_buffer_count = 3;
var allocator: std.mem.Allocator = undefined;
var debug_enabled: bool = undefined;
var gpu_validation_enabled: bool = undefined;
// workaround c-translation errors
const DXGI_PRESENT_ALLOW_TEARING: c.UINT = 0x00000200;
pub const InitOptions = struct {
debug_enabled: bool = builtin.mode == .Debug,
gpu_validation_enabled: bool = builtin.mode == .Debug,
};
pub fn init(alloc: std.mem.Allocator, options: InitOptions) !void {
allocator = alloc;
debug_enabled = options.debug_enabled;
gpu_validation_enabled = options.gpu_validation_enabled;
}
const MapCallback = struct {
buffer: *Buffer,
callback: sysgpu.Buffer.MapCallback,
userdata: ?*anyopaque,
};
fn setDebugName(object: *c.ID3D12Object, opt_label: ?[*:0]const u8) void {
if (opt_label) |label| {
const slice = std.mem.span(label);
_ = object.lpVtbl.*.SetPrivateData.?(
object,
&c.WKPDID_D3DDebugObjectName,
@intCast(slice.len),
slice.ptr,
);
} else {
_ = object.lpVtbl.*.SetPrivateData.?(
object,
&c.WKPDID_D3DDebugObjectName,
0,
null,
);
}
}
pub const Instance = struct {
manager: utils.Manager(Instance) = .{},
dxgi_factory: *c.IDXGIFactory4,
allow_tearing: bool,
pub fn init(desc: *const sysgpu.Instance.Descriptor) !*Instance {
// TODO
_ = desc;
var hr: c.HRESULT = undefined;
// DXGI Factory
var dxgi_factory: *c.IDXGIFactory4 = undefined;
hr = c.CreateDXGIFactory2(
if (debug_enabled) c.DXGI_CREATE_FACTORY_DEBUG else 0,
&c.IID_IDXGIFactory4,
@ptrCast(&dxgi_factory),
);
if (hr != c.S_OK) {
return error.CreateDXGIFactoryFailed;
}
errdefer _ = dxgi_factory.lpVtbl.*.Release.?(dxgi_factory);
var opt_dxgi_factory5: ?*c.IDXGIFactory5 = null;
_ = dxgi_factory.lpVtbl.*.QueryInterface.?(
dxgi_factory,
&c.IID_IDXGIFactory5,
@ptrCast(&opt_dxgi_factory5),
);
defer _ = if (opt_dxgi_factory5) |dxgi_factory5| dxgi_factory5.lpVtbl.*.Release.?(dxgi_factory5);
// Feature support
var allow_tearing: c.BOOL = c.FALSE;
if (opt_dxgi_factory5) |dxgi_factory5| {
hr = dxgi_factory5.lpVtbl.*.CheckFeatureSupport.?(
dxgi_factory5,
c.DXGI_FEATURE_PRESENT_ALLOW_TEARING,
&allow_tearing,
@sizeOf(@TypeOf(allow_tearing)),
);
}
// D3D12 Debug Layer
if (debug_enabled) {
var debug_controller: *c.ID3D12Debug1 = undefined;
hr = c.D3D12GetDebugInterface(&c.IID_ID3D12Debug1, @ptrCast(&debug_controller));
if (hr == c.S_OK) {
defer _ = debug_controller.lpVtbl.*.Release.?(debug_controller);
debug_controller.lpVtbl.*.EnableDebugLayer.?(debug_controller);
if (gpu_validation_enabled) {
debug_controller.lpVtbl.*.SetEnableGPUBasedValidation.?(
debug_controller,
c.TRUE,
);
}
}
}
// Result
const instance = try allocator.create(Instance);
instance.* = .{
.dxgi_factory = dxgi_factory,
.allow_tearing = allow_tearing == c.TRUE,
};
return instance;
}
pub fn deinit(instance: *Instance) void {
const dxgi_factory = instance.dxgi_factory;
_ = dxgi_factory.lpVtbl.*.Release.?(dxgi_factory);
Instance.reportLiveObjects();
allocator.destroy(instance);
}
pub fn createSurface(instance: *Instance, desc: *const sysgpu.Surface.Descriptor) !*Surface {
return Surface.init(instance, desc);
}
// Internal
pub fn reportLiveObjects() void {
var hr: c.HRESULT = undefined;
var dxgi_debug: *c.IDXGIDebug = undefined;
hr = c.DXGIGetDebugInterface1(0, &c.IID_IDXGIDebug, @ptrCast(&dxgi_debug));
if (hr == c.S_OK) {
defer _ = dxgi_debug.lpVtbl.*.Release.?(dxgi_debug);
_ = dxgi_debug.lpVtbl.*.ReportLiveObjects.?(
dxgi_debug,
c.DXGI_DEBUG_ALL,
c.DXGI_DEBUG_RLO_ALL,
);
}
}
};
pub const Adapter = struct {
manager: utils.Manager(Adapter) = .{},
instance: *Instance,
dxgi_adapter: *c.IDXGIAdapter1,
d3d_device: *c.ID3D12Device,
dxgi_desc: c.DXGI_ADAPTER_DESC1,
description: [256:0]u8 = undefined,
adapter_type: sysgpu.Adapter.Type = .unknown,
pub fn init(instance: *Instance, options: *const sysgpu.RequestAdapterOptions) !*Adapter {
var last_adapter_type: sysgpu.Adapter.Type = .unknown;
const dxgi_factory = instance.dxgi_factory;
var hr: c.HRESULT = undefined;
var i: u32 = 0;
var dxgi_adapter: *c.IDXGIAdapter1 = undefined;
var dxgi_desc: c.DXGI_ADAPTER_DESC1 = undefined;
var last_dxgi_adapter: ?*c.IDXGIAdapter1 = null;
var last_dxgi_desc: c.DXGI_ADAPTER_DESC1 = undefined;
while (dxgi_factory.lpVtbl.*.EnumAdapters1.?(
dxgi_factory,
i,
@ptrCast(&dxgi_adapter),
) != c.DXGI_ERROR_NOT_FOUND) : (i += 1) {
hr = dxgi_adapter.lpVtbl.*.GetDesc1.?(
dxgi_adapter,
&dxgi_desc,
);
var description: [256:0]u8 = undefined;
const l = try std.unicode.utf16LeToUtf8(&description, &dxgi_desc.Description);
description[l] = 0;
if ((dxgi_desc.Flags & c.DXGI_ADAPTER_FLAG_SOFTWARE) != 0) {
_ = dxgi_adapter.lpVtbl.*.Release.?(dxgi_adapter);
continue;
}
const adapter_type: sysgpu.Adapter.Type = blk: {
var d3d_device: *c.ID3D12Device = undefined;
hr = c.D3D12CreateDevice(
@ptrCast(dxgi_adapter),
c.D3D_FEATURE_LEVEL_11_0,
&c.IID_ID3D12Device,
@ptrCast(&d3d_device),
);
if (hr == c.S_OK) {
defer _ = d3d_device.lpVtbl.*.Release.?(d3d_device);
var arch = c.D3D12_FEATURE_DATA_ARCHITECTURE{};
_ = d3d_device.lpVtbl.*.CheckFeatureSupport.?(d3d_device, c.D3D12_FEATURE_ARCHITECTURE, &arch, @sizeOf(@TypeOf(arch)));
if (arch.UMA == 0) {
break :blk .discrete_gpu;
} else {
break :blk .integrated_gpu;
}
}
break :blk .unknown;
};
if (last_dxgi_adapter == null) {
last_dxgi_adapter = dxgi_adapter;
last_dxgi_desc = dxgi_desc;
last_adapter_type = adapter_type;
continue;
}
// Select the last discrete_gpu if power preference is high performance.
// Select the last integrated_gpu if power preference is not high performance.
// TOOD: Other selection criterias?
if ((options.power_preference == .high_performance and adapter_type == .discrete_gpu)
or (options.power_preference == .low_power and adapter_type != .discrete_gpu)) {
if (last_dxgi_adapter) |adapter| {
_ = adapter.lpVtbl.*.Release.?(adapter);
}
last_dxgi_adapter = dxgi_adapter;
last_dxgi_desc = dxgi_desc;
last_adapter_type = adapter_type;
}
}
if (last_dxgi_adapter) |selected_adapter| {
var d3d_device: *c.ID3D12Device = undefined;
hr = c.D3D12CreateDevice(
@ptrCast(selected_adapter),
c.D3D_FEATURE_LEVEL_11_0,
&c.IID_ID3D12Device,
@ptrCast(&d3d_device),
);
if (hr == c.S_OK) {
_ = selected_adapter.lpVtbl.*.AddRef.?(selected_adapter);
var adapter = try allocator.create(Adapter);
adapter.* = .{
.instance = instance,
.dxgi_adapter = selected_adapter,
.d3d_device = d3d_device,
.dxgi_desc = last_dxgi_desc,
.adapter_type = last_adapter_type,
};
const l = try std.unicode.utf16LeToUtf8(&adapter.description, &last_dxgi_desc.Description);
adapter.description[l] = 0;
return adapter;
}
}
return error.NoAdapterFound;
}
pub fn deinit(adapter: *Adapter) void {
const dxgi_adapter = adapter.dxgi_adapter;
const d3d_device = adapter.d3d_device;
_ = dxgi_adapter.lpVtbl.*.Release.?(dxgi_adapter);
_ = d3d_device.lpVtbl.*.Release.?(d3d_device);
allocator.destroy(adapter);
}
pub fn createDevice(adapter: *Adapter, desc: ?*const sysgpu.Device.Descriptor) !*Device {
return Device.init(adapter, desc);
}
pub fn getProperties(adapter: *Adapter) sysgpu.Adapter.Properties {
const dxgi_desc = adapter.dxgi_desc;
return .{
.vendor_id = dxgi_desc.VendorId,
.vendor_name = "", // TODO
.architecture = "", // TODO
.device_id = dxgi_desc.DeviceId,
.name = &adapter.description,
.driver_description = "", // TODO
.adapter_type = adapter.adapter_type,
.backend_type = .d3d12,
.compatibility_mode = .false,
};
}
};
pub const Surface = struct {
manager: utils.Manager(Surface) = .{},
hwnd: c.HWND,
pub fn init(instance: *Instance, desc: *const sysgpu.Surface.Descriptor) !*Surface {
_ = instance;
if (utils.findChained(sysgpu.Surface.DescriptorFromWindowsHWND, desc.next_in_chain.generic)) |win_desc| {
// workaround issues with @alignCast panicking as HWND is not a real pointer
var hwnd: c.HWND = undefined;
@memcpy(std.mem.asBytes(&hwnd), std.mem.asBytes(&win_desc.hwnd));
const surface = try allocator.create(Surface);
surface.* = .{ .hwnd = hwnd };
return surface;
} else {
return error.InvalidDescriptor;
}
}
pub fn deinit(surface: *Surface) void {
allocator.destroy(surface);
}
};
pub const Device = struct {
manager: utils.Manager(Device) = .{},
adapter: *Adapter,
d3d_device: *c.ID3D12Device,
queue: *Queue,
general_heap: DescriptorHeap = undefined,
sampler_heap: DescriptorHeap = undefined,
rtv_heap: DescriptorHeap = undefined,
dsv_heap: DescriptorHeap = undefined,
command_manager: CommandManager = undefined,
streaming_manager: StreamingManager = undefined,
reference_trackers: std.ArrayListUnmanaged(*ReferenceTracker) = .{},
mem_allocator: MemoryAllocator = undefined,
mem_allocator_textures: MemoryAllocator = undefined,
map_callbacks: std.ArrayListUnmanaged(MapCallback) = .{},
lost_cb: ?sysgpu.Device.LostCallback = null,
lost_cb_userdata: ?*anyopaque = null,
log_cb: ?sysgpu.LoggingCallback = null,
log_cb_userdata: ?*anyopaque = null,
err_cb: ?sysgpu.ErrorCallback = null,
err_cb_userdata: ?*anyopaque = null,
pub fn init(adapter: *Adapter, desc: ?*const sysgpu.Device.Descriptor) !*Device {
const d3d_device = adapter.d3d_device;
var hr: c.HRESULT = undefined;
// TODO
_ = desc;
// Debug Configuration
if (debug_enabled) {
var info_queue: *c.ID3D12InfoQueue = undefined;
hr = d3d_device.lpVtbl.*.QueryInterface.?(
d3d_device,
&c.IID_ID3D12InfoQueue,
@ptrCast(&info_queue),
);
if (hr == c.S_OK) {
defer _ = info_queue.lpVtbl.*.Release.?(info_queue);
var deny_ids = [_]c.D3D12_MESSAGE_ID{
c.D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE,
c.D3D12_MESSAGE_ID_CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE,
1328, //c.D3D12_MESSAGE_ID_CREATERESOURCE_STATE_IGNORED, // Required for naive barrier strategy, can be removed with render graphs
};
var severities = [_]c.D3D12_MESSAGE_SEVERITY{
c.D3D12_MESSAGE_SEVERITY_INFO,
c.D3D12_MESSAGE_SEVERITY_MESSAGE,
};
var filter = c.D3D12_INFO_QUEUE_FILTER{
.AllowList = .{
.NumCategories = 0,
.pCategoryList = null,
.NumSeverities = 0,
.pSeverityList = null,
.NumIDs = 0,
.pIDList = null,
},
.DenyList = .{
.NumCategories = 0,
.pCategoryList = null,
.NumSeverities = severities.len,
.pSeverityList = &severities,
.NumIDs = deny_ids.len,
.pIDList = &deny_ids,
},
};
hr = info_queue.lpVtbl.*.PushStorageFilter.?(
info_queue,
&filter,
);
std.debug.assert(hr == c.S_OK);
}
}
const queue = try allocator.create(Queue);
errdefer allocator.destroy(queue);
// Object
var device = try allocator.create(Device);
device.* = .{
.adapter = adapter,
.d3d_device = d3d_device,
.queue = queue,
};
// Initialize
device.queue.* = try Queue.init(device);
errdefer queue.deinit();
device.general_heap = try DescriptorHeap.init(
device,
c.D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
c.D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
general_heap_size,
general_block_size,
);
errdefer device.general_heap.deinit();
device.sampler_heap = try DescriptorHeap.init(
device,
c.D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
c.D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
sampler_heap_size,
sampler_block_size,
);
errdefer device.sampler_heap.deinit();
device.rtv_heap = try DescriptorHeap.init(
device,
c.D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
c.D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
rtv_heap_size,
rtv_block_size,
);
errdefer device.rtv_heap.deinit();
device.dsv_heap = try DescriptorHeap.init(
device,
c.D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
c.D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
dsv_heap_size,
dsv_block_size,
);
errdefer device.dsv_heap.deinit();
device.command_manager = CommandManager.init(device);
device.streaming_manager = try StreamingManager.init(device);
errdefer device.streaming_manager.deinit();
try device.mem_allocator.init(device);
try device.mem_allocator_textures.init(device);
return device;
}
pub fn deinit(device: *Device) void {
device.queue.waitUntil(device.queue.fence_value);
device.processQueuedOperations();
device.map_callbacks.deinit(allocator);
device.reference_trackers.deinit(allocator);
device.streaming_manager.deinit();
device.command_manager.deinit();
device.dsv_heap.deinit();
device.rtv_heap.deinit();
device.sampler_heap.deinit();
device.general_heap.deinit();
device.queue.manager.release();
device.mem_allocator.deinit();
allocator.destroy(device.queue);
allocator.destroy(device);
}
pub fn createBindGroup(device: *Device, desc: *const sysgpu.BindGroup.Descriptor) !*BindGroup {
return BindGroup.init(device, desc);
}
pub fn createBindGroupLayout(device: *Device, desc: *const sysgpu.BindGroupLayout.Descriptor) !*BindGroupLayout {
return BindGroupLayout.init(device, desc);
}
pub fn createBuffer(device: *Device, desc: *const sysgpu.Buffer.Descriptor) !*Buffer {
return Buffer.init(device, desc);
}
pub fn createCommandEncoder(device: *Device, desc: *const sysgpu.CommandEncoder.Descriptor) !*CommandEncoder {
return CommandEncoder.init(device, desc);
}
pub fn createComputePipeline(device: *Device, desc: *const sysgpu.ComputePipeline.Descriptor) !*ComputePipeline {
return ComputePipeline.init(device, desc);
}
pub fn createPipelineLayout(device: *Device, desc: *const sysgpu.PipelineLayout.Descriptor) !*PipelineLayout {
return PipelineLayout.init(device, desc);
}
pub fn createRenderPipeline(device: *Device, desc: *const sysgpu.RenderPipeline.Descriptor) !*RenderPipeline {
return RenderPipeline.init(device, desc);
}
pub fn createSampler(device: *Device, desc: *const sysgpu.Sampler.Descriptor) !*Sampler {
return Sampler.init(device, desc);
}
pub fn createShaderModuleAir(device: *Device, air: *shader.Air, label: [*:0]const u8) !*ShaderModule {
_ = label;
return ShaderModule.initAir(device, air);
}
pub fn createShaderModuleSpirv(device: *Device, code: [*]const u32, code_size: u32) !*ShaderModule {
_ = code;
_ = code_size;
_ = device;
return error.Unsupported;
}
pub fn createShaderModuleHLSL(device: *Device, code: []const u8) !*ShaderModule {
_ = device;
const module = try allocator.create(ShaderModule);
module.* = .{ .code = .{ .code = code } };
return module;
}
pub fn createShaderModuleMSL(
device: *Device,
label: [*:0]const u8,
code: []const u8,
workgroup_size: sysgpu.ShaderModule.WorkgroupSize,
) !*ShaderModule {
_ = label;
_ = code;
_ = device;
_ = workgroup_size;
return error.Unsupported;
}
pub fn createSwapChain(device: *Device, surface: *Surface, desc: *const sysgpu.SwapChain.Descriptor) !*SwapChain {
return SwapChain.init(device, surface, desc);
}
pub fn createTexture(device: *Device, desc: *const sysgpu.Texture.Descriptor) !*Texture {
return Texture.init(device, desc);
}
pub fn getQueue(device: *Device) !*Queue {
return device.queue;
}
pub fn tick(device: *Device) !void {
device.processQueuedOperations();
}
// Internal
pub fn processQueuedOperations(device: *Device) void {
// Reference trackers
{
const fence = device.queue.fence;
const completed_value = fence.lpVtbl.*.GetCompletedValue.?(fence);
var i: usize = 0;
while (i < device.reference_trackers.items.len) {
const reference_tracker = device.reference_trackers.items[i];
if (reference_tracker.fence_value <= completed_value) {
reference_tracker.deinit();
_ = device.reference_trackers.swapRemove(i);
} else {
i += 1;
}
}
}
// MapAsync
{
var i: usize = 0;
while (i < device.map_callbacks.items.len) {
const map_callback = device.map_callbacks.items[i];
if (map_callback.buffer.gpu_count == 0) {
map_callback.buffer.executeMapAsync(map_callback);
_ = device.map_callbacks.swapRemove(i);
} else {
i += 1;
}
}
}
}
pub fn createD3dBuffer(device: *Device, usage: sysgpu.Buffer.UsageFlags, size: u64) !Resource {
const resource_size = conv.d3d12ResourceSizeForBuffer(size, usage);
const heap_type = conv.d3d12HeapType(usage);
const resource_desc = c.D3D12_RESOURCE_DESC{
.Dimension = c.D3D12_RESOURCE_DIMENSION_BUFFER,
.Alignment = 0,
.Width = resource_size,
.Height = 1,
.DepthOrArraySize = 1,
.MipLevels = 1,
.Format = c.DXGI_FORMAT_UNKNOWN,
.SampleDesc = .{ .Count = 1, .Quality = 0 },
.Layout = c.D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
.Flags = conv.d3d12ResourceFlagsForBuffer(usage),
};
const read_state = conv.d3d12ResourceStatesForBufferRead(usage);
const initial_state = conv.d3d12ResourceStatesInitial(heap_type, read_state);
const create_desc = ResourceCreateDescriptor{
.location = if (usage.map_write)
.gpu_to_cpu
else if (usage.map_read)
.cpu_to_gpu
else
.gpu_only,
.resource_desc = &resource_desc,
.clear_value = null,
.resource_category = .buffer,
.initial_state = initial_state,
};
return try device.mem_allocator.createResource(&create_desc);
}
};
pub const ResourceCategory = enum {
buffer,
rtv_dsv_texture,
other_texture,
pub inline fn heapUsable(self: ResourceCategory, heap: HeapCategory) bool {
return switch (heap) {
.all => true,
.buffer => self == .buffer,
.rtv_dsv_texture => self == .rtv_dsv_texture,
.other_texture => self == .other_texture,
};
}
};
pub const HeapCategory = enum {
all,
buffer,
rtv_dsv_texture,
other_texture,
};
pub const AllocationCreateDescriptor = struct {
location: MemoryLocation,
size: u64,
alignment: u64,
resource_category: ResourceCategory,
};
pub const ResourceCreateDescriptor = struct {
location: MemoryLocation,
resource_category: ResourceCategory,
resource_desc: *const c.D3D12_RESOURCE_DESC,
clear_value: ?*const c.D3D12_CLEAR_VALUE,
initial_state: c.D3D12_RESOURCE_STATES,
};
pub const MemoryLocation = enum {
unknown,
gpu_only,
cpu_to_gpu,
gpu_to_cpu,
};
pub const AllocationSizes = struct {
device_memblock_size: u64 = 256 * 1024 * 1024,
host_memblock_size: u64 = 64 * 1024 * 1024,
const four_mb = 4 * 1024 * 1024;
const two_hundred_fifty_six_mb = 256 * 1024 * 1024;
pub fn init(
device_memblock_size: u64,
host_memblock_size: u64,
) AllocationSizes {
var use_device_memblock_size = std.math.clamp(
device_memblock_size,
four_mb,
two_hundred_fifty_six_mb,
);
var use_host_memblock_size = std.math.clamp(
host_memblock_size,
four_mb,
two_hundred_fifty_six_mb,
);
if (use_device_memblock_size % four_mb != 0) {
use_device_memblock_size = four_mb * (@divFloor(use_device_memblock_size, four_mb) + 1);
}
if (use_host_memblock_size % four_mb != 0) {
use_host_memblock_size = four_mb * (@divFloor(use_host_memblock_size, four_mb) + 1);
}
return .{
.device_memblock_size = use_device_memblock_size,
.host_memblock_size = use_host_memblock_size,
};
}
};
/// Stores a group of heaps
pub const MemoryAllocator = struct {
const max_memory_groups = 9;
device: *Device,
memory_groups: std.BoundedArray(MemoryGroup, max_memory_groups),
allocation_sizes: AllocationSizes,
/// a single heap,
/// use the gpu_allocator field to allocate chunks of memory
pub const MemoryHeap = struct {
index: usize,
heap: *c.ID3D12Heap,
size: u64,
gpu_allocator: gpu_allocator.Allocator,
pub fn init(
group: *MemoryGroup,
index: usize,
size: u64,
dedicated: bool,
) gpu_allocator.Error!MemoryHeap {
const heap = blk: {
var desc = c.D3D12_HEAP_DESC{
.SizeInBytes = size,
.Properties = group.heap_properties,
.Alignment = @intCast(c.D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT),
.Flags = switch (group.heap_category) {
.all => c.D3D12_HEAP_FLAG_NONE,
.buffer => c.D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
.rtv_dsv_texture => c.D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
.other_texture => c.D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
},
};
var heap: ?*c.ID3D12Heap = null;
const d3d_device = group.owning_pool.device.d3d_device;
const hr = d3d_device.lpVtbl.*.CreateHeap.?(
d3d_device,
&desc,
&c.IID_ID3D12Heap,
@ptrCast(&heap),
);
if (hr == c.E_OUTOFMEMORY) return gpu_allocator.Error.OutOfMemory;
if (hr != c.S_OK) return gpu_allocator.Error.Other;
break :blk heap.?;
};
return MemoryHeap{
.index = index,
.heap = heap,
.size = size,
.gpu_allocator = if (dedicated)
try gpu_allocator.Allocator.initDedicatedBlockAllocator(size)
else
try gpu_allocator.Allocator.initOffsetAllocator(allocator, @intCast(size), null),
};
}
pub fn deinit(self: *MemoryHeap) void {
_ = self.heap.lpVtbl.*.Release.?(self.heap);
self.gpu_allocator.deinit();
}
};
/// a group of multiple heaps with a single heap type
pub const MemoryGroup = struct {
owning_pool: *MemoryAllocator,
memory_location: MemoryLocation,
heap_category: HeapCategory,
heap_properties: c.D3D12_HEAP_PROPERTIES,
heaps: std.ArrayListUnmanaged(?MemoryHeap),
pub const GroupAllocation = struct {
allocation: gpu_allocator.Allocation,
heap: *MemoryHeap,
size: u64,
};
pub fn init(
owner: *MemoryAllocator,
memory_location: MemoryLocation,
category: HeapCategory,
properties: c.D3D12_HEAP_PROPERTIES,
) MemoryGroup {
return .{
.owning_pool = owner,
.memory_location = memory_location,
.heap_category = category,
.heap_properties = properties,
.heaps = .{},
};
}
pub fn deinit(self: *MemoryGroup) void {
for (self.heaps.items) |*heap| {
if (heap.*) |*h| h.deinit();
}
self.heaps.deinit(allocator);
}
pub fn allocate(self: *MemoryGroup, size: u64) gpu_allocator.Error!GroupAllocation {
const memblock_size: u64 = if (self.heap_properties.Type == c.D3D12_HEAP_TYPE_DEFAULT)
self.owning_pool.allocation_sizes.device_memblock_size
else
self.owning_pool.allocation_sizes.host_memblock_size;
if (size > memblock_size) {
return self.allocateDedicated(size);
}
var empty_heap_index: ?usize = null;
for (self.heaps.items, 0..) |*heap, index| {
if (heap.*) |*h| {
const allocation = h.gpu_allocator.allocate(@intCast(size)) catch |err| switch (err) {
gpu_allocator.Error.OutOfMemory => continue,
else => return err,
};
return GroupAllocation{
.allocation = allocation,
.heap = h,
.size = size,
};
} else if (empty_heap_index == null) {
empty_heap_index = index;
}
}
// couldn't allocate, use the empty heap if we got one
const heap = try self.addHeap(memblock_size, false, empty_heap_index);
const allocation = try heap.gpu_allocator.allocate(@intCast(size));
return GroupAllocation{
.allocation = allocation,
.heap = heap,
.size = size,
};
}
fn allocateDedicated(self: *MemoryGroup, size: u64) gpu_allocator.Error!GroupAllocation {
const memory_block = try self.addHeap(size, true, blk: {
for (self.heaps.items, 0..) |heap, index| {
if (heap == null) break :blk index;
}
break :blk null;
});
const allocation = try memory_block.gpu_allocator.allocate(@intCast(size));
return GroupAllocation{
.allocation = allocation,
.heap = memory_block,
.size = size,
};
}
pub fn free(self: *MemoryGroup, allocation: GroupAllocation) gpu_allocator.Error!void {
const heap = allocation.heap;
try heap.gpu_allocator.free(allocation.allocation);
if (heap.gpu_allocator.isEmpty()) {
const index = heap.index;
heap.deinit();
self.heaps.items[index] = null;
}
}
fn addHeap(self: *MemoryGroup, size: u64, dedicated: bool, replace: ?usize) gpu_allocator.Error!*MemoryHeap {
const heap_index: usize = blk: {
if (replace) |index| {
if (self.heaps.items[index]) |*heap| {
heap.deinit();
}
self.heaps.items[index] = null;
break :blk index;
} else {
_ = try self.heaps.addOne(allocator);
break :blk self.heaps.items.len - 1;
}
};
errdefer _ = self.heaps.popOrNull();
const heap = &self.heaps.items[heap_index].?;
heap.* = try MemoryHeap.init(
self,
heap_index,
size,
dedicated,
);
return heap;
}
};
pub const Allocation = struct {
allocation: gpu_allocator.Allocation,
heap: *MemoryHeap,
size: u64,
group: *MemoryGroup,
};
pub fn init(self: *MemoryAllocator, device: *Device) !void {
const HeapType = struct {
location: MemoryLocation,
properties: c.D3D12_HEAP_PROPERTIES,
};
const heap_types = [_]HeapType{ .{
.location = .gpu_only,
.properties = c.D3D12_HEAP_PROPERTIES{
.Type = c.D3D12_HEAP_TYPE_DEFAULT,
.CPUPageProperty = c.D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
.MemoryPoolPreference = c.D3D12_MEMORY_POOL_UNKNOWN,
.CreationNodeMask = 0,
.VisibleNodeMask = 0,
},
}, .{
.location = .cpu_to_gpu,
.properties = c.D3D12_HEAP_PROPERTIES{
.Type = c.D3D12_HEAP_TYPE_CUSTOM,
.CPUPageProperty = c.D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
.MemoryPoolPreference = c.D3D12_MEMORY_POOL_L0,
.CreationNodeMask = 0,
.VisibleNodeMask = 0,
},
}, .{
.location = .gpu_to_cpu,
.properties = c.D3D12_HEAP_PROPERTIES{
.Type = c.D3D12_HEAP_TYPE_CUSTOM,
.CPUPageProperty = c.D3D12_CPU_PAGE_PROPERTY_WRITE_BACK,
.MemoryPoolPreference = c.D3D12_MEMORY_POOL_L0,
.CreationNodeMask = 0,
.VisibleNodeMask = 0,
},
} };
self.* = .{
.device = device,
.memory_groups = std.BoundedArray(MemoryGroup, max_memory_groups).init(0) catch unreachable,
.allocation_sizes = .{},
};
var options: c.D3D12_FEATURE_DATA_D3D12_OPTIONS = undefined;
const hr = device.d3d_device.lpVtbl.*.CheckFeatureSupport.?(
device.d3d_device,
c.D3D12_FEATURE_D3D12_OPTIONS,
@ptrCast(&options),
@sizeOf(c.D3D12_FEATURE_DATA_D3D12_OPTIONS),
);
if (hr != c.S_OK) return gpu_allocator.Error.Other;
const tier_one_heap = options.ResourceHeapTier == c.D3D12_RESOURCE_HEAP_TIER_1;
self.memory_groups = std.BoundedArray(MemoryGroup, max_memory_groups).init(0) catch unreachable;
inline for (heap_types) |heap_type| {
if (tier_one_heap) {
self.memory_groups.appendAssumeCapacity(MemoryGroup.init(
self,
heap_type.location,
.buffer,
heap_type.properties,
));
self.memory_groups.appendAssumeCapacity(MemoryGroup.init(
self,
heap_type.location,
.rtv_dsv_texture,
heap_type.properties,
));
self.memory_groups.appendAssumeCapacity(MemoryGroup.init(
self,
heap_type.location,
.other_texture,
heap_type.properties,
));
} else {
self.memory_groups.appendAssumeCapacity(MemoryGroup.init(
self,
heap_type.location,
.all,
heap_type.properties,
));
}
}
}
pub fn deinit(self: *MemoryAllocator) void {
for (self.memory_groups.slice()) |*group| {
group.deinit();
}
}
pub fn reportMemoryLeaks(self: *const MemoryAllocator) void {
log.info("memory leaks:", .{});
var total_blocks: u64 = 0;
for (self.memory_groups.constSlice(), 0..) |mem_group, mem_group_index| {
log.info(" memory group {} ({s}, {s}):", .{
mem_group_index,
@tagName(mem_group.heap_category),
@tagName(mem_group.memory_location),
});
for (mem_group.heaps.items, 0..) |block, block_index| {
if (block) |found_block| {
log.info(" block {}; total size: {}; allocated: {};", .{
block_index,
found_block.size,
found_block.gpu_allocator.getAllocated(),
});
total_blocks += 1;
}
}
}
log.info("total blocks: {}", .{total_blocks});
}
pub fn allocate(self: *MemoryAllocator, desc: *const AllocationCreateDescriptor) gpu_allocator.Error!Allocation {
// TODO: handle alignment
for (self.memory_groups.slice()) |*memory_group| {
if (memory_group.memory_location != desc.location and desc.location != .unknown) continue;
if (!desc.resource_category.heapUsable(memory_group.heap_category)) continue;
const allocation = try memory_group.allocate(desc.size);
return Allocation{
.allocation = allocation.allocation,
.heap = allocation.heap,
.size = allocation.size,
.group = memory_group,
};
}
return gpu_allocator.Error.NoCompatibleMemoryFound;
}
pub fn free(self: *MemoryAllocator, allocation: Allocation) gpu_allocator.Error!void {
_ = self;
const group = allocation.group;
try group.free(MemoryGroup.GroupAllocation{
.allocation = allocation.allocation,
.heap = allocation.heap,
.size = allocation.size,
});
}
pub fn createResource(self: *MemoryAllocator, desc: *const ResourceCreateDescriptor) gpu_allocator.Error!Resource {
const d3d_device = self.device.d3d_device;
const allocation_desc = blk: {
var _out_allocation_info: c.D3D12_RESOURCE_ALLOCATION_INFO = undefined;
const allocation_info = d3d_device.lpVtbl.*.GetResourceAllocationInfo.?(
d3d_device,
&_out_allocation_info,
0,
1,
@ptrCast(desc.resource_desc),
);
// TODO: If size in bytes == UINT64_MAX then an error occured
break :blk AllocationCreateDescriptor{
.location = desc.location,
.size = allocation_info.*.SizeInBytes,
.alignment = allocation_info.*.Alignment,
.resource_category = desc.resource_category,
};
};
const allocation = try self.allocate(&allocation_desc);
var d3d_resource: ?*c.ID3D12Resource = null;
const hr = d3d_device.lpVtbl.*.CreatePlacedResource.?(
d3d_device,
allocation.heap.heap,
allocation.allocation.offset,
desc.resource_desc,
desc.initial_state,
desc.clear_value,
&c.IID_ID3D12Resource,
@ptrCast(&d3d_resource),
);
if (hr != c.S_OK) return gpu_allocator.Error.Other;
return Resource{
.mem_allocator = self,
.read_state = desc.initial_state,
.allocation = allocation,
.d3d_resource = d3d_resource.?,
.memory_location = desc.location,
.size = allocation.size,
};
}
pub fn destroyResource(self: *MemoryAllocator, resource: Resource) gpu_allocator.Error!void {
if (resource.allocation) |allocation| {
try self.free(allocation);
}
const d3d_resource = resource.d3d_resource;
_ = d3d_resource.lpVtbl.*.Release.?(d3d_resource);
}
};
const DescriptorAllocation = struct {
index: u32,
};
const DescriptorHeap = struct {
// Initial version supports fixed-block size allocation only
device: *Device,
d3d_heap: *c.ID3D12DescriptorHeap,
cpu_base: c.D3D12_CPU_DESCRIPTOR_HANDLE,
gpu_base: c.D3D12_GPU_DESCRIPTOR_HANDLE,
descriptor_size: u32,
descriptor_count: u32,
block_size: u32,
next_alloc: u32,
free_blocks: std.ArrayListUnmanaged(DescriptorAllocation) = .{},
pub fn init(
device: *Device,
heap_type: c.D3D12_DESCRIPTOR_HEAP_TYPE,
flags: c.D3D12_DESCRIPTOR_HEAP_FLAGS,
descriptor_count: u32,
block_size: u32,
) !DescriptorHeap {
const d3d_device = device.d3d_device;
var hr: c.HRESULT = undefined;
var d3d_heap: *c.ID3D12DescriptorHeap = undefined;
hr = d3d_device.lpVtbl.*.CreateDescriptorHeap.?(
d3d_device,
&c.D3D12_DESCRIPTOR_HEAP_DESC{
.Type = heap_type,
.NumDescriptors = descriptor_count,
.Flags = flags,
.NodeMask = 0,
},
&c.IID_ID3D12DescriptorHeap,
@ptrCast(&d3d_heap),
);
if (hr != c.S_OK) {
return error.CreateDescriptorHeapFailed;
}
errdefer _ = d3d_heap.lpVtbl.*.Release.?(d3d_heap);
const descriptor_size = d3d_device.lpVtbl.*.GetDescriptorHandleIncrementSize.?(
d3d_device,
heap_type,
);
var cpu_base: c.D3D12_CPU_DESCRIPTOR_HANDLE = undefined;
_ = d3d_heap.lpVtbl.*.GetCPUDescriptorHandleForHeapStart.?(
d3d_heap,
&cpu_base,
);
var gpu_base: c.D3D12_GPU_DESCRIPTOR_HANDLE = undefined;
if ((flags & c.D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE) != 0) {
_ = d3d_heap.lpVtbl.*.GetGPUDescriptorHandleForHeapStart.?(
d3d_heap,
&gpu_base,
);
} else {
gpu_base = .{ .ptr = 0 };
}
return .{
.device = device,
.d3d_heap = d3d_heap,
.cpu_base = cpu_base,
.gpu_base = gpu_base,
.descriptor_size = descriptor_size,
.descriptor_count = descriptor_count,
.block_size = block_size,
.next_alloc = 0,
};
}
pub fn deinit(heap: *DescriptorHeap) void {
const d3d_heap = heap.d3d_heap;
heap.free_blocks.deinit(allocator);
_ = d3d_heap.lpVtbl.*.Release.?(d3d_heap);
}
pub fn alloc(heap: *DescriptorHeap) !DescriptorAllocation {
// Recycle finished blocks
if (heap.free_blocks.items.len == 0) {
heap.device.processQueuedOperations();
}
// Create new block
if (heap.free_blocks.items.len == 0) {
if (heap.next_alloc == heap.descriptor_count)
return error.OutOfDescriptorMemory;
const index = heap.next_alloc;
heap.next_alloc += heap.block_size;
try heap.free_blocks.append(allocator, .{ .index = index });
}
// Result
return heap.free_blocks.pop();
}
pub fn free(heap: *DescriptorHeap, allocation: DescriptorAllocation) void {
heap.free_blocks.append(allocator, allocation) catch {
std.debug.panic("OutOfMemory", .{});
};
}
pub fn cpuDescriptor(heap: *DescriptorHeap, index: u32) c.D3D12_CPU_DESCRIPTOR_HANDLE {
return .{ .ptr = heap.cpu_base.ptr + index * heap.descriptor_size };
}
pub fn gpuDescriptor(heap: *DescriptorHeap, index: u32) c.D3D12_GPU_DESCRIPTOR_HANDLE {
return .{ .ptr = heap.gpu_base.ptr + index * heap.descriptor_size };
}
};
const CommandManager = struct {
device: *Device,
free_allocators: std.ArrayListUnmanaged(*c.ID3D12CommandAllocator) = .{},
free_command_lists: std.ArrayListUnmanaged(*c.ID3D12GraphicsCommandList) = .{},
pub fn init(device: *Device) CommandManager {
return .{
.device = device,
};
}
pub fn deinit(manager: *CommandManager) void {
for (manager.free_allocators.items) |command_allocator| {
_ = command_allocator.lpVtbl.*.Release.?(command_allocator);
}
for (manager.free_command_lists.items) |command_list| {
_ = command_list.lpVtbl.*.Release.?(command_list);
}
manager.free_allocators.deinit(allocator);
manager.free_command_lists.deinit(allocator);
}
pub fn createCommandAllocator(manager: *CommandManager) !*c.ID3D12CommandAllocator {
const d3d_device = manager.device.d3d_device;
var hr: c.HRESULT = undefined;
// Recycle finished allocators
if (manager.free_allocators.items.len == 0) {
manager.device.processQueuedOperations();
}
// Create new command allocator
if (manager.free_allocators.items.len == 0) {
var command_allocator: *c.ID3D12CommandAllocator = undefined;
hr = d3d_device.lpVtbl.*.CreateCommandAllocator.?(
d3d_device,
c.D3D12_COMMAND_LIST_TYPE_DIRECT,
&c.IID_ID3D12CommandAllocator,
@ptrCast(&command_allocator),
);
if (hr != c.S_OK) {
return error.CreateCommandAllocatorFailed;
}
try manager.free_allocators.append(allocator, command_allocator);
}
// Reset
const command_allocator = manager.free_allocators.pop();
hr = command_allocator.lpVtbl.*.Reset.?(command_allocator);
if (hr != c.S_OK) {
return error.ResetCommandAllocatorFailed;
}
return command_allocator;
}
pub fn destroyCommandAllocator(manager: *CommandManager, command_allocator: *c.ID3D12CommandAllocator) void {
manager.free_allocators.append(allocator, command_allocator) catch {
std.debug.panic("OutOfMemory", .{});
};
}
pub fn createCommandList(
manager: *CommandManager,
command_allocator: *c.ID3D12CommandAllocator,
) !*c.ID3D12GraphicsCommandList {
const d3d_device = manager.device.d3d_device;
var hr: c.HRESULT = undefined;
if (manager.free_command_lists.items.len == 0) {
var command_list: *c.ID3D12GraphicsCommandList = undefined;
hr = d3d_device.lpVtbl.*.CreateCommandList.?(
d3d_device,
0,
c.D3D12_COMMAND_LIST_TYPE_DIRECT,
command_allocator,
null,
&c.IID_ID3D12GraphicsCommandList,
@ptrCast(&command_list),
);
if (hr != c.S_OK) {
return error.CreateCommandListFailed;
}
return command_list;
}
const command_list = manager.free_command_lists.pop();
hr = command_list.lpVtbl.*.Reset.?(
command_list,
command_allocator,
null,
);
if (hr != c.S_OK) {
return error.ResetCommandListFailed;
}
return command_list;
}
pub fn destroyCommandList(manager: *CommandManager, command_list: *c.ID3D12GraphicsCommandList) void {
manager.free_command_lists.append(allocator, command_list) catch std.debug.panic("OutOfMemory", .{});
}
};
pub const StreamingManager = struct {
device: *Device,
free_buffers: std.ArrayListUnmanaged(Resource) = .{},
pub fn init(device: *Device) !StreamingManager {
return .{
.device = device,
};
}
pub fn deinit(manager: *StreamingManager) void {
for (manager.free_buffers.items) |*d3d_resource| {
d3d_resource.deinit();
}
manager.free_buffers.deinit(allocator);
}
pub fn acquire(manager: *StreamingManager) !Resource {
const device = manager.device;
// Recycle finished buffers
if (manager.free_buffers.items.len == 0) {
device.processQueuedOperations();
}
// Create new buffer
if (manager.free_buffers.items.len == 0) {
var resource = try device.createD3dBuffer(.{ .map_write = true }, upload_page_size);
errdefer _ = resource.deinit();
setDebugName(@ptrCast(resource.d3d_resource), "upload");
try manager.free_buffers.append(allocator, resource);
}
// Result
return manager.free_buffers.pop();
}
pub fn release(manager: *StreamingManager, resource: Resource) void {
manager.free_buffers.append(allocator, resource) catch {
std.debug.panic("OutOfMemory", .{});
};
}
};
pub const SwapChain = struct {
manager: utils.Manager(SwapChain) = .{},
device: *Device,
surface: *Surface,
queue: *Queue,
dxgi_swap_chain: *c.IDXGISwapChain3,
width: u32,
height: u32,
back_buffer_count: u32,
sync_interval: c.UINT,
present_flags: c.UINT,
textures: [max_back_buffer_count]*Texture,
views: [max_back_buffer_count]*TextureView,
fence_values: [max_back_buffer_count]u64,
buffer_index: u32 = 0,
pub fn init(device: *Device, surface: *Surface, desc: *const sysgpu.SwapChain.Descriptor) !*SwapChain {
const instance = device.adapter.instance;
const dxgi_factory = instance.dxgi_factory;
var hr: c.HRESULT = undefined;
device.processQueuedOperations();
// Swap Chain
const back_buffer_count: u32 = if (desc.present_mode == .mailbox) 3 else 2;
var swap_chain_desc = c.DXGI_SWAP_CHAIN_DESC1{
.Width = desc.width,
.Height = desc.height,
.Format = conv.dxgiFormatForTexture(desc.format),
.Stereo = c.FALSE,
.SampleDesc = .{ .Count = 1, .Quality = 0 },
.BufferUsage = conv.dxgiUsage(desc.usage),
.BufferCount = back_buffer_count,
.Scaling = c.DXGI_MODE_SCALING_UNSPECIFIED,
.SwapEffect = c.DXGI_SWAP_EFFECT_FLIP_DISCARD,
.AlphaMode = c.DXGI_ALPHA_MODE_UNSPECIFIED,
.Flags = if (instance.allow_tearing) c.DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING else 0,
};
var dxgi_swap_chain: *c.IDXGISwapChain3 = undefined;
hr = dxgi_factory.lpVtbl.*.CreateSwapChainForHwnd.?(
dxgi_factory,
@ptrCast(device.queue.d3d_command_queue),
surface.hwnd,
&swap_chain_desc,
null,
null,
@ptrCast(&dxgi_swap_chain),
);
if (hr != c.S_OK) {
return error.CreateSwapChainFailed;
}
errdefer _ = dxgi_swap_chain.lpVtbl.*.Release.?(dxgi_swap_chain);
// Views
var textures = std.BoundedArray(*Texture, max_back_buffer_count){};
var views = std.BoundedArray(*TextureView, max_back_buffer_count){};
var fence_values = std.BoundedArray(u64, max_back_buffer_count){};
errdefer {
for (views.slice()) |view| view.manager.release();
for (textures.slice()) |texture| texture.manager.release();
}
for (0..back_buffer_count) |i| {
var buffer: *c.ID3D12Resource = undefined;
hr = dxgi_swap_chain.lpVtbl.*.GetBuffer.?(
dxgi_swap_chain,
@intCast(i),
&c.IID_ID3D12Resource,
@ptrCast(&buffer),
);
if (hr != c.S_OK) {
return error.SwapChainGetBufferFailed;
}
const texture = try Texture.initForSwapChain(device, desc, buffer);
const view = try texture.createView(&sysgpu.TextureView.Descriptor{});
textures.appendAssumeCapacity(texture);
views.appendAssumeCapacity(view);
fence_values.appendAssumeCapacity(0);
}
// Result
const swapchain = try allocator.create(SwapChain);
swapchain.* = .{
.device = device,
.surface = surface,
.queue = device.queue,
.dxgi_swap_chain = dxgi_swap_chain,
.width = desc.width,
.height = desc.height,
.back_buffer_count = back_buffer_count,
.sync_interval = if (desc.present_mode == .immediate) 0 else 1,
.present_flags = if (desc.present_mode == .immediate and instance.allow_tearing) DXGI_PRESENT_ALLOW_TEARING else 0,
.textures = textures.buffer,
.views = views.buffer,
.fence_values = fence_values.buffer,
};
return swapchain;
}
pub fn deinit(swapchain: *SwapChain) void {
const dxgi_swap_chain = swapchain.dxgi_swap_chain;
const queue = swapchain.queue;
queue.waitUntil(queue.fence_value);
for (swapchain.views[0..swapchain.back_buffer_count]) |view| view.manager.release();
for (swapchain.textures[0..swapchain.back_buffer_count]) |texture| texture.manager.release();
_ = dxgi_swap_chain.lpVtbl.*.Release.?(dxgi_swap_chain);
allocator.destroy(swapchain);
}
pub fn getCurrentTextureView(swapchain: *SwapChain) !*TextureView {
const dxgi_swap_chain = swapchain.dxgi_swap_chain;
const fence_value = swapchain.fence_values[swapchain.buffer_index];
swapchain.queue.waitUntil(fence_value);
const index = dxgi_swap_chain.lpVtbl.*.GetCurrentBackBufferIndex.?(dxgi_swap_chain);
swapchain.buffer_index = index;
// TEMP - resolve reference tracking in main.zig
swapchain.views[index].manager.reference();
return swapchain.views[index];
}
pub fn present(swapchain: *SwapChain) !void {
const dxgi_swap_chain = swapchain.dxgi_swap_chain;
const queue = swapchain.queue;
var hr: c.HRESULT = undefined;
hr = dxgi_swap_chain.lpVtbl.*.Present.?(
dxgi_swap_chain,
swapchain.sync_interval,
swapchain.present_flags,
);
if (hr != c.S_OK) {
return error.PresentFailed;
}
queue.fence_value += 1;
try queue.signal();
swapchain.fence_values[swapchain.buffer_index] = queue.fence_value;
}
};
pub const Resource = struct {
// NOTE - this is a naive sync solution as a placeholder until render graphs are implemented
mem_allocator: ?*MemoryAllocator = null,
read_state: c.D3D12_RESOURCE_STATES,
allocation: ?MemoryAllocator.Allocation = null,
d3d_resource: *c.ID3D12Resource,
memory_location: MemoryLocation = .unknown,
size: u64 = 0,
pub fn init(
d3d_resource: *c.ID3D12Resource,
read_state: c.D3D12_RESOURCE_STATES,
) Resource {
return .{
.d3d_resource = d3d_resource,
.read_state = read_state,
};
}
pub fn deinit(resource: *Resource) void {
if (resource.mem_allocator) |mem_allocator| {
mem_allocator.destroyResource(resource.*) catch {};
} else {
_ = resource.d3d_resource.lpVtbl.*.Release.?(resource.d3d_resource);
}
}
};
pub const Buffer = struct {
manager: utils.Manager(Buffer) = .{},
device: *Device,
resource: Resource,
stage_buffer: ?*Buffer,
gpu_count: u32 = 0,
map: ?[*]u8,
// TODO - packed buffer descriptor struct
size: u64,
usage: sysgpu.Buffer.UsageFlags,
pub fn init(device: *Device, desc: *const sysgpu.Buffer.Descriptor) !*Buffer {
var hr: c.HRESULT = undefined;
var resource = try device.createD3dBuffer(desc.usage, desc.size);
errdefer resource.deinit();
if (desc.label) |label|
setDebugName(@ptrCast(resource.d3d_resource), label);
// Mapped at Creation
var stage_buffer: ?*Buffer = null;
var map: ?*anyopaque = null;
if (desc.mapped_at_creation == .true) {
var map_resource: *c.ID3D12Resource = undefined;
if (!desc.usage.map_write) {
stage_buffer = try Buffer.init(device, &.{
.usage = .{ .copy_src = true, .map_write = true },
.size = desc.size,
});
map_resource = stage_buffer.?.resource.d3d_resource;
} else {
map_resource = resource.d3d_resource;
}
// TODO - map status in callback instead of failure
hr = map_resource.lpVtbl.*.Map.?(map_resource, 0, null, &map);
if (hr != c.S_OK) {
return error.MapBufferAtCreationFailed;
}
}
// Result
const buffer = try allocator.create(Buffer);
buffer.* = .{
.device = device,
.resource = resource,
.stage_buffer = stage_buffer,
.map = @ptrCast(map),
.size = desc.size,
.usage = desc.usage,
};
return buffer;
}
pub fn deinit(buffer: *Buffer) void {
if (buffer.stage_buffer) |stage_buffer| stage_buffer.manager.release();
buffer.resource.deinit();
allocator.destroy(buffer);
}
pub fn getMappedRange(buffer: *Buffer, offset: usize, size: usize) !?*anyopaque {
return @ptrCast(buffer.map.?[offset .. offset + size]);
}
pub fn getSize(buffer: *Buffer) u64 {
return buffer.size;
}
pub fn getUsage(buffer: *Buffer) sysgpu.Buffer.UsageFlags {
return buffer.usage;
}
pub fn mapAsync(
buffer: *Buffer,
mode: sysgpu.MapModeFlags,
offset: usize,
size: usize,
callback: sysgpu.Buffer.MapCallback,
userdata: ?*anyopaque,
) !void {
_ = size;
_ = offset;
_ = mode;
const map_callback = MapCallback{ .buffer = buffer, .callback = callback, .userdata = userdata };
if (buffer.gpu_count == 0) {
buffer.executeMapAsync(map_callback);
} else {
try buffer.device.map_callbacks.append(allocator, map_callback);
}
}
pub fn setLabel(buffer: *Buffer, label: [*:0]const u8) void {
setDebugName(@ptrCast(buffer.resource.d3d_resource), label);
}
pub fn unmap(buffer: *Buffer) !void {
var map_resource: *c.ID3D12Resource = undefined;
if (buffer.stage_buffer) |stage_buffer| {
map_resource = stage_buffer.resource.d3d_resource;
const encoder = try buffer.device.queue.getCommandEncoder();
try encoder.copyBufferToBuffer(stage_buffer, 0, buffer, 0, buffer.size);
stage_buffer.manager.release();
buffer.stage_buffer = null;
} else {
map_resource = buffer.resource.d3d_resource;
}
map_resource.lpVtbl.*.Unmap.?(map_resource, 0, null);
}
// Internal
pub fn executeMapAsync(buffer: *Buffer, map_callback: MapCallback) void {
const d3d_resource = buffer.resource.d3d_resource;
var hr: c.HRESULT = undefined;
var map: ?*anyopaque = null;
hr = d3d_resource.lpVtbl.*.Map.?(d3d_resource, 0, null, &map);
if (hr != c.S_OK) {
map_callback.callback(.unknown, map_callback.userdata);
return;
}
buffer.map = @ptrCast(map);
map_callback.callback(.success, map_callback.userdata);
}
};
pub const Texture = struct {
manager: utils.Manager(Texture) = .{},
device: *Device,
resource: Resource,
// TODO - packed texture descriptor struct
usage: sysgpu.Texture.UsageFlags,
dimension: sysgpu.Texture.Dimension,
size: sysgpu.Extent3D,
format: sysgpu.Texture.Format,
mip_level_count: u32,
sample_count: u32,
pub fn init(device: *Device, desc: *const sysgpu.Texture.Descriptor) !*Texture {
const resource_desc = c.D3D12_RESOURCE_DESC{
.Dimension = conv.d3d12ResourceDimension(desc.dimension),
.Alignment = 0,
.Width = desc.size.width,
.Height = desc.size.height,
.DepthOrArraySize = @intCast(desc.size.depth_or_array_layers),
.MipLevels = @intCast(desc.mip_level_count),
.Format = conv.dxgiFormatForTextureResource(desc.format, desc.usage, desc.view_format_count),
.SampleDesc = .{ .Count = desc.sample_count, .Quality = 0 },
.Layout = c.D3D12_TEXTURE_LAYOUT_UNKNOWN,
.Flags = conv.d3d12ResourceFlagsForTexture(desc.usage, desc.format),
};
const read_state = conv.d3d12ResourceStatesForTextureRead(desc.usage);
const initial_state = read_state;
const clear_value = c.D3D12_CLEAR_VALUE{ .Format = resource_desc.Format };
// TODO: the code below was terribly broken, I rewrote it, Is it correct?
// const create_desc = ResourceCreateDescriptor{
// .location = .gpu_only,
// .resource_desc = if (utils.formatHasDepthOrStencil(desc.format) or desc.usage.render_attachment)
// &clear_value
// else
// null,
// .clear_value = null,
// .resource_category = .buffer,
// .initial_state = initial_state,
// };
const create_desc = ResourceCreateDescriptor{
.location = .gpu_only,
.resource_desc = &resource_desc,
.clear_value = if (utils.formatHasDepthOrStencil(desc.format) or desc.usage.render_attachment)
&clear_value
else
null,
// TODO: #1225: check if different textures need different resource categories.
.resource_category = .other_texture,
.initial_state = initial_state,
};
const resource = device.mem_allocator.createResource(&create_desc) catch
return error.CreateTextureFailed;
if (desc.label) |label|
setDebugName(@ptrCast(resource.d3d_resource), label);
// Result
const texture = try allocator.create(Texture);
texture.* = .{
.device = device,
.resource = resource,
.usage = desc.usage,
.dimension = desc.dimension,
.size = desc.size,
.format = desc.format,
.mip_level_count = desc.mip_level_count,
.sample_count = desc.sample_count,
};
return texture;
}
pub fn initForSwapChain(device: *Device, desc: *const sysgpu.SwapChain.Descriptor, d3d_resource: *c.ID3D12Resource) !*Texture {
const read_state = c.D3D12_RESOURCE_STATE_PRESENT;
const texture = try allocator.create(Texture);
texture.* = .{
.device = device,
.resource = Resource.init(d3d_resource, read_state),
.usage = desc.usage,
.dimension = .dimension_2d,
.size = .{ .width = desc.width, .height = desc.height, .depth_or_array_layers = 1 },
.format = desc.format,
.mip_level_count = 1,
.sample_count = 1,
};
return texture;
}
pub fn deinit(texture: *Texture) void {
texture.resource.deinit();
allocator.destroy(texture);
}
pub fn createView(texture: *Texture, desc: *const sysgpu.TextureView.Descriptor) !*TextureView {
return TextureView.init(texture, desc);
}
pub fn getWidth(texture: *Texture) u32 {
return texture.size.width;
}
pub fn getHeight(texture: *Texture) u32 {
return texture.size.height;
}
// Internal
pub fn calcSubresource(texture: *Texture, mip_level: u32, array_slice: u32) u32 {
return mip_level + (array_slice * texture.mip_level_count);
}
};
pub const TextureView = struct {
manager: utils.Manager(TextureView) = .{},
texture: *Texture,
format: sysgpu.Texture.Format,
dimension: sysgpu.TextureView.Dimension,
base_mip_level: u32,
mip_level_count: u32,
base_array_layer: u32,
array_layer_count: u32,
aspect: sysgpu.Texture.Aspect,
base_subresource: u32,
pub fn init(texture: *Texture, desc: *const sysgpu.TextureView.Descriptor) !*TextureView {
texture.manager.reference();
const texture_dimension: sysgpu.TextureView.Dimension = switch (texture.dimension) {
.dimension_1d => .dimension_1d,
.dimension_2d => .dimension_2d,
.dimension_3d => .dimension_3d,
};
const view = try allocator.create(TextureView);
view.* = .{
.texture = texture,
.format = if (desc.format != .undefined) desc.format else texture.format,
.dimension = if (desc.dimension != .dimension_undefined) desc.dimension else texture_dimension,
.base_mip_level = desc.base_mip_level,
.mip_level_count = desc.mip_level_count,
.base_array_layer = desc.base_array_layer,
.array_layer_count = desc.array_layer_count,
.aspect = desc.aspect,
.base_subresource = texture.calcSubresource(desc.base_mip_level, desc.base_array_layer),
};
return view;
}
pub fn deinit(view: *TextureView) void {
view.texture.manager.release();
allocator.destroy(view);
}
// Internal
pub fn width(view: *TextureView) u32 {
return @max(1, view.texture.size.width >> @intCast(view.base_mip_level));
}
pub fn height(view: *TextureView) u32 {
return @max(1, view.texture.size.height >> @intCast(view.base_mip_level));
}
pub fn srvDesc(view: *TextureView) c.D3D12_SHADER_RESOURCE_VIEW_DESC {
var srv_desc: c.D3D12_SHADER_RESOURCE_VIEW_DESC = undefined;
srv_desc.Format = conv.dxgiFormatForTextureView(view.format, view.aspect);
srv_desc.ViewDimension = conv.d3d12SrvDimension(view.dimension, view.texture.sample_count);
srv_desc.Shader4ComponentMapping = c.D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
switch (srv_desc.ViewDimension) {
c.D3D12_SRV_DIMENSION_TEXTURE1D => srv_desc.unnamed_0.Texture1D = .{
.MostDetailedMip = view.base_mip_level,
.MipLevels = view.mip_level_count,
.ResourceMinLODClamp = 0.0,
},
c.D3D12_SRV_DIMENSION_TEXTURE2D => srv_desc.unnamed_0.Texture2D = .{
.MostDetailedMip = view.base_mip_level,
.MipLevels = view.mip_level_count,
.PlaneSlice = 0, // TODO
.ResourceMinLODClamp = 0.0,
},
c.D3D12_SRV_DIMENSION_TEXTURE2DARRAY => srv_desc.unnamed_0.Texture2DArray = .{
.MostDetailedMip = view.base_mip_level,
.MipLevels = view.mip_level_count,
.FirstArraySlice = view.base_array_layer,
.ArraySize = view.array_layer_count,
.PlaneSlice = 0,
.ResourceMinLODClamp = 0.0,
},
c.D3D12_SRV_DIMENSION_TEXTURE2DMS => {},
c.D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY => srv_desc.unnamed_0.Texture2DMSArray = .{
.FirstArraySlice = view.base_array_layer,
.ArraySize = view.array_layer_count,
},
c.D3D12_SRV_DIMENSION_TEXTURE3D => srv_desc.unnamed_0.Texture3D = .{
.MostDetailedMip = view.base_mip_level,
.MipLevels = view.mip_level_count,
.ResourceMinLODClamp = 0.0,
},
c.D3D12_SRV_DIMENSION_TEXTURECUBE => srv_desc.unnamed_0.TextureCube = .{
.MostDetailedMip = view.base_mip_level,
.MipLevels = view.mip_level_count,
.ResourceMinLODClamp = 0.0,
},
c.D3D12_SRV_DIMENSION_TEXTURECUBEARRAY => srv_desc.unnamed_0.TextureCubeArray = .{
.MostDetailedMip = view.base_mip_level,
.MipLevels = view.mip_level_count,
.First2DArrayFace = view.base_array_layer, // TODO - does this need a conversion?
.NumCubes = view.array_layer_count, // TODO - does this need a conversion?
.ResourceMinLODClamp = 0.0,
},
else => {},
}
return srv_desc;
}
pub fn uavDesc(view: *TextureView) c.D3D12_UNORDERED_ACCESS_VIEW_DESC {
var uav_desc: c.D3D12_UNORDERED_ACCESS_VIEW_DESC = undefined;
uav_desc.Format = conv.dxgiFormatForTextureView(view.format, view.aspect);
uav_desc.ViewDimension = conv.d3d12UavDimension(view.dimension);
switch (uav_desc.ViewDimension) {
c.D3D12_UAV_DIMENSION_TEXTURE1D => uav_desc.unnamed_0.Texture1D = .{
.MipSlice = view.base_mip_level,
},
c.D3D12_UAV_DIMENSION_TEXTURE2D => uav_desc.unnamed_0.Texture2D = .{
.MipSlice = view.base_mip_level,
.PlaneSlice = 0, // TODO
},
c.D3D12_UAV_DIMENSION_TEXTURE2DARRAY => uav_desc.unnamed_0.Texture2DArray = .{
.MipSlice = view.base_mip_level,
.FirstArraySlice = view.base_array_layer,
.ArraySize = view.array_layer_count,
.PlaneSlice = 0,
},
c.D3D12_UAV_DIMENSION_TEXTURE3D => uav_desc.unnamed_0.Texture3D = .{
.MipSlice = view.base_mip_level,
.FirstWSlice = view.base_array_layer, // TODO - ??
.WSize = view.array_layer_count, // TODO - ??
},
else => {},
}
return uav_desc;
}
};
pub const Sampler = struct {
manager: utils.Manager(Sampler) = .{},
d3d_desc: c.D3D12_SAMPLER_DESC,
pub fn init(device: *Device, desc: *const sysgpu.Sampler.Descriptor) !*Sampler {
_ = device;
const d3d_desc = c.D3D12_SAMPLER_DESC{
.Filter = conv.d3d12Filter(desc.mag_filter, desc.min_filter, desc.mipmap_filter, desc.max_anisotropy),
.AddressU = conv.d3d12TextureAddressMode(desc.address_mode_u),
.AddressV = conv.d3d12TextureAddressMode(desc.address_mode_v),
.AddressW = conv.d3d12TextureAddressMode(desc.address_mode_w),
.MipLODBias = 0.0,
.MaxAnisotropy = desc.max_anisotropy,
.ComparisonFunc = if (desc.compare != .undefined) conv.d3d12ComparisonFunc(desc.compare) else c.D3D12_COMPARISON_FUNC_NEVER,
.BorderColor = [4]c.FLOAT{ 0.0, 0.0, 0.0, 0.0 },
.MinLOD = desc.lod_min_clamp,
.MaxLOD = desc.lod_max_clamp,
};
const sampler = try allocator.create(Sampler);
sampler.* = .{
.d3d_desc = d3d_desc,
};
return sampler;
}
pub fn deinit(sampler: *Sampler) void {
allocator.destroy(sampler);
}
};
pub const BindGroupLayout = struct {
const Entry = struct {
binding: u32,
visibility: sysgpu.ShaderStageFlags,
buffer: sysgpu.Buffer.BindingLayout = .{},
sampler: sysgpu.Sampler.BindingLayout = .{},
texture: sysgpu.Texture.BindingLayout = .{},
storage_texture: sysgpu.StorageTextureBindingLayout = .{},
range_type: c.D3D12_DESCRIPTOR_RANGE_TYPE,
table_index: ?u32,
dynamic_index: ?u32,
};
const DynamicEntry = struct {
parameter_type: c.D3D12_ROOT_PARAMETER_TYPE,
};
manager: utils.Manager(BindGroupLayout) = .{},
entries: std.ArrayListUnmanaged(Entry),
dynamic_entries: std.ArrayListUnmanaged(DynamicEntry),
general_table_size: u32,
sampler_table_size: u32,
pub fn init(device: *Device, desc: *const sysgpu.BindGroupLayout.Descriptor) !*BindGroupLayout {
_ = device;
var entries = std.ArrayListUnmanaged(Entry){};
errdefer entries.deinit(allocator);
var dynamic_entries = std.ArrayListUnmanaged(DynamicEntry){};
errdefer dynamic_entries.deinit(allocator);
var general_table_size: u32 = 0;
var sampler_table_size: u32 = 0;
for (0..desc.entry_count) |entry_index| {
const entry = desc.entries.?[entry_index];
var table_index: ?u32 = null;
var dynamic_index: ?u32 = null;
if (entry.buffer.has_dynamic_offset == .true) {
dynamic_index = @intCast(dynamic_entries.items.len);
try dynamic_entries.append(allocator, .{
.parameter_type = conv.d3d12RootParameterType(entry),
});
} else if (entry.sampler.type != .undefined) {
table_index = sampler_table_size;
sampler_table_size += 1;
} else {
table_index = general_table_size;
general_table_size += 1;
}
try entries.append(allocator, .{
.binding = entry.binding,
.visibility = entry.visibility,
.buffer = entry.buffer,
.sampler = entry.sampler,
.texture = entry.texture,
.storage_texture = entry.storage_texture,
.range_type = conv.d3d12DescriptorRangeType(entry),
.table_index = table_index,
.dynamic_index = dynamic_index,
});
}
const layout = try allocator.create(BindGroupLayout);
layout.* = .{
.entries = entries,
.dynamic_entries = dynamic_entries,
.general_table_size = general_table_size,
.sampler_table_size = sampler_table_size,
};
return layout;
}
pub fn deinit(layout: *BindGroupLayout) void {
layout.entries.deinit(allocator);
layout.dynamic_entries.deinit(allocator);
allocator.destroy(layout);
}
// Internal
pub fn getEntry(layout: *BindGroupLayout, binding: u32) ?*const Entry {
for (layout.entries.items) |*entry| {
if (entry.binding == binding)
return entry;
}
return null;
}
};
pub const BindGroup = struct {
const ResourceAccess = struct {
resource: *Resource,
uav: bool,
};
const DynamicResource = struct {
address: c.D3D12_GPU_VIRTUAL_ADDRESS,
parameter_type: c.D3D12_ROOT_PARAMETER_TYPE,
};
manager: utils.Manager(BindGroup) = .{},
device: *Device,
general_allocation: ?DescriptorAllocation,
general_table: ?c.D3D12_GPU_DESCRIPTOR_HANDLE,
sampler_allocation: ?DescriptorAllocation,
sampler_table: ?c.D3D12_GPU_DESCRIPTOR_HANDLE,
dynamic_resources: []DynamicResource,
buffers: std.ArrayListUnmanaged(*Buffer),
textures: std.ArrayListUnmanaged(*Texture),
accesses: std.ArrayListUnmanaged(ResourceAccess),
pub fn init(device: *Device, desc: *const sysgpu.BindGroup.Descriptor) !*BindGroup {
const d3d_device = device.d3d_device;
const layout: *BindGroupLayout = @ptrCast(@alignCast(desc.layout));
// General Descriptor Table
var general_allocation: ?DescriptorAllocation = null;
var general_table: ?c.D3D12_GPU_DESCRIPTOR_HANDLE = null;
if (layout.general_table_size > 0) {
const allocation = try device.general_heap.alloc();
general_allocation = allocation;
general_table = device.general_heap.gpuDescriptor(allocation.index);
for (0..desc.entry_count) |i| {
const entry = desc.entries.?[i];
const layout_entry = layout.getEntry(entry.binding) orelse return error.UnknownBinding;
if (layout_entry.sampler.type != .undefined)
continue;
if (layout_entry.table_index) |table_index| {
const dest_descriptor = device.general_heap.cpuDescriptor(allocation.index + table_index);
if (layout_entry.buffer.type != .undefined) {
const buffer: *Buffer = @ptrCast(@alignCast(entry.buffer.?));
const d3d_resource = buffer.resource.d3d_resource;
const buffer_location = d3d_resource.lpVtbl.*.GetGPUVirtualAddress.?(d3d_resource) + entry.offset;
switch (layout_entry.buffer.type) {
.undefined => unreachable,
.uniform => {
const cbv_desc: c.D3D12_CONSTANT_BUFFER_VIEW_DESC = .{
.BufferLocation = buffer_location,
.SizeInBytes = @intCast(utils.alignUp(entry.size, limits.min_uniform_buffer_offset_alignment)),
};
d3d_device.lpVtbl.*.CreateConstantBufferView.?(
d3d_device,
&cbv_desc,
dest_descriptor,
);
},
.storage => {
// TODO - switch to RWByteAddressBuffer after using DXC
const stride = entry.elem_size;
const uav_desc: c.D3D12_UNORDERED_ACCESS_VIEW_DESC = .{
.Format = c.DXGI_FORMAT_UNKNOWN,
.ViewDimension = c.D3D12_UAV_DIMENSION_BUFFER,
.unnamed_0 = .{
.Buffer = .{
.FirstElement = @intCast(entry.offset / stride),
.NumElements = @intCast(entry.size / stride),
.StructureByteStride = stride,
.CounterOffsetInBytes = 0,
.Flags = 0,
},
},
};
d3d_device.lpVtbl.*.CreateUnorderedAccessView.?(
d3d_device,
d3d_resource,
null,
&uav_desc,
dest_descriptor,
);
},
.read_only_storage => {
// TODO - switch to ByteAddressBuffer after using DXC
const stride = entry.elem_size;
const srv_desc: c.D3D12_SHADER_RESOURCE_VIEW_DESC = .{
.Format = c.DXGI_FORMAT_UNKNOWN,
.ViewDimension = c.D3D12_SRV_DIMENSION_BUFFER,
.Shader4ComponentMapping = c.D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
.unnamed_0 = .{
.Buffer = .{
.FirstElement = @intCast(entry.offset / stride),
.NumElements = @intCast(entry.size / stride),
.StructureByteStride = stride,
.Flags = 0,
},
},
};
d3d_device.lpVtbl.*.CreateShaderResourceView.?(
d3d_device,
d3d_resource,
&srv_desc,
dest_descriptor,
);
},
}
} else if (layout_entry.texture.sample_type != .undefined) {
const texture_view: *TextureView = @ptrCast(@alignCast(entry.texture_view.?));
const d3d_resource = texture_view.texture.resource.d3d_resource;
d3d_device.lpVtbl.*.CreateShaderResourceView.?(
d3d_device,
d3d_resource,
&texture_view.srvDesc(),
dest_descriptor,
);
} else if (layout_entry.storage_texture.format != .undefined) {
const texture_view: *TextureView = @ptrCast(@alignCast(entry.texture_view.?));
const d3d_resource = texture_view.texture.resource.d3d_resource;
d3d_device.lpVtbl.*.CreateUnorderedAccessView.?(
d3d_device,
d3d_resource,
null,
&texture_view.uavDesc(),
dest_descriptor,
);
}
}
}
}
// Sampler Descriptor Table
var sampler_allocation: ?DescriptorAllocation = null;
var sampler_table: ?c.D3D12_GPU_DESCRIPTOR_HANDLE = null;
if (layout.sampler_table_size > 0) {
const allocation = try device.sampler_heap.alloc();
sampler_allocation = allocation;
sampler_table = device.sampler_heap.gpuDescriptor(allocation.index);
for (0..desc.entry_count) |i| {
const entry = desc.entries.?[i];
const layout_entry = layout.getEntry(entry.binding) orelse return error.UnknownBinding;
if (layout_entry.sampler.type == .undefined)
continue;
if (layout_entry.table_index) |table_index| {
const dest_descriptor = device.sampler_heap.cpuDescriptor(allocation.index + table_index);
const sampler: *Sampler = @ptrCast(@alignCast(entry.sampler.?));
d3d_device.lpVtbl.*.CreateSampler.?(
d3d_device,
&sampler.d3d_desc,
dest_descriptor,
);
}
}
}
// Resource tracking and dynamic resources
var dynamic_resources = try allocator.alloc(DynamicResource, layout.dynamic_entries.items.len);
errdefer allocator.free(dynamic_resources);
var buffers = std.ArrayListUnmanaged(*Buffer){};
errdefer buffers.deinit(allocator);
var textures = std.ArrayListUnmanaged(*Texture){};
errdefer textures.deinit(allocator);
var accesses = std.ArrayListUnmanaged(ResourceAccess){};
errdefer accesses.deinit(allocator);
for (0..desc.entry_count) |i| {
const entry = desc.entries.?[i];
const layout_entry = layout.getEntry(entry.binding) orelse return error.UnknownBinding;
if (layout_entry.buffer.type != .undefined) {
const buffer: *Buffer = @ptrCast(@alignCast(entry.buffer.?));
const d3d_resource = buffer.resource.d3d_resource;
try buffers.append(allocator, buffer);
buffer.manager.reference();
const buffer_location = d3d_resource.lpVtbl.*.GetGPUVirtualAddress.?(d3d_resource) + entry.offset;
if (layout_entry.dynamic_index) |dynamic_index| {
const layout_dynamic_entry = layout.dynamic_entries.items[dynamic_index];
dynamic_resources[dynamic_index] = .{
.address = buffer_location,
.parameter_type = layout_dynamic_entry.parameter_type,
};
}
try accesses.append(allocator, .{ .resource = &buffer.resource, .uav = layout_entry.buffer.type == .storage });
} else if (layout_entry.sampler.type != .undefined) {} else if (layout_entry.texture.sample_type != .undefined) {
const texture_view: *TextureView = @ptrCast(@alignCast(entry.texture_view.?));
const texture = texture_view.texture;
try textures.append(allocator, texture);
texture.manager.reference();
try accesses.append(allocator, .{ .resource = &texture.resource, .uav = false });
} else if (layout_entry.storage_texture.format != .undefined) {
const texture_view: *TextureView = @ptrCast(@alignCast(entry.texture_view.?));
const texture = texture_view.texture;
try textures.append(allocator, texture);
texture.manager.reference();
try accesses.append(allocator, .{ .resource = &texture.resource, .uav = true });
}
}
const group = try allocator.create(BindGroup);
group.* = .{
.device = device,
.general_allocation = general_allocation,
.general_table = general_table,
.sampler_allocation = sampler_allocation,
.sampler_table = sampler_table,
.dynamic_resources = dynamic_resources,
.buffers = buffers,
.textures = textures,
.accesses = accesses,
};
return group;
}
pub fn deinit(group: *BindGroup) void {
if (group.general_allocation) |allocation|
group.device.general_heap.free(allocation);
if (group.sampler_allocation) |allocation|
group.device.sampler_heap.free(allocation);
for (group.buffers.items) |buffer| buffer.manager.release();
for (group.textures.items) |texture| texture.manager.release();
group.buffers.deinit(allocator);
group.textures.deinit(allocator);
group.accesses.deinit(allocator);
allocator.free(group.dynamic_resources);
allocator.destroy(group);
}
};
pub const PipelineLayout = struct {
pub const Function = struct {
stage: sysgpu.ShaderStageFlags,
shader_module: *ShaderModule,
entry_point: [*:0]const u8,
};
manager: utils.Manager(PipelineLayout) = .{},
root_signature: *c.ID3D12RootSignature,
group_layouts: []*BindGroupLayout,
group_parameter_indices: std.BoundedArray(u32, limits.max_bind_groups),
pub fn init(device: *Device, desc: *const sysgpu.PipelineLayout.Descriptor) !*PipelineLayout {
const d3d_device = device.d3d_device;
var hr: c.HRESULT = undefined;
// Per Bind Group:
// - up to 1 descriptor table for CBV/SRV/UAV
// - up to 1 descriptor table for Sampler
// - 1 root descriptor per dynamic resource
// Root signature 1.1 hints not supported yet
var group_layouts = try allocator.alloc(*BindGroupLayout, desc.bind_group_layout_count);
errdefer allocator.free(group_layouts);
var group_parameter_indices = std.BoundedArray(u32, limits.max_bind_groups){};
var parameter_count: u32 = 0;
var range_count: u32 = 0;
for (0..desc.bind_group_layout_count) |i| {
const layout: *BindGroupLayout = @ptrCast(@alignCast(desc.bind_group_layouts.?[i]));
layout.manager.reference();
group_layouts[i] = layout;
group_parameter_indices.appendAssumeCapacity(parameter_count);
var general_entry_count: u32 = 0;
var sampler_entry_count: u32 = 0;
for (layout.entries.items) |entry| {
if (entry.dynamic_index) |_| {
parameter_count += 1;
} else if (entry.sampler.type != .undefined) {
sampler_entry_count += 1;
range_count += 1;
} else {
general_entry_count += 1;
range_count += 1;
}
}
if (general_entry_count > 0)
parameter_count += 1;
if (sampler_entry_count > 0)
parameter_count += 1;
}
var parameters = try std.ArrayListUnmanaged(c.D3D12_ROOT_PARAMETER).initCapacity(allocator, parameter_count);
defer parameters.deinit(allocator);
var ranges = try std.ArrayListUnmanaged(c.D3D12_DESCRIPTOR_RANGE).initCapacity(allocator, range_count);
defer ranges.deinit(allocator);
for (0..desc.bind_group_layout_count) |group_index| {
const layout: *BindGroupLayout = group_layouts[group_index];
// General Table
{
const entry_range_base = ranges.items.len;
for (layout.entries.items) |entry| {
if (entry.dynamic_index == null and entry.sampler.type == .undefined) {
ranges.appendAssumeCapacity(.{
.RangeType = entry.range_type,
.NumDescriptors = 1,
.BaseShaderRegister = entry.binding,
.RegisterSpace = @intCast(group_index),
.OffsetInDescriptorsFromTableStart = c.D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
});
}
}
const entry_range_count = ranges.items.len - entry_range_base;
if (entry_range_count > 0) {
parameters.appendAssumeCapacity(.{
.ParameterType = c.D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.unnamed_0 = .{
.DescriptorTable = .{
.NumDescriptorRanges = @intCast(entry_range_count),
.pDescriptorRanges = &ranges.items[entry_range_base],
},
},
.ShaderVisibility = c.D3D12_SHADER_VISIBILITY_ALL,
});
}
}
// Sampler Table
{
const entry_range_base = ranges.items.len;
for (layout.entries.items) |entry| {
if (entry.dynamic_index == null and entry.sampler.type != .undefined) {
ranges.appendAssumeCapacity(.{
.RangeType = entry.range_type,
.NumDescriptors = 1,
.BaseShaderRegister = entry.binding,
.RegisterSpace = @intCast(group_index),
.OffsetInDescriptorsFromTableStart = c.D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
});
}
}
const entry_range_count = ranges.items.len - entry_range_base;
if (entry_range_count > 0) {
parameters.appendAssumeCapacity(.{
.ParameterType = c.D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.unnamed_0 = .{
.DescriptorTable = .{
.NumDescriptorRanges = @intCast(entry_range_count),
.pDescriptorRanges = &ranges.items[entry_range_base],
},
},
.ShaderVisibility = c.D3D12_SHADER_VISIBILITY_ALL,
});
}
}
// Dynamic Resources
for (layout.entries.items) |entry| {
if (entry.dynamic_index) |dynamic_index| {
const layout_dynamic_entry = layout.dynamic_entries.items[dynamic_index];
parameters.appendAssumeCapacity(.{
.ParameterType = layout_dynamic_entry.parameter_type,
.unnamed_0 = .{
.Descriptor = .{
.ShaderRegister = entry.binding,
.RegisterSpace = @intCast(group_index),
},
},
.ShaderVisibility = c.D3D12_SHADER_VISIBILITY_ALL,
});
}
}
}
var root_signature_blob: *c.ID3DBlob = undefined;
var opt_errors: ?*c.ID3DBlob = null;
hr = c.D3D12SerializeRootSignature(
&c.D3D12_ROOT_SIGNATURE_DESC{
.NumParameters = @intCast(parameters.items.len),
.pParameters = parameters.items.ptr,
.NumStaticSamplers = 0,
.pStaticSamplers = null,
.Flags = c.D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT, // TODO - would like a flag for this
},
c.D3D_ROOT_SIGNATURE_VERSION_1,
@ptrCast(&root_signature_blob),
@ptrCast(&opt_errors),
);
if (opt_errors) |errors| {
const message: [*:0]const u8 = @ptrCast(errors.lpVtbl.*.GetBufferPointer.?(errors).?);
std.debug.print("{s}\n", .{message});
_ = errors.lpVtbl.*.Release.?(errors);
}
if (hr != c.S_OK) {
return error.SerializeRootSignatureFailed;
}
defer _ = root_signature_blob.lpVtbl.*.Release.?(root_signature_blob);
var root_signature: *c.ID3D12RootSignature = undefined;
hr = d3d_device.lpVtbl.*.CreateRootSignature.?(
d3d_device,
0,
root_signature_blob.lpVtbl.*.GetBufferPointer.?(root_signature_blob),
root_signature_blob.lpVtbl.*.GetBufferSize.?(root_signature_blob),
&c.IID_ID3D12RootSignature,
@ptrCast(&root_signature),
);
errdefer _ = root_signature.lpVtbl.*.Release.?(root_signature);
// Result
const layout = try allocator.create(PipelineLayout);
layout.* = .{
.root_signature = root_signature,
.group_layouts = group_layouts,
.group_parameter_indices = group_parameter_indices,
};
return layout;
}
pub fn initDefault(device: *Device, default_pipeline_layout: utils.DefaultPipelineLayoutDescriptor) !*PipelineLayout {
const groups = default_pipeline_layout.groups;
var bind_group_layouts = std.BoundedArray(*sysgpu.BindGroupLayout, limits.max_bind_groups){};
defer {
for (bind_group_layouts.slice()) |bind_group_layout| bind_group_layout.release();
}
for (groups.slice()) |entries| {
const bind_group_layout = try device.createBindGroupLayout(
&sysgpu.BindGroupLayout.Descriptor.init(.{ .entries = entries.items }),
);
bind_group_layouts.appendAssumeCapacity(@ptrCast(bind_group_layout));
}
return device.createPipelineLayout(
&sysgpu.PipelineLayout.Descriptor.init(.{ .bind_group_layouts = bind_group_layouts.slice() }),
);
}
pub fn deinit(layout: *PipelineLayout) void {
const root_signature = layout.root_signature;
for (layout.group_layouts) |group_layout| group_layout.manager.release();
_ = root_signature.lpVtbl.*.Release.?(root_signature);
allocator.free(layout.group_layouts);
allocator.destroy(layout);
}
};
pub const ShaderModule = struct {
manager: utils.Manager(ShaderModule) = .{},
code: union(enum) {
code: []const u8,
air: *shader.Air,
},
pub fn initAir(device: *Device, air: *shader.Air) !*ShaderModule {
_ = device;
const module = try allocator.create(ShaderModule);
module.* = .{ .code = .{ .air = air } };
return module;
}
pub fn deinit(module: *ShaderModule) void {
if (module.code == .air) {
module.code.air.deinit(allocator);
allocator.destroy(module.code.air);
}
allocator.destroy(module);
}
// Internal
fn compile(module: *ShaderModule, entrypoint: [*:0]const u8, target: [*:0]const u8) !*c.ID3DBlob {
var hr: c.HRESULT = undefined;
const code = switch (module.code) {
.air => |air| try shader.CodeGen.generate(allocator, air, .hlsl, false, .{ .emit_source_file = "" }, null, null, null),
.code => |code| code,
};
defer if (module.code == .air) allocator.free(code);
var flags: u32 = 0;
if (debug_enabled)
flags |= c.D3DCOMPILE_DEBUG | c.D3DCOMPILE_SKIP_OPTIMIZATION;
var shader_blob: *c.ID3DBlob = undefined;
var opt_errors: ?*c.ID3DBlob = null;
hr = c.D3DCompile(
code.ptr,
code.len,
null,
null,
null,
entrypoint,
target,
flags,
0,
@ptrCast(&shader_blob),
@ptrCast(&opt_errors),
);
if (opt_errors) |errors| {
const message: [*:0]const u8 = @ptrCast(errors.lpVtbl.*.GetBufferPointer.?(errors).?);
std.debug.print("{s}\n", .{message});
_ = errors.lpVtbl.*.Release.?(errors);
}
if (hr != c.S_OK) {
return error.CompileShaderFailed;
}
return shader_blob;
}
};
pub const ComputePipeline = struct {
manager: utils.Manager(ComputePipeline) = .{},
device: *Device,
d3d_pipeline: *c.ID3D12PipelineState,
layout: *PipelineLayout,
pub fn init(device: *Device, desc: *const sysgpu.ComputePipeline.Descriptor) !*ComputePipeline {
const d3d_device = device.d3d_device;
var hr: c.HRESULT = undefined;
const compute_module: *ShaderModule = @ptrCast(@alignCast(desc.compute.module));
// Pipeline Layout
var layout: *PipelineLayout = undefined;
if (desc.layout) |layout_raw| {
layout = @ptrCast(@alignCast(layout_raw));
layout.manager.reference();
} else if (compute_module.code == .air) {
var layout_desc = utils.DefaultPipelineLayoutDescriptor.init(allocator);
defer layout_desc.deinit();
try layout_desc.addFunction(compute_module.code.air, .{ .compute = true }, desc.compute.entry_point);
layout = try PipelineLayout.initDefault(device, layout_desc);
} else {
@panic(
\\Cannot create pipeline descriptor autoamtically.
\\Please provide it yourself or write the shader in WGSL.
);
}
errdefer layout.manager.release();
// Shaders
const compute_shader = try compute_module.compile(desc.compute.entry_point, "cs_5_1");
defer _ = compute_shader.lpVtbl.*.Release.?(compute_shader);
// PSO
var d3d_pipeline: *c.ID3D12PipelineState = undefined;
hr = d3d_device.lpVtbl.*.CreateComputePipelineState.?(
d3d_device,
&c.D3D12_COMPUTE_PIPELINE_STATE_DESC{
.pRootSignature = layout.root_signature,
.CS = conv.d3d12ShaderBytecode(compute_shader),
.NodeMask = 0,
.CachedPSO = .{ .pCachedBlob = null, .CachedBlobSizeInBytes = 0 },
.Flags = c.D3D12_PIPELINE_STATE_FLAG_NONE,
},
&c.IID_ID3D12PipelineState,
@ptrCast(&d3d_pipeline),
);
if (hr != c.S_OK) {
return error.CreateComputePipelineFailed;
}
errdefer _ = d3d_pipeline.lpVtbl.*.Release.?(d3d_pipeline);
if (desc.label) |label|
setDebugName(@ptrCast(d3d_pipeline), label);
// Result
const pipeline = try allocator.create(ComputePipeline);
pipeline.* = .{
.device = device,
.d3d_pipeline = d3d_pipeline,
.layout = layout,
};
return pipeline;
}
pub fn deinit(pipeline: *ComputePipeline) void {
const d3d_pipeline = pipeline.d3d_pipeline;
pipeline.layout.manager.release();
_ = d3d_pipeline.lpVtbl.*.Release.?(d3d_pipeline);
allocator.destroy(pipeline);
}
pub fn getBindGroupLayout(pipeline: *ComputePipeline, group_index: u32) *BindGroupLayout {
return @ptrCast(pipeline.layout.group_layouts[group_index]);
}
};
pub const RenderPipeline = struct {
manager: utils.Manager(RenderPipeline) = .{},
device: *Device,
d3d_pipeline: *c.ID3D12PipelineState,
layout: *PipelineLayout,
topology: c.D3D12_PRIMITIVE_TOPOLOGY_TYPE,
vertex_strides: std.BoundedArray(c.UINT, limits.max_vertex_buffers),
pub fn init(device: *Device, desc: *const sysgpu.RenderPipeline.Descriptor) !*RenderPipeline {
const d3d_device = device.d3d_device;
var hr: c.HRESULT = undefined;
const vertex_module: *ShaderModule = @ptrCast(@alignCast(desc.vertex.module));
// Pipeline Layout
var layout: *PipelineLayout = undefined;
if (desc.layout) |layout_raw| {
layout = @ptrCast(@alignCast(layout_raw));
layout.manager.reference();
} else if (vertex_module.code == .air) {
var layout_desc = utils.DefaultPipelineLayoutDescriptor.init(allocator);
defer layout_desc.deinit();
try layout_desc.addFunction(vertex_module.code.air, .{ .vertex = true }, desc.vertex.entry_point);
if (desc.fragment) |frag| {
const frag_module: *ShaderModule = @ptrCast(@alignCast(frag.module));
if (frag_module.code == .air) {
try layout_desc.addFunction(frag_module.code.air, .{ .fragment = true }, frag.entry_point);
} else {
@panic(
\\Cannot create pipeline descriptor autoamtically.
\\Please provide it yourself or write the shader in WGSL.
);
}
}
layout = try PipelineLayout.initDefault(device, layout_desc);
} else {
@panic(
\\Cannot create pipeline descriptor autoamtically.
\\Please provide it yourself or write the shader in WGSL.
);
}
errdefer layout.manager.release();
// Shaders
const vertex_shader = try vertex_module.compile(desc.vertex.entry_point, "vs_5_1");
defer _ = vertex_shader.lpVtbl.*.Release.?(vertex_shader);
var opt_pixel_shader: ?*c.ID3DBlob = null;
if (desc.fragment) |frag| {
const frag_module: *ShaderModule = @ptrCast(@alignCast(frag.module));
opt_pixel_shader = try frag_module.compile(frag.entry_point, "ps_5_1");
}
defer if (opt_pixel_shader) |pixel_shader| {
_ = pixel_shader.lpVtbl.*.Release.?(pixel_shader);
};
// PSO
var input_elements = std.BoundedArray(c.D3D12_INPUT_ELEMENT_DESC, limits.max_vertex_buffers){};
var vertex_strides = std.BoundedArray(c.UINT, limits.max_vertex_buffers){};
for (0..desc.vertex.buffer_count) |i| {
const buffer = desc.vertex.buffers.?[i];
for (0..buffer.attribute_count) |j| {
const attr = buffer.attributes.?[j];
input_elements.appendAssumeCapacity(conv.d3d12InputElementDesc(i, buffer, attr));
}
vertex_strides.appendAssumeCapacity(@intCast(buffer.array_stride));
}
var num_render_targets: usize = 0;
var rtv_formats = [_]c.DXGI_FORMAT{c.DXGI_FORMAT_UNKNOWN} ** limits.max_color_attachments;
if (desc.fragment) |frag| {
num_render_targets = frag.target_count;
for (0..frag.target_count) |i| {
const target = frag.targets.?[i];
rtv_formats[i] = conv.dxgiFormatForTexture(target.format);
}
}
var d3d_pipeline: *c.ID3D12PipelineState = undefined;
hr = d3d_device.lpVtbl.*.CreateGraphicsPipelineState.?(
d3d_device,
&c.D3D12_GRAPHICS_PIPELINE_STATE_DESC{
.pRootSignature = layout.root_signature,
.VS = conv.d3d12ShaderBytecode(vertex_shader),
.PS = conv.d3d12ShaderBytecode(opt_pixel_shader),
.DS = conv.d3d12ShaderBytecode(null),
.HS = conv.d3d12ShaderBytecode(null),
.GS = conv.d3d12ShaderBytecode(null),
.StreamOutput = conv.d3d12StreamOutputDesc(),
.BlendState = conv.d3d12BlendDesc(desc),
.SampleMask = desc.multisample.mask,
.RasterizerState = conv.d3d12RasterizerDesc(desc),
.DepthStencilState = conv.d3d12DepthStencilDesc(desc.depth_stencil),
.InputLayout = .{
.pInputElementDescs = if (desc.vertex.buffer_count > 0) &input_elements.buffer else null,
.NumElements = @intCast(input_elements.len),
},
.IBStripCutValue = conv.d3d12IndexBufferStripCutValue(desc.primitive.strip_index_format),
.PrimitiveTopologyType = conv.d3d12PrimitiveTopologyType(desc.primitive.topology),
.NumRenderTargets = @intCast(num_render_targets),
.RTVFormats = rtv_formats,
.DSVFormat = if (desc.depth_stencil) |ds| conv.dxgiFormatForTexture(ds.format) else c.DXGI_FORMAT_UNKNOWN,
.SampleDesc = .{ .Count = desc.multisample.count, .Quality = 0 },
.NodeMask = 0,
.CachedPSO = .{ .pCachedBlob = null, .CachedBlobSizeInBytes = 0 },
.Flags = c.D3D12_PIPELINE_STATE_FLAG_NONE,
},
&c.IID_ID3D12PipelineState,
@ptrCast(&d3d_pipeline),
);
if (hr != c.S_OK) {
return error.CreateRenderPipelineFailed;
}
errdefer _ = d3d_pipeline.lpVtbl.*.Release.?(d3d_pipeline);
if (desc.label) |label|
setDebugName(@ptrCast(d3d_pipeline), label);
// Result
const pipeline = try allocator.create(RenderPipeline);
pipeline.* = .{
.d3d_pipeline = d3d_pipeline,
.device = device,
.layout = layout,
.topology = conv.d3d12PrimitiveTopology(desc.primitive.topology),
.vertex_strides = vertex_strides,
};
return pipeline;
}
pub fn deinit(pipeline: *RenderPipeline) void {
const d3d_pipeline = pipeline.d3d_pipeline;
pipeline.layout.manager.release();
_ = d3d_pipeline.lpVtbl.*.Release.?(d3d_pipeline);
allocator.destroy(pipeline);
}
pub fn getBindGroupLayout(pipeline: *RenderPipeline, group_index: u32) *BindGroupLayout {
return @ptrCast(pipeline.layout.group_layouts[group_index]);
}
};
pub const CommandBuffer = struct {
pub const StreamingResult = struct {
d3d_resource: *c.ID3D12Resource,
map: [*]u8,
offset: u32,
};
manager: utils.Manager(CommandBuffer) = .{},
device: *Device,
command_allocator: *c.ID3D12CommandAllocator,
command_list: *c.ID3D12GraphicsCommandList,
reference_tracker: *ReferenceTracker,
rtv_allocation: DescriptorAllocation = .{ .index = 0 },
rtv_next_index: u32 = rtv_block_size,
upload_buffer: ?*c.ID3D12Resource = null,
upload_map: ?[*]u8 = null,
upload_next_offset: u32 = upload_page_size,
pub fn init(device: *Device) !*CommandBuffer {
const command_allocator = try device.command_manager.createCommandAllocator();
errdefer device.command_manager.destroyCommandAllocator(command_allocator);
const command_list = try device.command_manager.createCommandList(command_allocator);
errdefer device.command_manager.destroyCommandList(command_list);
const heaps = [2]*c.ID3D12DescriptorHeap{ device.general_heap.d3d_heap, device.sampler_heap.d3d_heap };
command_list.lpVtbl.*.SetDescriptorHeaps.?(
command_list,
2,
&heaps,
);
const reference_tracker = try ReferenceTracker.init(device, command_allocator);
errdefer reference_tracker.deinit();
const command_buffer = try allocator.create(CommandBuffer);
command_buffer.* = .{
.device = device,
.command_allocator = command_allocator,
.command_list = command_list,
.reference_tracker = reference_tracker,
};
return command_buffer;
}
pub fn deinit(command_buffer: *CommandBuffer) void {
// reference_tracker lifetime is managed externally
// command_allocator lifetime is managed externally
// command_list lifetime is managed externally
allocator.destroy(command_buffer);
}
// Internal
pub fn upload(command_buffer: *CommandBuffer, size: u64) !StreamingResult {
if (command_buffer.upload_next_offset + size > upload_page_size) {
const streaming_manager = &command_buffer.device.streaming_manager;
var hr: c.HRESULT = undefined;
std.debug.assert(size <= upload_page_size); // TODO - support large uploads
const resource = try streaming_manager.acquire();
const d3d_resource = resource.d3d_resource;
try command_buffer.reference_tracker.referenceUploadPage(resource);
command_buffer.upload_buffer = d3d_resource;
var map: ?*anyopaque = null;
hr = d3d_resource.lpVtbl.*.Map.?(d3d_resource, 0, null, &map);
if (hr != c.S_OK) {
return error.MapForUploadFailed;
}
command_buffer.upload_map = @ptrCast(map);
command_buffer.upload_next_offset = 0;
}
const offset = command_buffer.upload_next_offset;
command_buffer.upload_next_offset = @intCast(utils.alignUp(offset + size, limits.min_uniform_buffer_offset_alignment));
return StreamingResult{
.d3d_resource = command_buffer.upload_buffer.?,
.map = command_buffer.upload_map.? + offset,
.offset = offset,
};
}
pub fn allocateRtvDescriptors(command_buffer: *CommandBuffer, count: usize) !c.D3D12_CPU_DESCRIPTOR_HANDLE {
if (count == 0) return .{ .ptr = 0 };
var rtv_heap = &command_buffer.device.rtv_heap;
if (command_buffer.rtv_next_index + count > rtv_block_size) {
command_buffer.rtv_allocation = try rtv_heap.alloc();
try command_buffer.reference_tracker.referenceRtvDescriptorBlock(command_buffer.rtv_allocation);
command_buffer.rtv_next_index = 0;
}
const index = command_buffer.rtv_next_index;
command_buffer.rtv_next_index = @intCast(index + count);
return rtv_heap.cpuDescriptor(command_buffer.rtv_allocation.index + index);
}
pub fn allocateDsvDescriptor(command_buffer: *CommandBuffer) !c.D3D12_CPU_DESCRIPTOR_HANDLE {
var dsv_heap = &command_buffer.device.dsv_heap;
const allocation = try dsv_heap.alloc();
try command_buffer.reference_tracker.referenceDsvDescriptorBlock(allocation);
return dsv_heap.cpuDescriptor(allocation.index);
}
};
pub const ReferenceTracker = struct {
device: *Device,
command_allocator: *c.ID3D12CommandAllocator,
fence_value: u64 = 0,
buffers: std.ArrayListUnmanaged(*Buffer) = .{},
textures: std.ArrayListUnmanaged(*Texture) = .{},
bind_groups: std.ArrayListUnmanaged(*BindGroup) = .{},
compute_pipelines: std.ArrayListUnmanaged(*ComputePipeline) = .{},
render_pipelines: std.ArrayListUnmanaged(*RenderPipeline) = .{},
rtv_descriptor_blocks: std.ArrayListUnmanaged(DescriptorAllocation) = .{},
dsv_descriptor_blocks: std.ArrayListUnmanaged(DescriptorAllocation) = .{},
upload_pages: std.ArrayListUnmanaged(Resource) = .{},
pub fn init(device: *Device, command_allocator: *c.ID3D12CommandAllocator) !*ReferenceTracker {
const tracker = try allocator.create(ReferenceTracker);
tracker.* = .{
.device = device,
.command_allocator = command_allocator,
};
return tracker;
}
pub fn deinit(tracker: *ReferenceTracker) void {
const device = tracker.device;
device.command_manager.destroyCommandAllocator(tracker.command_allocator);
for (tracker.buffers.items) |buffer| {
buffer.gpu_count -= 1;
buffer.manager.release();
}
for (tracker.textures.items) |texture| {
texture.manager.release();
}
for (tracker.bind_groups.items) |group| {
for (group.buffers.items) |buffer| buffer.gpu_count -= 1;
group.manager.release();
}
for (tracker.compute_pipelines.items) |pipeline| {
pipeline.manager.release();
}
for (tracker.render_pipelines.items) |pipeline| {
pipeline.manager.release();
}
for (tracker.rtv_descriptor_blocks.items) |block| {
device.rtv_heap.free(block);
}
for (tracker.dsv_descriptor_blocks.items) |block| {
device.dsv_heap.free(block);
}
for (tracker.upload_pages.items) |resource| {
device.streaming_manager.release(resource);
}
tracker.buffers.deinit(allocator);
tracker.textures.deinit(allocator);
tracker.bind_groups.deinit(allocator);
tracker.compute_pipelines.deinit(allocator);
tracker.render_pipelines.deinit(allocator);
tracker.rtv_descriptor_blocks.deinit(allocator);
tracker.dsv_descriptor_blocks.deinit(allocator);
tracker.upload_pages.deinit(allocator);
allocator.destroy(tracker);
}
pub fn referenceBuffer(tracker: *ReferenceTracker, buffer: *Buffer) !void {
buffer.manager.reference();
try tracker.buffers.append(allocator, buffer);
}
pub fn referenceTexture(tracker: *ReferenceTracker, texture: *Texture) !void {
texture.manager.reference();
try tracker.textures.append(allocator, texture);
}
pub fn referenceBindGroup(tracker: *ReferenceTracker, group: *BindGroup) !void {
group.manager.reference();
try tracker.bind_groups.append(allocator, group);
}
pub fn referenceComputePipeline(tracker: *ReferenceTracker, pipeline: *ComputePipeline) !void {
pipeline.manager.reference();
try tracker.compute_pipelines.append(allocator, pipeline);
}
pub fn referenceRenderPipeline(tracker: *ReferenceTracker, pipeline: *RenderPipeline) !void {
pipeline.manager.reference();
try tracker.render_pipelines.append(allocator, pipeline);
}
pub fn referenceRtvDescriptorBlock(tracker: *ReferenceTracker, block: DescriptorAllocation) !void {
try tracker.rtv_descriptor_blocks.append(allocator, block);
}
pub fn referenceDsvDescriptorBlock(tracker: *ReferenceTracker, block: DescriptorAllocation) !void {
try tracker.dsv_descriptor_blocks.append(allocator, block);
}
pub fn referenceUploadPage(tracker: *ReferenceTracker, upload_page: Resource) !void {
try tracker.upload_pages.append(allocator, upload_page);
}
pub fn submit(tracker: *ReferenceTracker, queue: *Queue) !void {
tracker.fence_value = queue.fence_value;
for (tracker.buffers.items) |buffer| {
buffer.gpu_count += 1;
}
for (tracker.bind_groups.items) |group| {
for (group.buffers.items) |buffer| buffer.gpu_count += 1;
}
try tracker.device.reference_trackers.append(allocator, tracker);
}
};
pub const CommandEncoder = struct {
manager: utils.Manager(CommandEncoder) = .{},
device: *Device,
command_buffer: *CommandBuffer,
reference_tracker: *ReferenceTracker,
state_tracker: StateTracker = .{},
pub fn init(device: *Device, desc: ?*const sysgpu.CommandEncoder.Descriptor) !*CommandEncoder {
// TODO
_ = desc;
const command_buffer = try CommandBuffer.init(device);
var encoder = try allocator.create(CommandEncoder);
encoder.* = .{
.device = device,
.command_buffer = command_buffer,
.reference_tracker = command_buffer.reference_tracker,
};
encoder.state_tracker.init(device);
return encoder;
}
pub fn deinit(encoder: *CommandEncoder) void {
encoder.state_tracker.deinit();
encoder.command_buffer.manager.release();
allocator.destroy(encoder);
}
pub fn beginComputePass(encoder: *CommandEncoder, desc: *const sysgpu.ComputePassDescriptor) !*ComputePassEncoder {
return ComputePassEncoder.init(encoder, desc);
}
pub fn beginRenderPass(encoder: *CommandEncoder, desc: *const sysgpu.RenderPassDescriptor) !*RenderPassEncoder {
try encoder.state_tracker.endPass();
return RenderPassEncoder.init(encoder, desc);
}
pub fn copyBufferToBuffer(
encoder: *CommandEncoder,
source: *Buffer,
source_offset: u64,
destination: *Buffer,
destination_offset: u64,
size: u64,
) !void {
const command_list = encoder.command_buffer.command_list;
try encoder.reference_tracker.referenceBuffer(source);
try encoder.reference_tracker.referenceBuffer(destination);
try encoder.state_tracker.transition(&source.resource, source.resource.read_state);
try encoder.state_tracker.transition(&destination.resource, c.D3D12_RESOURCE_STATE_COPY_DEST);
encoder.state_tracker.flush(command_list);
command_list.lpVtbl.*.CopyBufferRegion.?(
command_list,
destination.resource.d3d_resource,
destination_offset,
source.resource.d3d_resource,
source_offset,
size,
);
}
pub fn copyBufferToTexture(
encoder: *CommandEncoder,
source: *const sysgpu.ImageCopyBuffer,
destination: *const sysgpu.ImageCopyTexture,
copy_size_raw: *const sysgpu.Extent3D,
) !void {
const command_list = encoder.command_buffer.command_list;
const source_buffer: *Buffer = @ptrCast(@alignCast(source.buffer));
const destination_texture: *Texture = @ptrCast(@alignCast(destination.texture));
try encoder.reference_tracker.referenceBuffer(source_buffer);
try encoder.reference_tracker.referenceTexture(destination_texture);
try encoder.state_tracker.transition(&source_buffer.resource, source_buffer.resource.read_state);
try encoder.state_tracker.transition(&destination_texture.resource, c.D3D12_RESOURCE_STATE_COPY_DEST);
encoder.state_tracker.flush(command_list);
const copy_size = utils.calcExtent(destination_texture.dimension, copy_size_raw.*);
const destination_origin = utils.calcOrigin(destination_texture.dimension, destination.origin);
const destination_subresource_index = destination_texture.calcSubresource(destination.mip_level, destination_origin.array_slice);
std.debug.assert(copy_size.array_count == 1); // TODO
command_list.lpVtbl.*.CopyTextureRegion.?(
command_list,
&.{
.pResource = destination_texture.resource.d3d_resource,
.Type = c.D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.unnamed_0 = .{
.SubresourceIndex = destination_subresource_index,
},
},
destination_origin.x,
destination_origin.y,
destination_origin.z,
&.{
.pResource = source_buffer.resource.d3d_resource,
.Type = c.D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
.unnamed_0 = .{
.PlacedFootprint = .{
.Offset = source.layout.offset,
.Footprint = .{
.Format = conv.dxgiFormatForTexture(destination_texture.format),
.Width = copy_size.width,
.Height = copy_size.height,
.Depth = copy_size.depth,
.RowPitch = source.layout.bytes_per_row,
},
},
},
},
null,
);
}
pub fn copyTextureToTexture(
encoder: *CommandEncoder,
source: *const sysgpu.ImageCopyTexture,
destination: *const sysgpu.ImageCopyTexture,
copy_size_raw: *const sysgpu.Extent3D,
) !void {
const command_list = encoder.command_buffer.command_list;
const source_texture: *Texture = @ptrCast(@alignCast(source.texture));
const destination_texture: *Texture = @ptrCast(@alignCast(destination.texture));
try encoder.reference_tracker.referenceTexture(source_texture);
try encoder.reference_tracker.referenceTexture(destination_texture);
try encoder.state_tracker.transition(&source_texture.resource, source_texture.resource.read_state);
try encoder.state_tracker.transition(&destination_texture.resource, c.D3D12_RESOURCE_STATE_COPY_DEST);
encoder.state_tracker.flush(command_list);
const copy_size = utils.calcExtent(destination_texture.dimension, copy_size_raw.*);
const source_origin = utils.calcOrigin(source_texture.dimension, source.origin);
const destination_origin = utils.calcOrigin(destination_texture.dimension, destination.origin);
const source_subresource_index = source_texture.calcSubresource(source.mip_level, source_origin.array_slice);
const destination_subresource_index = destination_texture.calcSubresource(destination.mip_level, destination_origin.array_slice);
std.debug.assert(copy_size.array_count == 1); // TODO
command_list.lpVtbl.*.CopyTextureRegion.?(
command_list,
&.{
.pResource = destination_texture.resource.d3d_resource,
.Type = c.D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.unnamed_0 = .{
.SubresourceIndex = destination_subresource_index,
},
},
destination_origin.x,
destination_origin.y,
destination_origin.z,
&.{
.pResource = source_texture.resource.d3d_resource,
.Type = c.D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.unnamed_0 = .{
.SubresourceIndex = source_subresource_index,
},
},
&.{
.left = source_origin.x,
.top = source_origin.y,
.front = source_origin.z,
.right = source_origin.x + copy_size.width,
.bottom = source_origin.y + copy_size.height,
.back = source_origin.z + copy_size.depth,
},
);
}
pub fn finish(encoder: *CommandEncoder, desc: *const sysgpu.CommandBuffer.Descriptor) !*CommandBuffer {
const command_list = encoder.command_buffer.command_list;
var hr: c.HRESULT = undefined;
try encoder.state_tracker.endPass();
encoder.state_tracker.flush(command_list);
hr = command_list.lpVtbl.*.Close.?(command_list);
if (hr != c.S_OK) {
return error.CommandListCloseFailed;
}
if (desc.label) |label|
setDebugName(@ptrCast(command_list), label);
return encoder.command_buffer;
}
pub fn writeBuffer(encoder: *CommandEncoder, buffer: *Buffer, offset: u64, data: [*]const u8, size: u64) !void {
const command_list = encoder.command_buffer.command_list;
const stream = try encoder.command_buffer.upload(size);
@memcpy(stream.map[0..size], data[0..size]);
try encoder.reference_tracker.referenceBuffer(buffer);
try encoder.state_tracker.transition(&buffer.resource, c.D3D12_RESOURCE_STATE_COPY_DEST);
encoder.state_tracker.flush(command_list);
command_list.lpVtbl.*.CopyBufferRegion.?(
command_list,
buffer.resource.d3d_resource,
offset,
stream.d3d_resource,
stream.offset,
size,
);
}
pub fn writeTexture(
encoder: *CommandEncoder,
destination: *const sysgpu.ImageCopyTexture,
data: [*]const u8,
data_size: usize,
data_layout: *const sysgpu.Texture.DataLayout,
write_size_raw: *const sysgpu.Extent3D,
) !void {
const command_list = encoder.command_buffer.command_list;
const destination_texture: *Texture = @ptrCast(@alignCast(destination.texture));
const stream = try encoder.command_buffer.upload(data_size);
@memcpy(stream.map[0..data_size], data[0..data_size]);
try encoder.reference_tracker.referenceTexture(destination_texture);
try encoder.state_tracker.transition(&destination_texture.resource, c.D3D12_RESOURCE_STATE_COPY_DEST);
encoder.state_tracker.flush(command_list);
const write_size = utils.calcExtent(destination_texture.dimension, write_size_raw.*);
const destination_origin = utils.calcOrigin(destination_texture.dimension, destination.origin);
const destination_subresource_index = destination_texture.calcSubresource(destination.mip_level, destination_origin.array_slice);
std.debug.assert(write_size.array_count == 1); // TODO
command_list.lpVtbl.*.CopyTextureRegion.?(
command_list,
&.{
.pResource = destination_texture.resource.d3d_resource,
.Type = c.D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.unnamed_0 = .{
.SubresourceIndex = destination_subresource_index,
},
},
destination_origin.x,
destination_origin.y,
destination_origin.z,
&.{
.pResource = stream.d3d_resource,
.Type = c.D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
.unnamed_0 = .{
.PlacedFootprint = .{
.Offset = stream.offset,
.Footprint = .{
.Format = conv.dxgiFormatForTexture(destination_texture.format),
.Width = write_size.width,
.Height = write_size.height,
.Depth = write_size.depth,
.RowPitch = data_layout.bytes_per_row,
},
},
},
},
null,
);
}
};
pub const StateTracker = struct {
device: *Device = undefined,
written_set: std.AutoArrayHashMapUnmanaged(*Resource, c.D3D12_RESOURCE_STATES) = .{},
barriers: std.ArrayListUnmanaged(c.D3D12_RESOURCE_BARRIER) = .{},
pub fn init(tracker: *StateTracker, device: *Device) void {
tracker.device = device;
}
pub fn deinit(tracker: *StateTracker) void {
tracker.written_set.deinit(allocator);
tracker.barriers.deinit(allocator);
}
pub fn transition(tracker: *StateTracker, resource: *Resource, new_state: c.D3D12_RESOURCE_STATES) !void {
const current_state = tracker.written_set.get(resource) orelse resource.read_state;
if (current_state == c.D3D12_RESOURCE_STATE_UNORDERED_ACCESS and
new_state == c.D3D12_RESOURCE_STATE_UNORDERED_ACCESS)
{
try tracker.addUavBarrier(resource);
} else if (current_state != new_state) {
try tracker.written_set.put(allocator, resource, new_state);
try tracker.addTransitionBarrier(resource, current_state, new_state);
}
}
pub fn flush(tracker: *StateTracker, command_list: *c.ID3D12GraphicsCommandList) void {
if (tracker.barriers.items.len > 0) {
command_list.lpVtbl.*.ResourceBarrier.?(
command_list,
@intCast(tracker.barriers.items.len),
tracker.barriers.items.ptr,
);
tracker.barriers.clearRetainingCapacity();
}
}
pub fn endPass(tracker: *StateTracker) !void {
var it = tracker.written_set.iterator();
while (it.next()) |entry| {
const resource = entry.key_ptr.*;
const current_state = entry.value_ptr.*;
if (current_state != resource.read_state)
try tracker.addTransitionBarrier(resource, current_state, resource.read_state);
}
tracker.written_set.clearRetainingCapacity();
}
fn addUavBarrier(tracker: *StateTracker, resource: *Resource) !void {
try tracker.barriers.append(allocator, .{
.Type = c.D3D12_RESOURCE_BARRIER_TYPE_UAV,
.Flags = c.D3D12_RESOURCE_BARRIER_FLAG_NONE,
.unnamed_0 = .{
.UAV = .{
.pResource = resource.d3d_resource,
},
},
});
}
fn addTransitionBarrier(
tracker: *StateTracker,
resource: *Resource,
state_before: c.D3D12_RESOURCE_STATES,
state_after: c.D3D12_RESOURCE_STATES,
) !void {
try tracker.barriers.append(allocator, .{
.Type = c.D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = c.D3D12_RESOURCE_BARRIER_FLAG_NONE,
.unnamed_0 = .{
.Transition = .{
.pResource = resource.d3d_resource,
.Subresource = c.D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
.StateBefore = state_before,
.StateAfter = state_after,
},
},
});
}
};
pub const ComputePassEncoder = struct {
manager: utils.Manager(ComputePassEncoder) = .{},
command_list: *c.ID3D12GraphicsCommandList,
reference_tracker: *ReferenceTracker,
state_tracker: *StateTracker,
bind_groups: [limits.max_bind_groups]*BindGroup = undefined,
group_parameter_indices: []u32 = undefined,
pub fn init(cmd_encoder: *CommandEncoder, desc: *const sysgpu.ComputePassDescriptor) !*ComputePassEncoder {
_ = desc;
const command_list = cmd_encoder.command_buffer.command_list;
const encoder = try allocator.create(ComputePassEncoder);
encoder.* = .{
.command_list = command_list,
.reference_tracker = cmd_encoder.reference_tracker,
.state_tracker = &cmd_encoder.state_tracker,
};
return encoder;
}
pub fn deinit(encoder: *ComputePassEncoder) void {
allocator.destroy(encoder);
}
pub fn dispatchWorkgroups(
encoder: *ComputePassEncoder,
workgroup_count_x: u32,
workgroup_count_y: u32,
workgroup_count_z: u32,
) !void {
const command_list = encoder.command_list;
const bind_group_count = encoder.group_parameter_indices.len;
for (encoder.bind_groups[0..bind_group_count]) |group| {
for (group.accesses.items) |access| {
if (access.uav) {
try encoder.state_tracker.transition(access.resource, c.D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
} else {
try encoder.state_tracker.transition(access.resource, access.resource.read_state);
}
}
}
encoder.state_tracker.flush(command_list);
command_list.lpVtbl.*.Dispatch.?(
command_list,
workgroup_count_x,
workgroup_count_y,
workgroup_count_z,
);
}
pub fn end(encoder: *ComputePassEncoder) void {
_ = encoder;
}
pub fn setBindGroup(
encoder: *ComputePassEncoder,
group_index: u32,
group: *BindGroup,
dynamic_offset_count: usize,
dynamic_offsets: ?[*]const u32,
) !void {
const command_list = encoder.command_list;
try encoder.reference_tracker.referenceBindGroup(group);
encoder.bind_groups[group_index] = group;
var parameter_index = encoder.group_parameter_indices[group_index];
if (group.general_table) |table| {
command_list.lpVtbl.*.SetComputeRootDescriptorTable.?(
command_list,
parameter_index,
table,
);
parameter_index += 1;
}
if (group.sampler_table) |table| {
command_list.lpVtbl.*.SetComputeRootDescriptorTable.?(
command_list,
parameter_index,
table,
);
parameter_index += 1;
}
for (0..dynamic_offset_count) |i| {
const dynamic_resource = group.dynamic_resources[i];
const dynamic_offset = dynamic_offsets.?[i];
switch (dynamic_resource.parameter_type) {
c.D3D12_ROOT_PARAMETER_TYPE_CBV => command_list.lpVtbl.*.SetComputeRootConstantBufferView.?(
command_list,
parameter_index,
dynamic_resource.address + dynamic_offset,
),
c.D3D12_ROOT_PARAMETER_TYPE_SRV => command_list.lpVtbl.*.SetComputeRootShaderResourceView.?(
command_list,
parameter_index,
dynamic_resource.address + dynamic_offset,
),
c.D3D12_ROOT_PARAMETER_TYPE_UAV => command_list.lpVtbl.*.SetComputeRootUnorderedAccessView.?(
command_list,
parameter_index,
dynamic_resource.address + dynamic_offset,
),
else => {},
}
parameter_index += 1;
}
}
pub fn setPipeline(encoder: *ComputePassEncoder, pipeline: *ComputePipeline) !void {
const command_list = encoder.command_list;
try encoder.reference_tracker.referenceComputePipeline(pipeline);
encoder.group_parameter_indices = pipeline.layout.group_parameter_indices.slice();
command_list.lpVtbl.*.SetComputeRootSignature.?(
command_list,
pipeline.layout.root_signature,
);
command_list.lpVtbl.*.SetPipelineState.?(
command_list,
pipeline.d3d_pipeline,
);
}
};
pub const RenderPassEncoder = struct {
manager: utils.Manager(RenderPassEncoder) = .{},
command_list: *c.ID3D12GraphicsCommandList,
reference_tracker: *ReferenceTracker,
state_tracker: *StateTracker,
color_attachments: std.BoundedArray(sysgpu.RenderPassColorAttachment, limits.max_color_attachments) = .{},
depth_attachment: ?sysgpu.RenderPassDepthStencilAttachment,
group_parameter_indices: []u32 = undefined,
vertex_apply_count: u32 = 0,
vertex_buffer_views: [limits.max_vertex_buffers]c.D3D12_VERTEX_BUFFER_VIEW,
vertex_strides: []c.UINT = undefined,
pub fn init(cmd_encoder: *CommandEncoder, desc: *const sysgpu.RenderPassDescriptor) !*RenderPassEncoder {
const d3d_device = cmd_encoder.device.d3d_device;
const command_list = cmd_encoder.command_buffer.command_list;
var width: u32 = 0;
var height: u32 = 0;
var color_attachments: std.BoundedArray(sysgpu.RenderPassColorAttachment, limits.max_color_attachments) = .{};
var rtv_handles = try cmd_encoder.command_buffer.allocateRtvDescriptors(desc.color_attachment_count);
const descriptor_size = cmd_encoder.device.rtv_heap.descriptor_size;
var rtv_handle = rtv_handles;
for (0..desc.color_attachment_count) |i| {
const attach = desc.color_attachments.?[i];
if (attach.view) |view_raw| {
const view: *TextureView = @ptrCast(@alignCast(view_raw));
const texture = view.texture;
try cmd_encoder.reference_tracker.referenceTexture(texture);
try cmd_encoder.state_tracker.transition(&texture.resource, c.D3D12_RESOURCE_STATE_RENDER_TARGET);
width = view.width();
height = view.height();
color_attachments.appendAssumeCapacity(attach);
// TODO - rtvDesc()
d3d_device.lpVtbl.*.CreateRenderTargetView.?(
d3d_device,
texture.resource.d3d_resource,
null,
rtv_handle,
);
} else {
d3d_device.lpVtbl.*.CreateRenderTargetView.?(
d3d_device,
null,
&.{
.Format = c.DXGI_FORMAT_R8G8B8A8_UNORM,
.ViewDimension = c.D3D12_RTV_DIMENSION_TEXTURE2D,
.unnamed_0 = .{ .Texture2D = .{ .MipSlice = 0, .PlaneSlice = 0 } },
},
rtv_handle,
);
}
rtv_handle.ptr += descriptor_size;
}
var depth_attachment: ?sysgpu.RenderPassDepthStencilAttachment = null;
var dsv_handle: c.D3D12_CPU_DESCRIPTOR_HANDLE = .{ .ptr = 0 };
if (desc.depth_stencil_attachment) |attach| {
const view: *TextureView = @ptrCast(@alignCast(attach.view));
const texture = view.texture;
try cmd_encoder.reference_tracker.referenceTexture(texture);
try cmd_encoder.state_tracker.transition(&texture.resource, c.D3D12_RESOURCE_STATE_DEPTH_WRITE);
width = view.width();
height = view.height();
depth_attachment = attach.*;
dsv_handle = try cmd_encoder.command_buffer.allocateDsvDescriptor();
d3d_device.lpVtbl.*.CreateDepthStencilView.?(
d3d_device,
texture.resource.d3d_resource,
null,
dsv_handle,
);
}
cmd_encoder.state_tracker.flush(command_list);
command_list.lpVtbl.*.OMSetRenderTargets.?(
command_list,
@intCast(desc.color_attachment_count),
&rtv_handles,
c.TRUE,
if (desc.depth_stencil_attachment != null) &dsv_handle else null,
);
rtv_handle = rtv_handles;
for (0..desc.color_attachment_count) |i| {
const attach = desc.color_attachments.?[i];
if (attach.load_op == .clear) {
const clear_color = [4]f32{
@floatCast(attach.clear_value.r),
@floatCast(attach.clear_value.g),
@floatCast(attach.clear_value.b),
@floatCast(attach.clear_value.a),
};
command_list.lpVtbl.*.ClearRenderTargetView.?(
command_list,
rtv_handle,
&clear_color,
0,
null,
);
}
rtv_handle.ptr += descriptor_size;
}
if (desc.depth_stencil_attachment) |attach| {
var clear_flags: c.D3D12_CLEAR_FLAGS = 0;
if (attach.depth_load_op == .clear)
clear_flags |= c.D3D12_CLEAR_FLAG_DEPTH;
if (attach.stencil_load_op == .clear)
clear_flags |= c.D3D12_CLEAR_FLAG_STENCIL;
if (clear_flags != 0) {
command_list.lpVtbl.*.ClearDepthStencilView.?(
command_list,
dsv_handle,
clear_flags,
attach.depth_clear_value,
@intCast(attach.stencil_clear_value),
0,
null,
);
}
}
const viewport = c.D3D12_VIEWPORT{
.TopLeftX = 0,
.TopLeftY = 0,
.Width = @floatFromInt(width),
.Height = @floatFromInt(height),
.MinDepth = 0,
.MaxDepth = 1,
};
const scissor_rect = c.D3D12_RECT{
.left = 0,
.top = 0,
.right = @intCast(width),
.bottom = @intCast(height),
};
command_list.lpVtbl.*.RSSetViewports.?(command_list, 1, &viewport);
command_list.lpVtbl.*.RSSetScissorRects.?(command_list, 1, &scissor_rect);
// Result
const encoder = try allocator.create(RenderPassEncoder);
encoder.* = .{
.command_list = command_list,
.color_attachments = color_attachments,
.depth_attachment = depth_attachment,
.reference_tracker = cmd_encoder.reference_tracker,
.state_tracker = &cmd_encoder.state_tracker,
.vertex_buffer_views = std.mem.zeroes([limits.max_vertex_buffers]c.D3D12_VERTEX_BUFFER_VIEW),
};
return encoder;
}
pub fn deinit(encoder: *RenderPassEncoder) void {
allocator.destroy(encoder);
}
pub fn draw(
encoder: *RenderPassEncoder,
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
) !void {
const command_list = encoder.command_list;
encoder.applyVertexBuffers();
command_list.lpVtbl.*.DrawInstanced.?(
command_list,
vertex_count,
instance_count,
first_vertex,
first_instance,
);
}
pub fn drawIndexed(
encoder: *RenderPassEncoder,
index_count: u32,
instance_count: u32,
first_index: u32,
base_vertex: i32,
first_instance: u32,
) !void {
const command_list = encoder.command_list;
encoder.applyVertexBuffers();
command_list.lpVtbl.*.DrawIndexedInstanced.?(
command_list,
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
);
}
pub fn end(encoder: *RenderPassEncoder) !void {
const command_list = encoder.command_list;
for (encoder.color_attachments.slice()) |attach| {
const view: *TextureView = @ptrCast(@alignCast(attach.view.?));
if (attach.resolve_target) |resolve_target_raw| {
const resolve_target: *TextureView = @ptrCast(@alignCast(resolve_target_raw));
try encoder.reference_tracker.referenceTexture(resolve_target.texture);
try encoder.state_tracker.transition(&view.texture.resource, c.D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
try encoder.state_tracker.transition(&resolve_target.texture.resource, c.D3D12_RESOURCE_STATE_RESOLVE_DEST);
encoder.state_tracker.flush(command_list);
// Format
const resolve_d3d_resource = resolve_target.texture.resource.d3d_resource;
const view_d3d_resource = view.texture.resource.d3d_resource;
var d3d_desc: c.D3D12_RESOURCE_DESC = undefined;
var format: c.DXGI_FORMAT = undefined;
_ = resolve_d3d_resource.lpVtbl.*.GetDesc.?(resolve_d3d_resource, &d3d_desc);
format = d3d_desc.Format;
if (conv.dxgiFormatIsTypeless(format)) {
_ = view_d3d_resource.lpVtbl.*.GetDesc.?(view_d3d_resource, &d3d_desc);
format = d3d_desc.Format;
if (conv.dxgiFormatIsTypeless(format)) {
return error.NoTypedFormat;
}
}
command_list.lpVtbl.*.ResolveSubresource.?(
command_list,
resolve_target.texture.resource.d3d_resource,
resolve_target.base_subresource,
view.texture.resource.d3d_resource,
view.base_subresource,
format,
);
try encoder.state_tracker.transition(&resolve_target.texture.resource, resolve_target.texture.resource.read_state);
}
try encoder.state_tracker.transition(&view.texture.resource, view.texture.resource.read_state);
}
if (encoder.depth_attachment) |attach| {
const view: *TextureView = @ptrCast(@alignCast(attach.view));
try encoder.state_tracker.transition(&view.texture.resource, view.texture.resource.read_state);
}
}
pub fn setBindGroup(
encoder: *RenderPassEncoder,
group_index: u32,
group: *BindGroup,
dynamic_offset_count: usize,
dynamic_offsets: ?[*]const u32,
) !void {
const command_list = encoder.command_list;
try encoder.reference_tracker.referenceBindGroup(group);
var parameter_index = encoder.group_parameter_indices[group_index];
if (group.general_table) |table| {
command_list.lpVtbl.*.SetGraphicsRootDescriptorTable.?(
command_list,
parameter_index,
table,
);
parameter_index += 1;
}
if (group.sampler_table) |table| {
command_list.lpVtbl.*.SetGraphicsRootDescriptorTable.?(
command_list,
parameter_index,
table,
);
parameter_index += 1;
}
for (0..dynamic_offset_count) |i| {
const dynamic_resource = group.dynamic_resources[i];
const dynamic_offset = dynamic_offsets.?[i];
switch (dynamic_resource.parameter_type) {
c.D3D12_ROOT_PARAMETER_TYPE_CBV => command_list.lpVtbl.*.SetGraphicsRootConstantBufferView.?(
command_list,
parameter_index,
dynamic_resource.address + dynamic_offset,
),
c.D3D12_ROOT_PARAMETER_TYPE_SRV => command_list.lpVtbl.*.SetGraphicsRootShaderResourceView.?(
command_list,
parameter_index,
dynamic_resource.address + dynamic_offset,
),
c.D3D12_ROOT_PARAMETER_TYPE_UAV => command_list.lpVtbl.*.SetGraphicsRootUnorderedAccessView.?(
command_list,
parameter_index,
dynamic_resource.address + dynamic_offset,
),
else => {},
}
parameter_index += 1;
}
}
pub fn setIndexBuffer(
encoder: *RenderPassEncoder,
buffer: *Buffer,
format: sysgpu.IndexFormat,
offset: u64,
size: u64,
) !void {
const command_list = encoder.command_list;
const d3d_resource = buffer.resource.d3d_resource;
try encoder.reference_tracker.referenceBuffer(buffer);
const d3d_size: u32 = @intCast(if (size == sysgpu.whole_size) buffer.size - offset else size);
command_list.lpVtbl.*.IASetIndexBuffer.?(
command_list,
&c.D3D12_INDEX_BUFFER_VIEW{
.BufferLocation = d3d_resource.lpVtbl.*.GetGPUVirtualAddress.?(d3d_resource) + offset,
.SizeInBytes = d3d_size,
.Format = conv.dxgiFormatForIndex(format),
},
);
}
pub fn setPipeline(encoder: *RenderPassEncoder, pipeline: *RenderPipeline) !void {
const command_list = encoder.command_list;
try encoder.reference_tracker.referenceRenderPipeline(pipeline);
encoder.group_parameter_indices = pipeline.layout.group_parameter_indices.slice();
encoder.vertex_strides = pipeline.vertex_strides.slice();
command_list.lpVtbl.*.SetGraphicsRootSignature.?(
command_list,
pipeline.layout.root_signature,
);
command_list.lpVtbl.*.SetPipelineState.?(
command_list,
pipeline.d3d_pipeline,
);
command_list.lpVtbl.*.IASetPrimitiveTopology.?(
command_list,
pipeline.topology,
);
}
pub fn setScissorRect(encoder: *RenderPassEncoder, x: u32, y: u32, width: u32, height: u32) !void {
const command_list = encoder.command_list;
const scissor_rect = c.D3D12_RECT{
.left = @intCast(x),
.top = @intCast(y),
.right = @intCast(x + width),
.bottom = @intCast(y + height),
};
command_list.lpVtbl.*.RSSetScissorRects.?(command_list, 1, &scissor_rect);
}
pub fn setVertexBuffer(encoder: *RenderPassEncoder, slot: u32, buffer: *Buffer, offset: u64, size: u64) !void {
const d3d_resource = buffer.resource.d3d_resource;
try encoder.reference_tracker.referenceBuffer(buffer);
var view = &encoder.vertex_buffer_views[slot];
view.BufferLocation = d3d_resource.lpVtbl.*.GetGPUVirtualAddress.?(d3d_resource) + offset;
view.SizeInBytes = @intCast(size);
// StrideInBytes deferred until draw()
encoder.vertex_apply_count = @max(encoder.vertex_apply_count, slot + 1);
}
pub fn setViewport(
encoder: *RenderPassEncoder,
x: f32,
y: f32,
width: f32,
height: f32,
min_depth: f32,
max_depth: f32,
) !void {
const command_list = encoder.command_list;
const viewport = c.D3D12_VIEWPORT{
.TopLeftX = x,
.TopLeftY = y,
.Width = width,
.Height = height,
.MinDepth = min_depth,
.MaxDepth = max_depth,
};
command_list.lpVtbl.*.RSSetViewports.?(command_list, 1, &viewport);
}
// Private
fn applyVertexBuffers(encoder: *RenderPassEncoder) void {
if (encoder.vertex_apply_count > 0) {
const command_list = encoder.command_list;
for (0..encoder.vertex_apply_count) |i| {
var view = &encoder.vertex_buffer_views[i];
view.StrideInBytes = encoder.vertex_strides[i];
}
command_list.lpVtbl.*.IASetVertexBuffers.?(
command_list,
0,
encoder.vertex_apply_count,
&encoder.vertex_buffer_views,
);
encoder.vertex_apply_count = 0;
}
}
};
pub const Queue = struct {
manager: utils.Manager(Queue) = .{},
device: *Device,
d3d_command_queue: *c.ID3D12CommandQueue,
fence: *c.ID3D12Fence,
fence_value: u64 = 0,
fence_event: c.HANDLE,
command_encoder: ?*CommandEncoder = null,
pub fn init(device: *Device) !Queue {
const d3d_device = device.d3d_device;
var hr: c.HRESULT = undefined;
// Command Queue
var d3d_command_queue: *c.ID3D12CommandQueue = undefined;
hr = d3d_device.lpVtbl.*.CreateCommandQueue.?(
d3d_device,
&c.D3D12_COMMAND_QUEUE_DESC{
.Type = c.D3D12_COMMAND_LIST_TYPE_DIRECT,
.Priority = c.D3D12_COMMAND_QUEUE_PRIORITY_NORMAL,
.Flags = c.D3D12_COMMAND_QUEUE_FLAG_NONE,
.NodeMask = 0,
},
&c.IID_ID3D12CommandQueue,
@ptrCast(&d3d_command_queue),
);
if (hr != c.S_OK) {
return error.CreateCommandQueueFailed;
}
errdefer _ = d3d_command_queue.lpVtbl.*.Release.?(d3d_command_queue);
// Fence
var fence: *c.ID3D12Fence = undefined;
hr = d3d_device.lpVtbl.*.CreateFence.?(
d3d_device,
0,
c.D3D12_FENCE_FLAG_NONE,
&c.IID_ID3D12Fence,
@ptrCast(&fence),
);
if (hr != c.S_OK) {
return error.CreateFenceFailed;
}
errdefer _ = fence.lpVtbl.*.Release.?(fence);
// Fence Event
const fence_event = c.CreateEventW(null, c.FALSE, c.FALSE, null);
if (fence_event == null) {
return error.CreateEventFailed;
}
errdefer _ = c.CloseHandle(fence_event);
// Result
return .{
.device = device,
.d3d_command_queue = d3d_command_queue,
.fence = fence,
.fence_event = fence_event,
};
}
pub fn deinit(queue: *Queue) void {
const d3d_command_queue = queue.d3d_command_queue;
const fence = queue.fence;
queue.waitUntil(queue.fence_value);
if (queue.command_encoder) |command_encoder| command_encoder.manager.release();
_ = d3d_command_queue.lpVtbl.*.Release.?(d3d_command_queue);
_ = fence.lpVtbl.*.Release.?(fence);
_ = c.CloseHandle(queue.fence_event);
}
pub fn submit(queue: *Queue, command_buffers: []const *CommandBuffer) !void {
var command_manager = &queue.device.command_manager;
const d3d_command_queue = queue.d3d_command_queue;
var command_lists = try std.ArrayListUnmanaged(*c.ID3D12GraphicsCommandList).initCapacity(
allocator,
command_buffers.len + 1,
);
defer command_lists.deinit(allocator);
queue.fence_value += 1;
if (queue.command_encoder) |command_encoder| {
const command_buffer = try command_encoder.finish(&.{});
command_buffer.manager.reference(); // handled in main.zig
defer command_buffer.manager.release();
command_lists.appendAssumeCapacity(command_buffer.command_list);
try command_buffer.reference_tracker.submit(queue);
command_encoder.manager.release();
queue.command_encoder = null;
}
for (command_buffers) |command_buffer| {
command_lists.appendAssumeCapacity(command_buffer.command_list);
try command_buffer.reference_tracker.submit(queue);
}
d3d_command_queue.lpVtbl.*.ExecuteCommandLists.?(
d3d_command_queue,
@intCast(command_lists.items.len),
@ptrCast(command_lists.items.ptr),
);
for (command_lists.items) |command_list| {
command_manager.destroyCommandList(command_list);
}
try queue.signal();
}
pub fn writeBuffer(queue: *Queue, buffer: *Buffer, offset: u64, data: [*]const u8, size: u64) !void {
const encoder = try queue.getCommandEncoder();
try encoder.writeBuffer(buffer, offset, data, size);
}
pub fn writeTexture(
queue: *Queue,
destination: *const sysgpu.ImageCopyTexture,
data: [*]const u8,
data_size: usize,
data_layout: *const sysgpu.Texture.DataLayout,
write_size: *const sysgpu.Extent3D,
) !void {
const encoder = try queue.getCommandEncoder();
try encoder.writeTexture(destination, data, data_size, data_layout, write_size);
}
// Internal
pub fn signal(queue: *Queue) !void {
const d3d_command_queue = queue.d3d_command_queue;
var hr: c.HRESULT = undefined;
hr = d3d_command_queue.lpVtbl.*.Signal.?(
d3d_command_queue,
queue.fence,
queue.fence_value,
);
if (hr != c.S_OK) {
return error.SignalFailed;
}
}
pub fn waitUntil(queue: *Queue, fence_value: u64) void {
const fence = queue.fence;
const fence_event = queue.fence_event;
var hr: c.HRESULT = undefined;
const completed_value = fence.lpVtbl.*.GetCompletedValue.?(fence);
if (completed_value >= fence_value)
return;
hr = fence.lpVtbl.*.SetEventOnCompletion.?(
fence,
fence_value,
fence_event,
);
std.debug.assert(hr == c.S_OK);
const result = c.WaitForSingleObject(fence_event, c.INFINITE);
std.debug.assert(result == c.WAIT_OBJECT_0);
}
// Private
fn getCommandEncoder(queue: *Queue) !*CommandEncoder {
if (queue.command_encoder) |command_encoder| return command_encoder;
const command_encoder = try CommandEncoder.init(queue.device, &.{});
queue.command_encoder = command_encoder;
return command_encoder;
}
};