Use castle's stbi instead of zstbi

This commit is contained in:
2026-02-06 23:27:21 +01:00
parent 33a0b241ef
commit 39712e359d
10 changed files with 73 additions and 75 deletions

View File

@@ -7,15 +7,15 @@ 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 vulkan_dep = b.dependency("vulkan_zig", .{ .registry = b.path("vendor/vk.xml") });
const zglfw_dep = b.dependency("zglfw", .{ .import_vulkan = true });
const zstbi_dep = b.dependency("zstbi", .{});
const media_mod = media_dep.module("media");
const vecmath_mod = vecmath_dep.module("vecmath");
const vulkan_mod = vulkan_dep.module("vulkan-zig");
const zglfw_mod = zglfw_dep.module("root");
const zstbi_mod = zstbi_dep.module("root");
zglfw_mod.addImport("vulkan", vulkan_mod);
@@ -27,10 +27,10 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
});
exe_mod.addImport("media", media_mod);
exe_mod.addImport("vecmath", vecmath_mod);
exe_mod.addImport("vulkan", vulkan_mod);
exe_mod.addImport("zglfw", zglfw_mod);
exe_mod.addImport("zstbi", zstbi_mod);
exe_mod.linkLibrary(zglfw_lib);
const options = b.addOptions();

View File

@@ -5,6 +5,9 @@
.minimum_zig_version = "0.15.2",
.dependencies = .{
.media = .{
.path = "castle/packages/media",
},
.vecmath = .{
.path = "castle/packages/vecmath",
},
@@ -16,10 +19,6 @@
.url = "git+https://github.com/zig-gamedev/zglfw.git#6034a5623312c58bf5e64c1a2b686691c28575f4",
.hash = "zglfw-0.10.0-dev-zgVDNPKyIQCBi-wv_vxkvIQq1u0bP4D56Wszx_2mszc7",
},
.zstbi = .{
.url = "git+https://github.com/zig-gamedev/zstbi#2c4b3100ccb7aed90ecc9439030899764e2a8d47",
.hash = "zstbi-0.11.0-dev-L0Ea_yaWBwAHwFoCuyjkFyaiSsbjt4UOrkntR0c_nmzz",
},
},
.paths = .{

2
castle

Submodule castle updated: 31651dc96a...85f4957661

View File

@@ -4,6 +4,7 @@ const std = @import("std");
const c = @import("const.zig");
const glfw = @import("zglfw");
const math = @import("math.zig");
const media = @import("media");
const shaders = @import("shaders.zig");
const vk = @import("vulkan");
const vm = @import("vecmath");
@@ -43,6 +44,7 @@ directional_lights: shaders.DirectionalLightBuffer,
sampler: vk.Sampler,
deferred_command_buffers: std.ArrayList(CommandBuffer),
stbi: media.stbi,
blocks: Blocks,
materials: Materials,
textures: Textures,
@@ -59,6 +61,9 @@ 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);
errdefer stbi.deinit();
var materials = try Materials.init(engine, allocator);
errdefer materials.deinit(engine, allocator);
@@ -72,7 +77,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, allocator);
blocks.loadAll(engine, &materials, &textures, &stbi, allocator);
const sampler = try engine.createSampler(.{
.mag_filter = .linear,
@@ -433,10 +438,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, "Grass.json", allocator);
const block_dirt = try blocks.getOrLoad(engine, &materials, &textures, "Dirt.json", allocator);
const block_stone = try blocks.getOrLoad(engine, &materials, &textures, "Stone.json", allocator);
const block_bedrock = try blocks.getOrLoad(engine, &materials, &textures, "Bedrock.json", allocator);
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);
// VOLATILE Load all assets before this point
@@ -627,7 +632,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain
.elements = directional_lights_data,
});
var skybox = try Skybox.load("skybox.hdr", engine, 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);
errdefer skybox.deinit(engine);
return .{
@@ -652,6 +657,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain
.sampler = sampler,
.deferred_command_buffers = try .initCapacity(allocator, 4),
.stbi = stbi,
.blocks = blocks,
.materials = materials,
.textures = textures,
@@ -696,6 +702,7 @@ pub fn deinit(self: *Game) void {
self.textures.deinit(self.engine, self.allocator);
self.materials.deinit(self.engine, self.allocator);
self.blocks.deinit(self.allocator);
self.stbi.deinit();
self.* = undefined;
}

View File

@@ -264,7 +264,7 @@ 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, 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) catch |err| {
std.log.err("Error while placing voxel at {f}: {}", .{ target_vx, err });
break :blk;
};

View File

@@ -3,6 +3,7 @@
const Blocks = @This();
const std = @import("std");
const media = @import("media");
const voxels = @import("../voxels.zig");
const Atom = @import("../engine/Atom.zig").Atom;
@@ -134,6 +135,7 @@ pub fn getOrLoad(
engine: *Engine,
materials: *Materials,
textures: *Textures,
stbi: *media.stbi,
filename: []const u8,
temp_allocator: std.mem.Allocator,
) !Id {
@@ -152,7 +154,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, filename, temp_allocator);
const def = try loadBlock(engine, materials, textures, stbi, filename, temp_allocator);
self.map.putAssumeCapacityNoClobber(key, id);
self.array.appendAssumeCapacity(def);
@@ -166,6 +168,7 @@ pub fn getOrLoadAtom(
engine: *Engine,
materials: *Materials,
textures: *Textures,
stbi: *media.stbi,
filename: Atom,
temp_allocator: std.mem.Allocator,
) !Id {
@@ -182,7 +185,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, filename.toString(), temp_allocator);
const def = try loadBlock(engine, materials, textures, stbi, filename.toString(), temp_allocator);
self.map.putAssumeCapacityNoClobber(key, id);
self.array.appendAssumeCapacity(def);
@@ -196,6 +199,7 @@ pub fn loadAll(
engine: *Engine,
materials: *Materials,
textures: *Textures,
stbi: *media.stbi,
temp_allocator: std.mem.Allocator,
) void {
const cwd = std.fs.cwd();
@@ -216,7 +220,7 @@ pub fn loadAll(
continue;
}
_ = self.getOrLoad(engine, materials, textures, entry.name, temp_allocator) catch |err| {
_ = self.getOrLoad(engine, materials, textures, stbi, entry.name, temp_allocator) catch |err| {
std.log.err("Error while loading block definition entry {s}: {s}", .{ entry.name, @errorName(err) });
};
}
@@ -226,6 +230,7 @@ fn loadBlock(
engine: *Engine,
materials: *Materials,
textures: *Textures,
stbi: *media.stbi,
filename: []const u8,
temp_allocator: std.mem.Allocator,
) !Definition {
@@ -274,7 +279,7 @@ fn loadBlock(
return error.ParseError;
}
const material = try materials.getOrLoad(engine, textures, name, temp_allocator);
const material = try materials.getOrLoad(engine, textures, stbi, name, temp_allocator);
break :blk .initUniform(material);
}
@@ -284,7 +289,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, @field(walls, field.name).material, temp_allocator),
.material = try materials.getOrLoad(engine, textures, stbi, @field(walls, field.name).material, temp_allocator),
.transform = @field(walls, field.name).transform,
};
}

View File

@@ -3,6 +3,7 @@
const Materials = @This();
const std = @import("std");
const media = @import("media");
const shaders = @import("../shaders.zig");
const vk = @import("vulkan");
@@ -148,7 +149,7 @@ 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, 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) !Id {
if (maybe_filename) |filename| {
const key: Key = .{
// If the material already exists, then the atom must exist and the
@@ -165,7 +166,7 @@ pub fn getOrLoad(self: *Materials, engine: *Engine, textures: *Textures, maybe_f
const id = Id.fromIndexSafe(self.material_count) catch |err| switch (err) {
error.Overflow => return error.OutOfMaterials,
};
try self.loadMaterial(engine, textures, filename, id.toInt(), temp_allocator);
try self.loadMaterial(engine, textures, stbi, filename, id.toInt(), temp_allocator);
self.map.putAssumeCapacityNoClobber(key, id);
self.material_count += 1;
@@ -188,7 +189,7 @@ pub fn getOrLoad(self: *Materials, engine: *Engine, textures: *Textures, maybe_f
/// 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, 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) !Id {
if (filename != .empty) {
const key: Key = .{
.filename = filename,
@@ -203,7 +204,7 @@ pub fn getOrLoadAtom(self: *Materials, engine: *Engine, textures: *Textures, fil
const id = Id.fromIndexSafe(self.material_count) catch |err| switch (err) {
error.Overflow => return error.OutOfMaterials,
};
try self.loadMaterial(engine, textures, filename.toString(), id.toInt(), temp_allocator);
try self.loadMaterial(engine, textures, stbi, filename.toString(), id.toInt(), temp_allocator);
self.map.putAssumeCapacityNoClobber(key, id);
self.material_count += 1;
@@ -225,7 +226,7 @@ pub fn getOrLoadAtom(self: *Materials, engine: *Engine, textures: *Textures, fil
/// deinitialized or reset after this function returns. Note that during loading
/// the engine will make its own persistent allocations, so an out of memory
/// error is not necessarily related to `temp_allocator`.
pub fn loadAll(self: *Materials, engine: *Engine, textures: *Textures, temp_allocator: std.mem.Allocator) void {
pub fn loadAll(self: *Materials, engine: *Engine, textures: *Textures, stbi: *media.stbi, temp_allocator: std.mem.Allocator) void {
const cwd = std.fs.cwd();
var dir = cwd.openDir("assets/materials", .{ .iterate = true }) catch |err| {
@@ -244,13 +245,13 @@ pub fn loadAll(self: *Materials, engine: *Engine, textures: *Textures, temp_allo
continue;
}
_ = self.getOrLoad(engine, textures, entry.name, temp_allocator) catch |err| {
_ = self.getOrLoad(engine, textures, stbi, entry.name, temp_allocator) catch |err| {
std.log.err("Error while loading material entry {s}: {s}", .{ entry.name, @errorName(err) });
};
}
}
fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, 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) !void {
const MaterialJson = struct {
baseColor: [3]f32 = .{ 1, 1, 1 },
baseColorTexture: ?[]const u8 = null,
@@ -302,10 +303,10 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, filename
.normal_scale = material_json.normalScale,
.occlusion_texture_strength = material_json.occlusionTextureStrength,
.roughness = material_json.roughness,
.base_color_texture = try textures.getOrLoad(engine, material_json.baseColorTexture, .base_color, temp_allocator),
.emissive_texture = try textures.getOrLoad(engine, material_json.emissiveTexture, .emissive, temp_allocator),
.normal_texture = try textures.getOrLoad(engine, material_json.normalTexture, .normal, temp_allocator),
.occlusion_roughness_metallic_texture = try textures.getOrLoad(engine, material_json.occlusionRoughnessMetallicTexture, .occlusion_roughness_metallic, temp_allocator),
.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),
},
},
});

View File

@@ -3,7 +3,7 @@
const Textures = @This();
const std = @import("std");
const stbi = @import("zstbi");
const media = @import("media");
const Atom = @import("../engine/Atom.zig").Atom;
const Engine = @import("../engine/Engine.zig");
@@ -172,14 +172,7 @@ 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.
///
/// When a texture is being loaded, `temp_allocator` is used for temporary
/// allocations necessary to perform all operations. No memory allocated with
/// `temp_allocator` is retained, so the allocator can be 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: *Textures, engine: *Engine, maybe_filename: ?[]const u8, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Id {
pub fn getOrLoad(self: *Textures, engine: *Engine, stbi: *media.stbi, maybe_filename: ?[]const u8, usage: Texture.Usage) !Id {
if (maybe_filename) |filename| {
const key: Key = .{
// If the texture already exists, then the atom must exist and the
@@ -197,7 +190,7 @@ pub fn getOrLoad(self: *Textures, engine: *Engine, maybe_filename: ?[]const u8,
const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) {
error.Overflow => return error.OutOfTextures,
};
const texture = try loadTexture(engine, filename, usage, temp_allocator);
const texture = try loadTexture(engine, stbi, filename, usage);
self.map.putAssumeCapacityNoClobber(key, id);
self.array.appendAssumeCapacity(texture);
@@ -214,14 +207,7 @@ pub fn getOrLoad(self: *Textures, engine: *Engine, maybe_filename: ?[]const u8,
/// 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.
///
/// When a texture is being loaded, `temp_allocator` is used for temporary
/// allocations necessary to perform all operations. No memory allocated with
/// `temp_allocator` is retained, so the allocator can be deinitialized or reset
/// after this function returns. Note that during loading the engine will make
/// its own persistent allocations, so an out of memory is not necessarily
/// related to `temp_allocator`.
pub fn getOrLoadAtom(self: *Textures, engine: *Engine, filename: Atom, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Id {
pub fn getOrLoadAtom(self: *Textures, engine: *Engine, stbi: *media.stbi, filename: Atom, usage: Texture.Usage) !Id {
if (filename != .empty) {
const key: Key = .{
.filename = filename,
@@ -237,7 +223,7 @@ pub fn getOrLoadAtom(self: *Textures, engine: *Engine, filename: Atom, usage: Te
const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) {
error.Overflow => return error.OutOfTextures,
};
const texture = try loadTexture(engine, filename.toString(), usage, temp_allocator);
const texture = try loadTexture(engine, stbi, filename.toString(), usage);
self.map.putAssumeCapacityNoClobber(key, id);
self.array.appendAssumeCapacity(texture);
@@ -249,7 +235,7 @@ pub fn getOrLoadAtom(self: *Textures, engine: *Engine, filename: Atom, usage: Te
}
}
fn loadTexture(engine: *Engine, filename: []const u8, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Texture {
fn loadTexture(engine: *Engine, stbi: *media.stbi, filename: []const u8, usage: Texture.Usage) !Texture {
std.log.debug("Loading texture \"{s}\" as {s}...", .{ filename, @tagName(usage) });
const cwd = std.fs.cwd();
@@ -257,15 +243,25 @@ fn loadTexture(engine: *Engine, filename: []const u8, usage: Texture.Usage, temp
var dir = try cwd.openDir("assets/textures", .{});
defer dir.close();
const file_buf = try dir.readFileAlloc(temp_allocator, filename, std.math.maxInt(usize));
defer temp_allocator.free(file_buf);
var file = try dir.openFile(filename, .{});
defer file.close();
// 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);
const img = try stbi.loadDynamicIo(&reader.interface);
defer stbi.freeDynamic(img);
const data = img.data[0 .. img.width * img.height];
var img = try stbi.Image.loadFromMemory(file_buf, usage.samplesPerTexel());
defer img.deinit();
std.debug.assert(img.num_components == usage.samplesPerTexel());
if (usage == .normal) {
for (img.data) |*sample| {
sample.* = sample.* +% 128;
for (data) |*pixel| {
pixel.r = pixel.r +% 128;
pixel.g = pixel.g +% 128;
pixel.b = pixel.b +% 128;
pixel.a = pixel.a +% 128;
}
}
@@ -278,7 +274,7 @@ fn loadTexture(engine: *Engine, filename: []const u8, usage: Texture.Usage, temp
});
errdefer texture.deinit(engine);
try texture.writeRaw(engine, img.data);
try texture.writeRaw(engine, @ptrCast(data));
return texture;
}

View File

@@ -1,8 +1,8 @@
const Skybox = @This();
const std = @import("std");
const media = @import("media");
const shaders = @import("../shaders.zig");
const stbi = @import("zstbi");
const vk = @import("vulkan");
const vm = @import("vecmath");
@@ -25,7 +25,7 @@ descriptor_set: vk.DescriptorSet,
pipeline_layout: vk.PipelineLayout,
pipeline: vk.Pipeline,
pub fn load(filename: []const u8, engine: *Engine, 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) !Skybox {
std.log.debug("Loading skybox \"{s}\"...", .{filename});
// --- LOAD IMAGE FROM MEMORY ----------------------------------------------
@@ -38,14 +38,8 @@ pub fn load(filename: []const u8, engine: *Engine, cube_size: u32, global_unifor
const file_buf = try dir.readFileAlloc(temp_allocator, filename, std.math.maxInt(usize));
defer temp_allocator.free(file_buf);
var img = try stbi.Image.loadFromMemory(file_buf, 4);
defer img.deinit();
std.debug.assert(img.num_components == 4);
// clamp +inf to max half float
for (std.mem.bytesAsSlice(f16, img.data)) |*sample| {
sample.* = @min(sample.*, std.math.floatMax(f16));
}
const img = try stbi.loadHdrBuf(file_buf);
defer stbi.freeHdr(img);
// --- SYNCHRONIZATION PRIMITIVES ------------------------------------------
@@ -64,13 +58,13 @@ pub fn load(filename: []const u8, engine: *Engine, cube_size: u32, global_unifor
// --- LOAD IMAGE INTO STAGING BUFFER --------------------------------------
var staging_buffer = try StagingBuffer.init(engine, .{
.capacity = @intCast(img.data.len),
.capacity = @intCast(img.width * img.height * @sizeOf(vm.ColorHdr)),
.target_queue = .compute,
});
defer staging_buffer.deinit(engine);
const staging_memory = try staging_buffer.map(engine);
@memcpy(staging_memory, img.data);
@memcpy(staging_memory, @as([*]const u8, @ptrCast(img.data)));
staging_buffer.unmap(engine);
// --- CREATE EQUIRECTANGULAR IMAGE ----------------------------------------

View File

@@ -1,7 +1,6 @@
const std = @import("std");
const glfw = @import("zglfw");
const stbi = @import("zstbi");
const vk = @import("vulkan");
const c = @import("const.zig");
@@ -34,9 +33,6 @@ pub fn main() !void {
try atoms.init(allocator);
defer atoms.deinit();
stbi.init(allocator);
defer stbi.deinit();
glfw.init() catch |err| {
std.log.err("Could not initialize GLFW", .{});
return err;