diff --git a/examples/bench_cp.zig b/examples/bench_cp.zig index 635b14e..fb0056c 100644 --- a/examples/bench_cp.zig +++ b/examples/bench_cp.zig @@ -65,7 +65,7 @@ pub fn main(init: std.process.Init) !void { const gloc = grena.gpuAllocator(); - const add_pip = try GpuCompute.init(device, @embedFile("shaders/add.wgsl"), .{ .bindings = &.{ + const add_pip = try GpuCompute.init(gloc, @embedFile("shaders/add.wgsl"), .{ .bindings = &.{ .{ .element_size = @sizeOf(f16) }, .{ .element_size = @sizeOf(f16) }, .{ .element_size = @sizeOf(f16) }, diff --git a/examples/circle.zig b/examples/circle.zig index a508626..d3754e9 100644 --- a/examples/circle.zig +++ b/examples/circle.zig @@ -10,6 +10,10 @@ const GpuTextureView = gpu.GpuTextureView; const width: u32 = 512; const height: u32 = 512; +// Note: Everything using a GpuAllocator in init from an GpuAllocatorArena is safely +// tied to it which will automatically release them when deinit the arena itself. +// You can also manually call x.deinit() if desired. + pub fn main(init: std.process.Init) !void { const allocator = init.gpa; @@ -24,7 +28,7 @@ pub fn main(init: std.process.Init) !void { // 3. Load Render Pipeline const circle_rp = try GpuRender.init( - device, // Change to gloc + track them + gloc, @embedFile("shaders/circle.wgsl"), .{ .bindings = &.{}, .texture_format = .RGBA8Unorm, .topology = .TriangleStrip }, ); @@ -55,7 +59,6 @@ pub fn main(init: std.process.Init) !void { // 9. Write a simple ppm image try savePpm(init.io, "circle.ppm", width, height, pixels); - std.debug.print("Successfully rendered circle to circle.ppm!\n", .{}); } fn savePpm(io: std.Io, filename: []const u8, w: u32, h: u32, rgba_pixels: []const u8) !void { diff --git a/examples/compute.zig b/examples/compute.zig index 46c0176..2c44697 100644 --- a/examples/compute.zig +++ b/examples/compute.zig @@ -19,7 +19,7 @@ pub fn main(init: std.process.Init) !void { // 3. Load the WGSL compute pipeline const add_cp = try GpuCompute.init( - device, + gloc, @embedFile("shaders/add.wgsl"), .{ .bindings = &.{ .{ .element_size = @sizeOf(f16) }, diff --git a/src/GpuAllocator.zig b/src/GpuAllocator.zig index 517026e..977b327 100644 --- a/src/GpuAllocator.zig +++ b/src/GpuAllocator.zig @@ -8,6 +8,10 @@ pub const VTable = struct { freeTexture: *const fn (ctx: *anyopaque, buf_raw: c.WGPUTexture) void, allocTextureView: *const fn (ctx: *anyopaque, texture: c.WGPUTexture, desc: c.WGPUTextureViewDescriptor) anyerror!c.WGPUTextureView, freeTextureView: *const fn (ctx: *anyopaque, buf_raw: c.WGPUTextureView) void, + allocRenderPipeline: *const fn (ctx: *anyopaque, desc: c.WGPURenderPipelineDescriptor) anyerror!c.WGPURenderPipeline, + freeRenderPipeline: *const fn (ctx: *anyopaque, buf_raw: c.WGPURenderPipeline) void, + allocComputePipeline: *const fn (ctx: *anyopaque, desc: c.WGPUComputePipelineDescriptor) anyerror!c.WGPUComputePipeline, + freeComputePipeline: *const fn (ctx: *anyopaque, buf_raw: c.WGPUComputePipeline) void, }; device: GpuDevice, @@ -18,22 +22,38 @@ pub fn allocBuffer(self: @This(), desc: c.WGPUBufferDescriptor) !c.WGPUBuffer { return self.vtable.allocBuffer(self.ptr, desc); } -pub fn freeBuffer(self: @This(), buf_raw: c.WGPUBuffer) void { - self.vtable.freeBuffer(self.ptr, buf_raw); +pub fn freeBuffer(self: @This(), raw: c.WGPUBuffer) void { + self.vtable.freeBuffer(self.ptr, raw); } pub fn allocTexture(self: @This(), desc: c.WGPUTextureDescriptor) !c.WGPUTexture { return self.vtable.allocTexture(self.ptr, desc); } -pub fn freeTexture(self: @This(), buf_raw: c.WGPUTexture) void { - self.vtable.freeTexture(self.ptr, buf_raw); +pub fn freeTexture(self: @This(), raw: c.WGPUTexture) void { + self.vtable.freeTexture(self.ptr, raw); } pub fn allocTextureView(self: @This(), texture: c.WGPUTexture, desc: c.WGPUTextureViewDescriptor) !c.WGPUTextureView { return self.vtable.allocTextureView(self.ptr, texture, desc); } -pub fn freeTextureView(self: @This(), buf_raw: c.WGPUTextureView) void { - self.vtable.freeTextureView(self.ptr, buf_raw); +pub fn freeTextureView(self: @This(), raw: c.WGPUTextureView) void { + self.vtable.freeTextureView(self.ptr, raw); +} + +pub fn allocRenderPipeline(self: @This(), desc: c.WGPURenderPipelineDescriptor) !c.WGPURenderPipeline { + return self.vtable.allocRenderPipeline(self.ptr, desc); +} + +pub fn freeRenderPipeline(self: @This(), raw: c.WGPURenderPipeline) void { + self.vtable.freeRenderPipeline(self.ptr, raw); +} + +pub fn allocComputePipeline(self: @This(), desc: c.WGPUComputePipelineDescriptor) !c.WGPUComputePipeline { + return self.vtable.allocComputePipeline(self.ptr, desc); +} + +pub fn freeComputePipeline(self: @This(), raw: c.WGPUComputePipeline) void { + self.vtable.freeComputePipeline(self.ptr, raw); } diff --git a/src/GpuArena.zig b/src/GpuArena.zig index 0353a8d..7644caa 100644 --- a/src/GpuArena.zig +++ b/src/GpuArena.zig @@ -8,6 +8,8 @@ device: GpuDevice, tracked_buffers: std.AutoHashMap(c.WGPUBuffer, c.WGPUBufferDescriptor), tracked_textures: std.AutoHashMap(c.WGPUTexture, c.WGPUTextureDescriptor), tracked_views: std.AutoHashMap(c.WGPUTextureView, c.WGPUTextureViewDescriptor), +tracked_renders: std.AutoHashMap(c.WGPURenderPipeline, c.WGPURenderPipelineDescriptor), +tracked_computes: std.AutoHashMap(c.WGPUComputePipeline, c.WGPUComputePipelineDescriptor), allocated_vram_bytes: u64 = 0, pub fn init(cpu_allocator: std.mem.Allocator, device: GpuDevice) @This() { @@ -16,26 +18,38 @@ pub fn init(cpu_allocator: std.mem.Allocator, device: GpuDevice) @This() { .tracked_buffers = .init(cpu_allocator), .tracked_textures = .init(cpu_allocator), .tracked_views = .init(cpu_allocator), + .tracked_computes = .init(cpu_allocator), + .tracked_renders = .init(cpu_allocator), }; } pub fn deinit(self: *@This()) void { + const gloc = self.gpuAllocator(); + var it_buffer = self.tracked_buffers.keyIterator(); - while (it_buffer.next()) |buf_ptr| { - c.wgpuBufferDestroy(buf_ptr.*); - c.wgpuBufferRelease(buf_ptr.*); - } + while (it_buffer.next()) |buf_ptr| + gloc.freeBuffer(buf_ptr.*); self.tracked_buffers.deinit(); - var it_texture = self.tracked_textures.keyIterator(); - while (it_texture.next()) |tex_ptr| - c.wgpuTextureRelease(tex_ptr.*); + var it_tex = self.tracked_textures.keyIterator(); + while (it_tex.next()) |buf_ptr| + gloc.freeTexture(buf_ptr.*); self.tracked_textures.deinit(); var it_view = self.tracked_views.keyIterator(); - while (it_view.next()) |view_ptr| - c.wgpuTextureViewRelease(view_ptr.*); + while (it_view.next()) |buf_ptr| + gloc.freeTextureView(buf_ptr.*); self.tracked_views.deinit(); + + var it_render = self.tracked_renders.keyIterator(); + while (it_render.next()) |buf_ptr| + gloc.freeRenderPipeline(buf_ptr.*); + self.tracked_renders.deinit(); + + var it_compute = self.tracked_computes.keyIterator(); + while (it_compute.next()) |buf_ptr| + gloc.freeComputePipeline(buf_ptr.*); + self.tracked_computes.deinit(); } /// Returns the type-erased immutable interface wrapper @@ -50,12 +64,20 @@ pub fn gpuAllocator(self: *@This()) GpuAllocator { .freeTexture = freeTexture, .allocTextureView = allocTextureView, .freeTextureView = freeTextureView, + .allocRenderPipeline = allocRenderPipeline, + .freeRenderPipeline = freeRenderPipeline, + .allocComputePipeline = allocComputePipeline, + .freeComputePipeline = freeComputePipeline, }, }; } +// NOTE: I use ensureTotalCapacity so I know that try self.tracked_x.put will not fail! +// Like that I dont have to use errdefer to release what I just allocated in VRAM + fn allocBuffer(ctx: *anyopaque, desc: c.WGPUBufferDescriptor) anyerror!c.WGPUBuffer { const self: *@This() = @ptrCast(@alignCast(ctx)); + try self.tracked_buffers.ensureTotalCapacity(self.tracked_buffers.count() + 1); if (desc.size > self.device.limits.maxBufferSize) return error.SingleBufferExceedsLimit; @@ -63,15 +85,11 @@ fn allocBuffer(ctx: *anyopaque, desc: c.WGPUBufferDescriptor) anyerror!c.WGPUBuf if (desc.size + self.allocated_vram_bytes > self.device.config.vram_bytes_limit) return error.ExceedsVramBudget; - const buf = c.wgpuDeviceCreateBuffer(self.device.device, &desc) orelse return error.BufferAlloc; - errdefer { - c.wgpuBufferDestroy(buf); - c.wgpuBufferRelease(buf); - } + const raw = c.wgpuDeviceCreateBuffer(self.device.device, &desc) orelse return error.BufferAlloc; - try self.tracked_buffers.put(buf, desc); + self.tracked_buffers.putAssumeCapacity(raw, desc); self.allocated_vram_bytes += desc.size; - return buf; + return raw; } fn freeBuffer(ctx: *anyopaque, raw: c.WGPUBuffer) void { @@ -86,6 +104,7 @@ fn freeBuffer(ctx: *anyopaque, raw: c.WGPUBuffer) void { fn allocTexture(ctx: *anyopaque, desc: c.WGPUTextureDescriptor) anyerror!c.WGPUTexture { const self: *@This() = @ptrCast(@alignCast(ctx)); + try self.tracked_textures.ensureTotalCapacity(self.tracked_textures.count() + 1); const format: GpuTextureFormat = @enumFromInt(desc.format); const bytes_size = desc.size.width * desc.size.height * format.bytesPerPixel(); @@ -95,11 +114,11 @@ fn allocTexture(ctx: *anyopaque, desc: c.WGPUTextureDescriptor) anyerror!c.WGPUT if (bytes_size + self.allocated_vram_bytes > self.device.config.vram_bytes_limit) return error.ExceedsVramBudget; - const texture = c.wgpuDeviceCreateTexture(self.device.device, &desc) orelse return error.Texture; + const raw = c.wgpuDeviceCreateTexture(self.device.device, &desc) orelse return error.Texture; - try self.tracked_textures.put(texture, desc); + self.tracked_textures.putAssumeCapacity(raw, desc); self.allocated_vram_bytes += bytes_size; - return texture; + return raw; } fn freeTexture(ctx: *anyopaque, raw: c.WGPUTexture) void { @@ -117,9 +136,10 @@ fn freeTexture(ctx: *anyopaque, raw: c.WGPUTexture) void { fn allocTextureView(ctx: *anyopaque, texture: c.WGPUTexture, desc: c.WGPUTextureViewDescriptor) anyerror!c.WGPUTextureView { const self: *@This() = @ptrCast(@alignCast(ctx)); - const view = c.wgpuTextureCreateView(texture, &desc) orelse return error.View; - try self.tracked_views.put(view, desc); - return view; + try self.tracked_views.ensureTotalCapacity(self.tracked_views.count() + 1); + const raw = c.wgpuTextureCreateView(texture, &desc) orelse return error.View; + self.tracked_views.putAssumeCapacity(raw, desc); + return raw; } fn freeTextureView(ctx: *anyopaque, raw: c.WGPUTextureView) void { @@ -127,3 +147,31 @@ fn freeTextureView(ctx: *anyopaque, raw: c.WGPUTextureView) void { if (self.tracked_views.remove(raw)) c.wgpuTextureViewRelease(raw); } + +fn allocRenderPipeline(ctx: *anyopaque, desc: c.WGPURenderPipelineDescriptor) anyerror!c.WGPURenderPipeline { + const self: *@This() = @ptrCast(@alignCast(ctx)); + try self.tracked_renders.ensureTotalCapacity(self.tracked_renders.count() + 1); + const raw = c.wgpuDeviceCreateRenderPipeline(self.device.device, &desc) orelse return error.Pipeline; + self.tracked_renders.putAssumeCapacity(raw, desc); + return raw; +} + +fn freeRenderPipeline(ctx: *anyopaque, raw: c.WGPURenderPipeline) void { + const self: *@This() = @ptrCast(@alignCast(ctx)); + if (self.tracked_renders.remove(raw)) + c.wgpuRenderPipelineRelease(raw); +} + +fn allocComputePipeline(ctx: *anyopaque, desc: c.WGPUComputePipelineDescriptor) anyerror!c.WGPUComputePipeline { + const self: *@This() = @ptrCast(@alignCast(ctx)); + try self.tracked_computes.ensureTotalCapacity(self.tracked_computes.count() + 1); + const raw = c.wgpuDeviceCreateComputePipeline(self.device.device, &desc) orelse return error.Pipeline; + self.tracked_computes.putAssumeCapacity(raw, desc); + return raw; +} + +fn freeComputePipeline(ctx: *anyopaque, raw: c.WGPUComputePipeline) void { + const self: *@This() = @ptrCast(@alignCast(ctx)); + if (self.tracked_computes.remove(raw)) + c.wgpuComputePipelineRelease(raw); +} diff --git a/src/GpuCompute.zig b/src/GpuCompute.zig index 6838fa9..c0fc52e 100644 --- a/src/GpuCompute.zig +++ b/src/GpuCompute.zig @@ -20,30 +20,30 @@ pub const ComputeDef = struct { }; pip: c.WGPUComputePipeline, +gloc: GpuAllocator, def: ComputeDef, -pub fn init(device: GpuDevice, wgsl: []const u8, def: ComputeDef) !@This() { +pub fn init(gloc: GpuAllocator, wgsl: []const u8, def: ComputeDef) !@This() { var wgsl_src = c.WGPUShaderSourceWGSL{ .chain = .{ .sType = c.WGPUSType_ShaderSourceWGSL }, .code = sv(wgsl), }; - const shader = c.wgpuDeviceCreateShaderModule(device.device, &.{ + const shader = c.wgpuDeviceCreateShaderModule(gloc.device.device, &.{ .nextInChain = @ptrCast(&wgsl_src), }) orelse return error.Shader; defer c.wgpuShaderModuleRelease(shader); - const pip = c.wgpuDeviceCreateComputePipeline(device.device, &.{ - .compute = .{ .module = shader, .entryPoint = sv("main") }, - }) orelse return error.Pipeline; + const pip = try gloc.allocComputePipeline(.{ .compute = .{ .module = shader, .entryPoint = sv("main") } }); return .{ + .gloc = gloc, .pip = pip, .def = def, }; } pub fn deinit(self: @This()) void { - c.wgpuComputePipelineRelease(self.pip); + self.gloc.freeComputePipeline(self.pip); } /// Execute the compute pass with arbitrary buffer bindings via a tuple. diff --git a/src/GpuRender.zig b/src/GpuRender.zig index 48d83b0..4f26d5a 100644 --- a/src/GpuRender.zig +++ b/src/GpuRender.zig @@ -32,15 +32,16 @@ const GpuPrimitiveTopology = enum(c_uint) { Force32 = 0x7FFFFFFF, }; +gloc: GpuAllocator, pip: c.WGPURenderPipeline, def: GpuRenderDef, -pub fn init(device: GpuDevice, wgsl: []const u8, def: GpuRenderDef) !@This() { +pub fn init(gloc: GpuAllocator, wgsl: []const u8, def: GpuRenderDef) !@This() { var wgsl_src = c.WGPUShaderSourceWGSL{ .chain = .{ .sType = c.WGPUSType_ShaderSourceWGSL }, .code = sv(wgsl), }; - const shader = c.wgpuDeviceCreateShaderModule(device.device, &.{ + const shader = c.wgpuDeviceCreateShaderModule(gloc.device.device, &.{ .nextInChain = @ptrCast(&wgsl_src), }) orelse return error.Shader; defer c.wgpuShaderModuleRelease(shader); @@ -66,7 +67,7 @@ pub fn init(device: GpuDevice, wgsl: []const u8, def: GpuRenderDef) !@This() { }; // 3. Compile the Complete Render Pipeline - const pip = c.wgpuDeviceCreateRenderPipeline(device.device, &.{ + const pip = try gloc.allocRenderPipeline(.{ .vertex = .{ .module = shader, .entryPoint = sv(def.vertex_entry), @@ -83,16 +84,17 @@ pub fn init(device: GpuDevice, wgsl: []const u8, def: GpuRenderDef) !@This() { .alphaToCoverageEnabled = 0, }, .fragment = &fragment_state, - }) orelse return error.Pipeline; + }); return .{ + .gloc = gloc, .pip = pip, .def = def, }; } pub fn deinit(self: @This()) void { - c.wgpuRenderPipelineRelease(self.pip); + self.gloc.freeRenderPipeline(self.pip); } /// Execute the render pass targeting a specific frame texture view.