Basic working circle render pipeline

This commit is contained in:
adrien
2026-05-19 21:58:14 +02:00
parent 83ef8bcd12
commit ad3fcf2592
7 changed files with 391 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
const std = @import("std");
const c = @import("utils.zig").c;
const sv = @import("utils.zig").sv;
const GpuAllocator = @import("GpuAllocator.zig");
const GpuBuffer = @import("GpuBuffer.zig");
const GpuDevice = @import("GpuDevice.zig");
pub const Binding = struct {
element_size: u32 = 0,
};
pub const RenderDef = struct {
bindings: []const Binding = &.{},
/// The surface texture format we are rendering to (e.g., BGRA8Unorm)
texture_format: c.WGPUTextureFormat,
/// The names of the entry points inside your WGSL code
vertex_entry: []const u8 = "vs_main",
fragment_entry: []const u8 = "fs_main",
/// Primitive topology, default to triangle list
topology: c.WGPUPrimitiveTopology = c.WGPUPrimitiveTopology_TriangleList,
};
pip: c.WGPURenderPipeline,
def: RenderDef,
pub fn init(device: GpuDevice, wgsl: []const u8, def: RenderDef) !@This() {
var wgsl_src = c.WGPUShaderSourceWGSL{
.chain = .{ .sType = c.WGPUSType_ShaderSourceWGSL },
.code = sv(wgsl),
};
const shader = c.wgpuDeviceCreateShaderModule(device.device, &.{
.nextInChain = @ptrCast(&wgsl_src),
}) orelse return error.Shader;
defer c.wgpuShaderModuleRelease(shader);
// 1. Setup the Color Target State (where the fragment shader outputs)
const blend = c.WGPUBlendState{
.color = .{ .operation = c.WGPUBlendOperation_Add, .srcFactor = c.WGPUBlendFactor_SrcAlpha, .dstFactor = c.WGPUBlendFactor_OneMinusSrcAlpha },
.alpha = .{ .operation = c.WGPUBlendOperation_Add, .srcFactor = c.WGPUBlendFactor_One, .dstFactor = c.WGPUBlendFactor_Zero },
};
const color_target = c.WGPUColorTargetState{
.format = def.texture_format,
.blend = &blend,
.writeMask = c.WGPUColorWriteMask_All,
};
// 2. Setup the Fragment State
const fragment_state = c.WGPUFragmentState{
.module = shader,
.entryPoint = sv(def.fragment_entry),
.targetCount = 1,
.targets = &color_target,
};
// 3. Compile the Complete Render Pipeline
const pip = c.wgpuDeviceCreateRenderPipeline(device.device, &.{
.vertex = .{
.module = shader,
.entryPoint = sv(def.vertex_entry),
.bufferCount = 0, // Assuming procedural drawing (like our circle!)
},
.primitive = .{
.topology = def.topology,
.stripIndexFormat = c.WGPUIndexFormat_Undefined,
.frontFace = c.WGPUFrontFace_CCW,
.cullMode = c.WGPUCullMode_None,
},
.multisample = .{
.count = 1,
.mask = 0xFFFFFFFF,
.alphaToCoverageEnabled = 0,
},
.fragment = &fragment_state,
}) orelse return error.Pipeline;
return .{
.pip = pip,
.def = def,
};
}
pub fn deinit(self: @This()) void {
c.wgpuRenderPipelineRelease(self.pip);
}
/// Execute the render pass targeting a specific frame texture view.
/// Passes bind groups via a tuple exactly like your original compute setup.
pub fn draw(
self: @This(),
gloc: GpuAllocator,
target_view: c.WGPUTextureView,
vertex_count: u32,
args: anytype,
) !void {
const type_info = @typeInfo(@TypeOf(args));
if (type_info != .@"struct" or !type_info.@"struct".is_tuple)
@compileError("Expected a tuple of GpuBuffers for args. E.g. .{ uniform_buf }");
const fields = type_info.@"struct".fields;
if (fields.len != self.def.bindings.len)
return error.InvalidArgumentCount;
var entries_buf: [32]c.WGPUBindGroupEntry = undefined;
inline for (fields, 0..) |field, i| {
const buf = @field(args, field.name);
if (@TypeOf(buf) != GpuBuffer) {
@compileError("All arguments in the tuple must be of type GpuBuffer");
}
entries_buf[i] = .{
.binding = @intCast(i),
.buffer = buf.raw,
.offset = 0,
.size = buf.size,
};
}
const entries = entries_buf[0..fields.len];
// Create Render Bind Group from layout
const bgl = c.wgpuRenderPipelineGetBindGroupLayout(self.pip, 0);
defer c.wgpuBindGroupLayoutRelease(bgl);
const bg = c.wgpuDeviceCreateBindGroup(gloc.device.device, &.{
.layout = bgl,
.entries = entries.ptr,
.entryCount = @intCast(entries.len),
}) orelse return error.BindGroup;
defer c.wgpuBindGroupRelease(bg);
// Encode Render Command
const enc = c.wgpuDeviceCreateCommandEncoder(gloc.device.device, null) orelse return error.Encoder;
defer c.wgpuCommandEncoderRelease(enc);
const color_attachment = c.WGPURenderPassColorAttachment{
.view = target_view,
.resolveTarget = null,
.loadOp = c.WGPULoadOp_Clear,
.storeOp = c.WGPUStoreOp_Store,
.clearValue = .{ .r = 0.1, .g = 0.1, .b = 0.1, .a = 1.0 },
.depthSlice = c.WGPU_DEPTH_SLICE_UNDEFINED,
};
const pass_desc = c.WGPURenderPassDescriptor{
.colorAttachmentCount = 1,
.colorAttachments = &color_attachment,
.depthStencilAttachment = null,
};
const pass = c.wgpuCommandEncoderBeginRenderPass(enc, &pass_desc);
c.wgpuRenderPassEncoderSetPipeline(pass, self.pip);
if (fields.len > 0) {
c.wgpuRenderPassEncoderSetBindGroup(pass, 0, bg, 0, null);
}
// Draw! (Instead of Compute Dispatch)
c.wgpuRenderPassEncoderDraw(pass, vertex_count, 1, 0, 0);
c.wgpuRenderPassEncoderEnd(pass);
c.wgpuRenderPassEncoderRelease(pass);
const cmd = c.wgpuCommandEncoderFinish(enc, null);
defer c.wgpuCommandBufferRelease(cmd);
c.wgpuQueueSubmit(gloc.device.queue, 1, &cmd);
}