const Textures = @This(); const std = @import("std"); const stbi = @import("zstbi"); const Atoms = @import("Atoms.zig"); const Engine = @import("../engine/Engine.zig"); const Texture = @import("../engine/Texture.zig"); allocator: std.mem.Allocator, map: Map, array: Array, empty_base_color: Id, empty_emissive: Id, empty_normal: Id, empty_occlusuion_roughness_metallic: Id, pub const Id = extern struct { id: u32, }; pub const Key = struct { atom: Atoms.Atom, usage: Texture.Usage, }; pub const Map = std.AutoHashMapUnmanaged(Key, Id); pub const Array = std.ArrayList(Texture); pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures { var map: Map = .empty; errdefer map.deinit(allocator); var array: Array = try .initCapacity(allocator, 4); errdefer { for (array.items) |*texture| { texture.deinit(engine); } array.deinit(allocator); } const empty_base_color: Id = .{ .id = @intCast(array.items.len) }; const empty_base_color_texture: Texture = try .init(engine, 1, 1, .base_color); array.appendAssumeCapacity(empty_base_color_texture); const empty_emissive: Id = .{ .id = @intCast(array.items.len) }; const empty_emissive_texture: Texture = try .init(engine, 1, 1, .emissive); array.appendAssumeCapacity(empty_emissive_texture); const empty_normal: Id = .{ .id = @intCast(array.items.len) }; const empty_normal_texture: Texture = try .init(engine, 1, 1, .normal); array.appendAssumeCapacity(empty_normal_texture); const empty_occlusuion_roughness_metallic: Id = .{ .id = @intCast(array.items.len) }; const empty_occlusuion_roughness_metallic_texture: Texture = try .init(engine, 1, 1, .occlusion_roughness_metallic); array.appendAssumeCapacity(empty_occlusuion_roughness_metallic_texture); 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]i8, engine, &.{.{ 0, 0, 127, 127 }}); try empty_occlusuion_roughness_metallic_texture.write([4]u8, engine, &.{.{ 255, 255, 255, 255 }}); return .{ .allocator = allocator, .map = map, .array = array, .empty_base_color = empty_base_color, .empty_emissive = empty_emissive, .empty_normal = empty_normal, .empty_occlusuion_roughness_metallic = empty_occlusuion_roughness_metallic, }; } pub fn deinit(self: *Textures, engine: *Engine) void { for (self.array.items) |*texture| { texture.deinit(engine); } self.array.deinit(self.allocator); self.map.deinit(self.allocator); } pub fn getTexture(self: *const Textures, id: Id) ?*Texture { const index: usize = id.id; return if (index < self.array.items.len) &self.array.items[index] else null; } pub fn getId(self: *const Textures, key: Key) ?Id { return self.map.get(key); } 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); try self.array.ensureUnusedCapacity(self.allocator, 1); const texture = try self.loadTexture(engine, atoms, key); const id = self.nextId(); entry.value_ptr.* = id; self.array.appendAssumeCapacity(texture); return id; } } fn loadTexture(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Texture { const filename = atoms.getString(key.atom).?; std.log.debug("Loading texture \"{s}\" as {s}...", .{ filename, @tagName(key.usage) }); const cwd = std.fs.cwd(); var dir = try cwd.openDir("assets/textures", .{}); defer dir.close(); const file = try dir.openFile(filename, .{}); defer file.close(); const file_buf = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize)); defer self.allocator.free(file_buf); var img = try stbi.Image.loadFromMemory(file_buf, key.usage.sampleCount()); defer img.deinit(); std.debug.assert(img.num_components == key.usage.sampleCount()); var texture: Texture = try .init(engine, img.width, img.height, key.usage); errdefer texture.deinit(engine); try texture.write(u8, engine, img.data); return texture; } fn nextId(self: *const Textures) Id { const index = self.array.items.len; return .{ .id = @intCast(index) }; }