From ad80fb4fd9ada39d35b8bb2e6dc80cfeccd1d893 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Fri, 15 May 2026 02:20:08 +0200 Subject: [PATCH] Make use of some globals to stop typing `engine` everywhere. --- src/AppContext.zig | 20 +++ src/Chunks.zig | 49 +++--- src/Game.zig | 215 ++++++++++++------------- src/Gui.zig | 73 +++++---- src/Player.zig | 7 - src/assets/Blocks.zig | 67 ++++---- src/assets/Chunk.zig | 33 ++-- src/assets/Materials.zig | 80 +++++----- src/assets/Textures.zig | 68 ++++---- src/engine/Atom.zig | 133 ++++------------ src/engine/Atoms.zig | 80 ++++++++++ src/engine/CommandBuffer.zig | 27 ++-- src/engine/Engine.zig | 269 ++++++++++++++------------------ src/engine/GenericBuffer.zig | 33 ++-- src/engine/QueueSharingMode.zig | 20 --- src/engine/Skybox.zig | 67 ++++---- src/engine/StagingBuffer.zig | 23 ++- src/engine/Swapchain.zig | 116 ++++++++------ src/engine/Texture.zig | 50 +++--- src/engine/VkAllocator.zig | 131 +++++++++++----- src/main.zig | 139 ++++++++++------- 21 files changed, 902 insertions(+), 798 deletions(-) create mode 100644 src/AppContext.zig create mode 100644 src/engine/Atoms.zig delete mode 100644 src/engine/QueueSharingMode.zig diff --git a/src/AppContext.zig b/src/AppContext.zig new file mode 100644 index 0000000..007dc5c --- /dev/null +++ b/src/AppContext.zig @@ -0,0 +1,20 @@ +const AppContext = @This(); +const std = @import("std"); + +const glfw = @import("zglfw"); + +const Atoms = @import("engine/Atoms.zig"); +const Engine = @import("engine/Engine.zig"); +const Swapchain = @import("engine/Swapchain.zig"); +const VkAllocator = @import("engine/VkAllocator.zig"); + +pub var allocator_general: std.mem.Allocator = undefined; +pub var allocator_frame: std.mem.Allocator = undefined; +pub var allocator_persistent: std.mem.Allocator = undefined; +pub var io: std.Io = undefined; +pub var vk_allocator: *VkAllocator = undefined; + +pub var window: *glfw.Window = undefined; +pub var atoms: *Atoms = undefined; +pub var engine: *Engine = undefined; +pub var swapchain: *Swapchain = undefined; diff --git a/src/Chunks.zig b/src/Chunks.zig index e922cff..4986ba2 100644 --- a/src/Chunks.zig +++ b/src/Chunks.zig @@ -2,6 +2,7 @@ const Chunks = @This(); const std = @import("std"); const c = @import("const.zig"); +const ctx = @import("AppContext.zig"); const math = @import("math.zig"); const vk = @import("vulkan"); const vm = @import("vecmath"); @@ -24,12 +25,14 @@ pub const RaycastHit = struct { side: voxels.Orientation, }; -pub fn deinit(self: *Chunks, engine: *Engine, descriptor_pool: vk.DescriptorPool, allocator: std.mem.Allocator) void { +pub fn deinit(self: *Chunks, descriptor_pool: vk.DescriptorPool) void { + const allocator_general = ctx.allocator_general; + var it = self.chunks.valueIterator(); while (it.next()) |chunk| { - chunk.deinit(engine, descriptor_pool); + chunk.deinit(descriptor_pool); } - self.chunks.deinit(allocator); + self.chunks.deinit(allocator_general); self.* = undefined; } @@ -58,11 +61,9 @@ pub fn setVoxelAt( self: *Chunks, vx: vm.Vector3Int, id: Blocks.Id, - engine: *Engine, blocks: *const Blocks, descriptor_pool: vk.DescriptorPool, per_batch_descriptor_set_layout: vk.DescriptorSetLayout, - allocator: std.mem.Allocator, ) !void { const min_ck = vm.Vector3Int.initScalar(std.math.minInt(i16)); const max_ck = vm.Vector3Int.initScalar(std.math.maxInt(i16)); @@ -76,91 +77,91 @@ pub fn setVoxelAt( const y: i16 = @intCast(ck.y); const z: i16 = @intCast(ck.z); - const chunk = try self.getOrCreateChunk(ck, engine, descriptor_pool, per_batch_descriptor_set_layout, allocator); + const chunk = try self.getOrCreateChunk(ck, descriptor_pool, per_batch_descriptor_set_layout); const ckvx = vx.modScalar(c.vx_per_ck); const block = &chunk.blocks[@intCast(ckvx.z)][@intCast(ckvx.y)][@intCast(ckvx.x)]; if (block.* != id) { block.* = id; - try chunk.refresh(engine, blocks, .{ + try chunk.refresh(blocks, .{ .positive_x = self.chunks.getPtr(.{ x + 1, y, z }), .negative_x = self.chunks.getPtr(.{ x - 1, y, z }), .positive_y = self.chunks.getPtr(.{ x, y + 1, z }), .negative_y = self.chunks.getPtr(.{ x, y - 1, z }), .positive_z = self.chunks.getPtr(.{ x, y, z + 1 }), .negative_z = self.chunks.getPtr(.{ x, y, z - 1 }), - }, allocator); + }); if (ckvx.x == 0) { if (self.chunks.getPtr(.{ x - 1, y, z })) |neighbor| { - try neighbor.refresh(engine, blocks, .{ + try neighbor.refresh(blocks, .{ .positive_x = self.chunks.getPtr(.{ x, y, z }), .negative_x = self.chunks.getPtr(.{ x - 2, y, z }), .positive_y = self.chunks.getPtr(.{ x - 1, y + 1, z }), .negative_y = self.chunks.getPtr(.{ x - 1, y - 1, z }), .positive_z = self.chunks.getPtr(.{ x - 1, y, z + 1 }), .negative_z = self.chunks.getPtr(.{ x - 1, y, z - 1 }), - }, allocator); + }); } } else if (ckvx.x == c.vx_per_ck - 1) { if (self.chunks.getPtr(.{ x + 1, y, z })) |neighbor| { - try neighbor.refresh(engine, blocks, .{ + try neighbor.refresh(blocks, .{ .positive_x = self.chunks.getPtr(.{ x + 2, y, z }), .negative_x = self.chunks.getPtr(.{ x, y, z }), .positive_y = self.chunks.getPtr(.{ x + 1, y + 1, z }), .negative_y = self.chunks.getPtr(.{ x + 1, y - 1, z }), .positive_z = self.chunks.getPtr(.{ x + 1, y, z + 1 }), .negative_z = self.chunks.getPtr(.{ x + 1, y, z - 1 }), - }, allocator); + }); } } if (ckvx.y == 0) { if (self.chunks.getPtr(.{ x, y - 1, z })) |neighbor| { - try neighbor.refresh(engine, blocks, .{ + try neighbor.refresh(blocks, .{ .positive_x = self.chunks.getPtr(.{ x + 1, y - 1, z }), .negative_x = self.chunks.getPtr(.{ x - 1, y - 1, z }), .positive_y = self.chunks.getPtr(.{ x, y, z }), .negative_y = self.chunks.getPtr(.{ x, y - 2, z }), .positive_z = self.chunks.getPtr(.{ x, y - 1, z + 1 }), .negative_z = self.chunks.getPtr(.{ x, y - 1, z - 1 }), - }, allocator); + }); } } else if (ckvx.y == c.vx_per_ck - 1) { if (self.chunks.getPtr(.{ x, y + 1, z })) |neighbor| { - try neighbor.refresh(engine, blocks, .{ + try neighbor.refresh(blocks, .{ .positive_x = self.chunks.getPtr(.{ x + 1, y + 1, z }), .negative_x = self.chunks.getPtr(.{ x - 1, y + 1, z }), .positive_y = self.chunks.getPtr(.{ x, y + 2, z }), .negative_y = self.chunks.getPtr(.{ x, y, z }), .positive_z = self.chunks.getPtr(.{ x, y + 1, z + 1 }), .negative_z = self.chunks.getPtr(.{ x, y + 1, z - 1 }), - }, allocator); + }); } } if (ckvx.z == 0) { if (self.chunks.getPtr(.{ x, y, z - 1 })) |neighbor| { - try neighbor.refresh(engine, blocks, .{ + try neighbor.refresh(blocks, .{ .positive_x = self.chunks.getPtr(.{ x + 1, y, z - 1 }), .negative_x = self.chunks.getPtr(.{ x - 1, y, z - 1 }), .positive_y = self.chunks.getPtr(.{ x, y + 1, z - 1 }), .negative_y = self.chunks.getPtr(.{ x, y - 1, z - 1 }), .positive_z = self.chunks.getPtr(.{ x, y, z }), .negative_z = self.chunks.getPtr(.{ x, y, z - 2 }), - }, allocator); + }); } } else if (ckvx.z == c.vx_per_ck - 1) { if (self.chunks.getPtr(.{ x, y, z + 1 })) |neighbor| { - try neighbor.refresh(engine, blocks, .{ + try neighbor.refresh(blocks, .{ .positive_x = self.chunks.getPtr(.{ x + 1, y, z + 1 }), .negative_x = self.chunks.getPtr(.{ x - 1, y, z + 1 }), .positive_y = self.chunks.getPtr(.{ x, y + 1, z + 1 }), .negative_y = self.chunks.getPtr(.{ x, y - 1, z + 1 }), .positive_z = self.chunks.getPtr(.{ x, y, z + 2 }), .negative_z = self.chunks.getPtr(.{ x, y, z }), - }, allocator); + }); } } } @@ -667,11 +668,11 @@ pub fn raycast(self: *const Chunks, origin_sv: vm.Vector3Int, ray_sv: vm.Vector3 fn getOrCreateChunk( self: *Chunks, ck: vm.Vector3Int, - engine: *Engine, descriptor_pool: vk.DescriptorPool, per_batch_descriptor_set_layout: vk.DescriptorSetLayout, - allocator: std.mem.Allocator, ) !*Chunk { + const allocator_general = ctx.allocator_general; + const min_ck = vm.Vector3Int.initScalar(std.math.minInt(i16)); const max_ck = vm.Vector3Int.initScalar(std.math.maxInt(i16)); std.debug.assert((ck.x >= min_ck.x) | (ck.y >= min_ck.y) | (ck.z >= min_ck.z) | (ck.x <= max_ck.x) | (ck.y <= max_ck.y) | (ck.z <= max_ck.z)); @@ -682,7 +683,7 @@ fn getOrCreateChunk( @intCast(ck.z), }; - const entry = try self.chunks.getOrPut(allocator, key); + const entry = try self.chunks.getOrPut(allocator_general, key); if (entry.found_existing) { return entry.value_ptr; @@ -690,7 +691,7 @@ fn getOrCreateChunk( errdefer _ = self.chunks.remove(key); const origin = ck.asFloat().mulScalar(c.vx_per_ck); - const chunk = try Chunk.init(engine, .{ + const chunk = try Chunk.init(.{ .origin = origin, .descriptor_pool = descriptor_pool, .per_batch_descriptor_set_layout = per_batch_descriptor_set_layout, diff --git a/src/Game.zig b/src/Game.zig index b813c04..0e6e87d 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -2,6 +2,7 @@ const Game = @This(); const std = @import("std"); const c = @import("const.zig"); +const ctx = @import("AppContext.zig"); const glfw = @import("zglfw"); const math = @import("math.zig"); const media = @import("media"); @@ -25,10 +26,6 @@ const Swapchain = @import("engine/Swapchain.zig"); const Texture = @import("engine/Texture.zig"); const Textures = @import("assets/Textures.zig"); -allocator: std.mem.Allocator, -io: std.Io, -engine: *Engine, -swapchain: *Swapchain, global_descriptor_set_layout: vk.DescriptorSetLayout, per_batch_descriptor_set_layout: vk.DescriptorSetLayout, descriptor_pool: vk.DescriptorPool, @@ -64,24 +61,30 @@ const chunk_descriptor_pool = 1024; const camera_near_plane = 0.1; -pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain: *Swapchain) !Game { - var stbi = media.stbi.init(allocator, io); +pub fn init() !Game { + const allocator_general = ctx.allocator_general; + const allocator_frame = ctx.allocator_frame; + const io = ctx.io; + const engine = ctx.engine; + const swapchain = ctx.swapchain; + + var stbi = media.stbi.init(allocator_general, io); errdefer stbi.deinit(); - var materials = try Materials.init(engine, allocator); - errdefer materials.deinit(engine, allocator); + var materials = try Materials.init(); + errdefer materials.deinit(); - var textures = try Textures.init(engine, allocator); - errdefer textures.deinit(engine, allocator); + var textures = try Textures.init(); + errdefer textures.deinit(); - var blocks = try Blocks.init(allocator); - errdefer blocks.deinit(allocator); + var blocks = try Blocks.init(); + errdefer blocks.deinit(); // JANK HACK When this line is removed, capturing a frame with RenderDoc // will crash the game with segfault reading address 0x140 (presumably // within librenderdoc.so). - blocks.loadAll(engine, &materials, &textures, &stbi, allocator, io); + blocks.loadAll(&materials, &textures, &stbi); const sampler = try engine.createSampler(.{ .mag_filter = .linear, @@ -177,14 +180,14 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain defer engine.destroyShaderModule(fragment_shader); engine.setObjectName(fragment_shader, "SM main_frag", .{}); - var vertex_buffer = try shaders.VertexBuffer.init(engine, .{ + var vertex_buffer = try shaders.VertexBuffer.init(.{ .usage = .vertex, .target_queue = .graphics, .array_capacity = 4, .name = "QuadVB", }); - errdefer vertex_buffer.deinit(engine); - try vertex_buffer.write(engine, .{ + errdefer vertex_buffer.deinit(); + try vertex_buffer.write(.{ .elements = &.{ .init( .init(0, 0, 0), @@ -213,33 +216,33 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain }, }); - var index_buffer = try shaders.IndexBuffer.init(engine, .{ + var index_buffer = try shaders.IndexBuffer.init(.{ .usage = .index, .target_queue = .graphics, .array_capacity = 6, .name = "QuadIB", }); - errdefer index_buffer.deinit(engine); - try index_buffer.write(engine, .{ + errdefer index_buffer.deinit(); + try index_buffer.write(.{ .elements = &.{ 0, 1, 2, 2, 1, 3 }, }); - var global_uniforms = try shaders.GlobalUniformsBuffer.init(engine, .{ + var global_uniforms = try shaders.GlobalUniformsBuffer.init(.{ .usage = .uniform, .target_queue = .graphics, .name = "GlobalUniforms", }); - errdefer global_uniforms.deinit(engine); + errdefer global_uniforms.deinit(); - var global_uniforms_staging_buffer = try StagingBuffer.init(engine, .{ + var global_uniforms_staging_buffer = try StagingBuffer.init(.{ .capacity = @sizeOf(shaders.GlobalUniforms), .target_queue = .graphics, }); - errdefer global_uniforms_staging_buffer.deinit(engine); + errdefer global_uniforms_staging_buffer.deinit(); const global_uniforms_transfer_semaphores = blk: { - var semaphores: std.ArrayList(vk.Semaphore) = try .initCapacity(allocator, swapchain.swapchain_images.len); - errdefer semaphores.deinit(allocator); + var semaphores: std.ArrayList(vk.Semaphore) = try .initCapacity(allocator_general, swapchain.swapchain_images.len); + errdefer semaphores.deinit(allocator_general); errdefer for (semaphores.items) |x| engine.destroySemaphore(x); @@ -249,30 +252,30 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain semaphores.appendAssumeCapacity(semaphore); } - break :blk try semaphores.toOwnedSlice(allocator); + break :blk try semaphores.toOwnedSlice(allocator_general); }; errdefer { for (global_uniforms_transfer_semaphores) |semaphore| { engine.destroySemaphore(semaphore); } - allocator.free(global_uniforms_transfer_semaphores); + allocator_general.free(global_uniforms_transfer_semaphores); } - var point_lights = try shaders.PointLightBuffer.init(engine, .{ + var point_lights = try shaders.PointLightBuffer.init(.{ .usage = .storage, .target_queue = .graphics, .array_capacity = max_point_lights, .name = "PointLights", }); - errdefer point_lights.deinit(engine); + errdefer point_lights.deinit(); - var directional_lights = try shaders.DirectionalLightBuffer.init(engine, .{ + var directional_lights = try shaders.DirectionalLightBuffer.init(.{ .usage = .storage, .target_queue = .graphics, .array_capacity = max_directional_lights, .name = "DirectionalLights", }); - errdefer directional_lights.deinit(engine); + errdefer directional_lights.deinit(); const pipeline = try engine.createGraphicsPipeline(.{ .stages = &.{ @@ -444,14 +447,14 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain }); engine.setObjectName(global_descriptor_set, "DS Global", .{}); - const block_grass = try blocks.getOrLoad(engine, &materials, &textures, &stbi, "Grass.json", allocator, io); - const block_dirt = try blocks.getOrLoad(engine, &materials, &textures, &stbi, "Dirt.json", allocator, io); - const block_stone = try blocks.getOrLoad(engine, &materials, &textures, &stbi, "Stone.json", allocator, io); - const block_bedrock = try blocks.getOrLoad(engine, &materials, &textures, &stbi, "Bedrock.json", allocator, io); + const block_grass = try blocks.getOrLoad(&materials, &textures, &stbi, "Grass.json"); + const block_dirt = try blocks.getOrLoad(&materials, &textures, &stbi, "Dirt.json"); + const block_stone = try blocks.getOrLoad(&materials, &textures, &stbi, "Stone.json"); + const block_bedrock = try blocks.getOrLoad(&materials, &textures, &stbi, "Bedrock.json"); // VOLATILE Load all assets before this point - const descriptor_images = try allocator.alloc(vk.DescriptorImageInfo, textures.array.items.len); + const descriptor_images = try allocator_frame.alloc(vk.DescriptorImageInfo, textures.array.items.len); for (textures.array.items, descriptor_images) |texture, *info| { info.* = .{ .sampler = .null_handle, @@ -459,7 +462,6 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain .image_layout = .shader_read_only_optimal, }; } - defer allocator.free(descriptor_images); try engine.updateDescriptorSets(.{ .writes = &.{ @@ -537,9 +539,9 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain errdefer { var it = chunks.valueIterator(); while (it.next()) |chunk| { - chunk.deinit(engine, descriptor_pool); + chunk.deinit(descriptor_pool); } - chunks.deinit(allocator); + chunks.deinit(allocator_general); } const world_seed = engine.random.int(u64); @@ -557,10 +559,10 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain @floatFromInt(chunk_coords3[2]), ).mulScalar(16); - try chunks.ensureUnusedCapacity(allocator, 1); + try chunks.ensureUnusedCapacity(allocator_general, 1); chunks.putAssumeCapacityNoClobber( chunk_coords3, - try Chunk.init(engine, .{ + try Chunk.init(.{ .origin = origin, .descriptor_pool = descriptor_pool, .per_batch_descriptor_set_layout = per_batch_descriptor_set_layout, @@ -611,18 +613,18 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain while (chunk_it.next()) |entry| { const x, const y, const z = entry.key_ptr.*; const chunk = entry.value_ptr; - try chunk.refresh(engine, &blocks, .{ + try chunk.refresh(&blocks, .{ .positive_x = chunks.getPtr(.{ x + 1, y, z }), .negative_x = chunks.getPtr(.{ x - 1, y, z }), .positive_y = chunks.getPtr(.{ x, y + 1, z }), .negative_y = chunks.getPtr(.{ x, y - 1, z }), .positive_z = chunks.getPtr(.{ x, y, z + 1 }), .negative_z = chunks.getPtr(.{ x, y, z - 1 }), - }, allocator); + }); } const point_lights_data: []const shaders.PointLight = &.{}; - try point_lights.write(engine, .{ + try point_lights.write(.{ .header = @intCast(point_lights_data.len), .elements = point_lights_data, }); @@ -633,36 +635,25 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain .color = .init(0.3, 0.3, 0.3), }, }; - try directional_lights.write(engine, .{ + try directional_lights.write(.{ .header = @intCast(directional_lights_data.len), .elements = directional_lights_data, }); var skybox = try Skybox.load( "skybox.hdr", - engine, - swapchain, &stbi, 512, global_uniforms.buffer, - allocator, - io, ); - errdefer skybox.deinit(engine); + errdefer skybox.deinit(); var gui = try Gui.init( - allocator, - engine, - swapchain, global_uniforms.buffer, ); - errdefer gui.deinit(engine, allocator); + errdefer gui.deinit(); return .{ - .allocator = allocator, - .io = io, - .engine = engine, - .swapchain = swapchain, .global_descriptor_set_layout = global_descriptor_set_layout, .per_batch_descriptor_set_layout = per_batch_descriptor_set_layout, .descriptor_pool = descriptor_pool, @@ -679,7 +670,7 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain .point_lights = point_lights, .directional_lights = directional_lights, .sampler = sampler, - .deferred_command_buffers = try .initCapacity(allocator, 4), + .deferred_command_buffers = try .initCapacity(allocator_general, 4), .stbi = stbi, .blocks = blocks, @@ -694,49 +685,54 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain } pub fn deinit(self: *Game) void { + const allocator_general = ctx.allocator_general; + const engine = ctx.engine; + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); for (self.deferred_command_buffers.items) |*command_buffer| { - command_buffer.deinit(self.engine); + command_buffer.deinit(); } - self.deferred_command_buffers.deinit(self.allocator); + self.deferred_command_buffers.deinit(allocator_general); - self.vertex_buffer.deinit(self.engine); - self.index_buffer.deinit(self.engine); + self.vertex_buffer.deinit(); + self.index_buffer.deinit(); - self.chunks.deinit(self.engine, self.descriptor_pool, self.allocator); - self.skybox.deinit(self.engine); - self.gui.deinit(self.engine, self.allocator); + self.chunks.deinit(self.descriptor_pool); + self.skybox.deinit(); + self.gui.deinit(); - self.global_uniforms.deinit(self.engine); - self.global_uniforms_staging_buffer.deinit(self.engine); - self.point_lights.deinit(self.engine); - self.directional_lights.deinit(self.engine); + self.global_uniforms.deinit(); + self.global_uniforms_staging_buffer.deinit(); + self.point_lights.deinit(); + self.directional_lights.deinit(); for (self.global_uniforms_transfer_semaphores) |semaphore| { - self.engine.destroySemaphore(semaphore); + engine.destroySemaphore(semaphore); } - self.allocator.free(self.global_uniforms_transfer_semaphores); + allocator_general.free(self.global_uniforms_transfer_semaphores); - self.engine.destroyDescriptorPool(self.descriptor_pool); - self.engine.destroySampler(self.sampler); - self.engine.destroyPipeline(self.pipeline); - self.engine.destroyPipelineLayout(self.pipeline_layout); - self.engine.destroyDescriptorSetLayout(self.per_batch_descriptor_set_layout); - self.engine.destroyDescriptorSetLayout(self.global_descriptor_set_layout); + engine.destroyDescriptorPool(self.descriptor_pool); + engine.destroySampler(self.sampler); + engine.destroyPipeline(self.pipeline); + engine.destroyPipelineLayout(self.pipeline_layout); + engine.destroyDescriptorSetLayout(self.per_batch_descriptor_set_layout); + engine.destroyDescriptorSetLayout(self.global_descriptor_set_layout); - self.textures.deinit(self.engine, self.allocator); - self.materials.deinit(self.engine, self.allocator); - self.blocks.deinit(self.allocator); + self.textures.deinit(); + self.materials.deinit(); + self.blocks.deinit(); self.stbi.deinit(); self.* = undefined; } pub fn update(self: *Game, dt: f32) void { + const swapchain = ctx.swapchain; + self.gui.beginFrame(); self.player.update(dt, &self.chunks); - const extent = self.swapchain.extent; + const extent = swapchain.extent; const framebuffer_size = vm.Vector2.init( @floatFromInt(extent.width), @floatFromInt(extent.height), @@ -791,8 +787,12 @@ pub fn onMouseUp(self: *Game, button: glfw.MouseButton) void { } fn render(self: *Game) !void { + const allocator_general = ctx.allocator_general; + const window = ctx.window; + const swapchain = ctx.swapchain; + const framebuffer_width, const framebuffer_height = blk: { - const w, const h = self.engine.mode.surface.window.getFramebufferSize(); + const w, const h = window.getFramebufferSize(); break :blk [_]u32{ @intCast(w), @intCast(h) }; }; @@ -800,25 +800,25 @@ fn render(self: *Game) !void { return; } - if (framebuffer_width != self.swapchain.extent.width or framebuffer_height != self.swapchain.extent.height) { + if (framebuffer_width != swapchain.extent.width or framebuffer_height != swapchain.extent.height) { try self.recreateSwapchain(); } - if (self.swapchain.swapchain == .null_handle) { + if (swapchain.swapchain == .null_handle) { return; } - self.swapchain.acquire(self.engine) catch |err| switch (err) { + swapchain.acquire() catch |err| switch (err) { error.OutOfDateKHR => return self.recreateSwapchain(), else => return err, }; for (self.deferred_command_buffers.items) |*deferred_command_buffer| { - deferred_command_buffer.deinit(self.engine); + deferred_command_buffer.deinit(); } self.deferred_command_buffers.clearRetainingCapacity(); - const extent = self.swapchain.extent; + const extent = swapchain.extent; const framebuffer_size = vm.Vector2.init( @floatFromInt(extent.width), @@ -867,12 +867,12 @@ fn render(self: *Game) !void { .ambientLight = ambient_light, }; - const staging_memory = try self.global_uniforms_staging_buffer.map(self.engine); + const staging_memory = try self.global_uniforms_staging_buffer.map(); @memcpy(staging_memory, std.mem.asBytes(&global_uniforms_data)); - self.global_uniforms_staging_buffer.unmap(self.engine); + self.global_uniforms_staging_buffer.unmap(); - try self.deferred_command_buffers.ensureUnusedCapacity(self.allocator, 1); - const global_uniforms_transfer_command_buffer = try CommandBuffer.init(self.engine, .transfer); + try self.deferred_command_buffers.ensureUnusedCapacity(allocator_general, 1); + const global_uniforms_transfer_command_buffer = try CommandBuffer.init(.transfer); self.deferred_command_buffers.appendAssumeCapacity(global_uniforms_transfer_command_buffer); try global_uniforms_transfer_command_buffer.beginCommandBuffer(); @@ -889,17 +889,17 @@ fn render(self: *Game) !void { ); try global_uniforms_transfer_command_buffer.endCommandBuffer(); - try global_uniforms_transfer_command_buffer.submit(self.engine, .{ - .signal_semaphores = &.{self.global_uniforms_transfer_semaphores[self.swapchain.image_index.?]}, + try global_uniforms_transfer_command_buffer.submit(.{ + .signal_semaphores = &.{self.global_uniforms_transfer_semaphores[swapchain.image_index.?]}, }); - try self.deferred_command_buffers.ensureUnusedCapacity(self.allocator, 1); - const command_buffer: CommandBuffer = try .init(self.engine, .graphics); + try self.deferred_command_buffers.ensureUnusedCapacity(allocator_general, 1); + const command_buffer: CommandBuffer = try .init(.graphics); self.deferred_command_buffers.appendAssumeCapacity(command_buffer); try command_buffer.beginCommandBuffer(); { - const swapchain_image = &self.swapchain.swapchain_images[self.swapchain.image_index.?]; + const swapchain_image = &swapchain.swapchain_images[swapchain.image_index.?]; command_buffer.pipelineBarrier(.{ .src_stage_mask = .{ .color_attachment_output_bit = true }, @@ -942,7 +942,7 @@ fn render(self: *Game) !void { .new_layout = .depth_stencil_attachment_optimal, .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, - .image = self.swapchain.depth_texture.?.image, + .image = swapchain.depth_texture.?.image, .subresource_range = .{ .aspect_mask = .{ .depth_bit = true }, .base_mip_level = 0, @@ -975,7 +975,7 @@ fn render(self: *Game) !void { }, }, .depth_attachment = .{ - .image_view = self.swapchain.depth_texture.?.image_view, + .image_view = swapchain.depth_texture.?.image_view, .image_layout = .depth_attachment_optimal, .load_op = .clear, .store_op = .dont_care, @@ -1026,7 +1026,7 @@ fn render(self: *Game) !void { // --- RENDER GUI --- - try self.gui.draw(self.engine, command_buffer); + try self.gui.draw(command_buffer); // --- FINALIZE --- @@ -1056,11 +1056,11 @@ fn render(self: *Game) !void { } try command_buffer.endCommandBuffer(); - self.swapchain.present(self.engine, .{ + swapchain.present(.{ .command_buffer = command_buffer, .wait_semaphores = &.{ .{ - .semaphore = self.global_uniforms_transfer_semaphores[self.swapchain.image_index.?], + .semaphore = self.global_uniforms_transfer_semaphores[swapchain.image_index.?], .stage_flags = .{ .vertex_shader_bit = true }, }, }, @@ -1071,10 +1071,13 @@ fn render(self: *Game) !void { } fn recreateSwapchain(self: *Game) !void { - try self.swapchain.recreate(self.engine); + const engine = ctx.engine; + const swapchain = ctx.swapchain; + + try swapchain.recreate(); for (self.global_uniforms_transfer_semaphores, 0..) |*semaphore, i| { - self.engine.destroySemaphore(semaphore.*); - semaphore.* = try self.engine.createSemaphore(); - self.engine.setObjectName(semaphore.*, "S Transfer[{d}]", .{i}); + engine.destroySemaphore(semaphore.*); + semaphore.* = try engine.createSemaphore(); + engine.setObjectName(semaphore.*, "S Transfer[{d}]", .{i}); } } diff --git a/src/Gui.zig b/src/Gui.zig index 9741aa2..ec68a87 100644 --- a/src/Gui.zig +++ b/src/Gui.zig @@ -1,6 +1,7 @@ const Gui = @This(); const std = @import("std"); +const ctx = @import("AppContext.zig"); const shaders = @import("shaders.zig"); const vk = @import("vulkan"); const vm = @import("vecmath"); @@ -74,46 +75,47 @@ pub const max_text_draws = 4096; pub const max_batches = 1024; pub fn init( - allocator: std.mem.Allocator, - engine: *Engine, - swapchain: *Swapchain, global_uniforms_buffer: vk.Buffer, ) !Gui { - var box_gpu_buffer: GenericBuffer(void, Draw.Box) = try .init(engine, .{ + const allocator_general = ctx.allocator_general; + const engine = ctx.engine; + const swapchain = ctx.swapchain; + + var box_gpu_buffer: GenericBuffer(void, Draw.Box) = try .init(.{ .usage = .storage, .target_queue = .graphics, .array_capacity = max_box_draws, .name = "GUI box draws", }); - errdefer box_gpu_buffer.deinit(engine); + errdefer box_gpu_buffer.deinit(); - var text_gpu_buffer: GenericBuffer(void, Draw.Text) = try .init(engine, .{ + var text_gpu_buffer: GenericBuffer(void, Draw.Text) = try .init(.{ .usage = .storage, .target_queue = .graphics, .array_capacity = max_text_draws, .name = "GUI text draws", }); - errdefer text_gpu_buffer.deinit(engine); + errdefer text_gpu_buffer.deinit(); - var image_gpu_buffer: GenericBuffer(void, Draw.Image) = try .init(engine, .{ + var image_gpu_buffer: GenericBuffer(void, Draw.Image) = try .init(.{ .usage = .storage, .target_queue = .graphics, .array_capacity = max_image_draws, .name = "GUI image draws", }); - errdefer image_gpu_buffer.deinit(engine); + errdefer image_gpu_buffer.deinit(); - var box_cpu_buffer: std.ArrayList(Draw.Box) = try .initCapacity(allocator, max_box_draws); - errdefer box_cpu_buffer.deinit(allocator); + var box_cpu_buffer: std.ArrayList(Draw.Box) = try .initCapacity(allocator_general, max_box_draws); + errdefer box_cpu_buffer.deinit(allocator_general); - var text_cpu_buffer: std.ArrayList(Draw.Text) = try .initCapacity(allocator, max_text_draws); - errdefer text_cpu_buffer.deinit(allocator); + var text_cpu_buffer: std.ArrayList(Draw.Text) = try .initCapacity(allocator_general, max_text_draws); + errdefer text_cpu_buffer.deinit(allocator_general); - var image_cpu_buffer: std.ArrayList(Draw.Image) = try .initCapacity(allocator, max_image_draws); - errdefer image_cpu_buffer.deinit(allocator); + var image_cpu_buffer: std.ArrayList(Draw.Image) = try .initCapacity(allocator_general, max_image_draws); + errdefer image_cpu_buffer.deinit(allocator_general); - var batches: std.ArrayList(Batch) = try .initCapacity(allocator, max_batches); - errdefer batches.deinit(allocator); + var batches: std.ArrayList(Batch) = try .initCapacity(allocator_general, max_batches); + errdefer batches.deinit(allocator_general); const sampler = try engine.createSampler(.{ .mag_filter = .linear, @@ -169,14 +171,14 @@ pub fn init( defer engine.destroyShaderModule(box_fragment_shader); engine.setObjectName(box_fragment_shader, "SM gui_box_frag", .{}); - var index_buffer = try shaders.IndexBuffer.init(engine, .{ + var index_buffer = try shaders.IndexBuffer.init(.{ .usage = .index, .target_queue = .graphics, .array_capacity = 6, .name = "QuadIB", }); - errdefer index_buffer.deinit(engine); - try index_buffer.write(engine, .{ + errdefer index_buffer.deinit(); + try index_buffer.write(.{ .elements = &.{ 0, 1, 2, 2, 1, 3 }, }); @@ -362,25 +364,28 @@ pub fn init( }; } -pub fn deinit(self: *Gui, engine: *Engine, allocator: std.mem.Allocator) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with {*} and Allocator{{{*},{*}}}", .{ self, engine, allocator.ptr, allocator.vtable }); +pub fn deinit(self: *Gui) void { + const allocator_general = ctx.allocator_general; + const engine = ctx.engine; + + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); engine.destroyDescriptorPool(self.descriptor_pool); engine.destroyPipeline(self.box_pipeline); - self.index_buffer.deinit(engine); + self.index_buffer.deinit(); engine.destroyPipelineLayout(self.box_pipeline_layout); engine.destroyDescriptorSetLayout(self.box_descriptor_set_layout); engine.destroySampler(self.sampler); - self.batches.deinit(allocator); + self.batches.deinit(allocator_general); - self.image_cpu_buffer.deinit(allocator); - self.text_cpu_buffer.deinit(allocator); - self.box_cpu_buffer.deinit(allocator); + self.image_cpu_buffer.deinit(allocator_general); + self.text_cpu_buffer.deinit(allocator_general); + self.box_cpu_buffer.deinit(allocator_general); - self.image_gpu_buffer.deinit(engine); - self.text_gpu_buffer.deinit(engine); - self.box_gpu_buffer.deinit(engine); + self.image_gpu_buffer.deinit(); + self.text_gpu_buffer.deinit(); + self.box_gpu_buffer.deinit(); self.* = undefined; } @@ -425,12 +430,12 @@ fn extendOrAddBatch(self: *Gui, draw_type: std.meta.DeclEnum(Draw), instance: u3 } } -pub fn draw(self: *const Gui, engine: *Engine, command_buffer: CommandBuffer) !void { +pub fn draw(self: *const Gui, command_buffer: CommandBuffer) !void { command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16); - try self.box_gpu_buffer.write(engine, .{ .elements = self.box_cpu_buffer.items }); - try self.text_gpu_buffer.write(engine, .{ .elements = self.text_cpu_buffer.items }); - try self.image_gpu_buffer.write(engine, .{ .elements = self.image_cpu_buffer.items }); + try self.box_gpu_buffer.write(.{ .elements = self.box_cpu_buffer.items }); + try self.text_gpu_buffer.write(.{ .elements = self.text_cpu_buffer.items }); + try self.image_gpu_buffer.write(.{ .elements = self.image_cpu_buffer.items }); for (self.batches.items) |batch| { switch (batch.draw_type) { diff --git a/src/Player.zig b/src/Player.zig index e170b35..34a1eb4 100644 --- a/src/Player.zig +++ b/src/Player.zig @@ -253,11 +253,9 @@ pub fn onMouseDown(self: *Player, button: glfw.MouseButton, game: *Game) void { game.chunks.setVoxelAt( raycast_hit.voxel, .air, - game.engine, &game.blocks, game.descriptor_pool, game.per_batch_descriptor_set_layout, - game.allocator, ) catch |err| { std.log.err("Error while destroying voxel {f}: {}", .{ raycast_hit.voxel, err }); }; @@ -265,13 +263,10 @@ pub fn onMouseDown(self: *Player, button: glfw.MouseButton, game: *Game) void { .right => blk: { const target_vx = raycast_hit.voxel.add(raycast_hit.side.getSignVector()); const id = game.blocks.getOrLoad( - game.engine, &game.materials, &game.textures, &game.stbi, blocks[self.block_index], - game.allocator, - game.io, ) catch |err| { std.log.err("Error while placing voxel at {f}: {}", .{ target_vx, err }); break :blk; @@ -279,11 +274,9 @@ pub fn onMouseDown(self: *Player, button: glfw.MouseButton, game: *Game) void { game.chunks.setVoxelAt( target_vx, id, - game.engine, &game.blocks, game.descriptor_pool, game.per_batch_descriptor_set_layout, - game.allocator, ) catch |err| { std.log.err("Error while placing voxel at {f}: {}", .{ target_vx, err }); }; diff --git a/src/assets/Blocks.zig b/src/assets/Blocks.zig index 9895394..1154500 100644 --- a/src/assets/Blocks.zig +++ b/src/assets/Blocks.zig @@ -3,6 +3,7 @@ const Blocks = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const media = @import("media"); const voxels = @import("../voxels.zig"); @@ -76,23 +77,26 @@ pub const Definition = struct { } }; -/// Maps a key value to a block definition ID. +/// Maps a key value to a block definition ID. Preallocated with +/// `allocator_general`. map: std.AutoHashMapUnmanaged(Key, Id), /// Stores all `Definition` structs and maps a block definition ID to a -/// `Definition` struct. +/// `Definition` struct. Preallocated with `allocator_general`. array: std.ArrayList(Definition), /// With `@sizeOf(Definition) == 24` and `max_blocks = 4096`, the block /// definitions should take ~95.4 kiB in RAM. pub const max_blocks = 4096; -pub fn init(allocator: std.mem.Allocator) !Blocks { - var map: std.AutoHashMapUnmanaged(Key, Id) = .empty; - errdefer map.deinit(allocator); - try map.ensureTotalCapacity(allocator, max_blocks); +pub fn init() !Blocks { + const allocator_general = ctx.allocator_general; - var array: std.ArrayList(Definition) = try .initCapacity(allocator, max_blocks); - errdefer array.deinit(allocator); + var map: std.AutoHashMapUnmanaged(Key, Id) = .empty; + errdefer map.deinit(allocator_general); + try map.ensureTotalCapacity(allocator_general, max_blocks); + + var array: std.ArrayList(Definition) = try .initCapacity(allocator_general, max_blocks); + errdefer array.deinit(allocator_general); // VOLATILE Synchronize with explicit values on top of `Id` type. @@ -104,11 +108,13 @@ pub fn init(allocator: std.mem.Allocator) !Blocks { }; } -pub fn deinit(self: *Blocks, allocator: std.mem.Allocator) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with Allocator{{{*},{*}}}", .{ self, allocator.ptr, allocator.vtable }); +pub fn deinit(self: *Blocks) void { + const allocator_general = ctx.allocator_general; - self.array.deinit(allocator); - self.map.deinit(allocator); + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); + + self.array.deinit(allocator_general); + self.map.deinit(allocator_general); self.* = undefined; } @@ -132,18 +138,15 @@ pub fn getAtom(self: *const Blocks, filename: Atom) ?Id { pub fn getOrLoad( self: *Blocks, - engine: *Engine, materials: *Materials, textures: *Textures, stbi: *media.stbi, filename: []const u8, - temp_allocator: std.mem.Allocator, - io: std.Io, ) !Id { const key: Key = .{ // If the material already exists, then the atom must exist and the // following line will not return any error. - .filename = try .fromString(filename, io), + .filename = try .fromString(filename), }; // We don't use `getOrPutAssumeCapacity` method, because we might already be @@ -155,7 +158,7 @@ pub fn getOrLoad( const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) { error.Overflow => return error.OutOfBlocks, }; - const def = try loadBlock(engine, materials, textures, stbi, filename, temp_allocator, io); + const def = try loadBlock(materials, textures, stbi, filename); self.map.putAssumeCapacityNoClobber(key, id); self.array.appendAssumeCapacity(def); @@ -166,13 +169,10 @@ pub fn getOrLoad( pub fn getOrLoadAtom( self: *Blocks, - engine: *Engine, materials: *Materials, textures: *Textures, stbi: *media.stbi, filename: Atom, - temp_allocator: std.mem.Allocator, - io: std.Io, ) !Id { const key: Key = .{ .filename = filename, @@ -187,7 +187,7 @@ pub fn getOrLoadAtom( const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) { error.Overflow => return error.OutOfBlocks, }; - const def = try loadBlock(engine, materials, textures, stbi, filename.toString(), temp_allocator, io); + const def = try loadBlock(materials, textures, stbi, filename.toString()); self.map.putAssumeCapacityNoClobber(key, id); self.array.appendAssumeCapacity(def); @@ -198,13 +198,12 @@ pub fn getOrLoadAtom( pub fn loadAll( self: *Blocks, - engine: *Engine, materials: *Materials, textures: *Textures, stbi: *media.stbi, - temp_allocator: std.mem.Allocator, - io: std.Io, ) void { + const io = ctx.io; + const cwd = std.Io.Dir.cwd(); var dir = cwd.openDir(io, "assets/blocks", .{ .iterate = true }) catch |err| { @@ -223,21 +222,21 @@ pub fn loadAll( continue; } - _ = self.getOrLoad(engine, materials, textures, stbi, entry.name, temp_allocator, io) catch |err| { + _ = self.getOrLoad(materials, textures, stbi, entry.name) catch |err| { std.log.err("Error while loading block definition entry {s}: {s}", .{ entry.name, @errorName(err) }); }; } } fn loadBlock( - engine: *Engine, materials: *Materials, textures: *Textures, stbi: *media.stbi, filename: []const u8, - temp_allocator: std.mem.Allocator, - io: std.Io, ) !Definition { + const allocator_frame = ctx.allocator_frame; + const io = ctx.io; + const DefinitionJson = struct { pub const Wall = struct { material: ?[]const u8 = null, @@ -264,17 +263,13 @@ fn loadBlock( defer file.close(io); var file_reader = file.reader(io, &buffer); - var json_reader = std.json.Reader.init(temp_allocator, &file_reader.interface); - defer json_reader.deinit(); + var json_reader = std.json.Reader.init(allocator_frame, &file_reader.interface); - const parsed: std.json.Parsed(DefinitionJson) = try std.json.parseFromTokenSource(DefinitionJson, temp_allocator, &json_reader, .{ + const def_json = try std.json.parseFromTokenSourceLeaky(DefinitionJson, allocator_frame, &json_reader, .{ .duplicate_field_behavior = .@"error", .ignore_unknown_fields = false, .allocate = .alloc_if_needed, }); - defer parsed.deinit(); - - const def_json = parsed.value; const block: Definition = blk: { if (def_json.material) |name| { @@ -283,7 +278,7 @@ fn loadBlock( return error.ParseError; } - const material = try materials.getOrLoad(engine, textures, stbi, name, temp_allocator, io); + const material = try materials.getOrLoad(textures, stbi, name); break :blk .initUniform(material); } @@ -293,7 +288,7 @@ fn loadBlock( var ret: Definition.Walls = undefined; inline for (@typeInfo(voxels.Orientation).@"enum".fields) |field| { @field(ret, field.name) = .{ - .material = try materials.getOrLoad(engine, textures, stbi, @field(walls, field.name).material, temp_allocator, io), + .material = try materials.getOrLoad(textures, stbi, @field(walls, field.name).material), .transform = @field(walls, field.name).transform, }; } diff --git a/src/assets/Chunk.zig b/src/assets/Chunk.zig index 6841a3c..58e6ba7 100644 --- a/src/assets/Chunk.zig +++ b/src/assets/Chunk.zig @@ -1,6 +1,7 @@ const Chunk = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const math = @import("../math.zig"); const shaders = @import("../shaders.zig"); const vk = @import("vulkan"); @@ -40,19 +41,21 @@ pub const InitInfo = struct { pub const Neighbors = std.enums.EnumFieldStruct(voxels.Orientation, ?*const Chunk, null); -pub fn init(engine: *Engine, init_info: InitInfo) !Chunk { +pub fn init(init_info: InitInfo) !Chunk { + const engine = ctx.engine; + const descriptor_set = try engine.allocateDescriptorSet(.{ .descriptor_pool = init_info.descriptor_pool, .set_layout = init_info.per_batch_descriptor_set_layout, }); errdefer engine.freeDescriptorSet(init_info.descriptor_pool, descriptor_set); - var object_buffer: ObjectUniformsBuffer = try .init(engine, .{ + var object_buffer: ObjectUniformsBuffer = try .init(.{ .usage = .storage, .target_queue = .graphics, .array_capacity = initial_capacity, }); - errdefer object_buffer.deinit(engine); + errdefer object_buffer.deinit(); try engine.updateDescriptorSets(.{ .writes = &.{ @@ -89,18 +92,22 @@ pub fn init(engine: *Engine, init_info: InitInfo) !Chunk { }; } -pub fn deinit(self: *Chunk, engine: *Engine, descriptor_pool: vk.DescriptorPool) void { - std.log.scoped(.deinit).debug("Deinitializing chunk {d} with {*} and {s}#{X}", .{ self.chunk_id, engine, @typeName(vk.DescriptorPool), @intFromEnum(descriptor_pool) }); +pub fn deinit(self: *Chunk, descriptor_pool: vk.DescriptorPool) void { + const engine = ctx.engine; - self.object_buffer.deinit(engine); + std.log.scoped(.deinit).debug("Deinitializing chunk {d} with {s}#{X}", .{ self.chunk_id, @typeName(vk.DescriptorPool), @intFromEnum(descriptor_pool) }); + + self.object_buffer.deinit(); engine.freeDescriptorSet(descriptor_pool, self.descriptor_set); self.* = undefined; } -pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, neighbors: Neighbors, temp_allocator: std.mem.Allocator) !void { - var uniforms: std.ArrayList(shaders.ObjectUniforms) = try .initCapacity(temp_allocator, initial_capacity); - defer uniforms.deinit(temp_allocator); +pub fn refresh(self: *Chunk, blocks: *const Blocks, neighbors: Neighbors) !void { + const allocator_frame = ctx.allocator_frame; + const engine = ctx.engine; + + var uniforms: std.ArrayList(shaders.ObjectUniforms) = try .initCapacity(allocator_frame, initial_capacity); for (self.blocks, 0..) |plane, iz| { const fz: f32 = @floatFromInt(iz); @@ -116,7 +123,7 @@ pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, neighbors: const material = @field(block.walls, field.name).material; if (material != .empty and self.getOpposite(side, ix, iy, iz, blocks, neighbors) == .empty) { const matrix = getMatrix(side, origin); - try uniforms.append(temp_allocator, .{ + try uniforms.append(allocator_frame, .{ .matrixOStoWS = matrix, .matrixOStoWSNormal = matrix, .material = material, @@ -130,7 +137,7 @@ pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, neighbors: const object_count: u32 = @intCast(uniforms.items.len); if (self.object_buffer.array_capacity < object_count) { const desired_capacity = std.math.ceilPowerOfTwoAssert(u32, object_count); - const new_object_buffer: ObjectUniformsBuffer = try .init(engine, .{ + const new_object_buffer: ObjectUniformsBuffer = try .init(.{ .usage = .storage, .target_queue = .graphics, .array_capacity = desired_capacity, @@ -156,13 +163,13 @@ pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, neighbors: }, }); - self.object_buffer.deinit(engine); + self.object_buffer.deinit(); self.object_buffer = new_object_buffer; engine.setObjectName(new_object_buffer.buffer, "B Chunk[{d}]", .{self.chunk_id}); engine.setObjectName(new_object_buffer.device_memory, "DM Chunk[{d}]", .{self.chunk_id}); } - try self.object_buffer.write(engine, .{ .elements = uniforms.items }); + try self.object_buffer.write(.{ .elements = uniforms.items }); self.object_count = object_count; } diff --git a/src/assets/Materials.zig b/src/assets/Materials.zig index b6bc6db..861e3dd 100644 --- a/src/assets/Materials.zig +++ b/src/assets/Materials.zig @@ -3,6 +3,7 @@ const Materials = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const media = @import("media"); const shaders = @import("../shaders.zig"); const vk = @import("vulkan"); @@ -50,7 +51,7 @@ pub const Key = struct { filename: Atom, }; -/// Maps a key value to a material ID. +/// Maps a key value to a material ID. Preallocated with `allocator_general`. map: std.AutoHashMapUnmanaged(Key, Id), /// Stores all material data in a single contiguous storage buffer. Use the /// material ID as an index into this buffer. @@ -62,22 +63,24 @@ material_count: usize, /// storage buffer should take 208 kiB in VRAM. pub const max_materials = 4096; -pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Materials { - var map: std.AutoHashMapUnmanaged(Key, Id) = .empty; - errdefer map.deinit(allocator); - try map.ensureTotalCapacity(allocator, max_materials); +pub fn init() !Materials { + const allocator_general = ctx.allocator_general; - var material_buffer = try shaders.MaterialBuffer.init(engine, .{ + var map: std.AutoHashMapUnmanaged(Key, Id) = .empty; + errdefer map.deinit(allocator_general); + try map.ensureTotalCapacity(allocator_general, max_materials); + + var material_buffer = try shaders.MaterialBuffer.init(.{ .usage = .storage, .target_queue = .graphics, .array_capacity = max_materials, .name = "Materials", }); - errdefer material_buffer.deinit(engine); + errdefer material_buffer.deinit(); // VOLATILE Synchronize with explicit values on top of `Id` type. - try material_buffer.write(engine, .{ + try material_buffer.write(.{ .element_offset = Id.empty.toInt(), .elements = &.{ .{ @@ -103,11 +106,13 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Materials { }; } -pub fn deinit(self: *Materials, engine: *Engine, allocator: std.mem.Allocator) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with {*} and Allocator{{{*},{*}}}", .{ self, engine, allocator.ptr, allocator.vtable }); +pub fn deinit(self: *Materials) void { + const allocator_general = ctx.allocator_general; - self.material_buffer.deinit(engine); - self.map.deinit(allocator); + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); + + self.material_buffer.deinit(); + self.map.deinit(allocator_general); self.* = undefined; } @@ -151,18 +156,15 @@ pub fn getAtom(self: *const Materials, filename: Atom) ?Id { /// error is not necessarily related to `temp_allocator`. pub fn getOrLoad( self: *Materials, - engine: *Engine, textures: *Textures, stbi: *media.stbi, maybe_filename: ?[]const u8, - temp_allocator: std.mem.Allocator, - io: std.Io, ) !Id { if (maybe_filename) |filename| { const key: Key = .{ // If the material already exists, then the atom must exist and the // following line will not return any error. - .filename = try .fromString(filename, io), + .filename = try .fromString(filename), }; // We don't use `getOrPutAssumeCapacity` method, because we might already be @@ -174,7 +176,7 @@ pub fn getOrLoad( const id = Id.fromIndexSafe(self.material_count) catch |err| switch (err) { error.Overflow => return error.OutOfMaterials, }; - try self.loadMaterial(engine, textures, stbi, filename, id.toInt(), temp_allocator, io); + try self.loadMaterial(textures, stbi, filename, id.toInt()); self.map.putAssumeCapacityNoClobber(key, id); self.material_count += 1; @@ -199,12 +201,9 @@ pub fn getOrLoad( /// error is not necessarily related to `temp_allocator`. pub fn getOrLoadAtom( self: *Materials, - engine: *Engine, textures: *Textures, stbi: *media.stbi, filename: Atom, - temp_allocator: std.mem.Allocator, - io: std.Io, ) !Id { if (filename != .empty) { const key: Key = .{ @@ -220,7 +219,7 @@ pub fn getOrLoadAtom( const id = Id.fromIndexSafe(self.material_count) catch |err| switch (err) { error.Overflow => return error.OutOfMaterials, }; - try self.loadMaterial(engine, textures, stbi, filename.toString(), id.toInt(), temp_allocator, io); + try self.loadMaterial(textures, stbi, filename.toString(), id.toInt()); self.map.putAssumeCapacityNoClobber(key, id); self.material_count += 1; @@ -242,14 +241,20 @@ pub fn getOrLoadAtom( /// deinitialized or reset after this function returns. Note that during loading /// the engine will make its own persistent allocations, so an out of memory /// error is not necessarily related to `temp_allocator`. -pub fn loadAll(self: *Materials, engine: *Engine, textures: *Textures, stbi: *media.stbi, temp_allocator: std.mem.Allocator) void { - const cwd = std.fs.cwd(); +pub fn loadAll( + self: *Materials, + textures: *Textures, + stbi: *media.stbi, +) void { + const io = ctx.io; - var dir = cwd.openDir("assets/materials", .{ .iterate = true }) catch |err| { + const cwd = std.Io.Dir.cwd(); + + var dir = cwd.openDir(io, "assets/materials", .{ .iterate = true }) catch |err| { std.log.err("Error while opening metarials directory: {s}", .{@errorName(err)}); return; }; - defer dir.close(); + defer dir.close(io); var it = dir.iterate(); while (it.next() catch |err| { @@ -261,13 +266,16 @@ pub fn loadAll(self: *Materials, engine: *Engine, textures: *Textures, stbi: *me continue; } - _ = self.getOrLoad(engine, textures, stbi, entry.name, temp_allocator) catch |err| { + _ = self.getOrLoad(textures, stbi, entry.name) catch |err| { std.log.err("Error while loading material entry {s}: {s}", .{ entry.name, @errorName(err) }); }; } } -fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, stbi: *media.stbi, filename: []const u8, index: u32, temp_allocator: std.mem.Allocator, io: std.Io) !void { +fn loadMaterial(self: *Materials, textures: *Textures, stbi: *media.stbi, filename: []const u8, index: u32) !void { + const allocator_frame = ctx.allocator_frame; + const io = ctx.io; + const MaterialJson = struct { baseColor: [3]f32 = .{ 1, 1, 1 }, baseColorTexture: ?[]const u8 = null, @@ -296,19 +304,15 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, stbi: *m defer file.close(io); var file_reader = file.reader(io, &buffer); - var json_reader = std.json.Reader.init(temp_allocator, &file_reader.interface); - defer json_reader.deinit(); + var json_reader = std.json.Reader.init(allocator_frame, &file_reader.interface); - const parsed: std.json.Parsed(MaterialJson) = try std.json.parseFromTokenSource(MaterialJson, temp_allocator, &json_reader, .{ + const material_json = try std.json.parseFromTokenSourceLeaky(MaterialJson, allocator_frame, &json_reader, .{ .duplicate_field_behavior = .@"error", .ignore_unknown_fields = false, .allocate = .alloc_if_needed, }); - defer parsed.deinit(); - const material_json = parsed.value; - - try self.material_buffer.write(engine, .{ + try self.material_buffer.write(.{ .element_offset = index, .elements = &.{ .{ @@ -319,10 +323,10 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, stbi: *m .normal_scale = material_json.normalScale, .occlusion_texture_strength = material_json.occlusionTextureStrength, .roughness = material_json.roughness, - .base_color_texture = try textures.getOrLoad(engine, stbi, material_json.baseColorTexture, .base_color, io), - .emissive_texture = try textures.getOrLoad(engine, stbi, material_json.emissiveTexture, .emissive, io), - .normal_texture = try textures.getOrLoad(engine, stbi, material_json.normalTexture, .normal, io), - .occlusion_roughness_metallic_texture = try textures.getOrLoad(engine, stbi, material_json.occlusionRoughnessMetallicTexture, .occlusion_roughness_metallic, io), + .base_color_texture = try textures.getOrLoad(stbi, material_json.baseColorTexture, .base_color), + .emissive_texture = try textures.getOrLoad(stbi, material_json.emissiveTexture, .emissive), + .normal_texture = try textures.getOrLoad(stbi, material_json.normalTexture, .normal), + .occlusion_roughness_metallic_texture = try textures.getOrLoad(stbi, material_json.occlusionRoughnessMetallicTexture, .occlusion_roughness_metallic), }, }, }); diff --git a/src/assets/Textures.zig b/src/assets/Textures.zig index 96390e6..ad41979 100644 --- a/src/assets/Textures.zig +++ b/src/assets/Textures.zig @@ -3,6 +3,7 @@ const Textures = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const media = @import("media"); const Atom = @import("../engine/Atom.zig").Atom; @@ -56,31 +57,34 @@ pub const Key = struct { usage: Texture.Usage, }; -/// Maps a key value to a texture ID. +/// Maps a key value to a texture ID. Preallocated with `allocator_general`. map: std.AutoHashMapUnmanaged(Key, Id), /// Stores all `Texture` structs and maps a texture ID to a `Texture` struct. +/// Preallocated with `allocator_general`. array: std.ArrayList(Texture), /// 4096 textures of usage `.base_color` and 16×16 dimensions should take 4 MiB /// in VRAM. pub const max_textures = 4096; -pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { - var map: std.AutoHashMapUnmanaged(Key, Id) = .empty; - errdefer map.deinit(allocator); - try map.ensureTotalCapacity(allocator, max_textures); +pub fn init() !Textures { + const allocator_general = ctx.allocator_general; - var array: std.ArrayList(Texture) = try .initCapacity(allocator, max_textures); + var map: std.AutoHashMapUnmanaged(Key, Id) = .empty; + errdefer map.deinit(allocator_general); + try map.ensureTotalCapacity(allocator_general, max_textures); + + var array: std.ArrayList(Texture) = try .initCapacity(allocator_general, max_textures); errdefer { for (array.items) |*texture| { - texture.deinit(engine); + texture.deinit(); } - array.deinit(allocator); + array.deinit(allocator_general); } // VOLATILE Synchronize with explicit values on top of `Id` type. - const empty_base_color_texture = try Texture.init(engine, .{ + const empty_base_color_texture = try Texture.init(.{ .width = 1, .height = 1, .usage = .base_color, @@ -89,7 +93,7 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { }); array.appendAssumeCapacity(empty_base_color_texture); - const empty_emissive_texture = try Texture.init(engine, .{ + const empty_emissive_texture = try Texture.init(.{ .width = 1, .height = 1, .usage = .emissive, @@ -98,7 +102,7 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { }); array.appendAssumeCapacity(empty_emissive_texture); - const empty_normal_texture = try Texture.init(engine, .{ + const empty_normal_texture = try Texture.init(.{ .width = 1, .height = 1, .usage = .normal, @@ -107,7 +111,7 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { }); array.appendAssumeCapacity(empty_normal_texture); - const empty_occlusuion_roughness_metallic_texture = try Texture.init(engine, .{ + const empty_occlusuion_roughness_metallic_texture = try Texture.init(.{ .width = 1, .height = 1, .usage = .occlusion_roughness_metallic, @@ -116,10 +120,10 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { }); array.appendAssumeCapacity(empty_occlusuion_roughness_metallic_texture); - try empty_base_color_texture.writeSamples(u8, engine, &.{ 255, 255, 255, 255 }); - try empty_emissive_texture.writeSamples(f16, engine, &.{ 1.0, 1.0, 1.0, 1.0 }); - try empty_normal_texture.writeSamples(i8, engine, &.{ 0, 0, 127, 127 }); - try empty_occlusuion_roughness_metallic_texture.writeSamples(u8, engine, &.{ 255, 255, 255, 255 }); + try empty_base_color_texture.writeSamples(u8, &.{ 255, 255, 255, 255 }); + try empty_emissive_texture.writeSamples(f16, &.{ 1.0, 1.0, 1.0, 1.0 }); + try empty_normal_texture.writeSamples(i8, &.{ 0, 0, 127, 127 }); + try empty_occlusuion_roughness_metallic_texture.writeSamples(u8, &.{ 255, 255, 255, 255 }); return .{ .map = map, @@ -127,14 +131,16 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { }; } -pub fn deinit(self: *Textures, engine: *Engine, allocator: std.mem.Allocator) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with {*} and Allocator{{{*},{*}}}", .{ self, engine, allocator.ptr, allocator.vtable }); +pub fn deinit(self: *Textures) void { + const allocator_general = ctx.allocator_general; + + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); for (self.array.items) |*texture| { - texture.deinit(engine); + texture.deinit(); } - self.array.deinit(allocator); - self.map.deinit(allocator); + self.array.deinit(allocator_general); + self.map.deinit(allocator_general); self.* = undefined; } @@ -174,17 +180,15 @@ pub fn getAtom(self: *const Textures, filename: Atom, usage: Texture.Usage) ?Id /// usage. pub fn getOrLoad( self: *Textures, - engine: *Engine, stbi: *media.stbi, maybe_filename: ?[]const u8, usage: Texture.Usage, - io: std.Io, ) !Id { if (maybe_filename) |filename| { const key: Key = .{ // If the texture already exists, then the atom must exist and the // following line will not return any error. - .filename = try .fromString(filename, io), + .filename = try .fromString(filename), .usage = usage, }; @@ -197,7 +201,7 @@ pub fn getOrLoad( const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) { error.Overflow => return error.OutOfTextures, }; - const texture = try loadTexture(engine, stbi, filename, usage, io); + const texture = try loadTexture(stbi, filename, usage); self.map.putAssumeCapacityNoClobber(key, id); self.array.appendAssumeCapacity(texture); @@ -216,11 +220,9 @@ pub fn getOrLoad( /// usage. pub fn getOrLoadAtom( self: *Textures, - engine: *Engine, stbi: *media.stbi, filename: Atom, usage: Texture.Usage, - io: std.Io, ) !Id { if (filename != .empty) { const key: Key = .{ @@ -237,7 +239,7 @@ pub fn getOrLoadAtom( const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) { error.Overflow => return error.OutOfTextures, }; - const texture = try loadTexture(engine, stbi, filename.toString(), usage, io); + const texture = try loadTexture(stbi, filename.toString(), usage); self.map.putAssumeCapacityNoClobber(key, id); self.array.appendAssumeCapacity(texture); @@ -250,12 +252,12 @@ pub fn getOrLoadAtom( } fn loadTexture( - engine: *Engine, stbi: *media.stbi, filename: []const u8, usage: Texture.Usage, - io: std.Io, ) !Texture { + const io = ctx.io; + std.log.debug("Loading texture \"{s}\" as {s}...", .{ filename, @tagName(usage) }); const cwd = std.Io.Dir.cwd(); @@ -285,16 +287,16 @@ fn loadTexture( } } - var texture = try Texture.init(engine, .{ + var texture = try Texture.init(.{ .width = img.width, .height = img.height, .usage = usage, .target_queue = .graphics, .name = filename, }); - errdefer texture.deinit(engine); + errdefer texture.deinit(); - try texture.writeRaw(engine, @ptrCast(data)); + try texture.writeRaw(@ptrCast(data)); return texture; } diff --git a/src/engine/Atom.zig b/src/engine/Atom.zig index 316127d..6909ba9 100644 --- a/src/engine/Atom.zig +++ b/src/engine/Atom.zig @@ -1,16 +1,9 @@ -//! Module for string interning. A string can be converted to a stable integer -//! constant, called an *atom*. The value of an atom for a given string is -//! guaranteed to be stable throughout a program's runtime, but not across -//! different runs. There can be no more than 2¹⁶ atoms. -//! -//! Use this module to convert string IDs into numbers, so that they can be -//! compared more easily. The users of this module should import it with -//! `@import("Atom.zig").Atom` and use the methods available in the `Atom` type. - const std = @import("std"); +const ctx = @import("../AppContext.zig"); + pub const Atom = enum(u16) { - // VOLATILE Synchronize explicit values with `init` implementation. + // VOLATILE Synchronize explicit values with `Atoms.init` implementation. /// Atom representing an empty string, i.e. `""`. empty, @@ -36,29 +29,32 @@ pub const Atom = enum(u16) { /// Turn a string into an atom. Returns either an existing atom or makes a /// new one, if necessary. This will always produce a valid atom. Will not /// return any error if the atom already exists. - pub fn fromString(string: []const u8, io: std.Io) error{ Canceled, OutOfMemory, OutOfAtoms }!Atom { - try mutex.lock(io); - defer mutex.unlock(io); + pub fn fromString(string: []const u8) error{ OutOfMemory, OutOfAtoms }!Atom { + const allocator_general = ctx.allocator_general; + const allocator_persistent = ctx.allocator_persistent; + const io = ctx.io; + const atoms = ctx.atoms; - std.debug.assert(initialized); + atoms.mutex.lockUncancelable(io); + defer atoms.mutex.unlock(io); - const entry = try map.getOrPut(allocator, string); + const entry = try atoms.map.getOrPut(allocator_general, string); if (entry.found_existing) { return entry.value_ptr.*; } else { - errdefer _ = map.remove(string); - const atom = Atom.fromIndexSafe(array.items.len) catch |err| switch (err) { + errdefer _ = atoms.map.remove(string); + const atom = Atom.fromIndexSafe(atoms.array.items.len) catch |err| switch (err) { error.Overflow => return error.OutOfAtoms, }; - try array.ensureUnusedCapacity(allocator, 1); - const owned_string = try toOwnedString(string); + try atoms.array.ensureUnusedCapacity(allocator_general, 1); + const owned_string = try allocator_persistent.dupeZ(u8, string); entry.key_ptr.* = owned_string; entry.value_ptr.* = atom; - array.appendAssumeCapacity(owned_string); + atoms.array.appendAssumeCapacity(owned_string); return atom; } } @@ -66,13 +62,14 @@ pub const Atom = enum(u16) { /// Turn a string into an atom, if the string has been already registered as /// an atom. Returns `null` otherwise. This will always produce a valid /// atom. - pub fn fromStringIfExists(string: []const u8, io: std.Io) error{Canceled}!?Atom { - try mutex.lock(io); - defer mutex.unlock(io); + pub fn fromStringIfExists(string: []const u8) ?Atom { + const io = ctx.io; + const atoms = ctx.atoms; - std.debug.assert(initialized); + atoms.mutex.lockUncancelable(io); + defer atoms.mutex.unlock(io); - return map.get(string); + return atoms.map.get(string); } /// Cast an atom into an integer. @@ -81,87 +78,13 @@ pub const Atom = enum(u16) { } /// Cast an atom into a string. The caller asserts that the atom is valid. - pub fn toString(self: Atom, io: std.Io) error{Canceled}![:0]const u8 { - try mutex.lock(io); - defer mutex.unlock(io); + pub fn toString(self: Atom) [:0]const u8 { + const io = ctx.io; + const atoms = ctx.atoms; - std.debug.assert(initialized); + atoms.mutex.lockUncancelable(io); + defer atoms.mutex.unlock(io); - return array.items[self.toInt()]; + return atoms.array.items[self.toInt()]; } }; - -/// Flag for debug purposes, to catch misuses of the API. -var initialized: bool = false; - -/// Allocator used for `map` and `array`. Also used as a child allocator for -/// `string_arena`. -var allocator: std.mem.Allocator = undefined; - -/// Allocator for all string values. All values of `map` and keys of `map` are -/// allocated with this arena. The strings are allocated with a null terminator -/// for interoperability with libraries. -var string_arena: std.heap.ArenaAllocator = undefined; - -/// Maps a string value to an atom value. -var map: std.StringHashMapUnmanaged(Atom) = undefined; - -/// Maps an atom value to a string. -var array: std.ArrayList([:0]const u8) = undefined; - -/// Protects all reads and writes to `map` and `array`. -var mutex: std.Io.Mutex = .init; - -pub fn init(_allocator: std.mem.Allocator, io: std.Io) !void { - try mutex.lock(io); - defer mutex.unlock(io); - - std.debug.assert(!initialized); - - allocator = _allocator; - string_arena = .init(_allocator); - map = .{}; - array = .empty; - initialized = true; - - // VOLATILE Synchronize with explicit values on top of `Atom` type. - try map.put(allocator, "", .empty); - try array.append(allocator, ""); -} - -pub fn deinit(io: std.Io) void { - mutex.lockUncancelable(io); - defer mutex.unlock(io); - - std.log.scoped(.deinit).debug("Deinitializing atoms", .{}); - std.debug.assert(initialized); - - string_arena.deinit(); - map.deinit(allocator); - array.deinit(allocator); - - allocator = undefined; - string_arena = undefined; - map = undefined; - array = undefined; - initialized = false; -} - -/// Dump all atoms in a readable format. Use for debugging. Does not flush the -/// writer. -pub fn dump(writer: std.Io.Writer, io: std.Io) void { - mutex.lockUncancelable(io); - defer mutex.unlock(io); - - std.debug.assert(initialized); - - for (array.items, 0..) |string, i| { - const atom: u32 = @intCast(i); - writer.print("0x{X:0<8} {s}\n", .{ atom, string }); - } -} - -fn toOwnedString(string: []const u8) ![:0]const u8 { - const owned_string = try string_arena.allocator().dupeZ(u8, string); - return owned_string; -} diff --git a/src/engine/Atoms.zig b/src/engine/Atoms.zig new file mode 100644 index 0000000..a545816 --- /dev/null +++ b/src/engine/Atoms.zig @@ -0,0 +1,80 @@ +//! Module for string interning. A string can be converted to a stable integer +//! constant, called an *atom*. The value of an atom for a given string is +//! guaranteed to be stable throughout a program's runtime, but not across +//! different runs. There can be no more than 2¹⁶ atoms. +//! +//! Use this module to convert string IDs into numbers, so that they can be +//! compared more easily. The users of this module should instead import +//! `@import("Atom.zig").Atom` and use the methods available in the `Atom` type. +//! +//! This module is intended to be initialized once and persist until the end of +//! the whole application. Trying to use it in any other way will result in +//! weird behavior. + +const Atoms = @This(); +const std = @import("std"); + +const ctx = @import("../AppContext.zig"); + +const Atom = @import("Atom.zig").Atom; + +/// Maps a string value to an atom value. Uses `allocator_general`. +map: std.StringHashMapUnmanaged(Atom), + +/// Maps an atom value to a string. Uses `allocator_general`. +array: std.ArrayList([:0]const u8), + +/// Protects all reads and writes to `map` and `array`. +mutex: std.Io.Mutex, + +pub fn init() !*Atoms { + const allocator_general = ctx.allocator_general; + const allocator_persistent = ctx.allocator_persistent; + + const atoms = try allocator_persistent.create(Atoms); + errdefer allocator_persistent.destroy(atoms); + + atoms.* = .{ + .map = .empty, + .array = .empty, + .mutex = .init, + }; + + // VOLATILE Synchronize with explicit values on top of `Atom` type. + try atoms.map.put(allocator_general, "", .empty); + try atoms.array.append(allocator_general, ""); + + return atoms; +} + +pub fn deinit(self: *Atoms) void { + const allocator_general = ctx.allocator_general; + const io = ctx.io; + + { + // VOLATILE Unlock mutex before `self.* = undefined` line. + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); + + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); + + self.map.deinit(allocator_general); + self.array.deinit(allocator_general); + } + + self.* = undefined; +} + +/// Dump all atoms in a readable format. Use for debugging. Does not flush the +/// writer. +pub fn dump(self: *const Atoms, writer: *std.Io.Writer) void { + const io = ctx.io; + + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); + + for (self.array.items, 0..) |string, i| { + const atom: u16 = @intCast(i); + writer.print("0x{X:0<4} {s}\n", .{ atom, string }); + } +} diff --git a/src/engine/CommandBuffer.zig b/src/engine/CommandBuffer.zig index 69473b3..72447e0 100644 --- a/src/engine/CommandBuffer.zig +++ b/src/engine/CommandBuffer.zig @@ -9,28 +9,31 @@ const CommandBuffer = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const vk = @import("vulkan"); const Engine = @import("Engine.zig"); const QueueType = @import("QueueType.zig").QueueType; proxy: vk.CommandBufferProxy, -allocator: std.mem.Allocator, queue_type: QueueType, -pub fn init(engine: *Engine, queue_type: QueueType) !CommandBuffer { +pub fn init(queue_type: QueueType) !CommandBuffer { + const engine = ctx.engine; + const handle = try engine.allocateCommandBuffer(.{ .queue_type = queue_type, .level = .primary, }); return .{ .proxy = .init(handle, engine.device.wrapper), - .allocator = engine.vk_allocator.allocator, .queue_type = queue_type, }; } -pub fn deinit(self: *CommandBuffer, engine: *Engine) void { +pub fn deinit(self: *CommandBuffer) void { + const engine = ctx.engine; + engine.freeCommandBuffer(.{ .queue_type = self.queue_type, .command_buffer = self.proxy.handle, @@ -38,7 +41,9 @@ pub fn deinit(self: *CommandBuffer, engine: *Engine) void { self.* = undefined; } -pub fn submit(self: CommandBuffer, engine: *Engine, submit_info: Engine.SubmitInfo) !void { +pub fn submit(self: CommandBuffer, submit_info: Engine.SubmitInfo) !void { + const engine = ctx.engine; + try engine.queueSubmit(self.queue_type, self.proxy.handle, submit_info); } @@ -96,11 +101,9 @@ pub fn beginCommandBuffer(self: CommandBuffer) !void { } pub fn beginRendering(self: CommandBuffer, rendering_info: RenderingInfo) !void { - var arena: std.heap.ArenaAllocator = .init(self.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); + const allocator_frame = ctx.allocator_frame; - const color_attachments = try allocator.alloc(vk.RenderingAttachmentInfo, rendering_info.color_attachments.len); + const color_attachments = try allocator_frame.alloc(vk.RenderingAttachmentInfo, rendering_info.color_attachments.len); for (color_attachments, rendering_info.color_attachments) |*x, color_attachment| { x.* = .{ .image_view = color_attachment.image_view, @@ -187,9 +190,11 @@ pub fn bindIndexBuffer(self: CommandBuffer, buffer: vk.Buffer, offset: u64, inde } pub fn bindVertexBuffers(self: CommandBuffer, first_binding: u32, bindings: []const VertexBufferBinding) !void { + const allocator_frame = ctx.allocator_frame; + var vertex_buffer_bindings = std.MultiArrayList(VertexBufferBinding){}; - defer vertex_buffer_bindings.deinit(self.allocator); - try vertex_buffer_bindings.ensureTotalCapacity(self.allocator, bindings.len); + defer vertex_buffer_bindings.deinit(allocator_frame); + try vertex_buffer_bindings.ensureTotalCapacity(allocator_frame, bindings.len); for (bindings) |binding| { vertex_buffer_bindings.appendAssumeCapacity(binding); diff --git a/src/engine/Engine.zig b/src/engine/Engine.zig index f22b351..4f5d371 100644 --- a/src/engine/Engine.zig +++ b/src/engine/Engine.zig @@ -2,6 +2,7 @@ const Engine = @This(); const std = @import("std"); const c = @import("../const.zig"); +const ctx = @import("../AppContext.zig"); const glfw = @import("zglfw"); const vk = @import("vulkan"); @@ -10,17 +11,6 @@ const QueueType = @import("QueueType.zig").QueueType; const VkAllocator = @import("VkAllocator.zig"); const WaitSemaphore = @import("WaitSemaphore.zig"); -pub const Mode = union(enum) { - headless: void, - surface: struct { - window: *glfw.Window, - surface: vk.SurfaceKHR, - presentation_queue: Queue, - }, -}; - -pub const ModeTag = std.meta.Tag(Mode); - const QueueAllocator = struct { queue_families_properties_buffer: [max_queue_families]vk.QueueFamilyProperties, queue_families_in_use: [max_queue_families]u32, @@ -69,18 +59,15 @@ const QueueAllocations = struct { graphics_queue: Queue.Allocation, compute_queue: Queue.Allocation, transfer_queue: Queue.Allocation, - /// Defined only in `surface` mode, `undefined` otherwise. presentation_queue: Queue.Allocation, }; const DebugUtilsMessenger = if (debug) vk.DebugUtilsMessengerEXT else void; -mode: Mode, -vk_allocator: *VkAllocator, - base: vk.BaseWrapper, instance: vk.InstanceProxy, device: vk.DeviceProxy, +surface: vk.SurfaceKHR, debug_utils_messenger: DebugUtilsMessenger, physical_device: vk.PhysicalDevice, @@ -90,6 +77,7 @@ memory_heaps: std.ArrayList(vk.MemoryHeap), graphics_queue: Queue, compute_queue: Queue, transfer_queue: Queue, +presentation_queue: Queue, graphics_command_pool: vk.CommandPool, compute_command_pool: vk.CommandPool, @@ -99,15 +87,12 @@ prng_ptr: *std.Random.Pcg, random: std.Random, const debug = @import("builtin").mode == .Debug; - -const vk_application_info: vk.ApplicationInfo = .{ - .p_application_name = c.app_name, - .application_version = @bitCast(vk_version), - .p_engine_name = c.app_name, - .engine_version = @bitCast(vk_version), - .api_version = @bitCast(vk.API_VERSION_1_4), -}; - +const vk_version: vk.Version = vk.makeApiVersion( + 0, + c.app_version.major, + c.app_version.minor, + c.app_version.patch, +); const vk_debug_utils_messenger_create_info: vk.DebugUtilsMessengerCreateInfoEXT = .{ .message_severity = .{ .verbose_bit_ext = false, @@ -124,16 +109,13 @@ const vk_debug_utils_messenger_create_info: vk.DebugUtilsMessengerCreateInfoEXT .p_user_data = null, }; -const vk_version: vk.Version = vk.makeApiVersion( - 0, - c.app_version.major, - c.app_version.minor, - c.app_version.patch, -); +pub fn init() !*Engine { + const allocator_persistent = ctx.allocator_persistent; + const io = ctx.io; + const window = ctx.window; -pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Window) !Engine { - const vk_allocator = try VkAllocator.init(allocator, io); - errdefer vk_allocator.deinit(); + const engine = try allocator_persistent.create(Engine); + errdefer allocator_persistent.destroy(engine); // --- LOAD BASE DISPATCH -------------------------------------------------- @@ -149,35 +131,39 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo try enabled_extensions.appendBounded(vk.extensions.ext_debug_utils.name); } - if (maybe_window != null) { - const glfw_instance_extensions = try glfw.getRequiredInstanceExtensions(); - try enabled_extensions.appendSliceBounded(glfw_instance_extensions); - } + const glfw_instance_extensions = try glfw.getRequiredInstanceExtensions(); + try enabled_extensions.appendSliceBounded(glfw_instance_extensions); const enabled_layers: []const [*:0]const u8 = if (debug) &.{"VK_LAYER_KHRONOS_validation"} else &.{}; break :blk try base.createInstance(&.{ - .p_application_info = &vk_application_info, + .p_application_info = &.{ + .p_application_name = c.app_name, + .application_version = vk_version.toU32(), + .p_engine_name = c.app_name, + .engine_version = vk_version.toU32(), + .api_version = @bitCast(vk.API_VERSION_1_4), + }, .enabled_layer_count = enabled_layers.len, .pp_enabled_layer_names = enabled_layers.ptr, .enabled_extension_count = @intCast(enabled_extensions.items.len), .pp_enabled_extension_names = enabled_extensions.items.ptr, .p_next = if (debug) &vk_debug_utils_messenger_create_info else null, - }, &vk_allocator.interface); + }, ctx.vk_allocator.capture()); }; - const instance_wrapper_ptr = try allocator.create(vk.InstanceWrapper); - errdefer allocator.destroy(instance_wrapper_ptr); + const instance_wrapper_ptr = try allocator_persistent.create(vk.InstanceWrapper); + errdefer allocator_persistent.destroy(instance_wrapper_ptr); instance_wrapper_ptr.* = .load(instance_handle, base.dispatch.vkGetInstanceProcAddr.?); const instance = vk.InstanceProxy.init(instance_handle, instance_wrapper_ptr); - errdefer instance.destroyInstance(&vk_allocator.interface); + errdefer instance.destroyInstance(ctx.vk_allocator.capture()); // --- CREATE DEBUG UTILS MESSENGER ---------------------------------------- - const debug_utils_messenger: DebugUtilsMessenger = if (debug) try instance.createDebugUtilsMessengerEXT(&vk_debug_utils_messenger_create_info, &vk_allocator.interface) else {}; + const debug_utils_messenger: DebugUtilsMessenger = if (debug) try instance.createDebugUtilsMessengerEXT(&vk_debug_utils_messenger_create_info, ctx.vk_allocator.capture()) else {}; errdefer { - if (debug) instance.destroyDebugUtilsMessengerEXT(debug_utils_messenger, &vk_allocator.interface); + if (debug) instance.destroyDebugUtilsMessengerEXT(debug_utils_messenger, ctx.vk_allocator.capture()); } // --- CHOOSE PHYSICAL DEVICE, GET ITS MEMORY PROPERTIES ------------------- @@ -208,20 +194,20 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo const physical_device_memory_properties = instance.getPhysicalDeviceMemoryProperties(physical_device); - var memory_types = try std.ArrayList(vk.MemoryType).initCapacity(allocator, vk.MAX_MEMORY_TYPES); - errdefer memory_types.deinit(allocator); + var memory_types = try std.ArrayList(vk.MemoryType).initCapacity(allocator_persistent, vk.MAX_MEMORY_TYPES); + errdefer memory_types.deinit(allocator_persistent); memory_types.appendSliceAssumeCapacity(physical_device_memory_properties.memory_types[0..physical_device_memory_properties.memory_type_count]); - var memory_heaps = try std.ArrayList(vk.MemoryHeap).initCapacity(allocator, vk.MAX_MEMORY_HEAPS); - errdefer memory_heaps.deinit(allocator); + var memory_heaps = try std.ArrayList(vk.MemoryHeap).initCapacity(allocator_persistent, vk.MAX_MEMORY_HEAPS); + errdefer memory_heaps.deinit(allocator_persistent); memory_heaps.appendSliceAssumeCapacity(physical_device_memory_properties.memory_heaps[0..physical_device_memory_properties.memory_heap_count]); // --- CREATE SURFACE ------------------------------------------------------ var surface: vk.SurfaceKHR = undefined; - if (maybe_window) |window| try glfw.createWindowSurface(instance_handle, window, &vk_allocator.interface, &surface); + try glfw.createWindowSurface(instance_handle, window, ctx.vk_allocator.capture(), &surface); errdefer { - if (maybe_window != null) instance.destroySurfaceKHR(surface, &vk_allocator.interface); + instance.destroySurfaceKHR(surface, ctx.vk_allocator.capture()); } // --- ALLOCATE QUEUES ----------------------------------------------------- @@ -238,12 +224,12 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo const graphics_queue_family = findGraphicsQueueFamily(queue_families_properties) orelse return error.NoGraphicsQueue; const compute_queue_family = findComputeQueueFamily(queue_families_properties) orelse return error.NoComputeQueue; const transfer_queue_family = findTransferQueueFamily(queue_families_properties) orelse return error.NoTransferQueue; - const presentation_queue_family = if (maybe_window != null) (try findPresentationQueueFamily(queue_families_properties, instance, physical_device, surface) orelse return error.NoPresentationQueue) else undefined; + const presentation_queue_family = try findPresentationQueueFamily(queue_families_properties, instance, physical_device, surface) orelse return error.NoPresentationQueue; const graphics_queue_index = queue_allocator.allocateQueueIndex(graphics_queue_family); const compute_queue_index = queue_allocator.allocateQueueIndex(compute_queue_family); const transfer_queue_index = queue_allocator.allocateQueueIndex(transfer_queue_family); - const presentation_queue_index = if (maybe_window != null) queue_allocator.allocateQueueIndex(presentation_queue_family) else undefined; + const presentation_queue_index = queue_allocator.allocateQueueIndex(presentation_queue_family); const queue_priorities = [_]f32{1.0} ** max_queues; @@ -261,7 +247,7 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo .graphics_queue = .{ .family = graphics_queue_family, .index = graphics_queue_index }, .compute_queue = .{ .family = compute_queue_family, .index = compute_queue_index }, .transfer_queue = .{ .family = transfer_queue_family, .index = transfer_queue_index }, - .presentation_queue = if (maybe_window != null) .{ .family = presentation_queue_family, .index = presentation_queue_index } else undefined, + .presentation_queue = .{ .family = presentation_queue_family, .index = presentation_queue_index }, }; }; @@ -271,9 +257,7 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo var enabled_extensions_buffer: [16][*:0]const u8 = undefined; var enabled_extensions: std.ArrayList([*:0]const u8) = .initBuffer(&enabled_extensions_buffer); - if (maybe_window != null) { - try enabled_extensions.appendBounded(vk.extensions.khr_swapchain.name); - } + try enabled_extensions.appendBounded(vk.extensions.khr_swapchain.name); var enabled_features_vulkan10: vk.PhysicalDeviceFeatures2 = .{ .features = .{ @@ -319,35 +303,35 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo .p_queue_create_infos = queue_create_info.items.ptr, .enabled_extension_count = @intCast(enabled_extensions.items.len), .pp_enabled_extension_names = enabled_extensions.items.ptr, - }, &vk_allocator.interface); + }, ctx.vk_allocator.capture()); }; - const device_wrapper_ptr = try allocator.create(vk.DeviceWrapper); - errdefer allocator.destroy(device_wrapper_ptr); + const device_wrapper_ptr = try allocator_persistent.create(vk.DeviceWrapper); + errdefer allocator_persistent.destroy(device_wrapper_ptr); device_wrapper_ptr.* = .load(device_handle, instance.wrapper.dispatch.vkGetDeviceProcAddr.?); const device = vk.DeviceProxy.init(device_handle, device_wrapper_ptr); - errdefer device.destroyDevice(&vk_allocator.interface); + errdefer device.destroyDevice(ctx.vk_allocator.capture()); // --- CREATE COMMAND POOLS, GET QUEUES ------------------------------------ const graphics_command_pool = try device.createCommandPool(&.{ .flags = .{ .transient_bit = true }, .queue_family_index = queue_allocations.graphics_queue.family, - }, &vk_allocator.interface); - errdefer device.destroyCommandPool(graphics_command_pool, &vk_allocator.interface); + }, ctx.vk_allocator.capture()); + errdefer device.destroyCommandPool(graphics_command_pool, ctx.vk_allocator.capture()); const compute_command_pool = try device.createCommandPool(&.{ .flags = .{ .transient_bit = true }, .queue_family_index = queue_allocations.compute_queue.family, - }, &vk_allocator.interface); - errdefer device.destroyCommandPool(compute_command_pool, &vk_allocator.interface); + }, ctx.vk_allocator.capture()); + errdefer device.destroyCommandPool(compute_command_pool, ctx.vk_allocator.capture()); const transfer_command_pool = try device.createCommandPool(&.{ .flags = .{ .transient_bit = true }, .queue_family_index = queue_allocations.transfer_queue.family, - }, &vk_allocator.interface); - errdefer device.destroyCommandPool(transfer_command_pool, &vk_allocator.interface); + }, ctx.vk_allocator.capture()); + errdefer device.destroyCommandPool(transfer_command_pool, ctx.vk_allocator.capture()); const graphics_queue = Queue.init( device.getDeviceQueue( @@ -370,18 +354,18 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo ), queue_allocations.transfer_queue, ); - const presentation_queue: Queue = if (maybe_window != null) .init( + const presentation_queue = Queue.init( device.getDeviceQueue( queue_allocations.presentation_queue.family, queue_allocations.presentation_queue.index, ), queue_allocations.presentation_queue, - ) else undefined; + ); // --- CREATE AND SEED RNG ------------------------------------------------- - const prng_ptr = try allocator.create(std.Random.Pcg); - errdefer allocator.destroy(prng_ptr); + const prng_ptr = try allocator_persistent.create(std.Random.Pcg); + errdefer allocator_persistent.destroy(prng_ptr); const timestamp: u64 = @bitCast(@as(i64, @truncate(std.Io.Timestamp.now(io, .awake).nanoseconds))); prng_ptr.* = .init(timestamp); @@ -389,17 +373,11 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo // ------------------------------------------------------------------------- - return .{ - .mode = if (maybe_window) |window| .{ .surface = .{ - .window = window, - .surface = surface, - .presentation_queue = presentation_queue, - } } else .{ .headless = {} }, - .vk_allocator = vk_allocator, - + engine.* = .{ .base = base, .instance = instance, .device = device, + .surface = surface, .debug_utils_messenger = debug_utils_messenger, .physical_device = physical_device, @@ -409,6 +387,7 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo .graphics_queue = graphics_queue, .compute_queue = compute_queue, .transfer_queue = transfer_queue, + .presentation_queue = presentation_queue, .graphics_command_pool = graphics_command_pool, .compute_command_pool = compute_command_pool, @@ -417,35 +396,23 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, maybe_window: ?*glfw.Windo .prng_ptr = prng_ptr, .random = random, }; + + return engine; } pub fn deinit(self: *Engine) void { std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); - const allocator = self.vk_allocator.allocator; + self.device.destroyCommandPool(self.graphics_command_pool, ctx.vk_allocator.capture()); + self.device.destroyCommandPool(self.compute_command_pool, ctx.vk_allocator.capture()); + self.device.destroyCommandPool(self.transfer_command_pool, ctx.vk_allocator.capture()); - allocator.destroy(self.prng_ptr); + self.device.destroyDevice(ctx.vk_allocator.capture()); + self.instance.destroySurfaceKHR(self.surface, ctx.vk_allocator.capture()); - self.device.destroyCommandPool(self.graphics_command_pool, &self.vk_allocator.interface); - self.device.destroyCommandPool(self.compute_command_pool, &self.vk_allocator.interface); - self.device.destroyCommandPool(self.transfer_command_pool, &self.vk_allocator.interface); + if (debug) self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils_messenger, ctx.vk_allocator.capture()); - self.device.destroyDevice(&self.vk_allocator.interface); - allocator.destroy(self.device.wrapper); - - if (std.meta.activeTag(self.mode) == .surface) { - self.instance.destroySurfaceKHR(self.mode.surface.surface, &self.vk_allocator.interface); - } - - self.memory_heaps.deinit(allocator); - self.memory_types.deinit(allocator); - - if (debug) self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils_messenger, &self.vk_allocator.interface); - - self.instance.destroyInstance(&self.vk_allocator.interface); - allocator.destroy(self.instance.wrapper); - - self.vk_allocator.deinit(); + self.instance.destroyInstance(ctx.vk_allocator.capture()); self.* = undefined; } @@ -458,7 +425,7 @@ pub fn allocate(self: *const Engine, memory_requirements: vk.MemoryRequirements, return try self.device.allocateMemory(&.{ .allocation_size = memory_requirements.size, .memory_type_index = @truncate(i), - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); } } @@ -536,6 +503,8 @@ pub fn queueSubmit( command_buffer: vk.CommandBuffer, submit_info: SubmitInfo, ) !void { + const allocator_frame = ctx.allocator_frame; + const queue = switch (queue_type) { .graphics => self.graphics_queue.handle, .compute => self.compute_queue.handle, @@ -545,8 +514,8 @@ pub fn queueSubmit( const command_buffers = [_]vk.CommandBuffer{command_buffer}; var wait_semaphores = std.MultiArrayList(WaitSemaphore){}; - defer wait_semaphores.deinit(self.vk_allocator.allocator); - try wait_semaphores.ensureTotalCapacity(self.vk_allocator.allocator, submit_info.wait_semaphores.len); + defer wait_semaphores.deinit(allocator_frame); + try wait_semaphores.ensureTotalCapacity(allocator_frame, submit_info.wait_semaphores.len); for (submit_info.wait_semaphores) |wait_semaphore| { wait_semaphores.appendAssumeCapacity(wait_semaphore); @@ -577,7 +546,7 @@ pub fn queuePresent(self: *Engine, present_info: PresentInfo) !vk.Result { const swapchains = [_]vk.SwapchainKHR{present_info.swapchain}; const image_indices = [_]u32{present_info.image_index}; - const res = try self.device.queuePresentKHR(self.mode.surface.presentation_queue.handle, &.{ + const res = try self.device.queuePresentKHR(self.presentation_queue.handle, &.{ .wait_semaphore_count = @intCast(present_info.wait_semaphores.len), .p_wait_semaphores = present_info.wait_semaphores.ptr, .swapchain_count = swapchains.len, @@ -974,13 +943,11 @@ pub fn bindImageMemory(self: *Engine, image: vk.Image, memory: vk.DeviceMemory, } pub fn createBuffer(self: *Engine, create_info: BufferCreateInfo) !vk.Buffer { - var arena = self.makeArena(); - defer arena.deinit(); - const allocator = arena.allocator(); + const allocator_frame = ctx.allocator_frame; var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{}; for (create_info.queue_family_indices) |queue_family_index| { - try queue_family_indices_set.put(allocator, queue_family_index, {}); + try queue_family_indices_set.put(allocator_frame, queue_family_index, {}); } const queue_family_indices = queue_family_indices_set.keys(); @@ -992,14 +959,12 @@ pub fn createBuffer(self: *Engine, create_info: BufferCreateInfo) !vk.Buffer { .sharing_mode = if (queue_family_indices.len > 1) .concurrent else .exclusive, .queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0, .p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null, - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); return buffer; } pub fn createDescriptorSetLayout(self: *Engine, create_info: DescriptorSetLayoutCreateInfo) !vk.DescriptorSetLayout { - var arena = self.makeArena(); - defer arena.deinit(); - const allocator = arena.allocator(); + const allocator_frame = ctx.allocator_frame; const has_binding_flags = blk: { for (create_info.bindings) |binding| { @@ -1013,7 +978,7 @@ pub fn createDescriptorSetLayout(self: *Engine, create_info: DescriptorSetLayout var p_next: ?*const anyopaque = null; if (has_binding_flags) { - const descriptor_set_layout_bindings_flags = try allocator.alloc(vk.DescriptorBindingFlags, create_info.bindings.len); + const descriptor_set_layout_bindings_flags = try allocator_frame.alloc(vk.DescriptorBindingFlags, create_info.bindings.len); for (descriptor_set_layout_bindings_flags, create_info.bindings) |*x, binding| { x.* = binding.flags; } @@ -1024,7 +989,7 @@ pub fn createDescriptorSetLayout(self: *Engine, create_info: DescriptorSetLayout }; } - const bindings = try allocator.alloc(vk.DescriptorSetLayoutBinding, create_info.bindings.len); + const bindings = try allocator_frame.alloc(vk.DescriptorSetLayoutBinding, create_info.bindings.len); for (bindings, create_info.bindings) |*x, binding| { x.* = .{ .binding = binding.binding, @@ -1040,7 +1005,7 @@ pub fn createDescriptorSetLayout(self: *Engine, create_info: DescriptorSetLayout .flags = create_info.flags, .binding_count = @intCast(bindings.len), .p_bindings = bindings.ptr, - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); return descriptor_set_layout; } @@ -1050,21 +1015,19 @@ pub fn createDescriptorPool(self: *Engine, create_info: DescriptorPoolCreateInfo .max_sets = create_info.max_sets, .pool_size_count = @intCast(create_info.pool_sizes.len), .p_pool_sizes = create_info.pool_sizes.ptr, - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); return descriptor_pool; } pub fn createFence(self: *Engine, create_info: vk.FenceCreateInfo) !vk.Fence { - const fence = try self.device.createFence(&create_info, &self.vk_allocator.interface); + const fence = try self.device.createFence(&create_info, ctx.vk_allocator.capture()); return fence; } pub fn createGraphicsPipeline(self: *Engine, create_info: GraphicsPipelineCreateInfo) !vk.Pipeline { - var arena = self.makeArena(); - defer arena.deinit(); - const allocator = arena.allocator(); + const allocator_frame = ctx.allocator_frame; - const stages = try allocator.alloc(vk.PipelineShaderStageCreateInfo, create_info.stages.len); + const stages = try allocator_frame.alloc(vk.PipelineShaderStageCreateInfo, create_info.stages.len); for (stages, create_info.stages) |*x, stage| { x.* = .{ .flags = stage.flags, @@ -1073,7 +1036,7 @@ pub fn createGraphicsPipeline(self: *Engine, create_info: GraphicsPipelineCreate .module = stage.module, .p_specialization_info = blk: { if (stage.specialization_info) |y| { - const specialization_info = try allocator.create(vk.SpecializationInfo); + const specialization_info = try allocator_frame.create(vk.SpecializationInfo); specialization_info.* = .{ .map_entry_count = @intCast(y.map_entries.len), .p_map_entries = y.map_entries.ptr, @@ -1142,18 +1105,16 @@ pub fn createGraphicsPipeline(self: *Engine, create_info: GraphicsPipelineCreate }; var pipelines: [1]vk.Pipeline = undefined; - _ = try self.device.createGraphicsPipelines(.null_handle, &graphics_pipeline_create_infos, &self.vk_allocator.interface, &pipelines); + _ = try self.device.createGraphicsPipelines(.null_handle, &graphics_pipeline_create_infos, ctx.vk_allocator.capture(), &pipelines); return pipelines[0]; } pub fn createImage(self: *Engine, create_info: ImageCreateInfo) !vk.Image { - var arena = self.makeArena(); - defer arena.deinit(); - const allocator = arena.allocator(); + const allocator_frame = ctx.allocator_frame; var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{}; for (create_info.queue_family_indices) |queue_family_index| { - try queue_family_indices_set.put(allocator, queue_family_index, {}); + try queue_family_indices_set.put(allocator_frame, queue_family_index, {}); } const queue_family_indices = queue_family_indices_set.keys(); @@ -1172,7 +1133,7 @@ pub fn createImage(self: *Engine, create_info: ImageCreateInfo) !vk.Image { .queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0, .p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null, .initial_layout = create_info.initial_layout, - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); return image; } @@ -1184,7 +1145,7 @@ pub fn createImageView(self: *Engine, create_info: ImageViewCreateInfo) !vk.Imag .format = create_info.format, .components = create_info.components, .subresource_range = create_info.subresource_range, - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); return image_view; } @@ -1195,17 +1156,17 @@ pub fn createPipelineLayout(self: *Engine, create_info: PipelineLayoutCreateInfo .p_set_layouts = create_info.set_layouts.ptr, .push_constant_range_count = @intCast(create_info.push_constant_ranges.len), .p_push_constant_ranges = create_info.push_constant_ranges.ptr, - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); return pipeline_layout; } pub fn createSampler(self: *Engine, create_info: vk.SamplerCreateInfo) !vk.Sampler { - const sampler = try self.device.createSampler(&create_info, &self.vk_allocator.interface); + const sampler = try self.device.createSampler(&create_info, ctx.vk_allocator.capture()); return sampler; } pub fn createSemaphore(self: *Engine) !vk.Semaphore { - const semaphore = try self.device.createSemaphore(&.{}, &self.vk_allocator.interface); + const semaphore = try self.device.createSemaphore(&.{}, ctx.vk_allocator.capture()); return semaphore; } @@ -1214,18 +1175,16 @@ pub fn createShaderModule(self: *Engine, create_info: ShaderModuleCreateInfo) !v .flags = create_info.flags, .code_size = create_info.code.len, .p_code = @ptrCast(create_info.code.ptr), - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); return shader_module; } pub fn createSwapchain(self: *Engine, create_info: SwapchainCreateInfo) !vk.SwapchainKHR { - var arena = self.makeArena(); - defer arena.deinit(); - const allocator = arena.allocator(); + const allocator_frame = ctx.allocator_frame; var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{}; for (create_info.queue_family_indices) |queue_family_index| { - try queue_family_indices_set.put(allocator, queue_family_index, {}); + try queue_family_indices_set.put(allocator_frame, queue_family_index, {}); } const queue_family_indices = queue_family_indices_set.keys(); @@ -1247,56 +1206,56 @@ pub fn createSwapchain(self: *Engine, create_info: SwapchainCreateInfo) !vk.Swap .present_mode = create_info.present_mode, .clipped = create_info.clipped, .old_swapchain = create_info.old_swapchain, - }, &self.vk_allocator.interface); + }, ctx.vk_allocator.capture()); return swapchain; } pub fn destroyBuffer(self: *Engine, buffer: vk.Buffer) void { - self.device.destroyBuffer(buffer, &self.vk_allocator.interface); + self.device.destroyBuffer(buffer, ctx.vk_allocator.capture()); } pub fn destroyDescriptorPool(self: *Engine, descriptor_pool: vk.DescriptorPool) void { - self.device.destroyDescriptorPool(descriptor_pool, &self.vk_allocator.interface); + self.device.destroyDescriptorPool(descriptor_pool, ctx.vk_allocator.capture()); } pub fn destroyDescriptorSetLayout(self: *Engine, descriptor_set_layout: vk.DescriptorSetLayout) void { - self.device.destroyDescriptorSetLayout(descriptor_set_layout, &self.vk_allocator.interface); + self.device.destroyDescriptorSetLayout(descriptor_set_layout, ctx.vk_allocator.capture()); } pub fn destroyFence(self: *Engine, fence: vk.Fence) void { - self.device.destroyFence(fence, &self.vk_allocator.interface); + self.device.destroyFence(fence, ctx.vk_allocator.capture()); } pub fn destroyImage(self: *Engine, image: vk.Image) void { - self.device.destroyImage(image, &self.vk_allocator.interface); + self.device.destroyImage(image, ctx.vk_allocator.capture()); } pub fn destroyImageView(self: *Engine, image_view: vk.ImageView) void { - self.device.destroyImageView(image_view, &self.vk_allocator.interface); + self.device.destroyImageView(image_view, ctx.vk_allocator.capture()); } pub fn destroyPipeline(self: *Engine, pipeline: vk.Pipeline) void { - self.device.destroyPipeline(pipeline, &self.vk_allocator.interface); + self.device.destroyPipeline(pipeline, ctx.vk_allocator.capture()); } pub fn destroyPipelineLayout(self: *Engine, pipeline_layout: vk.PipelineLayout) void { - self.device.destroyPipelineLayout(pipeline_layout, &self.vk_allocator.interface); + self.device.destroyPipelineLayout(pipeline_layout, ctx.vk_allocator.capture()); } pub fn destroySampler(self: *Engine, sampler: vk.Sampler) void { - self.device.destroySampler(sampler, &self.vk_allocator.interface); + self.device.destroySampler(sampler, ctx.vk_allocator.capture()); } pub fn destroySemaphore(self: *Engine, semaphore: vk.Semaphore) void { - self.device.destroySemaphore(semaphore, &self.vk_allocator.interface); + self.device.destroySemaphore(semaphore, ctx.vk_allocator.capture()); } pub fn destroyShaderModule(self: *Engine, shader_module: vk.ShaderModule) void { - self.device.destroyShaderModule(shader_module, &self.vk_allocator.interface); + self.device.destroyShaderModule(shader_module, ctx.vk_allocator.capture()); } pub fn destroySwapchain(self: *Engine, swapchain: vk.SwapchainKHR) void { - self.device.destroySwapchainKHR(swapchain, &self.vk_allocator.interface); + self.device.destroySwapchainKHR(swapchain, ctx.vk_allocator.capture()); } pub fn deviceWaitIdle(self: *Engine) !void { @@ -1315,7 +1274,7 @@ pub fn freeDescriptorSet(self: *Engine, descriptor_pool: vk.DescriptorPool, desc } pub fn freeMemory(self: *Engine, device_memory: vk.DeviceMemory) void { - self.device.freeMemory(device_memory, &self.vk_allocator.interface); + self.device.freeMemory(device_memory, ctx.vk_allocator.capture()); } pub fn getBufferMemoryRequirements(self: *Engine, buffer: vk.Buffer) vk.MemoryRequirements { @@ -1327,12 +1286,12 @@ pub fn getImageMemoryRequirements(self: *Engine, image: vk.Image) vk.MemoryRequi } pub fn getPhysicalDeviceSurfaceCapabilities(self: *Engine) !vk.SurfaceCapabilitiesKHR { - const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.physical_device, self.mode.surface.surface); + const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.physical_device, self.surface); return surface_capabilities; } pub fn getPhysicalDeviceSurfaceFormatsAlloc(self: *Engine, allocator: std.mem.Allocator) ![]vk.SurfaceFormatKHR { - const surface_formats = try self.instance.getPhysicalDeviceSurfaceFormatsAllocKHR(self.physical_device, self.mode.surface.surface, allocator); + const surface_formats = try self.instance.getPhysicalDeviceSurfaceFormatsAllocKHR(self.physical_device, self.surface, allocator); return surface_formats; } @@ -1356,11 +1315,9 @@ pub fn unmapMemory(self: *Engine, memory: vk.DeviceMemory) void { } pub fn updateDescriptorSets(self: *Engine, update_info: DescriptorSetsUpdateInfo) !void { - var arena = self.makeArena(); - defer arena.deinit(); - const allocator = arena.allocator(); + const allocator_frame = ctx.allocator_frame; - const descriptor_writes = try allocator.alloc(vk.WriteDescriptorSet, update_info.writes.len); + const descriptor_writes = try allocator_frame.alloc(vk.WriteDescriptorSet, update_info.writes.len); for (descriptor_writes, update_info.writes) |*x, write| { x.* = switch (write.descriptor_infos) { .image => |y| .{ diff --git a/src/engine/GenericBuffer.zig b/src/engine/GenericBuffer.zig index 58a9c19..09c1e3e 100644 --- a/src/engine/GenericBuffer.zig +++ b/src/engine/GenericBuffer.zig @@ -1,5 +1,6 @@ const std = @import("std"); +const ctx = @import("../AppContext.zig"); const vk = @import("vulkan"); const CommandBuffer = @import("CommandBuffer.zig"); @@ -51,7 +52,9 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type { elements: []const Element = &.{}, }; - pub fn init(engine: *Engine, init_info: InitInfo) !Self { + pub fn init(init_info: InitInfo) !Self { + const engine = ctx.engine; + const array_size = try std.math.mul(u32, init_info.array_capacity, element_size); const size = try std.math.add(u32, array_offset, array_size); @@ -88,8 +91,10 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type { }; } - pub fn deinit(self: *Self, engine: *Engine) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with {*}", .{ self, engine }); + pub fn deinit(self: *Self) void { + const engine = ctx.engine; + + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); engine.freeMemory(self.device_memory); engine.destroyBuffer(self.buffer); @@ -97,7 +102,9 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type { self.* = undefined; } - pub fn write(self: Self, engine: *Engine, write_info: WriteInfo) !void { + pub fn write(self: Self, write_info: WriteInfo) !void { + const engine = ctx.engine; + const element_count = std.math.cast(u32, write_info.elements.len) orelse return error.Overflow; std.debug.assert(write_info.element_offset + element_count <= self.array_capacity); @@ -141,21 +148,21 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type { std.debug.assert(regions.items.len > 0); } - var staging_buffer: StagingBuffer = try .init(engine, .{ + var staging_buffer: StagingBuffer = try .init(.{ .target_queue = self.target_queue, .capacity = write_size, }); - defer staging_buffer.deinit(engine); + defer staging_buffer.deinit(); - const staging_memory = try staging_buffer.map(engine); + const staging_memory = try staging_buffer.map(); if (write_info.header) |header| { @memcpy(staging_memory[0..header_size], std.mem.asBytes(&header)); } @memcpy(staging_memory[array_write_offset..write_size], std.mem.sliceAsBytes(write_info.elements)); - staging_buffer.unmap(engine); + staging_buffer.unmap(); - var command_buffer: CommandBuffer = try .init(engine, .transfer); - defer command_buffer.deinit(engine); + var command_buffer: CommandBuffer = try .init(.transfer); + defer command_buffer.deinit(); try command_buffer.beginCommandBuffer(); command_buffer.copyBuffer(staging_buffer.buffer, self.buffer, regions.items); @@ -164,11 +171,13 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type { const fence = try engine.createFence(.{}); defer engine.destroyFence(fence); - try command_buffer.submit(engine, .{ .fence = fence }); + try command_buffer.submit(.{ .fence = fence }); try engine.waitForFence(fence); } - pub fn writeRaw(self: Self, engine: *Engine, data_offset: u32, data: []const u8) !void { + pub fn writeRaw(self: Self, data_offset: u32, data: []const u8) !void { + const engine = ctx.engine; + const array_size = self.array_capacity * element_size; const size = array_offset + array_size; diff --git a/src/engine/QueueSharingMode.zig b/src/engine/QueueSharingMode.zig deleted file mode 100644 index c033963..0000000 --- a/src/engine/QueueSharingMode.zig +++ /dev/null @@ -1,20 +0,0 @@ -const QueueSharingMode = @This(); - -const vk = @import("vulkan"); - -threadlocal var qsm: QueueSharingMode = undefined; - -buffer: [2]u32, -sharing_mode: vk.SharingMode, -queue_family_index_count: u32, -p_queue_family_indices: ?[*]const u32, - -pub fn resolve(a: u32, b: u32) *const QueueSharingMode { - const self = &qsm; - const same = a == b; - self.buffer = .{ a, b }; - self.sharing_mode = if (same) .exclusive else .concurrent; - self.queue_family_index_count = if (same) 0 else 2; - self.p_queue_family_indices = if (same) null else &self.buffer; - return self; -} diff --git a/src/engine/Skybox.zig b/src/engine/Skybox.zig index 9705e4c..ef72cc3 100644 --- a/src/engine/Skybox.zig +++ b/src/engine/Skybox.zig @@ -1,6 +1,7 @@ const Skybox = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const media = @import("media"); const shaders = @import("../shaders.zig"); const vk = @import("vulkan"); @@ -29,14 +30,15 @@ pipeline: vk.Pipeline, pub fn load( filename: []const u8, - engine: *Engine, - swapchain: *Swapchain, stbi: *media.stbi, cube_size: u32, global_uniforms_buffer: vk.Buffer, - temp_allocator: std.mem.Allocator, - io: std.Io, ) !Skybox { + const allocator_frame = ctx.allocator_frame; + const io = ctx.io; + const engine = ctx.engine; + const swapchain = ctx.swapchain; + std.log.debug("Loading skybox \"{s}\"...", .{filename}); // --- LOAD IMAGE FROM MEMORY ---------------------------------------------- @@ -46,8 +48,7 @@ pub fn load( var dir = try cwd.openDir(io, "assets", .{}); defer dir.close(io); - const file_buf = try dir.readFileAlloc(io, filename, temp_allocator, .unlimited); - defer temp_allocator.free(file_buf); + const file_buf = try dir.readFileAlloc(io, filename, allocator_frame, .unlimited); const img = try stbi.loadHdrBuf(file_buf); defer stbi.freeHdr(img); @@ -68,15 +69,15 @@ pub fn load( // --- LOAD IMAGE INTO STAGING BUFFER -------------------------------------- - var staging_buffer = try StagingBuffer.init(engine, .{ + var staging_buffer = try StagingBuffer.init(.{ .capacity = @intCast(img.width * img.height * @sizeOf(vm.ColorHdr)), .target_queue = .compute, }); - defer staging_buffer.deinit(engine); + defer staging_buffer.deinit(); - const staging_memory = try staging_buffer.map(engine); + const staging_memory = try staging_buffer.map(); @memcpy(staging_memory, @as([*]const u8, @ptrCast(img.data))); - staging_buffer.unmap(engine); + staging_buffer.unmap(); // --- CREATE EQUIRECTANGULAR IMAGE ---------------------------------------- @@ -126,8 +127,8 @@ pub fn load( // --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY ------------------------- - var transfer_command_buffer = try CommandBuffer.init(engine, .transfer); - defer transfer_command_buffer.deinit(engine); + var transfer_command_buffer = try CommandBuffer.init(.transfer); + defer transfer_command_buffer.deinit(); try transfer_command_buffer.beginCommandBuffer(); transfer_command_buffer.pipelineBarrier(.{ @@ -174,14 +175,14 @@ pub fn load( ); try transfer_command_buffer.endCommandBuffer(); - try transfer_command_buffer.submit(engine, .{ + try transfer_command_buffer.submit(.{ .signal_semaphores = &.{semaphore_transfer_transition}, }); // --- TRANSITION TO SHADER_READ_ONLY_OPTIMAL ------------------------------ - var transition1_command_buffer = try CommandBuffer.init(engine, .compute); - defer transition1_command_buffer.deinit(engine); + var transition1_command_buffer = try CommandBuffer.init(.compute); + defer transition1_command_buffer.deinit(); try transition1_command_buffer.beginCommandBuffer(); transition1_command_buffer.pipelineBarrier(.{ @@ -208,7 +209,7 @@ pub fn load( }); try transition1_command_buffer.endCommandBuffer(); - try transition1_command_buffer.submit(engine, .{ + try transition1_command_buffer.submit(.{ .wait_semaphores = &.{.{ .semaphore = semaphore_transfer_transition }}, .signal_semaphores = &.{semaphore_transition_compute}, }); @@ -326,7 +327,7 @@ pub fn load( .base_pipeline_handle = .null_handle, .base_pipeline_index = -1, }, - }, &engine.vk_allocator.interface, @ptrCast(&compute_pipeline)); + }, ctx.vk_allocator.capture(), @ptrCast(&compute_pipeline)); defer engine.destroyPipeline(compute_pipeline); const compute_descriptor_pool = try engine.createDescriptorPool(.{ @@ -392,8 +393,8 @@ pub fn load( // 1. Run compute shader - var compute_command_buffer = try CommandBuffer.init(engine, .compute); - defer compute_command_buffer.deinit(engine); + var compute_command_buffer = try CommandBuffer.init(.compute); + defer compute_command_buffer.deinit(); try compute_command_buffer.beginCommandBuffer(); compute_command_buffer.pipelineBarrier(.{ @@ -423,7 +424,7 @@ pub fn load( compute_command_buffer.proxy.dispatch(@divExact(cube_size, 8), @divExact(cube_size, 8), 6); try compute_command_buffer.endCommandBuffer(); - try compute_command_buffer.submit(engine, .{ + try compute_command_buffer.submit(.{ .wait_semaphores = &.{ .{ .semaphore = semaphore_transition_compute, @@ -435,8 +436,8 @@ pub fn load( // 2. Transition cubemap - var transition2_command_buffer = try CommandBuffer.init(engine, .graphics); - defer transition2_command_buffer.deinit(engine); + var transition2_command_buffer = try CommandBuffer.init(.graphics); + defer transition2_command_buffer.deinit(); try transition2_command_buffer.beginCommandBuffer(); transition2_command_buffer.pipelineBarrier(.{ @@ -463,7 +464,7 @@ pub fn load( }); try transition2_command_buffer.endCommandBuffer(); - try transition2_command_buffer.submit(engine, .{ + try transition2_command_buffer.submit(.{ .wait_semaphores = &.{.{ .semaphore = semaphore_compute_transition }}, .fence = fence, }); @@ -474,12 +475,12 @@ pub fn load( // --- SKYBOX PIPELINE ----------------------------------------------------- - var vertex_buffer = try GenericBuffer(void, vm.Vector3).init(engine, .{ + var vertex_buffer = try GenericBuffer(void, vm.Vector3).init(.{ .usage = .vertex, .target_queue = .graphics, .array_capacity = 8, }); - errdefer vertex_buffer.deinit(engine); + errdefer vertex_buffer.deinit(); // 6━━━━7 // ╱│ ╱┃ @@ -489,7 +490,7 @@ pub fn load( // ┃╱ ┃╱ │╱ // 0━━━━1 O────X - try vertex_buffer.write(engine, .{ + try vertex_buffer.write(.{ .elements = &.{ .init(-1, -1, -1), .init(1, -1, -1), @@ -502,14 +503,14 @@ pub fn load( }, }); - var index_buffer = try GenericBuffer(void, u16).init(engine, .{ + var index_buffer = try GenericBuffer(void, u16).init(.{ .usage = .index, .target_queue = .graphics, .array_capacity = 36, }); - errdefer index_buffer.deinit(engine); + errdefer index_buffer.deinit(); - try index_buffer.write(engine, .{ + try index_buffer.write(.{ .elements = &.{ // Positive X 3, 1, 7, @@ -760,15 +761,17 @@ pub fn load( }; } -pub fn deinit(self: *Skybox, engine: *Engine) void { +pub fn deinit(self: *Skybox) void { + const engine = ctx.engine; + engine.destroyPipeline(self.pipeline); engine.destroyPipelineLayout(self.pipeline_layout); engine.destroyDescriptorPool(self.descriptor_pool); engine.destroyDescriptorSetLayout(self.descriptor_set_layout); engine.destroySampler(self.sampler); - self.index_buffer.deinit(engine); - self.vertex_buffer.deinit(engine); + self.index_buffer.deinit(); + self.vertex_buffer.deinit(); engine.destroyImageView(self.image_view); engine.freeMemory(self.device_memory); diff --git a/src/engine/StagingBuffer.zig b/src/engine/StagingBuffer.zig index a93ff6f..99bb717 100644 --- a/src/engine/StagingBuffer.zig +++ b/src/engine/StagingBuffer.zig @@ -1,6 +1,7 @@ const StagingBuffer = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const vk = @import("vulkan"); const Engine = @import("Engine.zig"); @@ -15,7 +16,9 @@ pub const InitInfo = struct { capacity: u32, }; -pub fn init(engine: *Engine, init_info: InitInfo) !StagingBuffer { +pub fn init(init_info: InitInfo) !StagingBuffer { + const engine = ctx.engine; + const target_queue_family = switch (init_info.target_queue) { .graphics => engine.graphics_queue.allocation.family, .compute => engine.compute_queue.allocation.family, @@ -50,8 +53,10 @@ pub fn init(engine: *Engine, init_info: InitInfo) !StagingBuffer { }; } -pub fn deinit(self: *StagingBuffer, engine: *Engine) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with {*}", .{ self, engine }); +pub fn deinit(self: *StagingBuffer) void { + const engine = ctx.engine; + + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); engine.freeMemory(self.device_memory); engine.destroyBuffer(self.buffer); @@ -59,16 +64,20 @@ pub fn deinit(self: *StagingBuffer, engine: *Engine) void { self.* = undefined; } -pub fn map(self: StagingBuffer, engine: *Engine) ![]u8 { - const mapped_memory = try self.mapPartial(engine, 0, self.capacity); +pub fn map(self: StagingBuffer) ![]u8 { + const mapped_memory = try self.mapPartial(0, self.capacity); return mapped_memory; } -pub fn mapPartial(self: StagingBuffer, engine: *Engine, offset: u32, len: u32) ![]u8 { +pub fn mapPartial(self: StagingBuffer, offset: u32, len: u32) ![]u8 { + const engine = ctx.engine; + const mapped_memory = try engine.mapMemory(self.device_memory, offset, len, .{}); return mapped_memory; } -pub fn unmap(self: StagingBuffer, engine: *Engine) void { +pub fn unmap(self: StagingBuffer) void { + const engine = ctx.engine; + engine.unmapMemory(self.device_memory); } diff --git a/src/engine/Swapchain.zig b/src/engine/Swapchain.zig index 638eaf7..ab796a8 100644 --- a/src/engine/Swapchain.zig +++ b/src/engine/Swapchain.zig @@ -1,11 +1,11 @@ const Swapchain = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const vk = @import("vulkan"); const CommandBuffer = @import("CommandBuffer.zig"); const Engine = @import("Engine.zig"); -const QSM = @import("QueueSharingMode.zig"); const Texture = @import("Texture.zig"); const WaitSemaphore = @import("WaitSemaphore.zig"); @@ -13,6 +13,7 @@ params: Params, extent: vk.Extent2D = .{ .width = 0, .height = 0 }, swapchain: vk.SwapchainKHR = .null_handle, +/// Allocated with `allocator_general`. swapchain_images: []SwapchainImage = &.{}, depth_texture: ?Texture = null, image_index: ?u32 = null, @@ -24,28 +25,33 @@ pub const PresentResult = enum { suboptimal, }; -pub fn init(engine: *Engine) !Swapchain { - const params: Params = try .init(engine); - var swapchain: Swapchain = .{ - .params = params, +pub fn init() !*Swapchain { + const allocator_persistent = ctx.allocator_persistent; + + const swapchain = try allocator_persistent.create(Swapchain); + errdefer allocator_persistent.destroy(swapchain); + + swapchain.* = .{ + .params = try .init(), }; - try recreate(&swapchain, engine); + try swapchain.recreate(); return swapchain; } -pub fn deinit(self: *Swapchain, engine: *Engine) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with {*}", .{ self, engine }); +pub fn deinit(self: *Swapchain) void { + const allocator_general = ctx.allocator_general; + const engine = ctx.engine; - const allocator = engine.vk_allocator.allocator; + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); for (self.swapchain_images) |*swapchain_image| { - swapchain_image.deinit(engine); + swapchain_image.deinit(); } - allocator.free(self.swapchain_images); + allocator_general.free(self.swapchain_images); if (self.depth_texture) |*depth_texture| { - depth_texture.deinit(engine); + depth_texture.deinit(); } if (self.swapchain != .null_handle) { @@ -58,11 +64,15 @@ pub fn deinit(self: *Swapchain, engine: *Engine) void { self.* = undefined; } -pub fn recreate(self: *Swapchain, engine: *Engine) !void { - try engine.deviceWaitIdle(); // TODO LMAO +pub fn recreate(self: *Swapchain) !void { + const allocator_general = ctx.allocator_general; + const allocator_frame = ctx.allocator_frame; + const engine = ctx.engine; - const mode = &engine.mode.surface; - const allocator = engine.vk_allocator.allocator; + // TODO This is very "LMAO just wait for all" way to synchronize. It might + // be the only viable option, though. Either way, we should research what + // is "the way". + try engine.deviceWaitIdle(); const old_swapchain = self.swapchain; const old_swapchain_images = self.swapchain_images; @@ -70,7 +80,7 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { const old_semaphore_image_acquired = self.semaphore_image_acquired; const old_fence = self.fence; - const extent = try getCurrentExtent(engine); + const extent = try getCurrentExtent(); std.log.debug("Recreating swapchain with extent of {d}×{d}...", .{ extent.width, extent.height }); @@ -78,7 +88,7 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { const surface_capabilities = try engine.getPhysicalDeviceSurfaceCapabilities(); const new_swapchain = try engine.createSwapchain(.{ - .surface = mode.surface, + .surface = engine.surface, .min_image_count = self.params.image_count, .image_format = self.params.surface_format.format, .image_color_space = self.params.surface_format.color_space, @@ -87,7 +97,7 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { .image_usage = .{ .color_attachment_bit = true }, .queue_family_indices = &.{ engine.graphics_queue.allocation.family, - mode.presentation_queue.allocation.family, + engine.presentation_queue.allocation.family, }, .pre_transform = surface_capabilities.current_transform, .composite_alpha = .{ .opaque_bit_khr = true }, @@ -105,13 +115,13 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { // null and deinit the old swapchain and images. for (old_swapchain_images) |*swapchain_image| { - swapchain_image.deinit(engine); + swapchain_image.deinit(); } - allocator.free(self.swapchain_images); + allocator_general.free(self.swapchain_images); self.swapchain_images = &.{}; if (old_depth_texture.*) |*depth_texture| { - depth_texture.deinit(engine); + depth_texture.deinit(); } if (old_swapchain != .null_handle) { @@ -134,28 +144,27 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { // --- CREATE DEPTH TEXTURE ------------------------------------------------ - var new_depth_texture = try Texture.init(engine, .{ + var new_depth_texture = try Texture.init(.{ .width = extent.width, .height = extent.height, .target_queue = .graphics, .usage = .depth, .name = "@Depth", }); - errdefer new_depth_texture.deinit(engine); + errdefer new_depth_texture.deinit(); // --- CREATE NEW SWAPCHAIN IMAGES ----------------------------------------- const new_swapchain_images = blk: { - const images = try engine.getSwapchainImagesAlloc(new_swapchain, allocator); - defer allocator.free(images); + const images = try engine.getSwapchainImagesAlloc(new_swapchain, allocator_frame); - var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator, images.len); - errdefer swapchain_images.deinit(allocator); + var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator_general, images.len); + errdefer swapchain_images.deinit(allocator_general); - errdefer for (swapchain_images.items) |*x| x.deinit(engine); + errdefer for (swapchain_images.items) |*x| x.deinit(); for (images, 0..) |image, index| { - swapchain_images.appendAssumeCapacity(try SwapchainImage.init(engine, .{ + swapchain_images.appendAssumeCapacity(try SwapchainImage.init(.{ .image = image, .format = self.params.surface_format.format, .extent = extent, @@ -164,13 +173,13 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { })); } - break :blk try swapchain_images.toOwnedSlice(allocator); + break :blk try swapchain_images.toOwnedSlice(allocator_general); }; errdefer { for (new_swapchain_images) |*swapchain_image| { - swapchain_image.deinit(engine); + swapchain_image.deinit(); } - allocator.free(new_swapchain_images); + allocator_general.free(new_swapchain_images); } // --- CREATE SEMAPHORES AND FENCES ---------------------------------------- @@ -196,7 +205,9 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { self.fence = fence; } -pub fn acquire(self: *Swapchain, engine: *Engine) !void { +pub fn acquire(self: *Swapchain) !void { + const engine = ctx.engine; + try engine.waitForFence(self.fence); try engine.resetFence(self.fence); @@ -212,14 +223,15 @@ pub const PresentInfo = struct { wait_semaphores: []const WaitSemaphore = &.{}, }; -pub fn present(self: *Swapchain, engine: *Engine, present_info: PresentInfo) !void { - const allocator = engine.vk_allocator.allocator; +pub fn present(self: *Swapchain, present_info: PresentInfo) !void { + const allocator_frame = ctx.allocator_frame; + const engine = ctx.engine; + const current_swapchain_image = &self.swapchain_images[self.image_index.?]; // --- SUBMIT COMMAND BUFFER ----------------------------------------------- - var wait_semaphores: std.ArrayList(WaitSemaphore) = try .initCapacity(allocator, 1 + present_info.wait_semaphores.len); - defer wait_semaphores.deinit(allocator); + var wait_semaphores: std.ArrayList(WaitSemaphore) = try .initCapacity(allocator_frame, 1 + present_info.wait_semaphores.len); wait_semaphores.appendAssumeCapacity(.{ .semaphore = current_swapchain_image.semaphore_image_acquired, @@ -227,7 +239,7 @@ pub fn present(self: *Swapchain, engine: *Engine, present_info: PresentInfo) !vo }); wait_semaphores.appendSliceAssumeCapacity(present_info.wait_semaphores); - try present_info.command_buffer.submit(engine, .{ + try present_info.command_buffer.submit(.{ .wait_semaphores = wait_semaphores.items, .signal_semaphores = &.{current_swapchain_image.semaphore_render_finished}, .fence = self.fence, @@ -242,15 +254,17 @@ pub fn present(self: *Swapchain, engine: *Engine, present_info: PresentInfo) !vo }); } -fn getCurrentExtent(engine: *Engine) !vk.Extent2D { - const mode = &engine.mode.surface; +fn getCurrentExtent() !vk.Extent2D { + const window = ctx.window; + const engine = ctx.engine; + const surface_capabilities = try engine.getPhysicalDeviceSurfaceCapabilities(); if (surface_capabilities.current_extent.width != std.math.maxInt(u32) and surface_capabilities.current_extent.height != std.math.maxInt(u32)) { return surface_capabilities.current_extent; } - const framebuffer_width, const framebuffer_height = mode.window.getFramebufferSize(); + const framebuffer_width, const framebuffer_height = window.getFramebufferSize(); return .{ .width = std.math.clamp( @@ -270,14 +284,14 @@ const Params = struct { surface_format: vk.SurfaceFormatKHR, image_count: u32, - pub fn init(engine: *Engine) !Params { - const allocator = engine.vk_allocator.allocator; + pub fn init() !Params { + const allocator_frame = ctx.allocator_frame; + const engine = ctx.engine; const surface_capabilities = try engine.getPhysicalDeviceSurfaceCapabilities(); const surface_format = blk: { - const surface_formats = try engine.getPhysicalDeviceSurfaceFormatsAlloc(allocator); - defer allocator.free(surface_formats); + const surface_formats = try engine.getPhysicalDeviceSurfaceFormatsAlloc(allocator_frame); // Look for 8-bit BGRA sRGB surface format. @@ -323,7 +337,9 @@ const SwapchainImage = struct { index: usize, }; - fn init(engine: *Engine, props: InitProps) !SwapchainImage { + fn init(props: InitProps) !SwapchainImage { + const engine = ctx.engine; + const image_view = try engine.createImageView(.{ .image = props.image, .view_type = .@"2d", @@ -355,8 +371,10 @@ const SwapchainImage = struct { }; } - fn deinit(self: *SwapchainImage, engine: *Engine) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with {*}", .{ self, engine }); + fn deinit(self: *SwapchainImage) void { + const engine = ctx.engine; + + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); engine.destroySemaphore(self.semaphore_render_finished); engine.destroySemaphore(self.semaphore_image_acquired); diff --git a/src/engine/Texture.zig b/src/engine/Texture.zig index 4554ab7..4793c90 100644 --- a/src/engine/Texture.zig +++ b/src/engine/Texture.zig @@ -1,6 +1,7 @@ const Texture = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const vk = @import("vulkan"); const CommandBuffer = @import("CommandBuffer.zig"); @@ -79,18 +80,15 @@ width: u32, height: u32, usage: Usage, -pub fn init(engine: *Engine, init_info: InitInfo) !Texture { +pub fn init(init_info: InitInfo) !Texture { + const engine = ctx.engine; + const target_queue_family = switch (init_info.target_queue) { .graphics => engine.graphics_queue.allocation.family, .compute => engine.compute_queue.allocation.family, }; const transfer_queue_family = engine.transfer_queue.allocation.family; - const queue_family_indices: []const u32 = if (init_info.usage == .depth) - &.{target_queue_family} - else - &.{ target_queue_family, transfer_queue_family }; - const image = try engine.createImage(.{ .image_type = .@"2d", .format = init_info.usage.vkFormat(), @@ -108,7 +106,7 @@ pub fn init(engine: *Engine, init_info: InitInfo) !Texture { .transfer_dst_bit = init_info.usage != .depth, .sampled_bit = init_info.usage != .depth, }, - .queue_family_indices = queue_family_indices, + .queue_family_indices = &.{ target_queue_family, transfer_queue_family }, .initial_layout = .undefined, }); errdefer engine.destroyImage(image); @@ -156,8 +154,10 @@ pub fn init(engine: *Engine, init_info: InitInfo) !Texture { }; } -pub fn deinit(self: *Texture, engine: *Engine) void { - std.log.scoped(.deinit).debug("Deinitializing {*} with {*}", .{ self, engine }); +pub fn deinit(self: *Texture) void { + const engine = ctx.engine; + + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); engine.destroyImageView(self.image_view); engine.freeMemory(self.device_memory); @@ -166,7 +166,9 @@ pub fn deinit(self: *Texture, engine: *Engine) void { self.* = undefined; } -pub fn writeTexels(self: Texture, comptime TexelType: type, engine: *Engine, texels: []const TexelType) !void { +pub fn writeTexels(self: Texture, comptime TexelType: type, texels: []const TexelType) !void { + const engine = ctx.engine; + const texel_count = try std.math.mul(u32, self.width, self.height); std.debug.assert(texels.len == texel_count); switch (self.usage) { @@ -176,7 +178,7 @@ pub fn writeTexels(self: Texture, comptime TexelType: type, engine: *Engine, tex try self.writeRaw(engine, std.mem.sliceAsBytes(texels)); } -pub fn writeSamples(self: Texture, comptime SampleType: type, engine: *Engine, samples: []const SampleType) !void { +pub fn writeSamples(self: Texture, comptime SampleType: type, samples: []const SampleType) !void { const texel_count = try std.math.mul(u32, self.width, self.height); const sample_count = try std.math.mul(u32, texel_count, self.usage.samplesPerTexel()); std.debug.assert(samples.len == sample_count); @@ -184,29 +186,31 @@ pub fn writeSamples(self: Texture, comptime SampleType: type, engine: *Engine, s inline else => |x| std.debug.assert(SampleType == x.SampleType()), } - try self.writeRaw(engine, std.mem.sliceAsBytes(samples)); + try self.writeRaw(std.mem.sliceAsBytes(samples)); } -pub fn writeRaw(self: Texture, engine: *Engine, data: []const u8) !void { +pub fn writeRaw(self: Texture, data: []const u8) !void { + const engine = ctx.engine; + const texel_count = try std.math.mul(u32, self.width, self.height); const byte_length = try std.math.mul(u32, texel_count, self.usage.bytesPerTexel()); std.debug.assert(data.len == byte_length); std.debug.assert(self.usage != .depth); - var staging_buffer = try StagingBuffer.init(engine, .{ + var staging_buffer = try StagingBuffer.init(.{ .capacity = @intCast(byte_length), .target_queue = self.target_queue, }); - defer staging_buffer.deinit(engine); + defer staging_buffer.deinit(); - const staging_memory = try staging_buffer.map(engine); + const staging_memory = try staging_buffer.map(); @memcpy(staging_memory, data); - staging_buffer.unmap(engine); + staging_buffer.unmap(); // --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY ----------------- - var transfer_command_buffer: CommandBuffer = try .init(engine, .transfer); - defer transfer_command_buffer.deinit(engine); + var transfer_command_buffer: CommandBuffer = try .init(.transfer); + defer transfer_command_buffer.deinit(); try transfer_command_buffer.beginCommandBuffer(); @@ -259,14 +263,14 @@ pub fn writeRaw(self: Texture, engine: *Engine, data: []const u8) !void { const semaphore = try engine.createSemaphore(); defer engine.destroySemaphore(semaphore); - try transfer_command_buffer.submit(engine, .{ + try transfer_command_buffer.submit(.{ .signal_semaphores = &.{semaphore}, }); // --- TRANSITION TO SHADER_READ_ONLY_OPTIMAL ---------------------- - var graphics_command_buffer: CommandBuffer = try .init(engine, .graphics); - defer graphics_command_buffer.deinit(engine); + var graphics_command_buffer: CommandBuffer = try .init(.graphics); + defer graphics_command_buffer.deinit(); try graphics_command_buffer.beginCommandBuffer(); @@ -298,7 +302,7 @@ pub fn writeRaw(self: Texture, engine: *Engine, data: []const u8) !void { const fence = try engine.createFence(.{}); defer engine.destroyFence(fence); - try graphics_command_buffer.submit(engine, .{ + try graphics_command_buffer.submit(.{ .wait_semaphores = &.{.{ .semaphore = semaphore }}, .fence = fence, }); diff --git a/src/engine/VkAllocator.zig b/src/engine/VkAllocator.zig index eefa1dc..3e70529 100644 --- a/src/engine/VkAllocator.zig +++ b/src/engine/VkAllocator.zig @@ -1,13 +1,43 @@ const VkAllocator = @This(); const std = @import("std"); +const ctx = @import("../AppContext.zig"); const vk = @import("vulkan"); -allocator: std.mem.Allocator, -allocations: std.AutoHashMapUnmanaged(*anyopaque, usize) = .empty, -mutex: std.Io.Mutex = .init, -allocated_bytes: usize = 0, -io: std.Io, +const Allocation = struct { + len: usize, + stack_trace: [stack_frames]usize, + + fn capture(len: usize) Allocation { + return .{ + .len = len, + .stack_trace = stack_trace_buf, + }; + } + + fn dumpStackTrace(self: *Allocation) void { + const st = self.getStackTrace(); + std.debug.dumpStackTrace(&st); + } + + fn getStackTrace(self: *Allocation) std.debug.StackTrace { + var stack_trace_len: usize = 0; + while (stack_trace_len < self.stack_trace.len and self.stack_trace[stack_trace_len] != 0) { + stack_trace_len += 1; + } + return .{ + .return_addresses = self.stack_trace[0..stack_trace_len], + .skipped = if (stack_trace_len < self.stack_trace.len) .none else .unknown, + }; + } +}; + +threadlocal var stack_trace_buf: [stack_frames]usize = undefined; + +/// Uses `allocator_general`. +allocations: std.AutoHashMapUnmanaged(*anyopaque, Allocation), +mutex: std.Io.Mutex, +allocated_bytes: usize, interface: vk.AllocationCallbacks, @@ -16,48 +46,66 @@ interface: vk.AllocationCallbacks, // around it, but it would add much complexity. Instead, we allocate everything // with the same, relatively big alignment and hope for the best. const actual_alignment: std.mem.Alignment = .@"16"; +const debug = @import("builtin").mode == .Debug; +const stack_frames: usize = if (debug and std.debug.sys_can_stack_trace) 6 else 0; const log = std.log.scoped(.vk_allocator); -pub fn init(allocator: std.mem.Allocator, io: std.Io) !*VkAllocator { - // NOTE We allocate the structure to pin its address. - const self = try allocator.create(VkAllocator); +pub fn init() !*VkAllocator { + const allocator_persistent = ctx.allocator_persistent; - self.* = .{ - .allocator = allocator, + // NOTE We allocate the structure to pin its address. + const vk_allocator = try allocator_persistent.create(VkAllocator); + errdefer allocator_persistent.destroy(vk_allocator); + + vk_allocator.* = .{ + .allocations = .empty, + .mutex = .init, + .allocated_bytes = 0, .interface = .{ - .p_user_data = self, + .p_user_data = vk_allocator, .pfn_allocation = &allocationFunction, .pfn_reallocation = &reallocationFunction, .pfn_free = &freeFunction, }, - .io = io, }; - return self; + return vk_allocator; } pub fn deinit(self: *VkAllocator) void { + const allocator_general = ctx.allocator_general; + std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); + if (self.allocated_bytes > 0) { log.warn("{d} byte(s) still allocated while deinitializing", .{self.allocated_bytes}); } + if (self.allocations.size > 0) { log.warn("{d} allocation(s) still tracked while deinitializing", .{self.allocations.size}); var it = self.allocations.iterator(); var index: usize = 0; while (it.next()) |entry| : (index += 1) { - log.warn("Leaked allocation ({d}/{d}) at 0x{x} of {d} byte(s)", .{ index + 1, self.allocations.size, @intFromPtr(entry.key_ptr.*), entry.value_ptr.* }); - const memory = @as([*]align(actual_alignment.toByteUnits()) u8, @ptrCast(@alignCast(entry.key_ptr.*)))[0..entry.value_ptr.*]; - self.allocator.free(memory); + const ptr = entry.key_ptr.*; + const allocation = entry.value_ptr; + + log.warn("Leaked allocation ({d}/{d}) at 0x{x} of {d} byte(s)", .{ index + 1, self.allocations.size, @intFromPtr(ptr), allocation.len }); + allocation.dumpStackTrace(); + + const memory = @as([*]align(actual_alignment.toByteUnits()) u8, @ptrCast(@alignCast(ptr)))[0..allocation.len]; + allocator_general.free(memory); } } - const allocator = self.allocator; - - self.allocations.deinit(allocator); - + self.allocations.deinit(allocator_general); self.* = undefined; - allocator.destroy(self); +} + +pub noinline fn capture(self: *const VkAllocator) *const vk.AllocationCallbacks { + const ret_addr = @returnAddress(); + const stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &stack_trace_buf); + @memset(stack_trace_buf[@min(stack_trace.return_addresses.len, stack_trace_buf.len)..], 0); + return &self.interface; } fn allocationFunction( @@ -66,18 +114,21 @@ fn allocationFunction( alignment: usize, _: vk.SystemAllocationScope, ) callconv(vk.vulkan_call_conv) ?*anyopaque { + const allocator_general = ctx.allocator_general; + const io = ctx.io; + const self: *VkAllocator = @ptrCast(@alignCast(p_user_data.?)); const desired_alignment = std.mem.Alignment.fromByteUnits(alignment); std.debug.assert(std.mem.Alignment.compare(actual_alignment, .gte, desired_alignment)); - self.mutex.lock(self.io) catch return null; - defer self.mutex.unlock(self.io); + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); - self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null; - const memory = self.allocator.alignedAlloc(u8, actual_alignment, size) catch return null; + self.allocations.ensureUnusedCapacity(allocator_general, 1) catch return null; + const memory = allocator_general.alignedAlloc(u8, actual_alignment, size) catch return null; - self.allocations.putAssumeCapacity(memory.ptr, size); + self.allocations.putAssumeCapacity(memory.ptr, .capture(size)); self.allocated_bytes += size; //log.debug("Allocated {d} bytes(s) at 0x{x}", .{ size, @intFromPtr(memory.ptr) }); @@ -91,34 +142,37 @@ fn reallocationFunction( alignment: usize, _: vk.SystemAllocationScope, ) callconv(vk.vulkan_call_conv) ?*anyopaque { + const allocator_general = ctx.allocator_general; + const io = ctx.io; + const self: *VkAllocator = @ptrCast(@alignCast(p_user_data.?)); const desired_alignment = std.mem.Alignment.fromByteUnits(alignment); std.debug.assert(std.mem.Alignment.compare(actual_alignment, .gte, desired_alignment)); - self.mutex.lock(self.io) catch return null; - defer self.mutex.unlock(self.io); + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); // NOTE If we were pedantic, we would consider the fact that we might not // need unused capacity if the memory doesn't get relocated. - self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null; + self.allocations.ensureUnusedCapacity(allocator_general, 1) catch return null; const old_memory = if (maybe_p_original) |p_original| blk_then: { - const old_size = self.allocations.get(p_original).?; + const old_size = self.allocations.get(p_original).?.len; break :blk_then @as([*]align(actual_alignment.toByteUnits()) u8, @ptrCast(@alignCast(p_original)))[0..old_size]; } else blk_else: { break :blk_else @as([]align(actual_alignment.toByteUnits()) u8, &.{}); }; - const memory = self.allocator.realloc(old_memory, size) catch return null; + const memory = allocator_general.realloc(old_memory, size) catch return null; std.debug.assert(std.mem.isAligned(@intFromPtr(memory.ptr), alignment)); if (maybe_p_original) |p_original| { - const old_size = self.allocations.fetchRemove(p_original).?.value; + const old_size = self.allocations.fetchRemove(p_original).?.value.len; self.allocated_bytes -= old_size; } - self.allocations.putAssumeCapacityNoClobber(memory.ptr, size); + self.allocations.putAssumeCapacityNoClobber(memory.ptr, .capture(size)); self.allocated_bytes += size; //log.debug("Reallocated into {d} bytes(s) at 0x{x} from 0x{x}", .{ size, @intFromPtr(memory.ptr), @intFromPtr(maybe_p_original) }); @@ -129,16 +183,19 @@ fn freeFunction( p_user_data: ?*anyopaque, maybe_p_memory: ?*anyopaque, ) callconv(vk.vulkan_call_conv) void { + const allocator_general = ctx.allocator_general; + const io = ctx.io; + const self: *VkAllocator = @ptrCast(@alignCast(p_user_data.?)); if (maybe_p_memory) |p_memory| { - self.mutex.lockUncancelable(self.io); - defer self.mutex.unlock(self.io); + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); - const size = self.allocations.fetchRemove(p_memory).?.value; + const size = self.allocations.fetchRemove(p_memory).?.value.len; self.allocated_bytes -= size; const memory = @as([*]align(actual_alignment.toByteUnits()) u8, @ptrCast(@alignCast(p_memory)))[0..size]; - self.allocator.free(memory); + allocator_general.free(memory); } } diff --git a/src/main.zig b/src/main.zig index 60be351..6233382 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,11 +1,11 @@ const std = @import("std"); +const c = @import("const.zig"); +const ctx = @import("AppContext.zig"); const glfw = @import("zglfw"); const vk = @import("vulkan"); -const c = @import("const.zig"); - -const atoms = @import("engine/Atom.zig"); +const Atoms = @import("engine/Atoms.zig"); const Engine = @import("engine/Engine.zig"); const Swapchain = @import("engine/Swapchain.zig"); const Game = @import("Game.zig"); @@ -25,11 +25,20 @@ pub const std_options: std.Options = .{ }; pub fn main(init: std.process.Init) !void { - const allocator = init.gpa; - const io = init.io; + var arena_frame: std.heap.ArenaAllocator = .init(std.heap.page_allocator); + defer arena_frame.deinit(); - try atoms.init(allocator, io); - defer atoms.deinit(io); + ctx.allocator_general = init.gpa; + ctx.allocator_frame = arena_frame.allocator(); + ctx.allocator_persistent = init.arena.allocator(); + ctx.io = init.io; + ctx.vk_allocator = try .init(); + defer { + ctx.vk_allocator.deinit(); + ctx.vk_allocator = undefined; + } + + // --- INITIALIZE APP CONTEXT --- glfw.init() catch |err| { std.log.err("Could not initialize GLFW", .{}); @@ -43,46 +52,65 @@ pub fn main(init: std.process.Init) !void { } glfw.windowHint(.client_api, .no_api); - var window = glfw.Window.create(c.default_window_width, c.default_window_height, c.window_title, null, null) catch |err| { + ctx.window = glfw.Window.create(c.default_window_width, c.default_window_height, c.window_title, null, null) catch |err| { std.log.err("Could not create window", .{}); return err; }; - defer window.destroy(); - - window.setSizeLimits(c.min_window_width, c.min_window_height, -1, -1); - window.setInputMode(.cursor, .disabled) catch {}; - if (glfw.rawMouseMotionSupported()) { - window.setInputMode(.raw_mouse_motion, true) catch {}; + defer { + ctx.window.destroy(); + ctx.window = undefined; } - _ = window.setKeyCallback(keyCallback); - _ = window.setCursorPosCallback(cursorPosCallback); - _ = window.setMouseButtonCallback(mouseButtonCallback); + ctx.window.setSizeLimits(c.min_window_width, c.min_window_height, -1, -1); + ctx.window.setInputMode(.cursor, .disabled) catch {}; + if (glfw.rawMouseMotionSupported()) { + ctx.window.setInputMode(.raw_mouse_motion, true) catch {}; + } - var engine = try Engine.init(allocator, io, window); - defer engine.deinit(); + _ = ctx.window.setKeyCallback(keyCallback); + _ = ctx.window.setCursorPosCallback(cursorPosCallback); + _ = ctx.window.setMouseButtonCallback(mouseButtonCallback); - var swapchain = try Swapchain.init(&engine); - defer swapchain.deinit(&engine); + ctx.atoms = try .init(); + defer { + ctx.atoms.deinit(); + ctx.atoms = undefined; + } - var game = try Game.init(allocator, io, &engine, &swapchain); + ctx.engine = try .init(); + defer { + ctx.engine.deinit(); + ctx.engine = undefined; + } + + ctx.swapchain = try .init(); + defer { + ctx.swapchain.deinit(); + ctx.swapchain = undefined; + } + + // --- INITIALIZE THE GAME --- + + var game = try Game.init(); var callback_context: CallbackContext = blk: { - const cursor_last_xpos, const cursor_last_ypos = window.getCursorPos(); + const cursor_last_xpos, const cursor_last_ypos = ctx.window.getCursorPos(); break :blk .{ .game = &game, .cursor_last_xpos = cursor_last_xpos, .cursor_last_ypos = cursor_last_ypos, - .focused = (window.getInputMode(.cursor) catch .normal) == .disabled, + .focused = (ctx.window.getInputMode(.cursor) catch .normal) == .disabled, }; }; - window.setUserPointer(&callback_context); + ctx.window.setUserPointer(&callback_context); defer { - window.setUserPointer(null); + ctx.window.setUserPointer(null); game.deinit(); } + _ = arena_frame.reset(.retain_capacity); + var t1 = glfw.getTime(); - while (!window.shouldClose()) { + while (!ctx.window.shouldClose()) { glfw.pollEvents(); const t2 = glfw.getTime(); @@ -90,12 +118,13 @@ pub fn main(init: std.process.Init) !void { t1 = t2; game.update(dt); + _ = arena_frame.reset(.retain_capacity); } std.log.debug("Exitted the game loop", .{}); - engine.waitForFence(swapchain.fence) catch {}; - engine.deviceWaitIdle() catch {}; + ctx.engine.waitForFence(ctx.swapchain.fence) catch {}; + ctx.engine.deviceWaitIdle() catch {}; } const CallbackContext = struct { @@ -106,24 +135,24 @@ const CallbackContext = struct { }; fn keyCallback(window: *glfw.Window, key_code: glfw.Key, _: c_int, action: glfw.Action, _: glfw.Mods) callconv(.c) void { - const maybe_ctx = window.getUserPointer(CallbackContext); + const maybe_callback_ctx = window.getUserPointer(CallbackContext); if (key_code == .escape and action == .press and (window.getInputMode(.cursor) catch .normal) == .disabled) { window.setInputMode(.cursor, .normal) catch {}; - if (maybe_ctx) |ctx| { + if (maybe_callback_ctx) |callback_ctx| { const cursor_last_xpos, const cursor_last_ypos = window.getCursorPos(); - ctx.cursor_last_xpos = cursor_last_xpos; - ctx.cursor_last_ypos = cursor_last_ypos; - ctx.focused = false; + callback_ctx.cursor_last_xpos = cursor_last_xpos; + callback_ctx.cursor_last_ypos = cursor_last_ypos; + callback_ctx.focused = false; } return; } - if (maybe_ctx) |ctx| { - if (ctx.focused) { + if (maybe_callback_ctx) |callback_ctx| { + if (callback_ctx.focused) { switch (action) { - .press => ctx.game.onKeyDown(key_code), - .release => ctx.game.onKeyUp(key_code), + .press => callback_ctx.game.onKeyDown(key_code), + .release => callback_ctx.game.onKeyUp(key_code), .repeat => {}, } } @@ -131,36 +160,36 @@ fn keyCallback(window: *glfw.Window, key_code: glfw.Key, _: c_int, action: glfw. } fn cursorPosCallback(window: *glfw.Window, cursor_xpos: f64, cursor_ypos: f64) callconv(.c) void { - const maybe_ctx = window.getUserPointer(CallbackContext); - if (maybe_ctx) |ctx| { - if (ctx.focused) { - const dx: f32 = @floatCast(cursor_xpos - ctx.cursor_last_xpos); - const dy: f32 = @floatCast(cursor_ypos - ctx.cursor_last_ypos); - ctx.game.onMouseMove(dx, dy); - ctx.cursor_last_xpos = cursor_xpos; - ctx.cursor_last_ypos = cursor_ypos; + const maybe_callback_ctx = window.getUserPointer(CallbackContext); + if (maybe_callback_ctx) |callback_ctx| { + if (callback_ctx.focused) { + const dx: f32 = @floatCast(cursor_xpos - callback_ctx.cursor_last_xpos); + const dy: f32 = @floatCast(cursor_ypos - callback_ctx.cursor_last_ypos); + callback_ctx.game.onMouseMove(dx, dy); + callback_ctx.cursor_last_xpos = cursor_xpos; + callback_ctx.cursor_last_ypos = cursor_ypos; } } } fn mouseButtonCallback(window: *glfw.Window, button: glfw.MouseButton, action: glfw.Action, _: glfw.Mods) callconv(.c) void { - const maybe_ctx = window.getUserPointer(CallbackContext); + const maybe_callback_ctx = window.getUserPointer(CallbackContext); if (button == .left and action == .press and (window.getInputMode(.cursor) catch .normal) == .normal) { window.setInputMode(.cursor, .disabled) catch {}; - if (maybe_ctx) |ctx| { + if (maybe_callback_ctx) |callback_ctx| { const cursor_last_xpos, const cursor_last_ypos = window.getCursorPos(); - ctx.cursor_last_xpos = cursor_last_xpos; - ctx.cursor_last_ypos = cursor_last_ypos; - ctx.focused = true; + callback_ctx.cursor_last_xpos = cursor_last_xpos; + callback_ctx.cursor_last_ypos = cursor_last_ypos; + callback_ctx.focused = true; } } - if (maybe_ctx) |ctx| { - if (ctx.focused) { + if (maybe_callback_ctx) |callback_ctx| { + if (callback_ctx.focused) { switch (action) { - .press => ctx.game.onMouseDown(button), - .release => ctx.game.onMouseUp(button), + .press => callback_ctx.game.onMouseDown(button), + .release => callback_ctx.game.onMouseUp(button), .repeat => {}, } }