From 79c62141df61bd4df9082a1467eaf88e3e8c34f3 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Wed, 13 May 2026 00:43:49 +0200 Subject: [PATCH] Update to zig 0.16.0, update deps, Vulkan validation fixes --- .gitignore | 1 + build.zig | 8 ++++-- build.zig.zon | 8 +++--- castle | 2 +- src/Game.zig | 27 ++++++++++++++------ src/Player.zig | 10 +++++++- src/assets/Blocks.zig | 36 +++++++++++++++------------ src/assets/Materials.zig | 48 ++++++++++++++++++++++++------------ src/assets/Textures.zig | 44 ++++++++++++++++++++++++--------- src/engine/Atom.zig | 38 ++++++++++++++-------------- src/engine/CommandBuffer.zig | 28 +++++++++------------ src/engine/Engine.zig | 29 ++++++++++------------ src/engine/Skybox.zig | 23 +++++++++++------ src/engine/Swapchain.zig | 3 +++ src/engine/VkAllocator.zig | 18 ++++++++------ src/main.zig | 18 ++++++-------- 16 files changed, 204 insertions(+), 137 deletions(-) diff --git a/.gitignore b/.gitignore index 8c34726..bbf3cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .zig-cache zig-out +zig-pkg *.spv diff --git a/build.zig b/build.zig index d0bfa5b..817a4db 100644 --- a/build.zig +++ b/build.zig @@ -7,8 +7,12 @@ pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const llvm = b.option(bool, "llvm", "Use LLVM and LLD") orelse false; - const media_dep = b.dependency("media", .{}); - const vecmath_dep = b.dependency("vecmath", .{}); + const media_dep = b.dependency("media", .{ + .target = target, + }); + const vecmath_dep = b.dependency("vecmath", .{ + .target = target, + }); const vulkan_dep = b.dependency("vulkan_zig", .{ .registry = b.path("vendor/vk.xml") }); const zglfw_dep = b.dependency("zglfw", .{ .import_vulkan = true }); diff --git a/build.zig.zon b/build.zig.zon index c5a3db1..89c7662 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -12,12 +12,12 @@ .path = "castle/packages/vecmath", }, .vulkan_zig = .{ - .url = "git+https://github.com/Snektron/vulkan-zig.git#1446b0b994c2362264cc24513d7c7ec31b469c50", - .hash = "vulkan-0.0.0-r7Ytx_VDAwAiMl0YSu2UOkVMIGJN7CeIQaxJR-hUSfD6", + .url = "git+https://github.com/Snektron/vulkan-zig.git#f75b0011214705d6593e6ad64948c4832b1e6f27", + .hash = "vulkan-0.0.0-r7Ytx759AwBNdwE-gHKt3vzRQaU7RFerBg3FmD7iTEv4", }, .zglfw = .{ - .url = "git+https://github.com/zig-gamedev/zglfw.git#6034a5623312c58bf5e64c1a2b686691c28575f4", - .hash = "zglfw-0.10.0-dev-zgVDNPKyIQCBi-wv_vxkvIQq1u0bP4D56Wszx_2mszc7", + .url = "git+https://github.com/zig-gamedev/zglfw.git#51003c105d23db378bb59ce415a387b22f1b0892", + .hash = "zglfw-0.10.0-dev-zgVDNIy4IQDJNRy4jrP1As-SZxfJpuWhU1iJ-wBab_VD", }, }, diff --git a/castle b/castle index 85f4957..dc839e0 160000 --- a/castle +++ b/castle @@ -1 +1 @@ -Subproject commit 85f49576611c2cbb63f9785ac39abb75403aa203 +Subproject commit dc839e098c54483ddc957a1a7c46adba4036b8b1 diff --git a/src/Game.zig b/src/Game.zig index 8321760..0ce709e 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -24,6 +24,7 @@ const Swapchain = @import("engine/Swapchain.zig"); const Textures = @import("assets/Textures.zig"); allocator: std.mem.Allocator, +io: std.Io, engine: *Engine, swapchain: *Swapchain, global_descriptor_set_layout: vk.DescriptorSetLayout, @@ -60,8 +61,8 @@ const chunk_descriptor_pool = 1024; const camera_near_plane = 0.1; -pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain) !Game { - var stbi = media.stbi.init(allocator); +pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain: *Swapchain) !Game { + var stbi = media.stbi.init(allocator, io); errdefer stbi.deinit(); var materials = try Materials.init(engine, allocator); @@ -77,7 +78,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain // will crash the game with segfault reading address 0x140 (presumably // within librenderdoc.so). - blocks.loadAll(engine, &materials, &textures, &stbi, allocator); + blocks.loadAll(engine, &materials, &textures, &stbi, allocator, io); const sampler = try engine.createSampler(.{ .mag_filter = .linear, @@ -438,10 +439,10 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }); engine.setObjectName(global_descriptor_set, "DS Global", .{}); - const block_grass = try blocks.getOrLoad(engine, &materials, &textures, &stbi, "Grass.json", allocator); - const block_dirt = try blocks.getOrLoad(engine, &materials, &textures, &stbi, "Dirt.json", allocator); - const block_stone = try blocks.getOrLoad(engine, &materials, &textures, &stbi, "Stone.json", allocator); - const block_bedrock = try blocks.getOrLoad(engine, &materials, &textures, &stbi, "Bedrock.json", allocator); + 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); // VOLATILE Load all assets before this point @@ -632,11 +633,21 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain .elements = directional_lights_data, }); - var skybox = try Skybox.load("skybox.hdr", engine, &stbi, 512, global_uniforms.buffer, swapchain.render_pass, allocator); + var skybox = try Skybox.load( + "skybox.hdr", + engine, + &stbi, + 512, + global_uniforms.buffer, + swapchain.render_pass, + allocator, + io, + ); errdefer skybox.deinit(engine); return .{ .allocator = allocator, + .io = io, .engine = engine, .swapchain = swapchain, .global_descriptor_set_layout = global_descriptor_set_layout, diff --git a/src/Player.zig b/src/Player.zig index bf8f2fe..e170b35 100644 --- a/src/Player.zig +++ b/src/Player.zig @@ -264,7 +264,15 @@ 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) catch |err| { + 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; }; diff --git a/src/assets/Blocks.zig b/src/assets/Blocks.zig index e2bb4ae..9895394 100644 --- a/src/assets/Blocks.zig +++ b/src/assets/Blocks.zig @@ -138,11 +138,12 @@ pub fn getOrLoad( 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), + .filename = try .fromString(filename, io), }; // We don't use `getOrPutAssumeCapacity` method, because we might already be @@ -154,7 +155,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); + const def = try loadBlock(engine, materials, textures, stbi, filename, temp_allocator, io); self.map.putAssumeCapacityNoClobber(key, id); self.array.appendAssumeCapacity(def); @@ -171,6 +172,7 @@ pub fn getOrLoadAtom( stbi: *media.stbi, filename: Atom, temp_allocator: std.mem.Allocator, + io: std.Io, ) !Id { const key: Key = .{ .filename = filename, @@ -185,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); + const def = try loadBlock(engine, materials, textures, stbi, filename.toString(), temp_allocator, io); self.map.putAssumeCapacityNoClobber(key, id); self.array.appendAssumeCapacity(def); @@ -201,17 +203,18 @@ pub fn loadAll( textures: *Textures, stbi: *media.stbi, temp_allocator: std.mem.Allocator, + io: std.Io, ) void { - const cwd = std.fs.cwd(); + const cwd = std.Io.Dir.cwd(); - var dir = cwd.openDir("assets/blocks", .{ .iterate = true }) catch |err| { + var dir = cwd.openDir(io, "assets/blocks", .{ .iterate = true }) catch |err| { std.log.err("Error while opening block definitions directory: {s}", .{@errorName(err)}); return; }; - defer dir.close(); + defer dir.close(io); var it = dir.iterate(); - while (it.next() catch |err| { + while (it.next(io) catch |err| { std.log.err("Error while iterating over block definitions directory: {s}", .{@errorName(err)}); return; }) |entry| { @@ -220,7 +223,7 @@ pub fn loadAll( continue; } - _ = self.getOrLoad(engine, materials, textures, stbi, entry.name, temp_allocator) catch |err| { + _ = self.getOrLoad(engine, materials, textures, stbi, entry.name, temp_allocator, io) catch |err| { std.log.err("Error while loading block definition entry {s}: {s}", .{ entry.name, @errorName(err) }); }; } @@ -233,6 +236,7 @@ fn loadBlock( stbi: *media.stbi, filename: []const u8, temp_allocator: std.mem.Allocator, + io: std.Io, ) !Definition { const DefinitionJson = struct { pub const Wall = struct { @@ -248,18 +252,18 @@ fn loadBlock( std.log.debug("Loading block \"{s}\"...", .{filename}); - const cwd = std.fs.cwd(); + const cwd = std.Io.Dir.cwd(); - var dir = try cwd.openDir("assets/blocks", .{}); - defer dir.close(); + var dir = try cwd.openDir(io, "assets/blocks", .{}); + defer dir.close(io); // NOTE Buffer size approximated based on expected JSON structure. var buffer: [1024]u8 = undefined; - const file = try dir.openFile(filename, .{}); - defer file.close(); + const file = try dir.openFile(io, filename, .{}); + defer file.close(io); - var file_reader = file.reader(&buffer); + var file_reader = file.reader(io, &buffer); var json_reader = std.json.Reader.init(temp_allocator, &file_reader.interface); defer json_reader.deinit(); @@ -279,7 +283,7 @@ fn loadBlock( return error.ParseError; } - const material = try materials.getOrLoad(engine, textures, stbi, name, temp_allocator); + const material = try materials.getOrLoad(engine, textures, stbi, name, temp_allocator, io); break :blk .initUniform(material); } @@ -289,7 +293,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), + .material = try materials.getOrLoad(engine, textures, stbi, @field(walls, field.name).material, temp_allocator, io), .transform = @field(walls, field.name).transform, }; } diff --git a/src/assets/Materials.zig b/src/assets/Materials.zig index c440d70..b6bc6db 100644 --- a/src/assets/Materials.zig +++ b/src/assets/Materials.zig @@ -149,12 +149,20 @@ pub fn getAtom(self: *const Materials, filename: Atom) ?Id { /// 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 getOrLoad(self: *Materials, engine: *Engine, textures: *Textures, stbi: *media.stbi, maybe_filename: ?[]const u8, temp_allocator: std.mem.Allocator) !Id { +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), + .filename = try .fromString(filename, io), }; // We don't use `getOrPutAssumeCapacity` method, because we might already be @@ -166,7 +174,7 @@ pub fn getOrLoad(self: *Materials, engine: *Engine, textures: *Textures, stbi: * 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); + try self.loadMaterial(engine, textures, stbi, filename, id.toInt(), temp_allocator, io); self.map.putAssumeCapacityNoClobber(key, id); self.material_count += 1; @@ -189,7 +197,15 @@ pub fn getOrLoad(self: *Materials, engine: *Engine, textures: *Textures, stbi: * /// 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 getOrLoadAtom(self: *Materials, engine: *Engine, textures: *Textures, stbi: *media.stbi, filename: Atom, temp_allocator: std.mem.Allocator) !Id { +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 = .{ .filename = filename, @@ -204,7 +220,7 @@ pub fn getOrLoadAtom(self: *Materials, engine: *Engine, textures: *Textures, stb 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); + try self.loadMaterial(engine, textures, stbi, filename.toString(), id.toInt(), temp_allocator, io); self.map.putAssumeCapacityNoClobber(key, id); self.material_count += 1; @@ -251,7 +267,7 @@ pub fn loadAll(self: *Materials, engine: *Engine, textures: *Textures, stbi: *me } } -fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, stbi: *media.stbi, filename: []const u8, index: u32, temp_allocator: std.mem.Allocator) !void { +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 { const MaterialJson = struct { baseColor: [3]f32 = .{ 1, 1, 1 }, baseColorTexture: ?[]const u8 = null, @@ -268,18 +284,18 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, stbi: *m std.log.debug("Loading material \"{s}\"...", .{filename}); - const cwd = std.fs.cwd(); + const cwd = std.Io.Dir.cwd(); - var dir = try cwd.openDir("assets/materials", .{}); - defer dir.close(); + var dir = try cwd.openDir(io, "assets/materials", .{}); + defer dir.close(io); // NOTE Buffer size approximated based on expected JSON structure. var buffer: [512]u8 = undefined; - const file = try dir.openFile(filename, .{}); - defer file.close(); + const file = try dir.openFile(io, filename, .{}); + defer file.close(io); - var file_reader = file.reader(&buffer); + var file_reader = file.reader(io, &buffer); var json_reader = std.json.Reader.init(temp_allocator, &file_reader.interface); defer json_reader.deinit(); @@ -303,10 +319,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), - .emissive_texture = try textures.getOrLoad(engine, stbi, material_json.emissiveTexture, .emissive), - .normal_texture = try textures.getOrLoad(engine, stbi, material_json.normalTexture, .normal), - .occlusion_roughness_metallic_texture = try textures.getOrLoad(engine, stbi, material_json.occlusionRoughnessMetallicTexture, .occlusion_roughness_metallic), + .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), }, }, }); diff --git a/src/assets/Textures.zig b/src/assets/Textures.zig index c1a679a..96390e6 100644 --- a/src/assets/Textures.zig +++ b/src/assets/Textures.zig @@ -172,12 +172,19 @@ pub fn getAtom(self: *const Textures, filename: Atom, usage: Texture.Usage) ?Id /// if necessary. Will not return any error if the texture already exists. When /// the filename is `null`, returns an empty texture ID appropriate for given /// usage. -pub fn getOrLoad(self: *Textures, engine: *Engine, stbi: *media.stbi, maybe_filename: ?[]const u8, usage: Texture.Usage) !Id { +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), + .filename = try .fromString(filename, io), .usage = usage, }; @@ -190,7 +197,7 @@ pub fn getOrLoad(self: *Textures, engine: *Engine, stbi: *media.stbi, maybe_file 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); + const texture = try loadTexture(engine, stbi, filename, usage, io); self.map.putAssumeCapacityNoClobber(key, id); self.array.appendAssumeCapacity(texture); @@ -207,7 +214,14 @@ pub fn getOrLoad(self: *Textures, engine: *Engine, stbi: *media.stbi, maybe_file /// if necessary. Will not return any error if the texture already exists. When /// the filename is `.empty`, returns an empty texture ID appropriate for given /// usage. -pub fn getOrLoadAtom(self: *Textures, engine: *Engine, stbi: *media.stbi, filename: Atom, usage: Texture.Usage) !Id { +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 = .{ .filename = filename, @@ -223,7 +237,7 @@ pub fn getOrLoadAtom(self: *Textures, engine: *Engine, stbi: *media.stbi, filena 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); + const texture = try loadTexture(engine, stbi, filename.toString(), usage, io); self.map.putAssumeCapacityNoClobber(key, id); self.array.appendAssumeCapacity(texture); @@ -235,21 +249,27 @@ pub fn getOrLoadAtom(self: *Textures, engine: *Engine, stbi: *media.stbi, filena } } -fn loadTexture(engine: *Engine, stbi: *media.stbi, filename: []const u8, usage: Texture.Usage) !Texture { +fn loadTexture( + engine: *Engine, + stbi: *media.stbi, + filename: []const u8, + usage: Texture.Usage, + io: std.Io, +) !Texture { std.log.debug("Loading texture \"{s}\" as {s}...", .{ filename, @tagName(usage) }); - const cwd = std.fs.cwd(); + const cwd = std.Io.Dir.cwd(); - var dir = try cwd.openDir("assets/textures", .{}); - defer dir.close(); + var dir = try cwd.openDir(io, "assets/textures", .{}); + defer dir.close(io); - var file = try dir.openFile(filename, .{}); - defer file.close(); + var file = try dir.openFile(io, filename, .{}); + defer file.close(io); // The textures are expected to be small; a standard block base color as a // PNG takes well below 1 kiB. var buf: [4096]u8 = undefined; - var reader = file.reader(&buf); + var reader = file.reader(io, &buf); const img = try stbi.loadDynamicIo(&reader.interface); defer stbi.freeDynamic(img); diff --git a/src/engine/Atom.zig b/src/engine/Atom.zig index 8785bda..316127d 100644 --- a/src/engine/Atom.zig +++ b/src/engine/Atom.zig @@ -36,9 +36,9 @@ 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) error{ OutOfMemory, OutOfAtoms }!Atom { - mutex.lock(); - defer mutex.unlock(); + pub fn fromString(string: []const u8, io: std.Io) error{ Canceled, OutOfMemory, OutOfAtoms }!Atom { + try mutex.lock(io); + defer mutex.unlock(io); std.debug.assert(initialized); @@ -66,9 +66,9 @@ 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) ?Atom { - mutex.lock(); - defer mutex.unlock(); + pub fn fromStringIfExists(string: []const u8, io: std.Io) error{Canceled}!?Atom { + try mutex.lock(io); + defer mutex.unlock(io); std.debug.assert(initialized); @@ -81,9 +81,9 @@ pub const Atom = enum(u16) { } /// Cast an atom into a string. The caller asserts that the atom is valid. - pub fn toString(self: Atom) [:0]const u8 { - try mutex.lock(); - defer mutex.unlock(); + pub fn toString(self: Atom, io: std.Io) error{Canceled}![:0]const u8 { + try mutex.lock(io); + defer mutex.unlock(io); std.debug.assert(initialized); @@ -110,11 +110,11 @@ var map: std.StringHashMapUnmanaged(Atom) = undefined; var array: std.ArrayList([:0]const u8) = undefined; /// Protects all reads and writes to `map` and `array`. -var mutex: std.Thread.Mutex = .{}; +var mutex: std.Io.Mutex = .init; -pub fn init(_allocator: std.mem.Allocator) !void { - mutex.lock(); - defer mutex.unlock(); +pub fn init(_allocator: std.mem.Allocator, io: std.Io) !void { + try mutex.lock(io); + defer mutex.unlock(io); std.debug.assert(!initialized); @@ -129,9 +129,9 @@ pub fn init(_allocator: std.mem.Allocator) !void { try array.append(allocator, ""); } -pub fn deinit() void { - mutex.lock(); - defer mutex.unlock(); +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); @@ -149,9 +149,9 @@ pub fn deinit() void { /// Dump all atoms in a readable format. Use for debugging. Does not flush the /// writer. -pub fn dump(writer: std.Io.Writer) !void { - mutex.lock(); - defer mutex.unlock(); +pub fn dump(writer: std.Io.Writer, io: std.Io) void { + mutex.lockUncancelable(io); + defer mutex.unlock(io); std.debug.assert(initialized); diff --git a/src/engine/CommandBuffer.zig b/src/engine/CommandBuffer.zig index d98fb6e..a1df051 100644 --- a/src/engine/CommandBuffer.zig +++ b/src/engine/CommandBuffer.zig @@ -120,10 +120,8 @@ pub fn bindDescriptorSets( pipeline_bind_point, layout, first_set, - @intCast(descriptor_sets.len), - descriptor_sets.ptr, - @intCast(dynamic_offsets.len), - dynamic_offsets.ptr, + descriptor_sets, + dynamic_offsets, ); } @@ -146,9 +144,8 @@ pub fn bindVertexBuffers(self: CommandBuffer, first_binding: u32, bindings: []co self.proxy.bindVertexBuffers( first_binding, - @intCast(vertex_buffer_bindings.len), - vertex_buffer_bindings.items(.buffer).ptr, - vertex_buffer_bindings.items(.offset).ptr, + vertex_buffer_bindings.items(.buffer), + vertex_buffer_bindings.items(.offset), ); } @@ -176,7 +173,7 @@ pub fn copyBuffer( dst_buffer: vk.Buffer, regions: []const vk.BufferCopy, ) void { - self.proxy.copyBuffer(src_buffer, dst_buffer, @intCast(regions.len), regions.ptr); + self.proxy.copyBuffer(src_buffer, dst_buffer, regions); } pub fn copyBufferToImage( @@ -186,7 +183,7 @@ pub fn copyBufferToImage( dst_image_layout: vk.ImageLayout, regions: []const vk.BufferImageCopy, ) void { - self.proxy.copyBufferToImage(src_buffer, dst_image, dst_image_layout, @intCast(regions.len), regions.ptr); + self.proxy.copyBufferToImage(src_buffer, dst_image, dst_image_layout, regions); } pub fn pipelineBarrier(self: CommandBuffer, barrier: PipelineBarrier) void { @@ -194,19 +191,16 @@ pub fn pipelineBarrier(self: CommandBuffer, barrier: PipelineBarrier) void { barrier.src_stage_mask, barrier.dst_stage_mask, barrier.dependency_flags, - @intCast(barrier.memory_barriers.len), - barrier.memory_barriers.ptr, - @intCast(barrier.buffer_memory_barriers.len), - barrier.buffer_memory_barriers.ptr, - @intCast(barrier.image_memory_barriers.len), - barrier.image_memory_barriers.ptr, + barrier.memory_barriers, + barrier.buffer_memory_barriers, + barrier.image_memory_barriers, ); } pub fn setScissor(self: CommandBuffer, first_scissor: u32, scissors: []const vk.Rect2D) void { - self.proxy.setScissor(first_scissor, @intCast(scissors.len), scissors.ptr); + self.proxy.setScissor(first_scissor, scissors); } pub fn setViewport(self: CommandBuffer, first_viewport: u32, viewports: []const vk.Viewport) void { - self.proxy.setViewport(first_viewport, @intCast(viewports.len), viewports.ptr); + self.proxy.setViewport(first_viewport, viewports); } diff --git a/src/engine/Engine.zig b/src/engine/Engine.zig index 7281513..9ba474a 100644 --- a/src/engine/Engine.zig +++ b/src/engine/Engine.zig @@ -131,8 +131,8 @@ const vk_version: vk.Version = vk.makeApiVersion( c.app_version.patch, ); -pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine { - const vk_allocator = try VkAllocator.init(allocator); +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(); // --- LOAD BASE DISPATCH -------------------------------------------------- @@ -381,8 +381,8 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine { const prng_ptr = try allocator.create(std.Random.Pcg); errdefer allocator.destroy(prng_ptr); - const timestamp: u128 = @bitCast(std.time.nanoTimestamp()); - prng_ptr.* = .init(@truncate(timestamp)); + const timestamp: u64 = @bitCast(@as(i64, @truncate(std.Io.Timestamp.now(io, .awake).nanoseconds))); + prng_ptr.* = .init(timestamp); const random = prng_ptr.random(); // ------------------------------------------------------------------------- @@ -562,7 +562,7 @@ pub fn queueSubmit( }, }; - try self.device.queueSubmit(queue, submits.len, &submits, submit_info.fence); + try self.device.queueSubmit(queue, &submits, submit_info.fence); } pub const PresentInfo = struct { @@ -1152,7 +1152,7 @@ pub fn createGraphicsPipeline(self: *Engine, create_info: GraphicsPipelineCreate }; var pipelines: [1]vk.Pipeline = undefined; - _ = try self.device.createGraphicsPipelines(.null_handle, graphics_pipeline_create_infos.len, &graphics_pipeline_create_infos, &self.vk_allocator.interface, &pipelines); + _ = try self.device.createGraphicsPipelines(.null_handle, &graphics_pipeline_create_infos, &self.vk_allocator.interface, &pipelines); return pipelines[0]; } @@ -1357,12 +1357,12 @@ pub fn deviceWaitIdle(self: *Engine) !void { pub fn freeCommandBuffer(self: *Engine, free_info: CommandBufferFreeInfo) void { const command_pool = self.resolveCommandPool(free_info.queue_type); const command_buffers = [_]vk.CommandBuffer{free_info.command_buffer}; - self.device.freeCommandBuffers(command_pool, command_buffers.len, &command_buffers); + self.device.freeCommandBuffers(command_pool, &command_buffers); } pub fn freeDescriptorSet(self: *Engine, descriptor_pool: vk.DescriptorPool, descriptor_set: vk.DescriptorSet) void { const descriptor_sets = [_]vk.DescriptorSet{descriptor_set}; - self.device.freeDescriptorSets(descriptor_pool, descriptor_sets.len, &descriptor_sets) catch unreachable; + self.device.freeDescriptorSets(descriptor_pool, &descriptor_sets) catch unreachable; } pub fn freeMemory(self: *Engine, device_memory: vk.DeviceMemory) void { @@ -1398,7 +1398,8 @@ pub fn mapMemory(self: *Engine, memory: vk.DeviceMemory, offset: vk.DeviceSize, } pub fn resetFence(self: *Engine, fence: vk.Fence) !void { - try self.device.resetFences(1, @ptrCast(&fence)); + const fences = [_]vk.Fence{fence}; + try self.device.resetFences(&fences); } pub fn unmapMemory(self: *Engine, memory: vk.DeviceMemory) void { @@ -1446,14 +1447,10 @@ pub fn updateDescriptorSets(self: *Engine, update_info: DescriptorSetsUpdateInfo }; } - self.device.updateDescriptorSets( - @intCast(descriptor_writes.len), - descriptor_writes.ptr, - @intCast(update_info.copies.len), - update_info.copies.ptr, - ); + self.device.updateDescriptorSets(descriptor_writes, update_info.copies); } pub fn waitForFence(self: *Engine, fence: vk.Fence) !void { - _ = try self.device.waitForFences(1, @ptrCast(&fence), .true, 1 * std.time.ns_per_s); + const fences = [_]vk.Fence{fence}; + _ = try self.device.waitForFences(&fences, .true, 1 * std.time.ns_per_s); } diff --git a/src/engine/Skybox.zig b/src/engine/Skybox.zig index 2a7742e..dc994db 100644 --- a/src/engine/Skybox.zig +++ b/src/engine/Skybox.zig @@ -25,17 +25,26 @@ descriptor_set: vk.DescriptorSet, pipeline_layout: vk.PipelineLayout, pipeline: vk.Pipeline, -pub fn load(filename: []const u8, engine: *Engine, stbi: *media.stbi, cube_size: u32, global_uniforms_buffer: vk.Buffer, render_pass: vk.RenderPass, temp_allocator: std.mem.Allocator) !Skybox { +pub fn load( + filename: []const u8, + engine: *Engine, + stbi: *media.stbi, + cube_size: u32, + global_uniforms_buffer: vk.Buffer, + render_pass: vk.RenderPass, + temp_allocator: std.mem.Allocator, + io: std.Io, +) !Skybox { std.log.debug("Loading skybox \"{s}\"...", .{filename}); // --- LOAD IMAGE FROM MEMORY ---------------------------------------------- - const cwd = std.fs.cwd(); + const cwd = std.Io.Dir.cwd(); - var dir = try cwd.openDir("assets", .{}); - defer dir.close(); + var dir = try cwd.openDir(io, "assets", .{}); + defer dir.close(io); - const file_buf = try dir.readFileAlloc(temp_allocator, filename, std.math.maxInt(usize)); + const file_buf = try dir.readFileAlloc(io, filename, temp_allocator, .unlimited); defer temp_allocator.free(file_buf); const img = try stbi.loadHdrBuf(file_buf); @@ -169,7 +178,7 @@ pub fn load(filename: []const u8, engine: *Engine, stbi: *media.stbi, cube_size: // --- TRANSITION TO SHADER_READ_ONLY_OPTIMAL ------------------------------ - var transition1_command_buffer = try CommandBuffer.init(engine, .graphics); + var transition1_command_buffer = try CommandBuffer.init(engine, .compute); defer transition1_command_buffer.deinit(engine); try transition1_command_buffer.beginCommandBuffer(); @@ -304,7 +313,7 @@ pub fn load(filename: []const u8, engine: *Engine, stbi: *media.stbi, cube_size: defer engine.destroyShaderModule(compute_shader); var compute_pipeline: vk.Pipeline = undefined; - _ = try engine.device.createComputePipelines(.null_handle, 1, &.{ + _ = try engine.device.createComputePipelines(.null_handle, &.{ .{ .stage = .{ .stage = .{ .compute_bit = true }, diff --git a/src/engine/Swapchain.zig b/src/engine/Swapchain.zig index 7d8f01f..3e6d098 100644 --- a/src/engine/Swapchain.zig +++ b/src/engine/Swapchain.zig @@ -85,6 +85,9 @@ pub fn init(engine: *Engine) !Swapchain { .color_attachment_write_bit = true, .depth_stencil_attachment_write_bit = true, }, + .dependency_flags = .{ + .by_region_bit = true, + }, }, }, }); diff --git a/src/engine/VkAllocator.zig b/src/engine/VkAllocator.zig index 28f8e1b..d8bedab 100644 --- a/src/engine/VkAllocator.zig +++ b/src/engine/VkAllocator.zig @@ -5,8 +5,9 @@ const vk = @import("vulkan"); allocator: std.mem.Allocator, allocations: std.AutoHashMapUnmanaged(*anyopaque, usize) = .empty, -mutex: std.Thread.Mutex = .{}, +mutex: std.Io.Mutex = .init, allocated_bytes: usize = 0, +io: std.Io, interface: vk.AllocationCallbacks, @@ -18,7 +19,7 @@ const actual_alignment: std.mem.Alignment = .@"16"; const log = std.log.scoped(.vk_allocator); -pub fn init(allocator: std.mem.Allocator) !*VkAllocator { +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); @@ -30,6 +31,7 @@ pub fn init(allocator: std.mem.Allocator) !*VkAllocator { .pfn_reallocation = &reallocationFunction, .pfn_free = &freeFunction, }, + .io = io, }; return self; } @@ -69,8 +71,8 @@ fn allocationFunction( const desired_alignment = std.mem.Alignment.fromByteUnits(alignment); std.debug.assert(std.mem.Alignment.compare(actual_alignment, .gte, desired_alignment)); - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lock(self.io) catch return null; + defer self.mutex.unlock(self.io); self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null; const memory = self.allocator.alignedAlloc(u8, actual_alignment, size) catch return null; @@ -94,8 +96,8 @@ fn reallocationFunction( const desired_alignment = std.mem.Alignment.fromByteUnits(alignment); std.debug.assert(std.mem.Alignment.compare(actual_alignment, .gte, desired_alignment)); - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lock(self.io) catch return null; + defer self.mutex.unlock(self.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. @@ -130,8 +132,8 @@ fn freeFunction( const self: *VkAllocator = @ptrCast(@alignCast(p_user_data.?)); if (maybe_p_memory) |p_memory| { - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(); + defer self.mutex.unlock(self.io); const size = self.allocations.fetchRemove(p_memory).?.value; self.allocated_bytes -= size; diff --git a/src/main.zig b/src/main.zig index fe47377..60be351 100644 --- a/src/main.zig +++ b/src/main.zig @@ -24,14 +24,12 @@ pub const std_options: std.Options = .{ }, }; -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - const allocator = gpa.allocator(); - - try atoms.init(allocator); - defer atoms.deinit(); + try atoms.init(allocator, io); + defer atoms.deinit(io); glfw.init() catch |err| { std.log.err("Could not initialize GLFW", .{}); @@ -45,7 +43,7 @@ pub fn main() !void { } glfw.windowHint(.client_api, .no_api); - var window = glfw.Window.create(c.default_window_width, c.default_window_height, c.window_title, null) catch |err| { + var 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; }; @@ -61,13 +59,13 @@ pub fn main() !void { _ = window.setCursorPosCallback(cursorPosCallback); _ = window.setMouseButtonCallback(mouseButtonCallback); - var engine = try Engine.init(allocator, window); + var engine = try Engine.init(allocator, io, window); defer engine.deinit(); var swapchain = try Swapchain.init(&engine); defer swapchain.deinit(&engine); - var game = try Game.init(allocator, &engine, &swapchain); + var game = try Game.init(allocator, io, &engine, &swapchain); var callback_context: CallbackContext = blk: { const cursor_last_xpos, const cursor_last_ypos = window.getCursorPos(); break :blk .{