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
This commit is contained in:
sergeypdev 2024-12-08 21:39:09 +04:00
parent 4b6b859f40
commit 4fd797c048
10 changed files with 470 additions and 84 deletions

View File

@ -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

View File

@ -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

View File

@ -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: {

View File

@ -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;
}

View File

@ -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(&region_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,
};

View File

@ -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);
}
},

44
src/ShaderManager.zig Normal file
View File

@ -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;
}

View File

@ -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();

View File

@ -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();

View File

@ -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;
}