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 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; } pub 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, }; } 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); }