render-zig

A 3D rendering engine written in Zig
git clone git://git.christianermann.dev/render-zig
Log | Files | Refs

commit 25834a22302c617d1de9bc0529fd77efdfdf87d1
parent 0fd02f7226880b8e349ce4c58b490ee885715921
Author: Christian Ermann <christianermann@gmail.com>
Date:   Wed, 15 May 2024 16:49:57 -0400

Move render pipelines to separate file

Diffstat:
Msrc/main.zig | 292+++----------------------------------------------------------------------------
Asrc/render_pipeline.zig | 280+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 291 insertions(+), 281 deletions(-)

diff --git a/src/main.zig b/src/main.zig @@ -3,6 +3,9 @@ const glfw = @import("mach_glfw"); const gpu = @import("mach_gpu"); const builtin = @import("builtin"); +const RenderPipeline = @import("render_pipeline.zig").RenderPipeline; +const MeshRenderPipeline = @import("render_pipeline.zig").MeshRenderPipeline; + const Camera = @import("camera.zig"); const input = @import("input.zig"); @@ -46,7 +49,7 @@ fn glfwDetectBackendOptions() glfw.BackendOptions { }; } -const App = struct { +pub const App = struct { window: glfw.Window, instance: *gpu.Instance, surface: *gpu.Surface, @@ -297,97 +300,15 @@ const AppSwapChain = struct { target_descriptor: gpu.SwapChain.Descriptor, }; -const RenderPipeline = struct { - ptr: *anyopaque, - frameFn: *const fn (ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void, - - fn frame(self: *const RenderPipeline, pass: *gpu.RenderPassEncoder) void { - return self.frameFn(self.ptr, pass); - } -}; - -const DefaultRenderPipeline = struct { - gpu_pipeline: *gpu.RenderPipeline, - - pub fn init(app: *App) DefaultRenderPipeline { - const vs = - \\ @vertex fn main( - \\ @builtin(vertex_index) VertexIndex : u32 - \\ ) -> @builtin(position) vec4<f32> { - \\ var pos = array<vec2<f32>, 3>( - \\ vec2<f32>( 0.0, 0.5), - \\ vec2<f32>(-0.5, -0.5), - \\ vec2<f32>( 0.5, -0.5) - \\ ); - \\ return vec4<f32>(pos[VertexIndex], 0.0, 1.0); - \\ } - ; - const vs_module = app.device.createShaderModuleWGSL("default vertex shader", vs); - defer vs_module.release(); - - const vertex = gpu.VertexState{ - .module = vs_module, - .entry_point = "main", - }; - - const fs = - \\ @fragment fn main() -> @location(0) vec4<f32> { - \\ return vec4<f32>(1.0, 0.0, 0.0, 1.0); - \\ } - ; - const fs_module = app.device.createShaderModuleWGSL("default fragment shader", fs); - defer fs_module.release(); - - const blend: gpu.BlendState = .{}; - const color_target = gpu.ColorTargetState{ - .format = app.swap_chain.format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .label = "default render pipeline", - .fragment = &fragment, - .layout = null, - .depth_stencil = null, - .vertex = vertex, - .multisample = .{}, - .primitive = .{}, - }; - - return .{ - .gpu_pipeline = app.device.createRenderPipeline(&pipeline_descriptor), - }; - } - - pub fn frame(ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void { - const self: *DefaultRenderPipeline = @ptrCast(@alignCast(ptr)); - pass.setPipeline(self.gpu_pipeline); - pass.draw(3, 1, 0, 0); - } - - pub fn pipeline(self: *DefaultRenderPipeline) RenderPipeline { - return .{ - .ptr = self, - .frameFn = frame, - }; - } -}; - -const f32x3 = @Vector(3, f32); -const f32x4 = @Vector(4, f32); -const mat4 = [4]@Vector(4, f32); +pub const f32x3 = @Vector(3, f32); +pub const f32x4 = @Vector(4, f32); +pub const mat4 = [4]@Vector(4, f32); const InstanceData = struct { model_matrix: mat4, }; -const UniformData = struct { +pub const UniformData = struct { view_matrix: mat4, proj_matrix: mat4, }; @@ -605,197 +526,6 @@ pub const Mesh = struct { } }; -const UnlitRenderPipeline = struct { - gpu_pipeline: *gpu.RenderPipeline, - mesh_buffer: *const MeshBuffer, - bind_group: *gpu.BindGroup, - uniform_buffer: *gpu.Buffer, - - pub fn init(app: *App, mesh_buffer: *const MeshBuffer, _: std.mem.Allocator) !UnlitRenderPipeline { - const vs = - \\ struct VertexInput { - \\ @location(0) position: vec4<f32>, - \\ @location(1) normal: vec3<f32>, - \\ }; - \\ - \\ struct VertexOutput { - \\ @builtin(position) clip_position: vec4<f32>, - \\ @location(0) normal: vec3<f32>, - \\ }; - \\ - \\ @group(0) @binding(0) - \\ var<storage, read> instances: array<Instance>; - \\ struct Instance { - \\ model_matrix: mat4x4<f32>, - \\ }; - \\ - \\ @group(0) @binding(1) - \\ var<uniform> uniform_data: UniformData; - \\ struct UniformData { - \\ view_matrix: mat4x4<f32>, - \\ proj_matrix: mat4x4<f32>, - \\ }; - \\ - \\ @vertex fn main( - \\ in: VertexInput, - \\ @builtin(instance_index) idx: u32, - \\ ) -> VertexOutput { - \\ let model_matrix = instances[idx].model_matrix; - \\ let camera_matrix = uniform_data.proj_matrix * uniform_data.view_matrix; - \\ let matrix = camera_matrix * model_matrix; - \\ return VertexOutput(matrix * in.position, in.normal); - \\ } - ; - const vs_module = app.device.createShaderModuleWGSL("default vertex shader", vs); - defer vs_module.release(); - - const layouts = UnlitRenderPipeline.bufferLayouts(); - const vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &layouts, - }); - - const fs = - \\ struct VertexOutput { - \\ @builtin(position) position: vec4<f32>, - \\ @location(0) normal: vec3<f32>, - \\ }; - \\ - \\ @fragment fn main(in: VertexOutput) -> @location(0) vec4<f32> { - \\ let color = (in.normal + 1.0) * 0.5; - \\ return vec4<f32>(color, 1.0); - \\ } - ; - const fs_module = app.device.createShaderModuleWGSL("default fragment shader", fs); - defer fs_module.release(); - - const blend: gpu.BlendState = .{}; - const color_target = gpu.ColorTargetState{ - .format = app.swap_chain.format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const buffer_descriptor = gpu.Buffer.Descriptor{ - .size = @sizeOf(UniformData), - .usage = .{ .uniform = true, .copy_dst = true }, - .mapped_at_creation = .false, - }; - const uniform_buffer = app.device.createBuffer(&buffer_descriptor); - - const bind_group_layout = UnlitRenderPipeline.bindGroupLayout(app.device); - const bind_group_descriptor = gpu.BindGroup.Descriptor.init(.{ - .layout = bind_group_layout, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, mesh_buffer.instances.data, 0, mesh_buffer.instances.size), - gpu.BindGroup.Entry.buffer(1, uniform_buffer, 0, @sizeOf(UniformData)), - }, - }); - const bind_group = app.device.createBindGroup(&bind_group_descriptor); - - const pipeline_layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &.{bind_group_layout}, - }); - const pipeline_layout = app.device.createPipelineLayout(&pipeline_layout_descriptor); - defer pipeline_layout.release(); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .label = "default render pipeline", - .fragment = &fragment, - .layout = pipeline_layout, - .depth_stencil = &.{ - .format = .depth24_plus, - .depth_write_enabled = .true, - .depth_compare = .less, - }, - .vertex = vertex, - .multisample = .{}, - .primitive = .{ - .topology = .triangle_list, - .front_face = .ccw, - .cull_mode = .back, - }, - }; - - return .{ - .gpu_pipeline = app.device.createRenderPipeline(&pipeline_descriptor), - .mesh_buffer = mesh_buffer, - .bind_group = bind_group, - .uniform_buffer = uniform_buffer, - }; - } - - fn bufferLayouts() [3]gpu.VertexBufferLayout { - const positions = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(f32x4), - .step_mode = .vertex, - .attributes = &.{ - .{ .format = .float32x4, .shader_location = 0, .offset = 0 }, - }, - }); - const normals = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(f32x4), - .step_mode = .vertex, - .attributes = &.{ - .{ .format = .float32x3, .shader_location = 1, .offset = 0 }, - }, - }); - const tex_coords = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(f32x4), - .step_mode = .vertex, - .attributes = &.{ - .{ .format = .float32x3, .shader_location = 2, .offset = 0 }, - }, - }); - return .{ positions, normals, tex_coords }; - } - - fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout { - const descriptor = gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{ - gpu.BindGroupLayout.Entry.buffer(0, .{ - .vertex = true, - .fragment = false, - }, .read_only_storage, false, 0), - gpu.BindGroupLayout.Entry.buffer(1, .{ - .vertex = true, - .fragment = false, - }, .uniform, false, 0), - }, - }); - return device.createBindGroupLayout(&descriptor); - } - - pub fn frame(ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void { - const self: *UnlitRenderPipeline = @ptrCast(@alignCast(ptr)); - pass.setPipeline(self.gpu_pipeline); - - pass.setVertexBuffer(0, self.mesh_buffer.positions.data, 0, self.mesh_buffer.positions.size); - pass.setVertexBuffer(1, self.mesh_buffer.normals.data, 0, self.mesh_buffer.normals.size); - pass.setVertexBuffer(2, self.mesh_buffer.tex_coords.data, 0, self.mesh_buffer.tex_coords.size); - pass.setBindGroup(0, self.bind_group, null); - pass.setIndexBuffer(self.mesh_buffer.indices.data, .uint32, 0, self.mesh_buffer.indices.size); - - for (0..self.mesh_buffer.meshes.items.len) |mesh_idx| { - const offset = mesh_idx * 5 * @sizeOf(u32); - pass.drawIndexedIndirect(self.mesh_buffer.indirect.data, offset); - } - } - - pub fn pipeline(self: *UnlitRenderPipeline) RenderPipeline { - return .{ - .ptr = self, - .frameFn = frame, - }; - } -}; - pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); @@ -830,9 +560,9 @@ pub fn main() !void { .offset = .{ 0, -0.5, 0 }, }); - var drp = try UnlitRenderPipeline.init(&app, &mesh_buffer, allocator); + var rp = try MeshRenderPipeline.init(&app, &mesh_buffer, allocator); - const pipelines = [_]RenderPipeline{drp.pipeline()}; + const pipelines = [_]RenderPipeline{rp.pipeline()}; try app.inputs.append(camera.input.userInput()); @@ -846,7 +576,7 @@ pub fn main() !void { camera.update(0.01); camera.view(&uniform_data.view_matrix); camera.proj(&uniform_data.proj_matrix); - app.queue.writeBuffer(drp.uniform_buffer, 0, &[1]UniformData{uniform_data}); + app.queue.writeBuffer(rp.uniform_buffer, 0, &[1]UniformData{uniform_data}); try app.frame(&pipelines); } diff --git a/src/render_pipeline.zig b/src/render_pipeline.zig @@ -0,0 +1,280 @@ +const std = @import("std"); +const gpu = @import("mach_gpu"); + +const App = @import("main.zig").App; +const MeshBuffer = @import("main.zig").MeshBuffer; +const UniformData = @import("main.zig").UniformData; +const f32x4 = @import("main.zig").f32x4; + +pub const RenderPipeline = struct { + ptr: *anyopaque, + frameFn: *const fn (ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void, + + pub fn frame(self: *const RenderPipeline, pass: *gpu.RenderPassEncoder) void { + return self.frameFn(self.ptr, pass); + } +}; + +pub const TriangleRenderPipeline = struct { + gpu_pipeline: *gpu.RenderPipeline, + + pub fn init(app: *App) TriangleRenderPipeline { + const vs = + \\ @vertex fn main( + \\ @builtin(vertex_index) VertexIndex : u32 + \\ ) -> @builtin(position) vec4<f32> { + \\ var pos = array<vec2<f32>, 3>( + \\ vec2<f32>( 0.0, 0.5), + \\ vec2<f32>(-0.5, -0.5), + \\ vec2<f32>( 0.5, -0.5) + \\ ); + \\ return vec4<f32>(pos[VertexIndex], 0.0, 1.0); + \\ } + ; + const vs_module = app.device.createShaderModuleWGSL("example vertex shader", vs); + defer vs_module.release(); + + const vertex = gpu.VertexState{ + .module = vs_module, + .entry_point = "main", + }; + + const fs = + \\ @fragment fn main() -> @location(0) vec4<f32> { + \\ return vec4<f32>(1.0, 0.0, 0.0, 1.0); + \\ } + ; + const fs_module = app.device.createShaderModuleWGSL("example fragment shader", fs); + defer fs_module.release(); + + const blend: gpu.BlendState = .{}; + const color_target = gpu.ColorTargetState{ + .format = app.swap_chain.format, + .blend = &blend, + .write_mask = gpu.ColorWriteMaskFlags.all, + }; + const fragment = gpu.FragmentState.init(.{ + .module = fs_module, + .entry_point = "main", + .targets = &.{color_target}, + }); + + const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ + .label = "example render pipeline", + .fragment = &fragment, + .layout = null, + .depth_stencil = null, + .vertex = vertex, + .multisample = .{}, + .primitive = .{}, + }; + + return .{ + .gpu_pipeline = app.device.createRenderPipeline(&pipeline_descriptor), + }; + } + + pub fn frame(ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void { + const self: *TriangleRenderPipeline = @ptrCast(@alignCast(ptr)); + pass.setPipeline(self.gpu_pipeline); + pass.draw(3, 1, 0, 0); + } + + pub fn pipeline(self: *TriangleRenderPipeline) RenderPipeline { + return .{ + .ptr = self, + .frameFn = frame, + }; + } +}; + +pub const MeshRenderPipeline = struct { + gpu_pipeline: *gpu.RenderPipeline, + mesh_buffer: *const MeshBuffer, + bind_group: *gpu.BindGroup, + uniform_buffer: *gpu.Buffer, + + pub fn init(app: *App, mesh_buffer: *const MeshBuffer, _: std.mem.Allocator) !MeshRenderPipeline { + const vs = + \\ struct VertexInput { + \\ @location(0) position: vec4<f32>, + \\ @location(1) normal: vec3<f32>, + \\ }; + \\ + \\ struct VertexOutput { + \\ @builtin(position) clip_position: vec4<f32>, + \\ @location(0) normal: vec3<f32>, + \\ }; + \\ + \\ @group(0) @binding(0) + \\ var<storage, read> instances: array<Instance>; + \\ struct Instance { + \\ model_matrix: mat4x4<f32>, + \\ }; + \\ + \\ @group(0) @binding(1) + \\ var<uniform> uniform_data: UniformData; + \\ struct UniformData { + \\ view_matrix: mat4x4<f32>, + \\ proj_matrix: mat4x4<f32>, + \\ }; + \\ + \\ @vertex fn main( + \\ in: VertexInput, + \\ @builtin(instance_index) idx: u32, + \\ ) -> VertexOutput { + \\ let model_matrix = instances[idx].model_matrix; + \\ let camera_matrix = uniform_data.proj_matrix * uniform_data.view_matrix; + \\ let matrix = camera_matrix * model_matrix; + \\ return VertexOutput(matrix * in.position, in.normal); + \\ } + ; + const vs_module = app.device.createShaderModuleWGSL("default vertex shader", vs); + defer vs_module.release(); + + const layouts = MeshRenderPipeline.bufferLayouts(); + const vertex = gpu.VertexState.init(.{ + .module = vs_module, + .entry_point = "main", + .buffers = &layouts, + }); + + const fs = + \\ struct VertexOutput { + \\ @builtin(position) position: vec4<f32>, + \\ @location(0) normal: vec3<f32>, + \\ }; + \\ + \\ @fragment fn main(in: VertexOutput) -> @location(0) vec4<f32> { + \\ let color = (in.normal + 1.0) * 0.5; + \\ return vec4<f32>(color, 1.0); + \\ } + ; + const fs_module = app.device.createShaderModuleWGSL("default fragment shader", fs); + defer fs_module.release(); + + const blend: gpu.BlendState = .{}; + const color_target = gpu.ColorTargetState{ + .format = app.swap_chain.format, + .blend = &blend, + .write_mask = gpu.ColorWriteMaskFlags.all, + }; + const fragment = gpu.FragmentState.init(.{ + .module = fs_module, + .entry_point = "main", + .targets = &.{color_target}, + }); + + const buffer_descriptor = gpu.Buffer.Descriptor{ + .size = @sizeOf(UniformData), + .usage = .{ .uniform = true, .copy_dst = true }, + .mapped_at_creation = .false, + }; + const uniform_buffer = app.device.createBuffer(&buffer_descriptor); + + const bind_group_layout = MeshRenderPipeline.bindGroupLayout(app.device); + const bind_group_descriptor = gpu.BindGroup.Descriptor.init(.{ + .layout = bind_group_layout, + .entries = &.{ + gpu.BindGroup.Entry.buffer(0, mesh_buffer.instances.data, 0, mesh_buffer.instances.size), + gpu.BindGroup.Entry.buffer(1, uniform_buffer, 0, @sizeOf(UniformData)), + }, + }); + const bind_group = app.device.createBindGroup(&bind_group_descriptor); + + const pipeline_layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{ + .bind_group_layouts = &.{bind_group_layout}, + }); + const pipeline_layout = app.device.createPipelineLayout(&pipeline_layout_descriptor); + defer pipeline_layout.release(); + + const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ + .label = "default render pipeline", + .fragment = &fragment, + .layout = pipeline_layout, + .depth_stencil = &.{ + .format = .depth24_plus, + .depth_write_enabled = .true, + .depth_compare = .less, + }, + .vertex = vertex, + .multisample = .{}, + .primitive = .{ + .topology = .triangle_list, + .front_face = .ccw, + .cull_mode = .back, + }, + }; + + return .{ + .gpu_pipeline = app.device.createRenderPipeline(&pipeline_descriptor), + .mesh_buffer = mesh_buffer, + .bind_group = bind_group, + .uniform_buffer = uniform_buffer, + }; + } + + fn bufferLayouts() [3]gpu.VertexBufferLayout { + const positions = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x4, .shader_location = 0, .offset = 0 }, + }, + }); + const normals = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x3, .shader_location = 1, .offset = 0 }, + }, + }); + const tex_coords = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x3, .shader_location = 2, .offset = 0 }, + }, + }); + return .{ positions, normals, tex_coords }; + } + + fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout { + const descriptor = gpu.BindGroupLayout.Descriptor.init(.{ + .entries = &.{ + gpu.BindGroupLayout.Entry.buffer(0, .{ + .vertex = true, + .fragment = false, + }, .read_only_storage, false, 0), + gpu.BindGroupLayout.Entry.buffer(1, .{ + .vertex = true, + .fragment = false, + }, .uniform, false, 0), + }, + }); + return device.createBindGroupLayout(&descriptor); + } + + pub fn frame(ptr: *anyopaque, pass: *gpu.RenderPassEncoder) void { + const self: *MeshRenderPipeline = @ptrCast(@alignCast(ptr)); + pass.setPipeline(self.gpu_pipeline); + + pass.setVertexBuffer(0, self.mesh_buffer.positions.data, 0, self.mesh_buffer.positions.size); + pass.setVertexBuffer(1, self.mesh_buffer.normals.data, 0, self.mesh_buffer.normals.size); + pass.setVertexBuffer(2, self.mesh_buffer.tex_coords.data, 0, self.mesh_buffer.tex_coords.size); + pass.setBindGroup(0, self.bind_group, null); + pass.setIndexBuffer(self.mesh_buffer.indices.data, .uint32, 0, self.mesh_buffer.indices.size); + + for (0..self.mesh_buffer.meshes.items.len) |mesh_idx| { + const offset = mesh_idx * 5 * @sizeOf(u32); + pass.drawIndexedIndirect(self.mesh_buffer.indirect.data, offset); + } + } + + pub fn pipeline(self: *MeshRenderPipeline) RenderPipeline { + return .{ + .ptr = self, + .frameFn = frame, + }; + } +};