From 4fd797c048c702badc6c00a8a1a9d9f935bde021 Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Sun, 8 Dec 2024 21:39:09 +0400 Subject: [PATCH] Slowly building out the rendering framework - Ditch VMA for per frame data - Add basic global descriptor set and all the boilerplate to manage that - Add global uniform buffer that only has camera matrices right now - Implement a per frame GPU memory arena, one large buffer that wraps around holding data for all frames in flight - Get free look camera working again --- assets/shaders/global.glsl | 15 ++ assets/shaders/triangle.glsl | 15 +- src/AssetManager.zig | 16 +- src/GraphicsContext.zig | 33 +++ src/Render2.zig | 394 ++++++++++++++++++++++++++++++----- src/RenderGraph.zig | 4 +- src/ShaderManager.zig | 44 ++++ src/game.zig | 8 +- src/globals.zig | 6 +- tools/asset_compiler.zig | 19 +- 10 files changed, 470 insertions(+), 84 deletions(-) create mode 100644 assets/shaders/global.glsl create mode 100644 src/ShaderManager.zig diff --git a/assets/shaders/global.glsl b/assets/shaders/global.glsl new file mode 100644 index 0000000..8fc3205 --- /dev/null +++ b/assets/shaders/global.glsl @@ -0,0 +1,15 @@ +#ifndef GLOBAL_GLSL +#define GLOBAL_GLSL + +struct View +{ + mat4 world_to_clip; + mat4 view_to_clip; + mat4 world_to_view; +}; + +layout(binding = 0, std430) uniform GlobalUniform { + View view; +} Global; + +#endif // GLOBAL_GLSL diff --git a/assets/shaders/triangle.glsl b/assets/shaders/triangle.glsl index ba6fc46..fa8db20 100644 --- a/assets/shaders/triangle.glsl +++ b/assets/shaders/triangle.glsl @@ -1,17 +1,10 @@ #extension GL_EXT_buffer_reference : require +#extension GL_EXT_scalar_block_layout : require + +#include "global.glsl" #if VERTEX_SHADER -layout(buffer_reference, std430) readonly buffer CameraMatrices { - mat4 view_projection; - mat4 projection; - mat4 view; -}; - -layout(push_constant) uniform constants { - CameraMatrices camera_matrices; -} PushConstants; - vec2 positions[3] = vec2[]( vec2(-0.5, 0.5), vec2(0.5, 0.5), @@ -29,7 +22,7 @@ layout(location = 0) out vec3 VertexColor; void main() { VertexColor = colors[gl_VertexIndex]; - gl_Position = PushConstants.camera_matrices.view_projection * vec4(positions[gl_VertexIndex], 0.0, 1.0); + gl_Position = Global.view.world_to_clip * vec4(positions[gl_VertexIndex], 0.0, 1.0); } #endif diff --git a/src/AssetManager.zig b/src/AssetManager.zig index a437eb2..3160602 100644 --- a/src/AssetManager.zig +++ b/src/AssetManager.zig @@ -29,6 +29,7 @@ const sdl = @import("sdl.zig"); const tracy = @import("tracy"); const vk = @import("vk"); const GraphicsContext = @import("GraphicsContext.zig"); +const ShaderManager = @import("ShaderManager.zig"); pub const AssetId = assets.AssetId; pub const Handle = assets.Handle; @@ -59,6 +60,7 @@ asset_watcher: AssetWatcher = undefined, vertex_heap: VertexBufferHeap, gc: *GraphicsContext, +shaderman: *ShaderManager, const AssetWatcher = struct { assetman: *AssetManager, @@ -148,7 +150,7 @@ const AssetWatcher = struct { } }; -pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, gc: *GraphicsContext) AssetManager { +pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, gc: *GraphicsContext, shaderman: *ShaderManager) AssetManager { var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; const exe_dir_path = std.fs.selfExeDirPath(&buf) catch @panic("can't find self exe dir path"); const exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch @panic("can't open self exe dir path"); @@ -159,6 +161,7 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, gc: *G .exe_dir = exe_dir, .vertex_heap = VertexBufferHeap.init(allocator) catch @panic("OOM"), .gc = gc, + .shaderman = shaderman, }; } @@ -323,15 +326,10 @@ fn loadShaderProgramErr(self: *AssetManager, id: AssetId) !LoadedShaderProgram { var program: formats.ShaderProgram = undefined; try program.serialize(&serializer); + // TODO: parse from shaders or something const pipeline_layout = try self.gc.device.createPipelineLayout(&.{ - .push_constant_range_count = 1, - .p_push_constant_ranges = &.{ - vk.PushConstantRange{ - .stage_flags = .{ .vertex_bit = true }, - .offset = 0, - .size = 8, - }, - }, + .p_set_layouts = &.{self.shaderman.descriptor_set_layouts.global}, + .set_layout_count = 1, }, null); const pipeline = blk: { diff --git a/src/GraphicsContext.zig b/src/GraphicsContext.zig index 3fff4f5..50989b4 100644 --- a/src/GraphicsContext.zig +++ b/src/GraphicsContext.zig @@ -34,6 +34,7 @@ vkb: BaseDispatch = undefined, vki: InstanceDispatch = undefined, vkd: DeviceDispatch = undefined, device_info: SelectedPhysicalDevice = undefined, +memory_config: DeviceMemoryConfig = .{}, instance: Instance = undefined, device: Device = undefined, queues: DeviceQueues = undefined, @@ -361,6 +362,7 @@ pub fn init(self: *GraphicsContext, allocator: std.mem.Allocator, window: *c.SDL const physical_devices = try self.instance.enumeratePhysicalDevicesAlloc(fba.allocator()); self.device_info = try selectPhysicalDevice(self.instance, self.surface, physical_devices); const queue_config = try selectQueues(self.instance, self.device_info.physical_device); + self.memory_config = try selectMemoryPools(self.instance, self.device_info.physical_device); // p_next is not const in PhysicalDeviceVulkan12Features, so pass it like this var vulkan13_device_features = vk.PhysicalDeviceVulkan13Features{ @@ -620,6 +622,15 @@ const DeviceQueueConfig = struct { device_to_host: Config, }; +pub const VulkanMemoryType = struct { + type_index: u32 = 0, + size: u64 = 0, +}; + +const DeviceMemoryConfig = struct { + cpu_to_gpu: VulkanMemoryType = .{}, +}; + // Hardcode queue priorities, no idea why I would need to use them const queue_priorities = [_]f32{ 1.0, 1.0, 1.0, 1.0, 1.0 }; @@ -724,3 +735,25 @@ fn selectQueues(instance: Instance, device: vk.PhysicalDevice) !DeviceQueueConfi .device_to_host = device_to_host.?, }; } + +fn selectMemoryPools(instance: Instance, device: vk.PhysicalDevice) !DeviceMemoryConfig { + const mem_props = instance.getPhysicalDeviceMemoryProperties(device); + + var result: DeviceMemoryConfig = .{}; + var found_cpu_to_gpu = false; + for (mem_props.memory_types[0..mem_props.memory_type_count], 0..) |mem_type, i| { + + // CPU->GPU Memory, likely a small buffer of 256mb or less + if (!mem_type.property_flags.host_cached_bit and mem_type.property_flags.contains(.{ .device_local_bit = true, .host_visible_bit = true })) { + found_cpu_to_gpu = true; + result.cpu_to_gpu.type_index = @intCast(i); + result.cpu_to_gpu.size = mem_props.memory_heaps[mem_type.heap_index].size; + } + } + + if (!found_cpu_to_gpu) { + return error.UnsupportedMemoryTypes; + } + + return result; +} diff --git a/src/Render2.zig b/src/Render2.zig index bf107a8..66d1d32 100644 --- a/src/Render2.zig +++ b/src/Render2.zig @@ -1,6 +1,7 @@ const std = @import("std"); -const AssetManager = @import("AssetManager.zig"); const GraphicsContext = @import("GraphicsContext.zig"); +const AssetManager = @import("AssetManager.zig"); +const ShaderManager = @import("ShaderManager.zig"); const vk = @import("vk"); const a = @import("asset_manifest"); const za = @import("zalgebra"); @@ -9,23 +10,234 @@ const Mat4 = za.Mat4; const Render2 = @This(); +// TODO: support ortho +pub const Camera = struct { + pos: Vec3 = Vec3.zero(), + + fovy: f32 = 60, + aspect: f32 = 1, + near: f32 = 0.1, + far: f32 = 10, + + view_mat: Mat4 = Mat4.identity(), + + pub fn projection(self: *const Camera) Mat4 { + return za.perspective(self.fovy, self.aspect, self.near, self.far); + } +}; + +var default_camera: Camera = .{}; + const MAX_FRAME_LAG = 3; +const PER_FRAME_ARENA_SIZE = 64 * 1024 * 1024; // 64mb TODO: should I handle cases when even 64mb is not available -assetman: *AssetManager, gc: *GraphicsContext, +shaderman: *ShaderManager, +assetman: *AssetManager, command_pool: GraphicsContext.CommandPool, +vulkan_frame_arena: VulkanPerFrameArena, +camera: *Camera = &default_camera, -// NOTE: TEST -camera_matrices_buffer: GraphicsContext.Buffer, frame: u32 = 0, frame_data: [MAX_FRAME_LAG]FrameData = undefined, -pub fn init(assetman: *AssetManager, gc: *GraphicsContext) !Render2 { - var self = Render2{ - .assetman = assetman, +// Ring buffer/arena for per frame data +pub const VulkanPerFrameArena = struct { + const Self = @This(); + + pub const FrameRegion = struct { + start: u64 = 0, + end: u64 = 0, + + pub fn init(start: u64, end: u64) FrameRegion { + return FrameRegion{ .start = start, .end = end }; + } + + // If region is wrapping (end < start), returns 2 non wrapping regions + pub fn unwrap(self: *const FrameRegion, len: u64, out_non_wrapping_regions: []FrameRegion) []FrameRegion { + std.debug.assert(out_non_wrapping_regions.len >= 2); + + if (self.end < self.start) { + out_non_wrapping_regions[0].start = self.start; + out_non_wrapping_regions[0].end = len; + out_non_wrapping_regions[1].start = 0; + out_non_wrapping_regions[1].end = self.end; + return out_non_wrapping_regions[0..2]; + } else { + out_non_wrapping_regions[0] = self.*; + return out_non_wrapping_regions[0..1]; + } + } + + pub fn intersectsNonWrapping(self: *const FrameRegion, other: *const FrameRegion) bool { + return !(other.start > self.end or self.start > other.end); + } + + pub fn intersectsWrapping(self: *const FrameRegion, other: *const FrameRegion, len: u64) bool { + var buf_a: [2]FrameRegion = undefined; + var buf_b: [2]FrameRegion = undefined; + const non_wrapping_regions_a = self.unwrap(len, &buf_a); + const non_wrapping_regions_b = other.unwrap(len, &buf_b); + + for (non_wrapping_regions_a) |region_a| { + for (non_wrapping_regions_b) |region_b| { + if (region_a.intersectsNonWrapping(®ion_b)) { + return true; + } + } + } + + return false; + } + }; + + memory: vk.DeviceMemory, + size: u64, + tail: u64 = 0, + frame: u32 = 0, + + // Tracks where the start offset for each frame is, + // Allocations will fail if you + // NOTE: bug in zig? Tried to use [MAX_FRAME_LAG]?u64 here, but optional checks pass even when value is null, wtf?? + frame_regions: [MAX_FRAME_LAG]?FrameRegion = [_]?FrameRegion{null} ** MAX_FRAME_LAG, + + // Tracking allocated resources per frame, unfortunately have to wait for frame to finish before we can destroy them :( + buffers: [MAX_FRAME_LAG][1024]vk.Buffer = undefined, + buffer_counts: [MAX_FRAME_LAG]u16 = [_]u16{0} ** MAX_FRAME_LAG, + + pub fn init(memory: vk.DeviceMemory, size: u64) Self { + return Self{ + .memory = memory, + .size = size, + }; + } + + pub fn startFrame(self: *VulkanPerFrameArena, device: GraphicsContext.Device, frame_index: u32) void { + // TODO: tail pointer should be aligned to nonCoherentAtomSize to avoid accidentally flushing memory being used by previous frames + // if we end up allocating right up until the previous frame's head + // Record start position of this frame + if (self.frame_regions[self.frame]) |*cur_region| { + cur_region.end = self.tail; + } + self.frame = frame_index; + self.frame_regions[self.frame] = FrameRegion.init(self.tail, self.tail); + + for (self.buffers[self.frame][0..self.buffer_counts[self.frame]]) |buf| { + device.destroyBuffer(buf, null); + } + self.buffer_counts[self.frame] = 0; + } + + // Caller guarantees that memory from given frame can be safely stomped, buffers destroyed etc. + pub fn resetFrame(self: *VulkanPerFrameArena, frame_index: u32) void { + self.frame_regions[frame_index] = null; + } + + pub fn getModifiedMemoryRanges(self: *VulkanPerFrameArena, out_ranges: []vk.MappedMemoryRange) []const vk.MappedMemoryRange { + std.debug.assert(out_ranges.len >= 2); + std.debug.assert(self.frame_regions[self.frame] != null); + + const region = self.frame_regions[self.frame].?; + + // We wrapped, use two regions + if (self.tail < region.start) { + out_ranges[0] = vk.MappedMemoryRange{ + .memory = self.memory, + .offset = region.start, + .size = self.size - region.start, + }; + out_ranges[1] = vk.MappedMemoryRange{ + .memory = self.memory, + .offset = 0, + .size = self.tail, + }; + + return out_ranges[0..]; + } else { + out_ranges[0] = vk.MappedMemoryRange{ + .memory = self.memory, + .offset = region.start, + .size = self.tail - region.start, + }; + + return out_ranges[0..1]; + } + } + + // Finds offset where memory can be put, handles wrapping, doesn't handle inter-frame stomping + fn findSlotOptimistic(self: *const Self, size: u64, alignment: u64) !u64 { + const offset = std.mem.alignForward(u64, self.tail, alignment); + + if (offset + size <= self.size) { + return offset; + } else if (size <= self.size) { + return 0; + } else { + return error.OutOfMemory; + } + } + + fn findSlotChecked(self: *const Self, size: u64, alignment: u64) !u64 { + const next_frame = (self.frame + 1) % MAX_FRAME_LAG; + const offset = try self.findSlotOptimistic(size, alignment); + + if (self.frame_regions[next_frame]) |next_frame_region| { + const allocated_region = FrameRegion.init(offset, offset + size); + + if (next_frame_region.intersectsWrapping(&allocated_region, self.size)) { + return error.OverlapsPreviousFrame; + } + } + + return offset; + } + + pub fn allocate(self: *Self, size: u64, alignment: u64) !u64 { + const offset = try self.findSlotChecked(size, alignment); + + self.tail = offset + size; + + return offset; + } + + pub fn createBufferRaw(self: *Self, device: GraphicsContext.Device, usage: vk.BufferUsageFlags, size: u64, out_addr: *u64) !vk.Buffer { + // NOTE: Allocating buffers just in time, hopefully vulkan impl is smart about allocation here and not doing new each time... + const buffer = try device.createBuffer(&vk.BufferCreateInfo{ + .flags = .{}, + .usage = usage, + .size = size, + .sharing_mode = .exclusive, + }, null); + errdefer device.destroyBuffer(buffer, null); + const mem_reqs = device.getBufferMemoryRequirements(buffer); + + out_addr.* = try self.allocate(mem_reqs.size, mem_reqs.alignment); + + try device.bindBufferMemory(buffer, self.memory, out_addr.*); + + self.buffers[self.frame][self.buffer_counts[self.frame]] = buffer; + self.buffer_counts[self.frame] += 1; + + return buffer; + } + + pub fn reset(self: *Self) void { + self.head = 0; + } +}; + +pub fn init(self: *Render2, gc: *GraphicsContext, shaderman: *ShaderManager, assetman: *AssetManager) !void { + const per_frame_upload_memory = try gc.device.allocateMemory(&.{ + .memory_type_index = gc.memory_config.cpu_to_gpu.type_index, + .allocation_size = PER_FRAME_ARENA_SIZE, + }, null); + + self.* = Render2{ .gc = gc, + .shaderman = shaderman, + .assetman = assetman, .command_pool = try gc.queues.graphics.createCommandPool(.{ .reset_command_buffer_bit = true }), - .camera_matrices_buffer = undefined, + .vulkan_frame_arena = VulkanPerFrameArena.init(per_frame_upload_memory, PER_FRAME_ARENA_SIZE), }; errdefer self.command_pool.deinit(); @@ -33,31 +245,62 @@ pub fn init(assetman: *AssetManager, gc: *GraphicsContext) !Render2 { for (0..MAX_FRAME_LAG) |i| { self.frame_data[i] = try FrameData.init(gc, self.command_pool); } +} - self.camera_matrices_buffer = try self.gc.createBuffer(&.{ - .usage = .{ .storage_buffer_bit = true, .transfer_dst_bit = true, .shader_device_address_bit = true }, - .size = @sizeOf(CameraMatrices) * MAX_FRAME_LAG, - .sharing_mode = .exclusive, - }, &.{ - .usage = .auto, - .flags = .{ - .host_access_sequential_write_bit = true, - .host_access_allow_transfer_instead_bit = true, - .mapped_bit = true, - }, - }); - // const mem_props = self.camera_matrices_buffer.getAllocationMemoryProperties(); +fn createPerFrameBuffer(self: *Render2, usage: vk.BufferUsageFlags, size: u64, out_addr: *u64) !vk.Buffer { + while (true) { + if (self.vulkan_frame_arena.createBufferRaw(self.gc.device, usage, size, out_addr)) |buffer| { + return buffer; + } else |err| switch (err) { + error.OverlapsPreviousFrame => { + const overlapped_frame = (self.frame + 1) % MAX_FRAME_LAG; - // // TODO: Assuming unified memory or resizable bar right now, should not assume that - // std.debug.assert(mem_props.contains(.{ .host_visible_bit = true })); + std.debug.print("Vulkan Frame Allocator Overlapped frame {}, waiting for it to finish...", .{overlapped_frame}); - return self; + try self.frame_data[overlapped_frame].waitForDrawAndReset(self.gc.device); + self.vulkan_frame_arena.resetFrame(overlapped_frame); + }, + else => return err, + } + } +} + +fn frameAllocMemReqs(self: *Render2, mem_reqs: vk.MemoryRequirements) !u64 { + return self.frameAlloc(mem_reqs.size, mem_reqs.alignment); } pub fn draw(self: *Render2) !void { + const device = self.gc.device; const frame = &self.frame_data[self.frame]; try frame.waitForDrawAndReset(self.gc.device); + self.vulkan_frame_arena.resetFrame(self.frame); + self.vulkan_frame_arena.startFrame(self.gc.device, self.frame); + + const frame_arena_mem: []u8 = @as([*c]u8, @ptrCast((try device.mapMemory(self.vulkan_frame_arena.memory, 0, self.vulkan_frame_arena.size, .{})).?))[0..self.vulkan_frame_arena.size]; + + var global_buffer_addr: u64 = 0; + const global_uniform_buffer = try self.createPerFrameBuffer(.{ .uniform_buffer_bit = true }, @sizeOf(GlobalUniform), &global_buffer_addr); + + { + const global_uniform: *align(1) GlobalUniform = std.mem.bytesAsValue(GlobalUniform, frame_arena_mem[global_buffer_addr .. global_buffer_addr + @sizeOf(GlobalUniform)]); + + { + const view = self.camera.view_mat; + // const fwidth: f32 = @floatFromInt(self.gc.swapchain_extent.width); + // const fheight: f32 = @floatFromInt(self.gc.swapchain_extent.height); + const projection = self.camera.projection(); + const view_projection = projection.mul(view); + + global_uniform.* = .{ + .view = .{ + .world_to_view = view, + .view_to_clip = projection, + .world_to_clip = view_projection, + }, + }; + } + } // Move this out into a separate func const swapchain_image_index: u32 = try self.gc.acquireSwapchainImage(frame.acquire_swapchain_image); @@ -70,26 +313,43 @@ pub fn draw(self: *Render2) !void { try cmds.beginCommandBuffer(&.{}); { - // Camera matrices - { - try self.camera_matrices_buffer.sync(cmds, .{ .stage_mask = .{ .host_bit = true }, .access_mask = .{ .host_write_bit = true } }); - const c_matrices: [*c]CameraMatrices = @alignCast(@ptrCast(self.camera_matrices_buffer.allocation_info.pMappedData.?)); - const matrices: *CameraMatrices = &c_matrices[0..MAX_FRAME_LAG][self.frame]; + // Transition global uniform buffer + cmds.pipelineBarrier2(&vk.DependencyInfo{ + .buffer_memory_barrier_count = 1, + .p_buffer_memory_barriers = &.{ + vk.BufferMemoryBarrier2{ + .buffer = global_uniform_buffer, + .src_stage_mask = .{ .host_bit = true }, + .src_access_mask = .{ .host_write_bit = true }, + .dst_stage_mask = .{ .vertex_shader_bit = true }, + .dst_access_mask = .{ .shader_read_bit = true }, + .offset = 0, + .size = @sizeOf(GlobalUniform), + .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + }, + }, + }); - const view = Mat4.lookAt(Vec3.new(0, 0, 1), Vec3.new(0, 0, 0), Vec3.new(0, 1, 0)); - const fwidth: f32 = @floatFromInt(self.gc.swapchain_extent.width); - const fheight: f32 = @floatFromInt(self.gc.swapchain_extent.height); - const projection = Mat4.perspective(60, fwidth / fheight, 0.1, 1000); - const view_projection = projection.mul(view); - matrices.* = .{ - .view = view, - .projection = projection, - .view_projection = view_projection, - }; - - try self.camera_matrices_buffer.flush(self.frame * @sizeOf(CameraMatrices), @sizeOf(CameraMatrices)); - try self.camera_matrices_buffer.sync(cmds, .{ .stage_mask = .{ .vertex_shader_bit = true }, .access_mask = .{ .shader_read_bit = true } }); - } + const global_descriptor_set = try frame.allocateDescriptorSet(device, self.shaderman.descriptor_set_layouts.global); + device.updateDescriptorSets(1, &.{ + vk.WriteDescriptorSet{ + .dst_set = global_descriptor_set, + .dst_binding = 0, + .dst_array_element = 0, + .descriptor_type = .uniform_buffer, + .descriptor_count = 1, + .p_buffer_info = &.{ + vk.DescriptorBufferInfo{ + .buffer = global_uniform_buffer, + .offset = 0, + .range = @sizeOf(GlobalUniform), + }, + }, + .p_image_info = &[_]vk.DescriptorImageInfo{}, + .p_texel_buffer_view = &[_]vk.BufferView{}, + }, + }, 0, null); try current_image.sync(cmds, .{ .stage_mask = .{ .color_attachment_output_bit = true }, .access_mask = .{ .color_attachment_write_bit = true } }, .attachment_optimal); { @@ -115,9 +375,7 @@ pub fn draw(self: *Render2) !void { const triangle = self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.triangle); cmds.bindPipeline(.graphics, triangle.pipeline); - - const device_address = self.gc.device.getBufferDeviceAddress(&.{ .buffer = self.camera_matrices_buffer.handle }); - cmds.pushConstants(triangle.layout, .{ .vertex_bit = true }, 0, 8, &device_address); + cmds.bindDescriptorSets(.graphics, triangle.layout, 0, 1, &.{global_descriptor_set}, 0, null); cmds.setViewportWithCount(1, &.{vk.Viewport{ .x = 0, @@ -139,6 +397,13 @@ pub fn draw(self: *Render2) !void { } try cmds.endCommandBuffer(); + var vulkan_frame_arena_modified_ranges_buf: [2]vk.MappedMemoryRange = undefined; + const vulkan_frame_arena_modified_ranges = self.vulkan_frame_arena.getModifiedMemoryRanges(&vulkan_frame_arena_modified_ranges_buf); + try device.flushMappedMemoryRanges(@intCast(vulkan_frame_arena_modified_ranges.len), vulkan_frame_arena_modified_ranges.ptr); + + // NOTE: Unmap DEVICE_LOCAL, HOST_VISIBLE memory before submit as it can be slow on Windows (according to Reddit...) + device.unmapMemory(self.vulkan_frame_arena.memory); + try self.gc.queues.graphics.submit( &GraphicsContext.SubmitInfo{ .wait_semaphores = &.{frame.acquire_swapchain_image}, @@ -183,6 +448,7 @@ const FrameData = struct { draw_fence: vk.Fence, command_buffer: GraphicsContext.CommandBuffer, + descriptor_pool: vk.DescriptorPool = .null_handle, pub fn init(gc: *GraphicsContext, command_pool: GraphicsContext.CommandPool) !FrameData { return FrameData{ @@ -191,19 +457,47 @@ const FrameData = struct { .draw_fence = try gc.device.createFence(&.{ .flags = .{ .signaled_bit = true } }, null), .command_buffer = try command_pool.allocateCommandBuffer(), + .descriptor_pool = try gc.device.createDescriptorPool(&vk.DescriptorPoolCreateInfo{ + .max_sets = 1024, + .p_pool_sizes = &.{ + vk.DescriptorPoolSize{ + .type = .uniform_buffer, + .descriptor_count = 8, + }, + }, + .pool_size_count = 1, + }, null), + + // TODO: maybe cache memory requirements? }; } + pub fn allocateDescriptorSet(self: *FrameData, device: GraphicsContext.Device, layout: vk.DescriptorSetLayout) !vk.DescriptorSet { + var result: [1]vk.DescriptorSet = .{.null_handle}; + try device.allocateDescriptorSets(&vk.DescriptorSetAllocateInfo{ + .descriptor_pool = self.descriptor_pool, + .descriptor_set_count = 1, + .p_set_layouts = &.{layout}, + }, &result); + return result[0]; + } + pub fn waitForDrawAndReset(self: *FrameData, device: GraphicsContext.Device) !void { _ = try device.waitForFences(1, &.{self.draw_fence}, vk.TRUE, std.math.maxInt(u64)); try device.resetFences(1, &.{self.draw_fence}); try self.command_buffer.resetCommandBuffer(.{ .release_resources_bit = true }); + + try device.resetDescriptorPool(self.descriptor_pool, .{}); } }; -const CameraMatrices = extern struct { - view_projection: Mat4, - projection: Mat4, - view: Mat4, +const GlobalUniform = extern struct { + pub const View = extern struct { + world_to_clip: Mat4, + view_to_clip: Mat4, + world_to_view: Mat4, + }; + + view: View, }; diff --git a/src/RenderGraph.zig b/src/RenderGraph.zig index 0ccecf3..fb94070 100644 --- a/src/RenderGraph.zig +++ b/src/RenderGraph.zig @@ -20,7 +20,9 @@ test "RenderGraph" { fn setup(ctx: *PassSetupContext) void { ctx.createTexture2D(GBuffer, 1024, 1024, vk.Format.r8g8b8a8_unorm); } - fn render(ctx: *GraphicsContext.CommandBuffer) void { + fn render(cmd_buf: *GraphicsContext.CommandBuffer) void { + + cmd_buf.bindDescriptorBuffersEXT(buffer_count: u32, p_binding_infos: [*]const DescriptorBufferBindingInfoEXT) ctx.getTexture2D(GBuffer); } }, diff --git a/src/ShaderManager.zig b/src/ShaderManager.zig new file mode 100644 index 0000000..5c49394 --- /dev/null +++ b/src/ShaderManager.zig @@ -0,0 +1,44 @@ +const std = @import("std"); +const vk = @import("vk"); +const GraphicsContext = @import("GraphicsContext.zig"); + +pub const ShaderManager = @This(); + +pub const DescriptorSets = struct { + const Global = struct { + pub const UBO = enum(u32) { + GlobalUniform = 0, + }; + }; +}; + +pub const DescriptorSetLayouts = struct { + global: vk.DescriptorSetLayout = .null_handle, +}; + +gc: *GraphicsContext, +descriptor_set_layouts: DescriptorSetLayouts = .{}, + +pub fn init(gc: *GraphicsContext) !ShaderManager { + var self = ShaderManager{ + .gc = gc, + }; + + // Global Descriptor Set Layout + { + const descriptor_set_layout_bindings = [_]vk.DescriptorSetLayoutBinding{ + vk.DescriptorSetLayoutBinding{ + .descriptor_type = .uniform_buffer, + .binding = 0, + .descriptor_count = 1, + .stage_flags = vk.ShaderStageFlags.fromInt(0x7FFFFFFF), // SHADER_STAGE_ALL + }, + }; + self.descriptor_set_layouts.global = try self.gc.device.createDescriptorSetLayout(&.{ + .p_bindings = &descriptor_set_layout_bindings, + .binding_count = descriptor_set_layout_bindings.len, + }, null); + } + + return self; +} diff --git a/src/game.zig b/src/game.zig index a71685b..0e3ff81 100644 --- a/src/game.zig +++ b/src/game.zig @@ -4,6 +4,7 @@ const InitMemory = globals.InitMemory; const GameMemory = globals.GameMemory; const c = @import("sdl.zig"); // const gl = @import("gl.zig"); +const ShaderManager = @import("ShaderManager.zig"); const AssetManager = @import("AssetManager.zig"); const Render = @import("Render.zig"); const Render2 = @import("Render2.zig"); @@ -174,12 +175,13 @@ export fn game_init(global_allocator: *std.mem.Allocator) void { globals.g_mem.* = .{ .global_allocator = global_allocator.*, .frame_fba = std.heap.FixedBufferAllocator.init(frame_arena_buffer), - .assetman = AssetManager.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_init.gc), - .render2 = Render2.init(&globals.g_mem.assetman, &globals.g_init.gc) catch @panic("OOM"), + .shaderman = ShaderManager.init(&globals.g_init.gc) catch @panic("ShaderManager.init"), + .assetman = AssetManager.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_init.gc, &globals.g_mem.shaderman), // .render = Render.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_mem.assetman), .world = .{ .frame_arena = globals.g_mem.frame_fba.allocator() }, }; - globals.g_mem.render.camera = &globals.g_mem.free_cam.camera; + globals.g_mem.render2.init(&globals.g_init.gc, &globals.g_mem.shaderman, &globals.g_mem.assetman) catch @panic("OOM"); + globals.g_mem.render2.camera = &globals.g_mem.free_cam.camera; std.log.debug("actual ptr: {}, correct ptr {}", .{ globals.g_mem.assetman.frame_arena.ptr, globals.g_mem.frame_fba.allocator().ptr }); globals.g_assetman = &globals.g_mem.assetman; globals.g_assetman.initWatch(); diff --git a/src/globals.zig b/src/globals.zig index d0ed809..f7b9a50 100644 --- a/src/globals.zig +++ b/src/globals.zig @@ -3,6 +3,7 @@ const c = @import("sdl.zig"); const Render = @import("Render.zig"); const Render2 = @import("Render2.zig"); const AssetManager = @import("AssetManager.zig"); +const ShaderManager = @import("ShaderManager.zig"); const World = @import("entity.zig").World; const GraphicsContext = @import("GraphicsContext.zig"); @@ -35,9 +36,10 @@ pub const InitMemory = struct { pub const GameMemory = struct { global_allocator: std.mem.Allocator, frame_fba: std.heap.FixedBufferAllocator, + shaderman: ShaderManager, assetman: AssetManager, render: Render = undefined, - render2: Render2, + render2: Render2 = undefined, performance_frequency: u64 = 0, last_frame_time: u64 = 0, delta_time: f32 = 0.0000001, @@ -63,7 +65,7 @@ pub const FreeLookCamera = struct { yaw: f32 = 0, move_speed: f32 = 0.5, - camera: Render.Camera = .{}, + camera: Render2.Camera = .{}, pub fn update(self: *FreeLookCamera, dt: f32, move: Vec3, look: Vec2) void { self.yaw += look.x(); diff --git a/tools/asset_compiler.zig b/tools/asset_compiler.zig index 12f014f..35be608 100644 --- a/tools/asset_compiler.zig +++ b/tools/asset_compiler.zig @@ -586,8 +586,8 @@ fn readFileContents(allocator: std.mem.Allocator, path: []const u8) ![]u8 { // Returns spirv binary source // Caller owns memory fn processShader(allocator: std.mem.Allocator, flags: []const []const u8, input: []const u8, maybe_dep_file: ?[]const u8) ![]u8 { - const old_depfile_contents = if (maybe_dep_file) |dep| try readFileContents(allocator, dep) else try allocator.alloc(u8, 0); - defer allocator.free(old_depfile_contents); + // const old_depfile_contents = if (maybe_dep_file) |dep| try readFileContents(allocator, dep) else try allocator.alloc(u8, 0); + // defer allocator.free(old_depfile_contents); // TODO: make sure output is stdout const result = try std.process.Child.run(.{ @@ -614,13 +614,16 @@ fn processShader(allocator: std.mem.Allocator, flags: []const []const u8, input: }, } + // NOTE: Dep file is technically incorrect, but zig build system doesn't care, it will collect all dependencies after colon + // even if they are not for the same file it's processing + // TODO: figure out a better way to handle depfile - if (maybe_dep_file) |dep_file| { - const file = try std.fs.cwd().openFile(dep_file, .{ .mode = .read_write }); - defer file.close(); - try file.seekFromEnd(0); - try file.writeAll(old_depfile_contents); - } + // if (maybe_dep_file) |dep_file| { + // const file = try std.fs.cwd().openFile(dep_file, .{ .mode = .read_write }); + // defer file.close(); + // try file.seekFromEnd(0); + // try file.writeAll(old_depfile_contents); + // } return result.stdout; }