diff --git a/src/StorageBuffer.zig b/src/StorageBuffer.zig new file mode 100644 index 0000000..8421e5f --- /dev/null +++ b/src/StorageBuffer.zig @@ -0,0 +1,53 @@ +const zgpu = @import("zgpu"); + +const main = @import("main.zig"); + +pub fn StorageBuffer(comptime T: type) type { + return struct { + storage_buffer_handle: zgpu.BufferHandle, + + pub fn init(desired_capacity: usize) @This() { + // NOTE For some reason, the method function call syntax doesn't compile + const storage_buffer_handle = zgpu.GraphicsContext.createBuffer(main.gctx, .{ + .usage = .{ .copy_dst = true, .storage = true }, + .size = desired_capacity * @sizeOf(T), + }); + + return .{ + .storage_buffer_handle = storage_buffer_handle, + }; + } + + pub fn deinit(self: *@This()) void { + main.gctx.releaseResource(self.storage_buffer_handle); + self.* = undefined; + } + + pub fn capacity(self: @This()) usize { + return @divExact(self.info().size, @sizeOf(T)); + } + + pub fn write(self: @This(), offset: usize, data: []const T) void { + main.gctx.queue.writeBuffer(self.obj(), offset, T, data); + } + + pub fn ensureCapacityDiscard(self: *@This(), desired_capacity: usize) void { + if (self.capacity() < desired_capacity) { + main.gctx.releaseResource(self.storage_buffer_handle); + // NOTE For some reason, the method function call syntax doesn't compile + self.storage_buffer_handle = zgpu.GraphicsContext.createBuffer(main.gctx, .{ + .usage = .{ .copy_dst = true, .storage = true }, + .size = desired_capacity * @sizeOf(T), + }); + } + } + + pub fn info(self: @This()) zgpu.BufferInfo { + return main.gctx.lookupResourceInfo(self.vertex_buffer_handle).?; + } + + pub fn obj(self: @This()) zgpu.wgpu.Buffer { + return main.gctx.lookupResource(self.vertex_buffer_handle).?; + } + }; +} diff --git a/src/game.zig b/src/game.zig index f21b128..065b7ea 100644 --- a/src/game.zig +++ b/src/game.zig @@ -2,38 +2,239 @@ const std = @import("std"); const zglfw = @import("zglfw"); const zgui = @import("zgui"); +const zgpu = @import("zgpu"); const main = @import("main.zig"); - -var show_console: bool = false; -var show_demo_window: bool = false; +const IndexBuffer = @import("IndexBuffer.zig"); +const Samplers = @import("Samplers.zig"); +const StorageBuffer = @import("StorageBuffer.zig").StorageBuffer; +const Texture = @import("Texture.zig"); +const VertexBuffer = @import("VertexBuffer.zig").VertexBuffer; const Vertex = extern struct { - position: [3]f32, + position_os: [3]f32, tex_coord: [2]f32, - normal: [3]f32, - tangent: [4]f32, + normal_os: [3]f32, + tangent_os: [4]f32, pub fn init(x: f32, y: f32, z: f32, u: f32, v: f32, nx: f32, ny: f32, nz: f32, tx: f32, ty: f32, tz: f32, tw: f32) Vertex { return .{ - .position = .{ x, y, z }, + .position_os = .{ x, y, z }, .tex_coord = .{ u, v }, - .normal = .{ nx, ny, nz }, - .tangent = .{ tx, ty, tw, tz }, + .normal_os = .{ nx, ny, nz }, + .tangent_os = .{ tx, ty, tw, tz }, }; } }; -const vertex_buffer = [_]Vertex{ - Vertex.init(-0.5, -0.5, 0, 0, 1, 0, 0, 1, 1, 0, 0, -1), - Vertex.init(0.5, -0.5, 0, 1, 1, 0, 0, 1, 1, 0, 0, -1), - Vertex.init(-0.5, 0.5, 0, 0, 0, 0, 0, 1, 1, 0, 0, -1), - Vertex.init(0.5, 0.5, 0, 1, 0, 0, 0, 1, 1, 0, 0, -1), +const PointLight = extern struct { + position_ws: [3]f32, + color: [3]f32, }; -const index_buffer = [_]u16{ 0, 1, 2, 2, 1, 3 }; +const DirectionalLight = extern struct { + direction_ws: [3]f32, + color: [3]f32, +}; -pub fn init() void {} +const GlobalUniforms = extern struct { + matrix_ws_to_vs: [16]f32, + matrix_vs_to_cs: [16]f32, + ambient_light: [3]f32, + point_light_count: u32, + directional_light_count: u32, +}; + +const ObjectUniforms = extern struct { + matrix_os_to_ws: [16]f32, + matrix_os_to_ws_normal: [16]f32, +}; + +var show_console: bool = false; +var show_demo_window: bool = false; + +var global_bind_group_layout: zgpu.BindGroupLayoutHandle = undefined; +var per_material_bind_group_layout: zgpu.BindGroupLayoutHandle = undefined; +var per_object_bind_group_layout: zgpu.BindGroupLayoutHandle = undefined; + +var vertex_buffer: VertexBuffer(Vertex) = undefined; +var index_buffer: IndexBuffer = undefined; + +var block_pipeline: zgpu.RenderPipelineHandle = undefined; + +var point_light_buffer: StorageBuffer(PointLight) = undefined; +var directional_light_buffer: StorageBuffer(DirectionalLight) = undefined; +var sampler: zgpu.SamplerHandle = undefined; +var base_color_texture: Texture = undefined; +var occlusion_roughness_metallic_texture: Texture = undefined; +var normal_texture: Texture = undefined; + +var global_bind_group: zgpu.BindGroupHandle = undefined; +var per_material_bind_group: zgpu.BindGroupHandle = undefined; +var per_object_bind_group: zgpu.BindGroupHandle = undefined; + +pub fn init() !void { + global_bind_group_layout = main.gctx.createBindGroupLayout(&.{ + zgpu.bufferEntry(0, .{ .vertex = true, .fragment = true }, .uniform, true, 0), + zgpu.bufferEntry(1, .{ .fragment = true }, .read_only_storage, false, 0), + zgpu.bufferEntry(2, .{ .fragment = true }, .read_only_storage, false, 0), + zgpu.samplerEntry(3, .{ .fragment = true }, .filtering), + zgpu.textureEntry(4, .{ .fragment = true }, .float, .tvdim_2d_array, false), + zgpu.textureEntry(5, .{ .fragment = true }, .float, .tvdim_2d_array, false), + zgpu.textureEntry(6, .{ .fragment = true }, .float, .tvdim_2d_array, false), + }); + errdefer main.gctx.releaseResource(global_bind_group_layout); + + per_material_bind_group_layout = main.gctx.createBindGroupLayout(&.{ + zgpu.bufferEntry(0, .{ .fragment = true }, .uniform, true, 0), + }); + errdefer main.gctx.releaseResource(per_material_bind_group_layout); + + per_object_bind_group_layout = main.gctx.createBindGroupLayout(&.{ + zgpu.bufferEntry(0, .{ .fragment = true }, .uniform, true, 0), + }); + errdefer main.gctx.releaseResource(per_object_bind_group_layout); + + vertex_buffer = VertexBuffer(Vertex).init(4); + vertex_buffer.write(0, &[_]Vertex{ + Vertex.init(-0.5, -0.5, 0, 0, 1, 0, 0, 1, 1, 0, 0, -1), + Vertex.init(0.5, -0.5, 0, 1, 1, 0, 0, 1, 1, 0, 0, -1), + Vertex.init(-0.5, 0.5, 0, 0, 0, 0, 0, 1, 1, 0, 0, -1), + Vertex.init(0.5, 0.5, 0, 1, 0, 0, 0, 1, 1, 0, 0, -1), + }); + errdefer vertex_buffer.deinit(); + + index_buffer = IndexBuffer.init(6); + index_buffer.write(0, &[_]u16{ 0, 1, 2, 2, 1, 3 }); + errdefer index_buffer.deinit(); + + block_pipeline = block_pipeline: { + const pipeline_layout = main.gctx.createPipelineLayout(&.{ + global_bind_group_layout, + per_material_bind_group_layout, + per_object_bind_group_layout, + }); + defer main.gctx.releaseResource(pipeline_layout); + + const module = zgpu.createWgslShaderModule(main.gctx.device, @embedFile("../shaders/block.wgsl"), null); + defer module.release(); + + const vertex_buffers = [_]zgpu.wgpu.VertexBufferLayout{ + vertexBufferLayoutFromStruct(Vertex), + }; + + const targets = [_]zgpu.wgpu.ColorTargetState{.{ + .format = zgpu.GraphicsContext.swapchain_format, + .blend = &.{ + .color = .{ + .operation = .add, + .src_factor = .one, + .dst_factor = .one_minus_src_alpha, + }, + .alpha = .{ + .operation = .add, + .src_factor = .one, + .dst_factor = .one_minus_src_alpha, + }, + }, + }}; + + const pipeline_descriptor = zgpu.wgpu.RenderPipelineDescriptor{ + .vertex = .{ + .module = module, + .entry_point = "vert", + .buffer_count = vertex_buffers.len, + .buffers = &vertex_buffers, + }, + .fragment = .{ + .module = module, + .entry_point = "frag", + .target_count = targets.len, + .targets = &targets, + }, + .primitive = .{ + .cull_mode = .back, + }, + }; + break :block_pipeline main.gctx.createRenderPipeline(pipeline_layout, pipeline_descriptor); + }; + errdefer main.gctx.releaseResource(block_pipeline); + + point_light_buffer = StorageBuffer(PointLight).init(4); + errdefer point_light_buffer.deinit(); + + directional_light_buffer = StorageBuffer(DirectionalLight).init(4); + errdefer directional_light_buffer.deinit(); + + sampler = main.gctx.createSampler(.{ + .address_mode_u = .repeat, + .address_mode_v = .repeat, + .address_mode_w = .repeat, + .mag_filter = .linear, + .min_filter = .linear, + .mipmap_filter = .linear, + }); + errdefer main.gctx.releaseResource(sampler); + + const tile_count = 16; + + const base_color_texture_data = @embedFile("../library/textures/BaseColor"); + const base_color_tile_size = std.math.sqrt(@divExact(base_color_texture_data.len, 4 * tile_count)); + base_color_texture = Texture.init2DArray(base_color_tile_size, base_color_tile_size, tile_count); + errdefer base_color_texture.deinit(); + main.gctx.queue.writeTexture( + .{ .texture = base_color_texture.texObj() }, + .{ .bytes_per_row = 4 * base_color_tile_size, .rows_per_image = base_color_tile_size }, + .{ .width = base_color_tile_size, .height = base_color_tile_size, .depth_or_array_layers = tile_count }, + u8, + base_color_texture_data, + ); + + const occlusion_roughness_metallic_texture_data = @embedFile("../library/textures/OcclusionRoughnessMetallic"); + const occlusion_roughness_metallic_tile_size = std.math.sqrt(@divExact(occlusion_roughness_metallic_texture_data.len, 4 * tile_count)); + occlusion_roughness_metallic_texture = Texture.init2DArray(occlusion_roughness_metallic_tile_size, occlusion_roughness_metallic_tile_size, tile_count); + errdefer occlusion_roughness_metallic_texture.deinit(); + main.gctx.queue.writeTexture( + .{ .texture = occlusion_roughness_metallic_texture.texObj() }, + .{ .bytes_per_row = 4 * occlusion_roughness_metallic_tile_size, .rows_per_image = occlusion_roughness_metallic_tile_size }, + .{ .width = occlusion_roughness_metallic_tile_size, .height = occlusion_roughness_metallic_tile_size, .depth_or_array_layers = tile_count }, + u8, + occlusion_roughness_metallic_texture_data, + ); + + const normal_texture_data = @embedFile("../library/textures/Normal"); + const normal_tile_size = std.math.sqrt(@divExact(normal_texture_data.len, 4 * tile_count)); + normal_texture = Texture.init2DArray(normal_tile_size, normal_tile_size, tile_count); + errdefer normal_texture.deinit(); + main.gctx.queue.writeTexture( + .{ .texture = normal_texture.texObj() }, + .{ .bytes_per_row = 4 * normal_tile_size, .rows_per_image = normal_tile_size }, + .{ .width = normal_tile_size, .height = normal_tile_size, .depth_or_array_layers = tile_count }, + u8, + normal_texture_data, + ); + + global_bind_group = main.gctx.createBindGroup(global_bind_group_layout, &.{ + .{ .binding = 0, .buffer_handle = main.gctx.uniforms.buffer, .size = @sizeOf(GlobalUniforms) }, + .{ .binding = 1, .buffer_handle = point_light_buffer.storage_buffer_handle, .size = point_light_buffer.info().size }, + .{ .binding = 2, .buffer_handle = directional_light_buffer.storage_buffer_handle, .size = directional_light_buffer.info().size }, + .{ .binding = 3, .sampler_handle = sampler }, + .{ .binding = 4, .texture_view_handle = base_color_texture.texture_view_handle }, + .{ .binding = 5, .texture_view_handle = occlusion_roughness_metallic_texture.texture_view_handle }, + .{ .binding = 6, .texture_view_handle = normal_texture.texture_view_handle }, + }); + errdefer main.gctx.releaseResource(global_bind_group); + + per_material_bind_group = main.gctx.createBindGroup(per_material_bind_group, &.{ + .{ .binding = 0, .buffer_handle = main.gctx.uniforms.buffer, .size = @sizeOf(u32) }, + }); + errdefer main.gctx.releaseResource(per_material_bind_group); + + per_object_bind_group = main.gctx.createBindGroup(per_object_bind_group, &.{ + .{ .binding = 0, .buffer_handle = main.gctx.uniforms.buffer, .size = @sizeOf(ObjectUniforms) }, + }); + errdefer main.gctx.releaseResource(per_object_bind_group); +} pub fn update(dt: f32) void { _ = dt; @@ -45,6 +246,68 @@ pub fn update(dt: f32) void { if (show_demo_window) { zgui.showDemoWindow(&show_demo_window); } + + const back_buffer_view = main.gctx.swapchain.getCurrentTextureView(); + defer back_buffer_view.release(); + + const commands = commands: { + const encoder = main.gctx.device.createCommandEncoder(null); + defer encoder.release(); + + { + const color_attachments = [_]zgpu.wgpu.RenderPassColorAttachment{.{ + .view = back_buffer_view, + .load_op = .clear, + .store_op = .store, + .clear_value = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, + }}; + const pass = encoder.beginRenderPass(.{ + .color_attachment_count = color_attachments.len, + .color_attachments = &color_attachments, + }); + defer { + pass.end(); + pass.release(); + } + + const block_pipeline_obj = main.gctx.lookupResource(block_pipeline).?; + const global_bind_group_obj = main.gctx.lookupResource(global_bind_group).?; + const per_material_bind_group_obj = main.gctx.lookupResource(per_material_bind_group).?; + const per_object_bind_group_obj = main.gctx.lookupResource(per_object_bind_group).?; + + pass.setPipeline(block_pipeline_obj); + + const vb = vertex_buffer.info(); + const ib = index_buffer.info(); + + pass.setVertexBuffer(0, vb.gpuobj.?, 0, vb.size); + pass.setIndexBuffer(ib.gpuobj.?, .uint16, 0, ib.size); + + const global_uniforms_offsets = allocateUniform(GlobalUniforms{ + .matrix_ws_to_vs = .{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, + .matrix_vs_to_cs = .{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, + .ambient_light = .{ 0, 0, 0 }, + .point_light_count = 0, + .directional_light_count = 0, + }); + pass.setBindGroup(0, global_bind_group_obj, &global_uniforms_offsets); + + const per_material_offsets = allocateUniform(@as(u32, 0)); + pass.setBindGroup(1, per_material_bind_group_obj, &per_material_offsets); + + const per_object_offsets = allocateUniform(ObjectUniforms{ + .matrix_os_to_ws = .{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, + .matrix_os_to_ws_normal = .{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, + }); + pass.setBindGroup(2, per_object_bind_group_obj, &per_object_offsets); + + pass.drawIndexed(@intCast(@divExact(ib.size, @sizeOf(u16))), 1, 0, 0, 0); + } + break :commands encoder.finish(null); + }; + defer commands.release(); + + main.gctx.submit(&.{commands}); } pub fn charCallback( @@ -103,7 +366,9 @@ pub fn scrollCallback( _ = yoffset; } -pub fn deinit() void {} +pub fn deinit() void { + Samplers.deinit(); +} fn showConsole() void { const display_size = zgui.io.getDisplaySize(); @@ -133,3 +398,76 @@ fn showConsole() void { } zgui.end(); } + +fn allocateUniform(value: anytype) [1]u32 { + const mem = main.gctx.uniformsAllocate(@TypeOf(value), 1); + mem.slice[0] = value; + return [1]u32{mem.offset}; +} + +fn vertexAttributesFromStruct(comptime T: type) [structFieldCount(T)]zgpu.wgpu.VertexAttribute { + const struct_info = @typeInfo(T).Struct; + if (struct_info.layout != .Extern) { + @compileError("Vertex struct " ++ @typeName(T) ++ " does not have extern layout"); + } + + comptime var ret: [structFieldCount(T)]zgpu.wgpu.VertexAttribute = undefined; + inline for (struct_info.fields, 0..) |struct_field, i| { + const vertex_format: zgpu.wgpu.VertexFormat = switch (struct_field.type) { + [2]u8 => .uint8x2, + [4]u8 => .uint8x4, + + [2]i8 => .sint8x2, + [4]i8 => .sint8x4, + + [2]u16 => .uint16x2, + [4]u16 => .uint16x4, + + [2]i16 => .sint16x2, + [4]i16 => .sint16x4, + + [2]f16 => .float16x2, + [4]f16 => .float16x4, + + f32 => .float32, + [1]f32 => .float32, + [2]f32 => .float32x2, + [3]f32 => .float32x3, + [4]f32 => .float32x4, + + u32 => .uint32, + [1]u32 => .uint32, + [2]u32 => .uint32x2, + [3]u32 => .uint32x3, + [4]u32 => .uint32x4, + + i32 => .sint32, + [1]i32 => .sint32, + [2]i32 => .sint32x2, + [3]i32 => .sint32x3, + [4]i32 => .sint32x4, + else => @compileError("Vertex attribute of type " ++ @typeName(struct_field.type) ++ " not supported"), + }; + + ret[i] = .{ + .format = vertex_format, + .offset = @offsetOf(T, struct_field.name), + .shader_location = i, + }; + } + + return ret; +} + +fn vertexBufferLayoutFromStruct(comptime T: type) zgpu.wgpu.VertexBufferLayout { + return .{ + .array_stride = @sizeOf(T), + .step_mode = .vertex, + .attribute_count = structFieldCount(T), + .attributes = comptime &vertexAttributesFromStruct(T), + }; +} + +fn structFieldCount(comptime T: type) comptime_int { + return @typeInfo(T).Struct.fields.len; +} diff --git a/src/main.zig b/src/main.zig index 92438cc..9b436db 100644 --- a/src/main.zig +++ b/src/main.zig @@ -129,7 +129,7 @@ pub fn main() !void { const zone_init_game = ztracy.ZoneN(@src(), "Init game"); - game.init(); + try game.init(); defer game.deinit(); zone_init_game.End();