add initSpritesheet() and deinitSpritesheet()

This commit is contained in:
Rokas Puzonas 2026-07-11 17:40:21 +03:00
parent c09ff2aad0
commit ed560d3da1
3 changed files with 109 additions and 33 deletions

View File

@ -54,8 +54,6 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
.rect = Rect.init(1, 0, 1, 1).multiply(tile_size) .rect = Rect.init(1, 0, 1, 1).multiply(tile_size)
}); });
try Gfx.packSprites();
self.* = App{ self.* = App{
.bg = .black, .bg = .black,
.player_pos = .init(100, 100), .player_pos = .init(100, 100),

View File

@ -28,6 +28,7 @@ const State = struct {
default_sprite: Sprite.Id, default_sprite: Sprite.Id,
error_sprite: Sprite.Id, error_sprite: Sprite.Id,
default_spritesheet: Spritesheet.Id,
default_sampler: sg.Sampler, default_sampler: sg.Sampler,
quads_buffer: [2048]Quad, quads_buffer: [2048]Quad,
@ -38,9 +39,9 @@ const State = struct {
textures_buffer: [Texture.max_textures]Texture.SlotMap.Slot, textures_buffer: [Texture.max_textures]Texture.SlotMap.Slot,
textures: Texture.SlotMap, textures: Texture.SlotMap,
spritesheet_texture: Texture.Id, spritesheets_buffer: [Spritesheet.max_spritesheets]Spritesheet.SlotMap.Slot,
spritesheet_size: Vec2, spritesheets: Spritesheet.SlotMap,
spritesheet_stale: bool,
sprites_buffer: [Sprite.max_sprites]Sprite.SlotMap.Slot, sprites_buffer: [Sprite.max_sprites]Sprite.SlotMap.Slot,
sprites: Sprite.SlotMap, sprites: Sprite.SlotMap,
}; };
@ -137,11 +138,11 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
.textures = .init(.initBuffer(&self.textures_buffer)), .textures = .init(.initBuffer(&self.textures_buffer)),
.default_sprite = undefined, .default_sprite = undefined,
.error_sprite = undefined, .error_sprite = undefined,
.spritesheet_stale = false,
.sprites_buffer = undefined, .sprites_buffer = undefined,
.sprites = .init(.initBuffer(&self.sprites_buffer)), .sprites = .init(.initBuffer(&self.sprites_buffer)),
.spritesheet_texture = undefined, .default_spritesheet = undefined,
.spritesheet_size = .init(0, 0) .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 { pub fn deinit() void {
@ -257,7 +258,8 @@ pub fn beginFrame() void {
.swapchain = sglue.swapchain() .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.views[shd.VIEW_tex] = spritesheet_texture.view;
self.bindings.samplers[shd.SMP_smp] = self.default_sampler; self.bindings.samplers[shd.SMP_smp] = self.default_sampler;
} }
@ -432,9 +434,12 @@ pub fn draw(opts: DrawOptions) void {
var texture = opts.texture; var texture = opts.texture;
if (!self.textures.exists(texture)) { if (!self.textures.exists(texture)) {
if (getSpriteUVRect(self.error_sprite)) |error_uv| { if (getSpriteUVRect(self.error_sprite)) |error_uv| {
texture = self.spritesheet_texture;
quad.setUVRect(error_uv); quad.setUVRect(error_uv);
quad.setColor(.white); quad.setColor(.white);
const sprite = self.sprites.getAssumeExists(self.error_sprite);
const spritesheet = self.spritesheets.getAssumeExists(sprite.spritesheet);
texture = spritesheet.texture;
} else { } else {
log.err("Failed to draw error texture", .{}); log.err("Failed to draw error texture", .{});
return; return;
@ -462,6 +467,7 @@ pub fn initSprite() !Sprite.Id {
const sprite = self.sprites.getAssumeExists(id); const sprite = self.sprites.getAssumeExists(id);
sprite.* = .{ sprite.* = .{
.spritesheet = self.default_spritesheet,
.data = null, .data = null,
.position = null .position = null
}; };
@ -477,7 +483,11 @@ pub fn deinitSprite(id: Sprite.Id) void {
data.deinit(self.gpa); data.deinit(self.gpa);
} }
self.sprites.removeAssumeExists(id); 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.data = sprite_data;
sprite.position = null; 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 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. // 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. // 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; continue;
} }
const spritesheet = try ImageData.initRGBA8(self.gpa, texture_size, texture_size); const image_data = try ImageData.initRGBA8(self.gpa, texture_size, texture_size);
defer spritesheet.deinit(self.gpa); defer image_data.deinit(self.gpa);
{ {
var i: usize = 0; var i: usize = 0;
@ -578,7 +598,7 @@ pub fn packSprites() !void {
const x: u32 = @intCast(rects[i].x); const x: u32 = @intCast(rects[i].x);
const y: u32 = @intCast(rects[i].y); const y: u32 = @intCast(rects[i].y);
sprite.position = .initFromInt(u32, x, y); sprite.position = .initFromInt(u32, x, y);
spritesheet.copyFrom( image_data.copyFrom(
sprite_data, sprite_data,
x, x,
y, 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); spritesheet.size = .initFromInt(u32, texture_size, texture_size);
self.spritesheet_stale = false; spritesheet.stale = false;
return; return;
} }
@ -613,18 +633,19 @@ pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
return null; return null;
} }
if (self.spritesheet_stale) { const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return null;
packSprites() catch |e| {
packSprites(sprite.spritesheet) catch |e| {
log.warn("Failed to pack sprites: {}", .{e}); log.warn("Failed to pack sprites: {}", .{e});
return null;
}; };
}
assert(sprite.position != null); assert(sprite.position != null);
const sprite_size = Vec2.initFromInt(u32, sprite.data.?.width, sprite.data.?.height); const sprite_size = Vec2.initFromInt(u32, sprite.data.?.width, sprite.data.?.height);
return Rect{ return Rect{
.pos = sprite.position.?.divide(self.spritesheet_size), .pos = sprite.position.?.divide(spritesheet.size),
.size = sprite_size.divide(self.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.setColor(color);
quad.setRect(.{ .pos = pos, .size = size }); quad.setRect(.{ .pos = pos, .size = size });
var texture: ?Texture.Id = null;
if (getSpriteUVRect(id)) |sprite_uv| { if (getSpriteUVRect(id)) |sprite_uv| {
quad.setUVRect(sprite_uv); quad.setUVRect(sprite_uv);
} else if (getSpriteUVRect(self.error_sprite)) |error_uv| { 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.setUVRect(error_uv);
quad.setColor(.white); 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(.{ draw(.{
.texture = self.spritesheet_texture, .texture = texture.?,
.quad = quad .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 { pub const ImageData = struct {
const Pixels = union(enum) { const Pixels = union(enum) {
none, none,
@ -755,6 +815,7 @@ pub const Texture = struct {
}; };
pub const Sprite = struct { pub const Sprite = struct {
spritesheet: Spritesheet.Id,
data: ?ImageData, data: ?ImageData,
position: ?Vec2, position: ?Vec2,
@ -763,6 +824,16 @@ pub const Sprite = struct {
pub const Id = SlotMap.Id; 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 { pub const Vertex = extern struct {
position: Vec2, position: Vec2,
texcoord: Vec2, texcoord: Vec2,

View File

@ -51,7 +51,7 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type {
index: Index, index: Index,
pub fn format(self: Id, writer: *std.Io.Writer) std.Io.Writer.Error!void { 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; 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 { pub fn getAssumeExists(self: *Self, id: Id) *Value {
assert(self.exists(id)); assert(self.exists(id));
return &self.slots.items[id.index].value; return self.get(id).?;
} }
pub fn removeAssumeExists(self: *Self, id: Id) void { pub fn removeAssumeExists(self: *Self, id: Id) void {