diff --git a/src/Game.zig b/src/Game.zig index 93bf81b..728abc0 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -5,6 +5,8 @@ const glfw = @import("zglfw"); const math = @import("math.zig"); +const Atoms = @import("assets/Atoms.zig"); +const Textures = @import("assets/Textures.zig"); const Materials = @import("assets/Materials.zig"); const Swapchain = @import("engine/Swapchain.zig"); const Iterator3 = math.Iterator3; @@ -16,7 +18,9 @@ const Vector3 = math.Vector3; allocator: std.mem.Allocator, swapchain: *Swapchain, +atoms: Atoms, materials: Materials, +textures: Textures, camera_position: Vector3 = .init(0, 0, 1.62), camera_pitch: f32 = 0, @@ -38,24 +42,30 @@ const max_directional_lights = 4; const player_speed = 5.0; pub fn init(allocator: std.mem.Allocator, swapchain: *Swapchain) !Game { - var materials = try Materials.init(allocator); - errdefer materials.deinit(); + var atoms = Atoms.init(allocator); + errdefer atoms.deinit(); - var it = materials.map.iterator(); - while (it.next()) |entry| { - std.debug.print("Material: {s}\n", .{entry.key_ptr.*}); - std.debug.print("Value: {}\n", .{entry.value_ptr.*}); - } + var materials = try Materials.init(allocator, swapchain.engine); + errdefer materials.deinit(swapchain.engine); + + var textures = try Textures.init(allocator, swapchain.engine); + errdefer textures.deinit(swapchain.engine); + + _ = try materials.getOrLoadId(swapchain.engine, &textures, &atoms, try atoms.getOrPutAtom("Bricks.json")); return .{ .allocator = allocator, .swapchain = swapchain, + .atoms = atoms, .materials = materials, + .textures = textures, }; } pub fn deinit(self: *Game) void { - self.materials.deinit(); + self.textures.deinit(self.swapchain.engine); + self.materials.deinit(self.swapchain.engine); + self.atoms.deinit(); self.* = undefined; } diff --git a/src/asset_pipeline.zig b/src/asset_pipeline.zig deleted file mode 100644 index 39713a5..0000000 --- a/src/asset_pipeline.zig +++ /dev/null @@ -1,219 +0,0 @@ -const std = @import("std"); - -const main = @import("main.zig"); -const zstbi = @import("zstbi"); - -pub const Texture = struct { - width: u32, - height: u32, - depth: u32, - data: []u8, - - pub fn deinit(self: *Texture, allocator: std.mem.Allocator) void { - allocator.free(self.data); - self.* = undefined; - } -}; - -pub const TextureHdr = struct { - width: u32, - height: u32, - depth: u32, - data: []f16, - - pub fn deinit(self: *TextureHdr, allocator: std.mem.Allocator) void { - allocator.free(self.data); - self.* = undefined; - } -}; - -pub const AssetMap = std.hash_map.StringHashMapUnmanaged; - -pub fn visitTextures(allocator: std.mem.Allocator) AssetMap(Texture) { - zstbi.init(allocator); - defer zstbi.deinit(); - - const cwd = std.fs.cwd(); - const sub_path = "assets/textures"; - - var ret: AssetMap(Texture) = .empty; - - var textures_dir = cwd.openDir(sub_path, .{ .iterate = true }) catch |err| { - std.log.err("Could not open \"{s}\" directory: {s}", .{ sub_path, @errorName(err) }); - return ret; - }; - defer textures_dir.close(); - - var it = textures_dir.iterate(); - while (it.next() catch |err| blk: { - std.log.err("Directory iteration interrupted due to an error: {s}", .{@errorName(err)}); - break :blk null; - }) |entry| { - if (entry.kind != .file) { - std.log.warn("Skipping \"{s}\", which is not a file.", .{entry.name}); - continue; - } - - var texture = visitTexture(allocator, textures_dir, entry.name) catch { - continue; - }; - - const name = allocator.dupe(u8, std.fs.path.stem(entry.name)) catch { - std.log.err("Ran out of memory while trying to allocate asset name", .{}); - texture.deinit(allocator); - continue; - }; - - ret.putNoClobber(allocator, name, texture) catch { - std.log.err("Ran out of memory while trying to save asset to map", .{}); - texture.deinit(allocator); - allocator.free(name); - continue; - }; - } - - return ret; -} - -fn visitTexture(allocator: std.mem.Allocator, dir: std.fs.Dir, filename: []const u8) !Texture { - std.log.info("Processing \"{s}\"...", .{filename}); - - const file = dir.openFile(filename, .{}) catch |err| { - std.log.err("Could not open \"{s}\" file: {s}", .{ filename, @errorName(err) }); - return err; - }; - defer file.close(); - - const file_buf = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { - std.log.err("Could not read \"{s}\" file contents due to an error: {s}", .{ filename, @errorName(err) }); - return err; - }; - defer allocator.free(file_buf); - - var img = zstbi.Image.loadFromMemory(file_buf, 4) catch |err| { - std.log.err("Error reading \"{s}\" as an image file: {s}", .{ filename, @errorName(err) }); - return err; - }; - defer img.deinit(); - - std.log.debug("size: {}×{} | components: {}", .{ img.width, img.height, img.num_components }); - - const grid_w = 4; - const grid_h = 4; - const tile_count = grid_w * grid_h; - - const tile_w = std.math.divExact(u32, img.width, grid_w) catch |err| { - std.log.err("Cannot divide image width ({}) by {}: {s}", .{ img.width, grid_w, @errorName(err) }); - return err; - }; - const tile_h = std.math.divExact(u32, img.height, grid_h) catch |err| { - std.log.err("Cannot divide image height ({}) by {}: {s}", .{ img.height, grid_h, @errorName(err) }); - return err; - }; - - std.log.debug("tile size: {}×{}", .{ tile_w, tile_h }); - - const data = allocator.alloc(u8, 4 * tile_w * tile_h * tile_count) catch |err| { - std.log.err("Ran out of memory while trying to allocate output buffer", .{}); - return err; - }; - errdefer allocator.free(data); - - rearrange(grid_w, grid_h, tile_w, tile_h, img.data, data); - - return .{ - .width = tile_w, - .height = tile_h, - .depth = tile_count, - .data = data, - }; -} - -pub fn visitSkybox(allocator: std.mem.Allocator) !TextureHdr { - zstbi.init(allocator); - defer zstbi.deinit(); - - const cwd = std.fs.cwd(); - const filename = "assets/skybox.hdr"; - std.log.info("Processing \"{s}\"...", .{filename}); - - const file = cwd.openFile(filename, .{}) catch |err| { - std.log.err("Could not open \"{s}\" file: {s}", .{ filename, @errorName(err) }); - return err; - }; - defer file.close(); - - const file_buf = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { - std.log.err("Could not read \"{s}\" file contents due to an error: {s}", .{ filename, @errorName(err) }); - return err; - }; - defer allocator.free(file_buf); - - var img = zstbi.Image.loadFromMemory(file_buf, 4) catch |err| { - std.log.err("Error reading \"{s}\" as an image file: {s}", .{ filename, @errorName(err) }); - return err; - }; - defer img.deinit(); - - std.log.debug("size: {}×{} | components: {} | hdr: {}", .{ img.width, img.height, img.num_components, img.is_hdr }); - - const data = allocator.alloc(f16, 4 * img.width * img.height) catch |err| { - std.log.err("Ran out of memory while trying to allocate output buffer", .{}); - return err; - }; - errdefer allocator.free(data); - - @memcpy(data, std.mem.bytesAsSlice(f16, img.data)); - - return .{ - .width = img.width, - .height = img.height, - .depth = 1, - .data = data, - }; -} - -fn rearrange(grid_w: u32, grid_h: u32, tile_w: u32, tile_h: u32, inbuf: []const u8, outbuf: []u8) void { - std.log.debug("rearrange: {}×{} grid of {}×{} tiles", .{ grid_w, grid_h, tile_w, tile_h }); - - const row_size = 4 * tile_w; - const row_stride = row_size * grid_w; - const tile_stride = row_stride * tile_h; - - std.debug.assert(inbuf.len == tile_stride * grid_h); - std.debug.assert(outbuf.len == tile_stride * grid_h); - - var outptr: usize = 0; - - var tile_y: u32 = 0; - while (tile_y < grid_h) : (tile_y += 1) { - const tile_byte_offset = tile_y * tile_stride; - - var tile_x: u32 = 0; - while (tile_x < grid_w) : (tile_x += 1) { - const column_byte_offset = tile_x * row_size; - - var row: u32 = 0; - while (row < tile_h) : (row += 1) { - const row_byte_offset = row * row_stride + tile_byte_offset; - const byte_offset = row_byte_offset + column_byte_offset; - - @memcpy( - outbuf[outptr .. outptr + row_size], - inbuf[byte_offset .. byte_offset + row_size], - ); - - outptr += row_size; - } - } - } -} - -pub fn deinitTextures(allocator: std.mem.Allocator, map: *AssetMap(Texture)) void { - var it = map.iterator(); - while (it.next()) |entry| { - allocator.free(entry.key_ptr.*); - entry.value_ptr.deinit(allocator); - } - map.deinit(allocator); -} diff --git a/src/assets/Atoms.zig b/src/assets/Atoms.zig index dcf007f..d42507c 100644 --- a/src/assets/Atoms.zig +++ b/src/assets/Atoms.zig @@ -11,14 +11,14 @@ pub const Atom = extern struct { }; pub const Map = std.StringHashMapUnmanaged(Atom); -pub const Array = std.ArrayList([]const u16); +pub const Array = std.ArrayList([]const u8); pub fn init(allocator: std.mem.Allocator) Atoms { return .{ .allocator = allocator, .string_arena_state = .{}, .map = .empty, - .next_index = 0, + .array = .empty, }; } @@ -44,7 +44,7 @@ pub fn getOrPutAtom(self: *Atoms, string: []const u8) !Atom { if (entry.found_existing) { return entry.value_ptr.*; } else { - errdefer self.map.remove(string); + errdefer _ = self.map.remove(string); try self.array.ensureUnusedCapacity(self.allocator, 1); const owned_string = try self.toOwnedString(string); @@ -60,7 +60,7 @@ pub fn getOrPutAtom(self: *Atoms, string: []const u8) !Atom { } } -fn toOwnedString(self: *const Atoms, string: []const u8) ![]const u8 { +fn toOwnedString(self: *Atoms, string: []const u8) ![]const u8 { var string_arena = self.string_arena_state.promote(std.heap.page_allocator); defer self.string_arena_state = string_arena.state; diff --git a/src/assets/Materials.zig b/src/assets/Materials.zig index faf4ad5..821b6ae 100644 --- a/src/assets/Materials.zig +++ b/src/assets/Materials.zig @@ -5,10 +5,15 @@ const vk = @import("vulkan"); const Atoms = @import("Atoms.zig"); const Engine = @import("../engine/Engine.zig"); +const StorageBuffer = @import("../engine/StorageBuffer.zig"); const Textures = @import("Textures.zig"); allocator: std.mem.Allocator, map: Map, +storage_buffer: StorageBuffer, +next_id: Id, + +pub const initial_capacity = 4; pub const Id = extern struct { id: u32, @@ -30,22 +35,44 @@ pub const Material = extern struct { roughness: f32, }; -pub fn init(allocator: std.mem.Allocator) !Materials { +pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Materials { var map: Map = .empty; errdefer map.deinit(allocator); + var storage_buffer: StorageBuffer = try .init(engine, void, Material, initial_capacity); + errdefer storage_buffer.deinit(engine); + return .{ .allocator = allocator, .map = map, + .storage_buffer = storage_buffer, + .next_id = .{ .id = 0 }, }; } -pub fn deinit(self: *Materials) void { - self.arena.deinit(); +pub fn deinit(self: *Materials, engine: *Engine) void { + self.storage_buffer.deinit(engine); + self.map.deinit(self.allocator); self.* = undefined; } -fn loadMaterial(self: *Materials, textures: *Textures, engine: *Engine, atoms: *Atoms, key: Atoms.Atom) !Id { +pub fn getId(self: *const Materials, key: Atoms.Atom) ?Id { + return self.map.get(key); +} + +pub fn getOrLoadId(self: *Materials, engine: *Engine, textures: *Textures, atoms: *Atoms, key: Atoms.Atom) !Id { + const entry = try self.map.getOrPut(self.allocator, key); + + if (entry.found_existing) { + return entry.value_ptr.*; + } else { + const id = try self.loadMaterial(engine, textures, atoms, key); + entry.value_ptr.* = id; + return id; + } +} + +fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: *Atoms, key: Atoms.Atom) !Id { const MaterialJson = struct { baseColor: [3]f32 = .{ 1, 1, 1 }, baseColorTexture: ?[]const u8 = null, @@ -89,7 +116,7 @@ fn loadMaterial(self: *Materials, textures: *Textures, engine: *Engine, atoms: * const base_color_texture = blk: { if (material_json.baseColorTexture) |name| { const atom = try atoms.getOrPutAtom(name); - break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .base_color }); + break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .base_color }); } else { break :blk textures.empty_base_color; } @@ -98,7 +125,7 @@ fn loadMaterial(self: *Materials, textures: *Textures, engine: *Engine, atoms: * const emissive_texture = blk: { if (material_json.emissiveTexture) |name| { const atom = try atoms.getOrPutAtom(name); - break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .emissive }); + break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .emissive }); } else { break :blk textures.empty_emissive; } @@ -107,7 +134,7 @@ fn loadMaterial(self: *Materials, textures: *Textures, engine: *Engine, atoms: * const normal_texture = blk: { if (material_json.normalTexture) |name| { const atom = try atoms.getOrPutAtom(name); - break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .normal }); + break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .normal }); } else { break :blk textures.empty_normal; } @@ -116,23 +143,37 @@ fn loadMaterial(self: *Materials, textures: *Textures, engine: *Engine, atoms: * const occlusion_roughness_metallic_texture = blk: { if (material_json.occlusionRoughnessMetallicTexture) |name| { const atom = try atoms.getOrPutAtom(name); - break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .occlusion_roughness_metallic }); + break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .occlusion_roughness_metallic }); } else { break :blk textures.empty_occlusuion_roughness_metallic; } }; - const material: Material = .{ - .base_color = material_json.baseColor, - .base_color_texture = base_color_texture, - .emissive = material_json.emissive, - .emissive_texture = emissive_texture, - .ior = material_json.ior, - .metallic = material_json.metallic, - .normal_scale = material_json.normalScale, - .normal_texture = normal_texture, - .occlusion_roughness_metallic_texture = occlusion_roughness_metallic_texture, - .occlusion_texture_strength = material_json.occlusionTextureStrength, - .roughness = material_json.roughness, + const next_id = self.next_id; + const offset: usize = next_id.id; + + if (offset >= self.storage_buffer.array_capacity) { + try self.storage_buffer.enlarge(void, Material, engine, self.storage_buffer.array_capacity * 2); + } + + const materials = [_]Material{ + .{ + .base_color = material_json.baseColor, + .base_color_texture = base_color_texture, + .emissive = material_json.emissive, + .emissive_texture = emissive_texture, + .ior = material_json.ior, + .metallic = material_json.metallic, + .normal_scale = material_json.normalScale, + .normal_texture = normal_texture, + .occlusion_roughness_metallic_texture = occlusion_roughness_metallic_texture, + .occlusion_texture_strength = material_json.occlusionTextureStrength, + .roughness = material_json.roughness, + }, }; + + try self.storage_buffer.writeOffset(void, Material, engine, {}, offset, &materials); + self.next_id = .{ .id = next_id.id + 1 }; + + return next_id; } diff --git a/src/assets/Textures.zig b/src/assets/Textures.zig index cf3377f..126e41e 100644 --- a/src/assets/Textures.zig +++ b/src/assets/Textures.zig @@ -34,7 +34,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures { var array: Array = try .initCapacity(allocator, 4); errdefer { - for (array.items) |texture| { + for (array.items) |*texture| { texture.deinit(engine); } array.deinit(allocator); @@ -56,12 +56,13 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures { const empty_occlusuion_roughness_metallic_texture: Texture = try .init(engine, 1, 1, .occlusion_roughness_metallic); array.appendAssumeCapacity(empty_occlusuion_roughness_metallic_texture); - empty_base_color_texture.write([3]u8, &.{.{ 255, 255, 255 }}); - empty_emissive_texture.write([3]f32, &.{.{ 1.0, 1.0, 1.0 }}); - empty_normal_texture.write([3]u8, &.{.{ 128, 128, 255 }}); - empty_occlusuion_roughness_metallic_texture.write([3]u8, &.{.{ 255, 255, 255 }}); + try empty_base_color_texture.write([4]u8, engine, &.{.{ 255, 255, 255, 255 }}); + try empty_emissive_texture.write([4]f16, engine, &.{.{ 1.0, 1.0, 1.0, 1.0 }}); + try empty_normal_texture.write([4]u8, engine, &.{.{ 128, 128, 255, 255 }}); + try empty_occlusuion_roughness_metallic_texture.write([4]u8, engine, &.{.{ 255, 255, 255, 255 }}); return .{ + .allocator = allocator, .map = map, .array = array, @@ -72,13 +73,13 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures { }; } -pub fn deinit(self: *Textures, allocator: std.mem.Allocator, engine: *Engine) void { - for (self.array.items) |texture| { +pub fn deinit(self: *Textures, engine: *Engine) void { + for (self.array.items) |*texture| { texture.deinit(engine); } - self.array.deinit(allocator); + self.array.deinit(self.allocator); - self.map.deinit(allocator); + self.map.deinit(self.allocator); } pub fn getTexture(self: *const Textures, id: Id) ?*Texture { @@ -90,15 +91,15 @@ pub fn getId(self: *const Textures, key: Key) ?Id { return self.map.get(key); } -pub fn getOrLoadId(self: *Textures, engine: *Engine, key: Key) !Id { +pub fn getOrLoadId(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Id { const entry = try self.map.getOrPut(self.allocator, key); if (entry.found_existing) { return entry.value_ptr.*; } else { - errdefer self.map.remove(key); + errdefer _ = self.map.remove(key); try self.array.ensureUnusedCapacity(self.allocator, 1); - const texture = try self.loadTexture(engine, key); + const texture = try self.loadTexture(engine, atoms, key); const id = self.nextId(); @@ -109,13 +110,14 @@ pub fn getOrLoadId(self: *Textures, engine: *Engine, key: Key) !Id { } } -fn loadTexture(self: *Textures, engine: *Engine, key: Key) !Texture { +fn loadTexture(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Texture { + const filename = atoms.getString(key.atom).?; const cwd = std.fs.cwd(); var dir = try cwd.openDir("assets/textures", .{}); defer dir.close(); - const file = try dir.openFile(key.name, .{}); + const file = try dir.openFile(filename, .{}); defer file.close(); const file_buf = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize)); @@ -128,7 +130,7 @@ fn loadTexture(self: *Textures, engine: *Engine, key: Key) !Texture { var texture: Texture = try .init(engine, img.width, img.height, key.usage); errdefer texture.deinit(engine); - texture.write(u8, img.data); + try texture.write(u8, engine, img.data); return texture; } diff --git a/src/engine/Engine.zig b/src/engine/Engine.zig index 8df2d90..da39f1b 100644 --- a/src/engine/Engine.zig +++ b/src/engine/Engine.zig @@ -290,7 +290,14 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine { 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(&vk_allocator.interface); + + // --- GET QUEUES, CREATE COMMAND POOLS ------------------------------------ + + const graphics_queue = try Queue.initAllocation(device, queue_allocations.graphics_queue, false, &vk_allocator.interface); + const compute_queue = try Queue.initAllocation(device, queue_allocations.compute_queue, true, &vk_allocator.interface); + const transfer_queue = try Queue.initAllocation(device, queue_allocations.transfer_queue, true, &vk_allocator.interface); + const presentation_queue = if (maybe_window != null) try Queue.initAllocation(device, queue_allocations.presentation_queue, false, &vk_allocator.interface) else undefined; // ------------------------------------------------------------------------- @@ -298,7 +305,7 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine { .mode = if (maybe_window) |window| .{ .surface = .{ .window = window, .surface = surface, - .presentation_queue = .initAllocation(device, queue_allocations.presentation_queue), + .presentation_queue = presentation_queue, } } else .{ .headless = {} }, .vk_allocator = vk_allocator, @@ -311,15 +318,22 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine { .memory_types = memory_types, .memory_heaps = memory_heaps, - .graphics_queue = .initAllocation(device, queue_allocations.graphics_queue), - .compute_queue = .initAllocation(device, queue_allocations.compute_queue), - .transfer_queue = .initAllocation(device, queue_allocations.transfer_queue), + .graphics_queue = graphics_queue, + .compute_queue = compute_queue, + .transfer_queue = transfer_queue, }; } pub fn deinit(self: *Engine) void { const allocator = self.vk_allocator.allocator; + if (std.meta.activeTag(self.mode) == .surface) { + self.mode.surface.presentation_queue.deinit(self.device, &self.vk_allocator.interface); + } + self.transfer_queue.deinit(self.device, &self.vk_allocator.interface); + self.compute_queue.deinit(self.device, &self.vk_allocator.interface); + self.graphics_queue.deinit(self.device, &self.vk_allocator.interface); + self.device.destroyDevice(&self.vk_allocator.interface); allocator.destroy(self.device.wrapper); @@ -348,13 +362,63 @@ 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); } } return error.NoSuitableMemoryType; } +pub fn allocateTransferCommandBuffer(self: *const Engine) !vk.CommandBufferProxy { + var command_buffer: vk.CommandBuffer = undefined; + try self.device.allocateCommandBuffers(&.{ + .command_pool = self.transfer_queue.command_pool, + .level = .primary, + .command_buffer_count = 1, + }, @ptrCast(&command_buffer)); + return .init(command_buffer, self.device.wrapper); +} + +pub fn allocateGraphicsCommandBuffer(self: *const Engine) !vk.CommandBufferProxy { + var command_buffer: vk.CommandBuffer = undefined; + try self.device.allocateCommandBuffers(&.{ + .command_pool = self.graphics_queue.command_pool, + .level = .primary, + .command_buffer_count = 1, + }, @ptrCast(&command_buffer)); + return .init(command_buffer, self.device.wrapper); +} + +pub fn freeTransferCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy) void { + self.device.freeCommandBuffers(self.transfer_queue.command_pool, 1, @ptrCast(&command_buffer)); +} + +pub fn freeGraphicsCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy) void { + self.device.freeCommandBuffers(self.graphics_queue.command_pool, 1, @ptrCast(&command_buffer)); +} + +pub fn submitTransferCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy, fence: vk.Fence) !void { + const submits = [_]vk.SubmitInfo{ + .{ + .command_buffer_count = 1, + .p_command_buffers = @ptrCast(&command_buffer.handle), + }, + }; + + try self.device.queueSubmit(self.transfer_queue.handle, submits.len, &submits, fence); +} + +pub fn submitGraphicsCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy, fence: vk.Fence) !void { + const submits = [_]vk.SubmitInfo{ + .{ + .command_buffer_count = 1, + .p_command_buffers = @ptrCast(&command_buffer.handle), + }, + }; + + try self.device.queueSubmit(self.graphics_queue.handle, submits.len, &submits, fence); +} + fn debugUtilsMessengerCallback( severity: vk.DebugUtilsMessageSeverityFlagsEXT, message_type: vk.DebugUtilsMessageTypeFlagsEXT, diff --git a/src/engine/IndexBuffer.zig b/src/engine/IndexBuffer.zig index c8c2ffb..32040c1 100644 --- a/src/engine/IndexBuffer.zig +++ b/src/engine/IndexBuffer.zig @@ -5,6 +5,7 @@ const vk = @import("vulkan"); const Engine = @import("Engine.zig"); const StagingBuffer = @import("StagingBuffer.zig"); +const QSM = @import("QueueSharingMode.zig"); buffer: vk.Buffer, memory: vk.DeviceMemory, @@ -13,14 +14,17 @@ index_count: usize, pub fn init(engine: *Engine, index_count: usize) !IndexBuffer { const size = std.math.mul(usize, index_count, @sizeOf(u16)) catch return error.OutOfMemory; + const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family); const buffer = try engine.device.createBuffer(&.{ .size = size, .usage = .{ .transfer_dst_bit = true, .index_buffer_bit = true, }, - .sharing_mode = .exclusive, - }, null); + .sharing_mode = qsm.sharing_mode, + .queue_family_index_count = qsm.queue_family_index_count, + .p_queue_family_indices = qsm.p_queue_family_indices, + }, &engine.vk_allocator.interface); errdefer engine.device.destroyBuffer(buffer); const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); @@ -46,6 +50,36 @@ pub fn deinit(self: *IndexBuffer, engine: *Engine) void { pub fn write(self: IndexBuffer, engine: *Engine, indices: []const u16) !void { std.debug.assert(indices.len == self.index_count); + const fence = try engine.device.createFence(.{}, &engine.vk_allocator.interface); + defer engine.device.destroyFence(fence, &engine.vk_allocator.interface); + + const size = std.mem.sliceAsBytes(indices).len; + const staging_buffer: StagingBuffer = .init(engine, std.mem.sliceAsBytes(indices), engine.graphics_queue.allocation.family); defer staging_buffer.deinit(engine); + + const command_buffer = try engine.allocateTransferCommandBuffer(); + defer engine.freeTransferCommandBuffer(command_buffer); + + try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + + const regions = [_]vk.BufferCopy{ + .{ + .src_offset = 0, + .dst_offset = 0, + .size = size, + }, + }; + + command_buffer.copyBuffer( + staging_buffer.buffer, + self.buffer, + regions.len, + ®ions, + ); + + try command_buffer.endCommandBuffer(); + try engine.submitTransferCommandBuffer(command_buffer, fence); + + _ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); } diff --git a/src/engine/Queue.zig b/src/engine/Queue.zig index 7d5b36a..845e46b 100644 --- a/src/engine/Queue.zig +++ b/src/engine/Queue.zig @@ -9,11 +9,27 @@ pub const Allocation = struct { }; handle: vk.Queue, +command_pool: vk.CommandPool, allocation: Allocation, -pub fn initAllocation(device: vk.DeviceProxy, allocation: Allocation) Queue { +pub fn initAllocation(device: vk.DeviceProxy, allocation: Allocation, transient: bool, p_allocator: ?*const vk.AllocationCallbacks) !Queue { + const command_pool = try device.createCommandPool(&.{ + .flags = .{ + .transient_bit = transient, + .reset_command_buffer_bit = true, + }, + .queue_family_index = allocation.family, + }, p_allocator); + errdefer device.destroyCommandPool(command_pool, p_allocator); + return .{ .handle = device.getDeviceQueue(allocation.family, allocation.index), .allocation = allocation, + .command_pool = command_pool, }; } + +pub fn deinit(self: *Queue, device: vk.DeviceProxy, p_allocator: ?*const vk.AllocationCallbacks) void { + device.destroyCommandPool(self.command_pool, p_allocator); + self.* = undefined; +} diff --git a/src/engine/QueueSharingMode.zig b/src/engine/QueueSharingMode.zig new file mode 100644 index 0000000..c033963 --- /dev/null +++ b/src/engine/QueueSharingMode.zig @@ -0,0 +1,20 @@ +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/StagingBuffer.zig b/src/engine/StagingBuffer.zig index bdf6067..fa10bc8 100644 --- a/src/engine/StagingBuffer.zig +++ b/src/engine/StagingBuffer.zig @@ -4,6 +4,7 @@ const std = @import("std"); const vk = @import("vulkan"); const Engine = @import("Engine.zig"); +const QSM = @import("QueueSharingMode.zig"); buffer: vk.Buffer, memory: vk.DeviceMemory, @@ -11,18 +12,17 @@ memory: vk.DeviceMemory, pub fn init(engine: *Engine, data: []const u8, destination_queue_family: u32) !StagingBuffer { const transfer_queue_family = engine.transfer_queue.allocation.family; - const single_queue_family = transfer_queue_family == destination_queue_family; - const queue_family_indices: []const u32 = if (single_queue_family) &.{} else &.{ transfer_queue_family, destination_queue_family }; - + const qsm = QSM.resolve(destination_queue_family, transfer_queue_family); const buffer = try engine.device.createBuffer(&.{ .size = data.len, .usage = .{ .transfer_src_bit = true, }, - .sharing_mode = if (single_queue_family) .exclusive else .concurrent, - .p_queue_family_indices = queue_family_indices.ptr, - }, null); - errdefer engine.device.destroyBuffer(buffer); + .sharing_mode = qsm.sharing_mode, + .queue_family_index_count = qsm.queue_family_index_count, + .p_queue_family_indices = qsm.p_queue_family_indices, + }, &engine.vk_allocator.interface); + errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface); const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); const memory = try engine.allocate( @@ -48,7 +48,7 @@ pub fn init(engine: *Engine, data: []const u8, destination_queue_family: u32) !S pub fn deinit(self: *StagingBuffer, engine: *Engine) void { engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); - engine.device.destroyBuffer(self.buffer); + engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface); self.* = undefined; } diff --git a/src/engine/StorageBuffer.zig b/src/engine/StorageBuffer.zig index 954db78..538cf88 100644 --- a/src/engine/StorageBuffer.zig +++ b/src/engine/StorageBuffer.zig @@ -5,6 +5,7 @@ const vk = @import("vulkan"); const Engine = @import("Engine.zig"); const StagingBuffer = @import("StagingBuffer.zig"); +const QSM = @import("QueueSharingMode.zig"); buffer: vk.Buffer, memory: vk.DeviceMemory, @@ -19,17 +20,21 @@ pub fn init(engine: *Engine, comptime PrefixType: type, comptime ElementType: ty const element_size = @sizeOf(ElementType); const array_capacity_in_bytes = std.math.mul(usize, array_capacity, element_size) catch return error.OutOfMemory; - const size = std.math.add(array_offset, array_capacity_in_bytes) catch return error.OutOfMemory; + const size = std.math.add(usize, array_offset, array_capacity_in_bytes) catch return error.OutOfMemory; + const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family); const buffer = try engine.device.createBuffer(&.{ .size = size, .usage = .{ + .transfer_src_bit = true, .transfer_dst_bit = true, .storage_buffer_bit = true, }, - .sharing_mode = .exclusive, - }, null); - errdefer engine.device.destroyBuffer(buffer); + .sharing_mode = qsm.sharing_mode, + .queue_family_index_count = qsm.queue_family_index_count, + .p_queue_family_indices = qsm.p_queue_family_indices, + }, &engine.vk_allocator.interface); + errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface); const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true }); @@ -49,26 +54,147 @@ pub fn init(engine: *Engine, comptime PrefixType: type, comptime ElementType: ty pub fn deinit(self: *StorageBuffer, engine: *Engine) void { engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); - engine.device.destroyBuffer(self.buffer); + engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface); self.* = undefined; } -pub fn write(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, elements: []const ElementType) !void { - std.debug.assert(elements.len <= self.array_capacity); +pub fn enlarge(self: *StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, array_capacity: usize) !void { + std.debug.assert(array_capacity > self.array_capacity); std.debug.assert(@sizeOf(PrefixType) == self.prefix_size); std.debug.assert(@sizeOf(ElementType) == self.element_size); std.debug.assert(std.mem.isAligned(self.array_offset, @alignOf(ElementType))); - const array_size_in_bytes = elements.len * elements.len; + const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface); + defer engine.device.destroyFence(fence, &engine.vk_allocator.interface); + + const prefix_size = @sizeOf(PrefixType); + const array_offset = std.mem.alignForward(usize, prefix_size, @alignOf(ElementType)); + const element_size = @sizeOf(ElementType); + + const old_array_size_in_bytes = self.array_capacity * @sizeOf(ElementType); + const old_size = self.array_offset + old_array_size_in_bytes; + + const new_array_capacity_in_bytes = std.math.mul(usize, array_capacity, element_size) catch return error.OutOfMemory; + const new_size = std.math.add(usize, array_offset, new_array_capacity_in_bytes) catch return error.OutOfMemory; + + const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family); + const buffer = try engine.device.createBuffer(&.{ + .size = new_size, + .usage = .{ + .transfer_src_bit = true, + .transfer_dst_bit = true, + .storage_buffer_bit = true, + }, + .sharing_mode = qsm.sharing_mode, + .queue_family_index_count = qsm.queue_family_index_count, + .p_queue_family_indices = qsm.p_queue_family_indices, + }, &engine.vk_allocator.interface); + errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface); + + const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); + const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true }); + errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface); + + try engine.device.bindBufferMemory(buffer, memory, 0); + + const command_buffer = try engine.allocateTransferCommandBuffer(); + defer engine.freeTransferCommandBuffer(command_buffer); + + try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + + const regions = [_]vk.BufferCopy{ + .{ + .src_offset = 0, + .dst_offset = 0, + .size = old_size, + }, + }; + + command_buffer.copyBuffer( + self.buffer, + buffer, + regions.len, + ®ions, + ); + + try command_buffer.endCommandBuffer(); + try engine.submitTransferCommandBuffer(command_buffer, fence); + + _ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); + + engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); + engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface); + + self.buffer = buffer; + self.memory = memory; + self.array_capacity = array_capacity; +} + +pub fn write(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, elements: []const ElementType) !void { + try self.writeOffset(PrefixType, ElementType, engine, prefix, 0, elements); +} + +pub fn writeOffset(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, offset: usize, elements: []const ElementType) !void { + std.debug.assert(offset + elements.len <= self.array_capacity); + std.debug.assert(@sizeOf(PrefixType) == self.prefix_size); + std.debug.assert(@sizeOf(ElementType) == self.element_size); + std.debug.assert(std.mem.isAligned(self.array_offset, @alignOf(ElementType))); + + const array_size_in_bytes = elements.len * @sizeOf(ElementType); const size = self.array_offset + array_size_in_bytes; + var regions_buffer: [2]vk.BufferCopy = undefined; + var regions: std.ArrayList(vk.BufferCopy) = .initBuffer(®ions_buffer); + + if (self.prefix_size > 0) { + regions.appendAssumeCapacity(.{ + .src_offset = 0, + .dst_offset = 0, + .size = self.prefix_size, + }); + } + + if (array_size_in_bytes > 0) { + regions.appendAssumeCapacity(.{ + .src_offset = self.array_offset, + .dst_offset = self.array_offset + offset * @sizeOf(ElementType), + .size = array_size_in_bytes, + }); + } + + if (regions.items.len == 0) { + std.log.warn("Zero-length StorageBuffer({s}, {s}) write", .{ @typeName(PrefixType), @typeName(ElementType) }); + return; + } + + const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface); + defer engine.device.destroyFence(fence, &engine.vk_allocator.interface); + const data = try engine.vk_allocator.allocator.alloc(u8, size); defer engine.vk_allocator.allocator.free(data); @memcpy(data[0..@sizeOf(PrefixType)], std.mem.asBytes(&prefix)); @memcpy(data[self.array_offset..], std.mem.sliceAsBytes(elements)); - const staging_buffer: StagingBuffer = .init(engine, data, engine.graphics_queue.allocation.family); + var staging_buffer: StagingBuffer = try .init(engine, data, engine.graphics_queue.allocation.family); defer staging_buffer.deinit(engine); + + const command_buffer = try engine.allocateTransferCommandBuffer(); + defer engine.freeTransferCommandBuffer(command_buffer); + + try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + + command_buffer.copyBuffer( + staging_buffer.buffer, + self.buffer, + @intCast(regions.items.len), + regions.items.ptr, + ); + + try command_buffer.endCommandBuffer(); + + try engine.submitTransferCommandBuffer(command_buffer, fence); + + _ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); } diff --git a/src/engine/Swapchain.zig b/src/engine/Swapchain.zig index de21028..7aafff8 100644 --- a/src/engine/Swapchain.zig +++ b/src/engine/Swapchain.zig @@ -4,6 +4,7 @@ const std = @import("std"); const vk = @import("vulkan"); const Engine = @import("Engine.zig"); +const QSM = @import("QueueSharingMode.zig"); engine: *Engine, params: Params, @@ -92,12 +93,7 @@ pub fn recreate(self: *Swapchain) !void { const surface_capabilities = try self.engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.engine.physical_device, mode.surface); - const graphics_queue_family = self.engine.graphics_queue.allocation.family; - const presentation_queue_family = mode.presentation_queue.allocation.family; - - const single_queue_family = graphics_queue_family == presentation_queue_family; - const queue_family_indices: []const u32 = if (single_queue_family) &.{} else &.{ graphics_queue_family, presentation_queue_family }; - + const qsm = QSM.resolve(self.engine.graphics_queue.allocation.family, mode.presentation_queue.allocation.family); const new_swapchain = try self.engine.device.createSwapchainKHR(&.{ .surface = mode.surface, .min_image_count = self.params.image_count, @@ -106,9 +102,9 @@ pub fn recreate(self: *Swapchain) !void { .image_extent = extent, .image_array_layers = 1, .image_usage = .{ .color_attachment_bit = true }, - .image_sharing_mode = if (single_queue_family) .exclusive else .concurrent, - .queue_family_index_count = @intCast(queue_family_indices.len), - .p_queue_family_indices = queue_family_indices.ptr, + .image_sharing_mode = qsm.sharing_mode, + .queue_family_index_count = qsm.queue_family_index_count, + .p_queue_family_indices = qsm.p_queue_family_indices, .pre_transform = surface_capabilities.current_transform, .composite_alpha = .{ .opaque_bit_khr = true }, .present_mode = .fifo_khr, diff --git a/src/engine/Texture.zig b/src/engine/Texture.zig index 265cda8..8ad0f73 100644 --- a/src/engine/Texture.zig +++ b/src/engine/Texture.zig @@ -4,6 +4,8 @@ const std = @import("std"); const vk = @import("vulkan"); const Engine = @import("Engine.zig"); +const StagingBuffer = @import("StagingBuffer.zig"); +const QSM = @import("QueueSharingMode.zig"); pub const Usage = enum { base_color, @@ -13,19 +15,19 @@ pub const Usage = enum { pub fn format(self: Usage) vk.Format { return switch (self) { - .base_color => .r8g8b8_srgb, - .normal => .r8g8b8_snorm, - .occlusion_roughness_metallic => .r8g8b8_unorm, - .emissive => .r16g16b16_sfloat, + .base_color => .r8g8b8a8_srgb, + .normal => .r8g8b8a8_snorm, + .occlusion_roughness_metallic => .r8g8b8a8_unorm, + .emissive => .r16g16b16a16_sfloat, }; } - pub fn sampleCount(self: Usage) usize { + pub fn sampleCount(self: Usage) u32 { return switch (self) { - .base_color => 3, - .normal => 3, - .occlusion_roughness_metallic => 3, - .emissive => 3, + .base_color => 4, + .normal => 4, + .occlusion_roughness_metallic => 4, + .emissive => 4, }; } @@ -38,7 +40,7 @@ pub const Usage = enum { }; } - pub fn sampleSize(self: Usage) usize { + pub fn sampleSize(self: Usage) u32 { return switch (self) { inline else => |x| @sizeOf(SampleType(x)), }; @@ -48,7 +50,7 @@ pub const Usage = enum { return [self.sampleCount()]SampleType(self); } - pub fn texelSize(self: Usage) usize { + pub fn texelSize(self: Usage) u32 { return switch (self) { inline else => |x| @sizeOf(TexelType(x)), }; @@ -66,6 +68,7 @@ usage: Usage, pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture { const format: vk.Format = usage.format(); + const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family); const image = try engine.device.createImage(&.{ .image_type = .@"2d", .format = format, @@ -79,10 +82,12 @@ pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture { .samples = .{ .@"1_bit" = true }, .tiling = .optimal, .usage = .{ - .transfer_src_bit = true, + .transfer_dst_bit = true, .sampled_bit = true, }, - .sharing_mode = .exclusive, + .sharing_mode = qsm.sharing_mode, + .queue_family_index_count = qsm.queue_family_index_count, + .p_queue_family_indices = qsm.p_queue_family_indices, .initial_layout = .undefined, }, &engine.vk_allocator.interface); errdefer engine.device.destroyImage(image, &engine.vk_allocator.interface); @@ -107,7 +112,7 @@ pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture { .aspect_mask = .{ .color_bit = true }, .base_mip_level = 0, .level_count = 1, - .base_array_level = 0, + .base_array_layer = 0, .layer_count = 1, }, }, &engine.vk_allocator.interface); @@ -119,6 +124,7 @@ pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture { .memory = memory, .width = width, .height = height, + .usage = usage, }; } @@ -130,10 +136,123 @@ pub fn deinit(self: *Texture, engine: *Engine) void { self.* = undefined; } -pub fn write(self: Texture, comptime T: type, data: []const T) void { - const bytes_per_texel = self.format.texelSize(); +pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T) !void { + const bytes_per_texel = self.usage.texelSize(); const bytes_per_row = self.width * bytes_per_texel; - const byte_length = self.height * bytes_per_row; + const byte_length = @as(usize, self.height) * bytes_per_row; std.debug.assert(data.len * @sizeOf(T) == byte_length); + + const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface); + defer engine.device.destroyFence(fence, &engine.vk_allocator.interface); + + var staging_buffer: StagingBuffer = try .init(engine, std.mem.sliceAsBytes(data), engine.graphics_queue.allocation.family); + defer staging_buffer.deinit(engine); + + const transfer_command_buffer = try engine.allocateTransferCommandBuffer(); + defer engine.freeTransferCommandBuffer(transfer_command_buffer); + + const graphics_command_buffer = try engine.allocateGraphicsCommandBuffer(); + defer engine.freeGraphicsCommandBuffer(graphics_command_buffer); + + try transfer_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + + const pre_copy_barriers = [_]vk.ImageMemoryBarrier{ + .{ + .src_access_mask = .{}, + .dst_access_mask = .{ .transfer_write_bit = true }, + .old_layout = .undefined, + .new_layout = .transfer_dst_optimal, + .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .image = self.image, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, + }, + }; + + transfer_command_buffer.pipelineBarrier( + .{ .top_of_pipe_bit = true }, + .{ .transfer_bit = true }, + .{}, + 0, + null, + 0, + null, + pre_copy_barriers.len, + &pre_copy_barriers, + ); + + const regions = [_]vk.BufferImageCopy{ + .{ + .buffer_offset = 0, + .buffer_row_length = self.width, + .buffer_image_height = self.height, + .image_subresource = .{ + .aspect_mask = .{ .color_bit = true }, + .mip_level = 0, + .base_array_layer = 0, + .layer_count = 1, + }, + .image_offset = .{ .x = 0, .y = 0, .z = 0 }, + .image_extent = .{ .width = self.width, .height = self.height, .depth = 1 }, + }, + }; + + transfer_command_buffer.copyBufferToImage( + staging_buffer.buffer, + self.image, + .transfer_dst_optimal, + regions.len, + ®ions, + ); + + try transfer_command_buffer.endCommandBuffer(); + try engine.submitTransferCommandBuffer(transfer_command_buffer, fence); + + _ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); + try engine.device.resetFences(1, @ptrCast(&fence)); + + try graphics_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + + const post_copy_barriers = [_]vk.ImageMemoryBarrier{ + .{ + .src_access_mask = .{ .transfer_write_bit = true }, + .dst_access_mask = .{ .shader_read_bit = true }, + .old_layout = .transfer_dst_optimal, + .new_layout = .shader_read_only_optimal, + .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .image = self.image, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, + }, + }; + + graphics_command_buffer.pipelineBarrier( + .{ .transfer_bit = true }, + .{ .fragment_shader_bit = true }, + .{}, + 0, + null, + 0, + null, + post_copy_barriers.len, + &post_copy_barriers, + ); + + try graphics_command_buffer.endCommandBuffer(); + try engine.submitGraphicsCommandBuffer(graphics_command_buffer, fence); + + _ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); } diff --git a/src/engine/VertexBuffer.zig b/src/engine/VertexBuffer.zig index 8a83ff3..80d2862 100644 --- a/src/engine/VertexBuffer.zig +++ b/src/engine/VertexBuffer.zig @@ -5,6 +5,7 @@ const vk = @import("vulkan"); const Engine = @import("Engine.zig"); const StagingBuffer = @import("StagingBuffer.zig"); +const QSM = @import("QueueSharingMode.zig"); buffer: vk.Buffer, memory: vk.DeviceMemory, @@ -15,14 +16,17 @@ pub fn init(engine: *Engine, comptime VertexType: type, vertex_count: usize) !Ve const vertex_size = @sizeOf(VertexType); const size = std.math.mul(usize, vertex_count, vertex_size) catch return error.OutOfMemory; + const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family); const buffer = try engine.device.createBuffer(&.{ .size = size, .usage = .{ .transfer_dst_bit = true, .vertex_buffer_bit = true, }, - .sharing_mode = .exclusive, - }, null); + .sharing_mode = qsm.sharing_mode, + .queue_family_index_count = qsm.queue_family_index_count, + .p_queue_family_indices = qsm.p_queue_family_indices, + }, &engine.vk_allocator.interface); errdefer engine.device.destroyBuffer(buffer); const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); @@ -50,6 +54,36 @@ pub fn write(self: VertexBuffer, comptime VertexType: type, engine: *Engine, ver std.debug.assert(vertices.len == self.vertex_count); std.debug.assert(@sizeOf(VertexType) == self.vertex_size); + const fence = try engine.device.createFence(.{}, &engine.vk_allocator.interface); + defer engine.device.destroyFence(fence, &engine.vk_allocator.interface); + + const size = std.mem.sliceAsBytes(vertices).len; + const staging_buffer: StagingBuffer = .init(engine, std.mem.sliceAsBytes(vertices), engine.graphics_queue.allocation.family); defer staging_buffer.deinit(engine); + + const command_buffer = try engine.allocateTransferCommandBuffer(); + defer engine.freeTransferCommandBuffer(command_buffer); + + try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + + const regions = [_]vk.BufferCopy{ + .{ + .src_offset = 0, + .dst_offset = 0, + .size = size, + }, + }; + + command_buffer.copyBuffer( + staging_buffer.buffer, + self.buffer, + regions.len, + ®ions, + ); + + try command_buffer.endCommandBuffer(); + try engine.submitTransferCommandBuffer(command_buffer, fence); + + _ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); } diff --git a/src/skybox.zig b/src/skybox.zig deleted file mode 100644 index 10476cd..0000000 --- a/src/skybox.zig +++ /dev/null @@ -1,111 +0,0 @@ -const shaders = @import("shaders"); -const sokol = @import("sokol"); - -const sg = sokol.gfx; - -const ap = @import("asset_pipeline.zig"); -const game = @import("game.zig"); -const main = @import("main.zig"); -const math = @import("math.zig"); -const samplers = @import("samplers.zig"); - -const cubemap_size = 512; - -var skybox_equirectangular_texture: sg.Image = undefined; -var skybox_equirectangular_texture_view: sg.View = undefined; - -var skybox_cubemap_texture: sg.Image = undefined; -var skybox_cubemap_texture_view: sg.View = undefined; - -pub fn init() void { - var image = ap.visitSkybox(main.allocator) catch unreachable; - defer image.deinit(main.allocator); - - skybox_equirectangular_texture = sg.makeImage(.{ - .type = ._2D, - .usage = .{ .immutable = true }, - .width = @intCast(image.width), - .height = @intCast(image.height), - .num_slices = @intCast(image.depth), - .pixel_format = .RGBA16F, - .data = blk: { - var ret: sg.ImageData = .{}; - ret.mip_levels[0] = sg.asRange(image.data); - break :blk ret; - }, - .label = "Skybox Equirectangular Texture", - }); - skybox_equirectangular_texture_view = sg.makeView(.{ - .texture = .{ .image = skybox_equirectangular_texture }, - .label = "Skybox Equirectangular TV", - }); - - skybox_cubemap_texture = sg.makeImage(.{ - .type = .CUBE, - .usage = .{ - .immutable = true, - .storage_image = true, - }, - .width = cubemap_size, - .height = cubemap_size, - .pixel_format = .RGBA16F, - .label = "Skybox Cubemap Texture", - }); - skybox_cubemap_texture_view = sg.makeView(.{ - .texture = .{ .image = skybox_cubemap_texture }, - .label = "Skybox Cubemap TV", - }); - - { - sg.beginPass(.{ .compute = true }); - defer sg.endPass(); - - const pipeline = sg.makePipeline(.{ - .shader = sg.makeShader(shaders.equirectangularToCubemapShaderDesc(sg.queryBackend())), - .compute = true, - .label = "EquirectangularToCubemap Compute Pipeline", - }); - defer sg.destroyPipeline(pipeline); - - sg.applyPipeline(pipeline); - - const sampler = samplers.getOrCreateSampler(.{ - .wrap_u = .REPEAT, - .wrap_v = .REPEAT, - .wrap_w = .REPEAT, - .min_filter = .LINEAR, - .mag_filter = .LINEAR, - .mipmap_filter = .LINEAR, - }) catch unreachable; - - var layer_index: i32 = 0; - while (layer_index < 6) : (layer_index += 1) { - const skybox_cubemap_image_storage_view = sg.makeView(.{ - .storage_image = .{ - .image = skybox_cubemap_texture, - .slice = layer_index, - }, - }); - defer sg.destroyView(skybox_cubemap_image_storage_view); - - var bindings: sg.Bindings = .{}; - - bindings.views[shaders.VIEW__EquirectangularTexture] = skybox_equirectangular_texture_view; - bindings.views[shaders.VIEW__CubemapImage] = skybox_cubemap_image_storage_view; - - bindings.samplers[shaders.SMP__EquirectangularSampler] = sampler; - sg.applyBindings(bindings); - - sg.applyUniforms(0, sg.asRange(&shaders.LayerIndex{ - ._LayerIndex = layer_index, - })); - - sg.dispatch(@divExact(cubemap_size, 8), @divExact(cubemap_size, 8), 1); - } - } -} - -pub fn deinit() void { - sg.destroyView(skybox_equirectangular_texture_view); - sg.destroyImage(skybox_equirectangular_texture); -} diff --git a/src/tiles.zig b/src/tiles.zig deleted file mode 100644 index 6722f85..0000000 --- a/src/tiles.zig +++ /dev/null @@ -1,195 +0,0 @@ -const shaders = @import("shaders"); -const sokol = @import("sokol"); - -const sg = sokol.gfx; - -const ap = @import("asset_pipeline.zig"); -const game = @import("game.zig"); -const main = @import("main.zig"); -const math = @import("math.zig"); -const samplers = @import("samplers.zig"); - -const Matrix4x4 = math.Matrix4x4; -const Quaternion = math.Quaternion; -const Vector3 = math.Vector3; - -var vertex_buffer: sg.Buffer = undefined; -var index_buffer: sg.Buffer = undefined; - -var pipeline: sg.Pipeline = undefined; - -var base_color_texture: sg.Image = undefined; -var base_color_texture_view: sg.View = undefined; - -var occlusion_roughness_metallic_texture: sg.Image = undefined; -var occlusion_roughness_metallic_texture_view: sg.View = undefined; - -var normal_texture: sg.Image = undefined; -var normal_texture_view: sg.View = undefined; - -var bindings: sg.Bindings = undefined; - -pub fn init() void { - var textures = ap.visitTextures(main.allocator); - defer ap.deinitTextures(main.allocator, &textures); - - const base_color_image = textures.getPtr("BaseColor").?; - const normal_image = textures.getPtr("Normal").?; - const occlusion_roughness_metallic_image = textures.getPtr("OcclusionRoughnessMetallic").?; - - vertex_buffer = sg.makeBuffer(.{ - .data = sg.asRange(&[_]f32{ - // positionOS texCoord normalOS tangentOS - -0.5, -0.5, 0, 0, 1, 0, 0, 1, 1, 0, 0, -1, - 0.5, -0.5, 0, 1, 1, 0, 0, 1, 1, 0, 0, -1, - -0.5, 0.5, 0, 0, 0, 0, 0, 1, 1, 0, 0, -1, - 0.5, 0.5, 0, 1, 0, 0, 0, 1, 1, 0, 0, -1, - }), - .usage = .{ - .immutable = true, - .vertex_buffer = true, - }, - .label = "Tile Quad VB", - }); - - index_buffer = sg.makeBuffer(.{ - .data = sg.asRange(&[_]u16{ 0, 1, 2, 2, 1, 3 }), - .usage = .{ - .immutable = true, - .index_buffer = true, - }, - .label = "Tile Quad IB", - }); - - pipeline = sg.makePipeline(.{ - .shader = sg.makeShader(shaders.mainShaderDesc(sg.queryBackend())), - .layout = blk: { - var ret: sg.VertexLayoutState = .{}; - ret.attrs[shaders.ATTR_main_positionOS].format = .FLOAT3; - ret.attrs[shaders.ATTR_main_texCoord].format = .FLOAT2; - ret.attrs[shaders.ATTR_main_normalOS].format = .FLOAT3; - ret.attrs[shaders.ATTR_main_tangentOS].format = .FLOAT4; - break :blk ret; - }, - .depth = .{ - .compare = .GREATER, - }, - .primitive_type = .TRIANGLES, - .index_type = .UINT16, - .cull_mode = .BACK, - .face_winding = .CCW, - .label = "Tile Pipeline", - }); - - const sampler = samplers.getOrCreateSampler(.{ - .wrap_u = .REPEAT, - .wrap_v = .REPEAT, - .wrap_w = .REPEAT, - .min_filter = .LINEAR, - .mag_filter = .LINEAR, - .mipmap_filter = .LINEAR, - }) catch unreachable; - - base_color_texture = sg.makeImage(.{ - .type = .ARRAY, - .usage = .{ .immutable = true }, - .width = @intCast(base_color_image.width), - .height = @intCast(base_color_image.height), - .num_slices = @intCast(base_color_image.depth), - .pixel_format = .RGBA8, - .data = blk: { - var ret: sg.ImageData = .{}; - ret.mip_levels[0] = sg.asRange(base_color_image.data); - break :blk ret; - }, - .label = "Tile BaseColor Texture", - }); - base_color_texture_view = sg.makeView(.{ - .texture = .{ .image = base_color_texture }, - .label = "Tile BaseColor TV", - }); - - occlusion_roughness_metallic_texture = sg.makeImage(.{ - .type = .ARRAY, - .usage = .{ .immutable = true }, - .width = @intCast(occlusion_roughness_metallic_image.width), - .height = @intCast(occlusion_roughness_metallic_image.height), - .num_slices = @intCast(occlusion_roughness_metallic_image.depth), - .pixel_format = .RGBA8, - .data = blk: { - var ret: sg.ImageData = .{}; - ret.mip_levels[0] = sg.asRange(occlusion_roughness_metallic_image.data); - break :blk ret; - }, - .label = "Tile ORM Texture", - }); - occlusion_roughness_metallic_texture_view = sg.makeView(.{ - .texture = .{ .image = occlusion_roughness_metallic_texture }, - .label = "Tile ORM TV", - }); - - normal_texture = sg.makeImage(.{ - .type = .ARRAY, - .usage = .{ .immutable = true }, - .width = @intCast(normal_image.width), - .height = @intCast(normal_image.height), - .num_slices = @intCast(normal_image.depth), - .pixel_format = .RGBA8, - .data = blk: { - var ret: sg.ImageData = .{}; - ret.mip_levels[0] = sg.asRange(normal_image.data); - break :blk ret; - }, - .label = "Tile Normal Texture", - }); - normal_texture_view = sg.makeView(.{ - .texture = .{ .image = normal_texture }, - .label = "Tile Normal TV", - }); - - bindings = .{}; - - bindings.vertex_buffers[0] = vertex_buffer; - bindings.index_buffer = index_buffer; - - bindings.views[shaders.VIEW_Point_Lights] = game.point_light_buffer_view; - bindings.views[shaders.VIEW_Directional_Lights] = game.directional_light_buffer_view; - bindings.views[shaders.VIEW__BaseColorTexture] = base_color_texture_view; - bindings.views[shaders.VIEW__OcclusionRoughnessMetallicTexture] = occlusion_roughness_metallic_texture_view; - bindings.views[shaders.VIEW__NormalTexture] = normal_texture_view; - - bindings.samplers[shaders.SMP__Sampler] = sampler; -} - -pub fn deinit() void { - sg.destroyView(normal_texture_view); - sg.destroyImage(normal_texture); - sg.destroyView(occlusion_roughness_metallic_texture_view); - sg.destroyImage(occlusion_roughness_metallic_texture); - sg.destroyView(base_color_texture_view); - sg.destroyImage(base_color_texture); - sg.destroyPipeline(pipeline); - sg.destroyBuffer(index_buffer); - sg.destroyBuffer(vertex_buffer); -} - -pub fn bind() void { - sg.applyPipeline(pipeline); - sg.applyBindings(bindings); -} - -pub fn draw(translation: Vector3, rotation: Quaternion, texture_index: u32) void { - const matrix_os_to_ws = Matrix4x4.initTranslationRotationScale(translation, rotation, .one); - const matrix_os_to_ws_normal = Matrix4x4.inverseTransposeAffine(matrix_os_to_ws); - - sg.applyUniforms(shaders.UB_Material_Uniforms, sg.asRange(&shaders.MaterialUniforms{ - ._TextureIndex = @intCast(texture_index), - })); - - sg.applyUniforms(shaders.UB_Object_Uniforms, sg.asRange(&shaders.ObjectUniforms{ - ._MatrixOStoWS = matrix_os_to_ws.asArray(), - ._MatrixOStoWSNormal = matrix_os_to_ws_normal.asArray(), - })); - - sg.draw(0, 6, 1); -}