From ed560d3da1e07c0abcffe8e3f7e887a608cd1676 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 11 Jul 2026 17:40:21 +0300 Subject: [PATCH] add initSpritesheet() and deinitSpritesheet() --- src/app.zig | 2 - src/graphics.zig | 129 ++++++++++++++++++++++++++++++++++++----------- src/slot_map.zig | 11 +++- 3 files changed, 109 insertions(+), 33 deletions(-) diff --git a/src/app.zig b/src/app.zig index dae3429..e663944 100644 --- a/src/app.zig +++ b/src/app.zig @@ -54,8 +54,6 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 { .rect = Rect.init(1, 0, 1, 1).multiply(tile_size) }); - try Gfx.packSprites(); - self.* = App{ .bg = .black, .player_pos = .init(100, 100), diff --git a/src/graphics.zig b/src/graphics.zig index 61c9c48..626af77 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -28,6 +28,7 @@ const State = struct { default_sprite: Sprite.Id, error_sprite: Sprite.Id, + default_spritesheet: Spritesheet.Id, default_sampler: sg.Sampler, quads_buffer: [2048]Quad, @@ -38,9 +39,9 @@ const State = struct { textures_buffer: [Texture.max_textures]Texture.SlotMap.Slot, textures: Texture.SlotMap, - spritesheet_texture: Texture.Id, - spritesheet_size: Vec2, - spritesheet_stale: bool, + spritesheets_buffer: [Spritesheet.max_spritesheets]Spritesheet.SlotMap.Slot, + spritesheets: Spritesheet.SlotMap, + sprites_buffer: [Sprite.max_sprites]Sprite.SlotMap.Slot, sprites: Sprite.SlotMap, }; @@ -137,11 +138,11 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void { .textures = .init(.initBuffer(&self.textures_buffer)), .default_sprite = undefined, .error_sprite = undefined, - .spritesheet_stale = false, .sprites_buffer = undefined, .sprites = .init(.initBuffer(&self.sprites_buffer)), - .spritesheet_texture = undefined, - .spritesheet_size = .init(0, 0) + .default_spritesheet = undefined, + .spritesheets_buffer = undefined, + .spritesheets = .init(.initBuffer(&self.spritesheets_buffer)) }; { @@ -195,7 +196,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void { }); } - self.spritesheet_texture = try initTexture(); + self.default_spritesheet = try initSpritesheet(); } pub fn deinit() void { @@ -257,7 +258,8 @@ pub fn beginFrame() void { .swapchain = sglue.swapchain() }); - const spritesheet_texture = self.textures.getAssumeExists(self.spritesheet_texture); + const default_spritesheet = self.spritesheets.getAssumeExists(self.default_spritesheet); + const spritesheet_texture = self.textures.getAssumeExists(default_spritesheet.texture); self.bindings.views[shd.VIEW_tex] = spritesheet_texture.view; self.bindings.samplers[shd.SMP_smp] = self.default_sampler; } @@ -432,9 +434,12 @@ pub fn draw(opts: DrawOptions) void { var texture = opts.texture; if (!self.textures.exists(texture)) { if (getSpriteUVRect(self.error_sprite)) |error_uv| { - texture = self.spritesheet_texture; quad.setUVRect(error_uv); quad.setColor(.white); + + const sprite = self.sprites.getAssumeExists(self.error_sprite); + const spritesheet = self.spritesheets.getAssumeExists(sprite.spritesheet); + texture = spritesheet.texture; } else { log.err("Failed to draw error texture", .{}); return; @@ -462,6 +467,7 @@ pub fn initSprite() !Sprite.Id { const sprite = self.sprites.getAssumeExists(id); sprite.* = .{ + .spritesheet = self.default_spritesheet, .data = null, .position = null }; @@ -477,7 +483,11 @@ pub fn deinitSprite(id: Sprite.Id) void { data.deinit(self.gpa); } self.sprites.removeAssumeExists(id); - self.spritesheet_stale = true; + + + if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| { + spritesheet.stale = true; + } } } @@ -522,11 +532,21 @@ pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void { sprite.data = sprite_data; sprite.position = null; - self.spritesheet_stale = true; + if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| { + spritesheet.stale = true; + } } -pub fn packSprites() !void { +pub fn packSprites(id: Spritesheet.Id) !void { const self = &g_state; + const spritesheet = self.spritesheets.get(id) orelse { + log.warn("Attempt to pack spritesheet that doesn't exist: {}", .{id}); + return; + }; + + if (!spritesheet.stale) { + return; + } // TODO: Add a smarter startegy for picking the initial texture size. // One idea is to calculate the sum area of all sprites and pick a atlas size based on that. @@ -564,8 +584,8 @@ pub fn packSprites() !void { continue; } - const spritesheet = try ImageData.initRGBA8(self.gpa, texture_size, texture_size); - defer spritesheet.deinit(self.gpa); + const image_data = try ImageData.initRGBA8(self.gpa, texture_size, texture_size); + defer image_data.deinit(self.gpa); { var i: usize = 0; @@ -578,7 +598,7 @@ pub fn packSprites() !void { const x: u32 = @intCast(rects[i].x); const y: u32 = @intCast(rects[i].y); sprite.position = .initFromInt(u32, x, y); - spritesheet.copyFrom( + image_data.copyFrom( sprite_data, x, y, @@ -590,10 +610,10 @@ pub fn packSprites() !void { } } - setTexture(self.spritesheet_texture, spritesheet); + setTexture(spritesheet.texture, image_data); - self.spritesheet_size = .initFromInt(u32, texture_size, texture_size); - self.spritesheet_stale = false; + spritesheet.size = .initFromInt(u32, texture_size, texture_size); + spritesheet.stale = false; return; } @@ -613,18 +633,19 @@ pub fn getSpriteUVRect(id: Sprite.Id) ?Rect { return null; } - if (self.spritesheet_stale) { - packSprites() catch |e| { - log.warn("Failed to pack sprites: {}", .{e}); - }; - } + const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return null; + + packSprites(sprite.spritesheet) catch |e| { + log.warn("Failed to pack sprites: {}", .{e}); + return null; + }; assert(sprite.position != null); const sprite_size = Vec2.initFromInt(u32, sprite.data.?.width, sprite.data.?.height); return Rect{ - .pos = sprite.position.?.divide(self.spritesheet_size), - .size = sprite_size.divide(self.spritesheet_size), + .pos = sprite.position.?.divide(spritesheet.size), + .size = sprite_size.divide(spritesheet.size), }; } @@ -635,19 +656,58 @@ pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void { quad.setColor(color); quad.setRect(.{ .pos = pos, .size = size }); + var texture: ?Texture.Id = null; if (getSpriteUVRect(id)) |sprite_uv| { quad.setUVRect(sprite_uv); - } else if (getSpriteUVRect(self.error_sprite)) |error_uv| { - quad.setUVRect(error_uv); - quad.setColor(.white); + if (self.sprites.get(id)) |sprite| { + if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| { + texture = spritesheet.texture; + } + } + + } + + if (texture == null) { + if (getSpriteUVRect(self.error_sprite)) |error_uv| { + quad.setUVRect(error_uv); + quad.setColor(.white); + + const sprite = self.sprites.getAssumeExists(self.error_sprite); + const spritesheet = self.spritesheets.getAssumeExists(sprite.spritesheet); + texture = spritesheet.texture; + } + } + + if (texture == null) { + log.warn("Failed to draw sprite", .{}); + return; } draw(.{ - .texture = self.spritesheet_texture, + .texture = texture.?, .quad = quad }); } +pub fn initSpritesheet() !Spritesheet.Id { + // TODO: self.spritesheet_texture = try initTexture(); + const self = &g_state; + const id = try self.spritesheets.insertBounded(); + + const spritesheet = self.spritesheets.getAssumeExists(id); + spritesheet.* = .{ + .texture = try initTexture(), + .size = .init(0, 0), + .stale = false, + }; + + return id; +} + +pub fn deinitSpritesheet(id: Spritesheet.Id) void { + _ = id; // autofix +} + pub const ImageData = struct { const Pixels = union(enum) { none, @@ -755,6 +815,7 @@ pub const Texture = struct { }; pub const Sprite = struct { + spritesheet: Spritesheet.Id, data: ?ImageData, position: ?Vec2, @@ -763,6 +824,16 @@ pub const Sprite = struct { pub const Id = SlotMap.Id; }; +pub const Spritesheet = struct { + texture: Texture.Id, + size: Vec2, + stale: bool, + + const max_spritesheets = 64; + const SlotMap = SlotMapType(std.math.IntFittingRange(0, max_spritesheets-1), u8, Spritesheet); + pub const Id = SlotMap.Id; +}; + pub const Vertex = extern struct { position: Vec2, texcoord: Vec2, diff --git a/src/slot_map.zig b/src/slot_map.zig index 5bbb9c2..400ff6f 100644 --- a/src/slot_map.zig +++ b/src/slot_map.zig @@ -51,7 +51,7 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type { index: Index, pub fn format(self: Id, writer: *std.Io.Writer) std.Io.Writer.Error!void { - try writer.print("Id{{ {}, {} }}", .{ self.index, self.generation }); + try writer.print("Id({s}){{ {}, {} }}", .{ @typeName(Value), self.index, self.generation }); } }; @@ -173,9 +173,16 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type { return slot.used and slot.generation == id.generation; } + pub fn get(self: *Self, id: Id) ?*Value { + if (self.exists(id)) { + return &self.slots.items[id.index].value; + } + return null; + } + pub fn getAssumeExists(self: *Self, id: Id) *Value { assert(self.exists(id)); - return &self.slots.items[id.index].value; + return self.get(id).?; } pub fn removeAssumeExists(self: *Self, id: Id) void {