add nil graphics objects

This commit is contained in:
Rokas Puzonas 2026-07-11 20:20:45 +03:00
parent ed560d3da1
commit bf325b74b5
3 changed files with 208 additions and 114 deletions

View File

@ -42,13 +42,13 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
const tile_size = Vec2.init(64, 64); const tile_size = Vec2.init(64, 64);
const player_sprite = try Gfx.initSprite(); const player_sprite = Gfx.initSprite();
Gfx.setSprite(player_sprite, .{ Gfx.setSprite(player_sprite, .{
.image = spritesheet, .image = spritesheet,
.rect = Rect.init(0, 4, 1, 1).multiply(tile_size) .rect = Rect.init(0, 4, 1, 1).multiply(tile_size)
}); });
const wall_sprite = try Gfx.initSprite(); const wall_sprite = Gfx.initSprite();
Gfx.setSprite(wall_sprite, .{ Gfx.setSprite(wall_sprite, .{
.image = spritesheet, .image = spritesheet,
.rect = Rect.init(1, 0, 1, 1).multiply(tile_size) .rect = Rect.init(1, 0, 1, 1).multiply(tile_size)

View File

@ -38,12 +38,15 @@ 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,
nil_texture: Texture.Id,
spritesheets_buffer: [Spritesheet.max_spritesheets]Spritesheet.SlotMap.Slot, spritesheets_buffer: [Spritesheet.max_spritesheets]Spritesheet.SlotMap.Slot,
spritesheets: Spritesheet.SlotMap, spritesheets: Spritesheet.SlotMap,
nil_spritesheet: Spritesheet.Id,
sprites_buffer: [Sprite.max_sprites]Sprite.SlotMap.Slot, sprites_buffer: [Sprite.max_sprites]Sprite.SlotMap.Slot,
sprites: Sprite.SlotMap, sprites: Sprite.SlotMap,
nil_sprite: Sprite.Id,
}; };
var g_state: State = undefined; var g_state: State = undefined;
@ -134,17 +137,52 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
.quads_buffer = undefined, .quads_buffer = undefined,
.quads = .initBuffer(&self.quads_buffer), .quads = .initBuffer(&self.quads_buffer),
.clear_color = .black, .clear_color = .black,
.textures_buffer = undefined,
.textures = .init(.initBuffer(&self.textures_buffer)),
.default_sprite = undefined, .default_sprite = undefined,
.error_sprite = undefined, .error_sprite = undefined,
.default_spritesheet = undefined,
.textures_buffer = undefined,
.textures = .init(.initBuffer(&self.textures_buffer)),
.nil_texture = undefined,
.sprites_buffer = undefined, .sprites_buffer = undefined,
.sprites = .init(.initBuffer(&self.sprites_buffer)), .sprites = .init(.initBuffer(&self.sprites_buffer)),
.default_spritesheet = undefined, .nil_sprite = undefined,
.spritesheets_buffer = undefined, .spritesheets_buffer = undefined,
.spritesheets = .init(.initBuffer(&self.spritesheets_buffer)) .spritesheets = .init(.initBuffer(&self.spritesheets_buffer)),
.nil_spritesheet = undefined,
}; };
self.nil_texture = self.textures.insertAssumeCapacity();
{
const nil_texture = self.textures.getAssumeExists(self.nil_texture);
nil_texture.* = Texture{
.image = .{ .id = sg.invalid_id },
.view = .{ .id = sg.invalid_id },
};
}
self.nil_spritesheet = self.spritesheets.insertAssumeCapacity();
{
const nil_spritesheet = self.spritesheets.getAssumeExists(self.nil_spritesheet);
nil_spritesheet.* = Spritesheet{
.texture = self.nil_texture,
.size = .init(0, 0),
.stale = false
};
}
self.nil_sprite = self.sprites.insertAssumeCapacity();
{
const nil_sprite = self.sprites.getAssumeExists(self.nil_sprite);
nil_sprite.* = Sprite{
.spritesheet = self.nil_spritesheet,
.data = .none,
.position = null
};
}
{ {
const width = 8; const width = 8;
const height = 8; const height = 8;
@ -152,7 +190,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
var pixels: [width * height * 4]u8 = undefined; var pixels: [width * height * 4]u8 = undefined;
@memset(&pixels, 0xFF); @memset(&pixels, 0xFF);
self.default_sprite = try initSprite(); self.default_sprite = initSprite();
setSprite(self.default_sprite, .{ setSprite(self.default_sprite, .{
.image = ImageData{ .image = ImageData{
.width = width, .width = width,
@ -186,7 +224,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
} }
} }
self.error_sprite = try initSprite(); self.error_sprite = initSprite();
setSprite(self.error_sprite, .{ setSprite(self.error_sprite, .{
.image = .{ .image = .{
.width = width, .width = width,
@ -196,7 +234,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
}); });
} }
self.default_spritesheet = try initSpritesheet(); self.default_spritesheet = initSpritesheet();
} }
pub fn deinit() void { pub fn deinit() void {
@ -343,15 +381,24 @@ pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
}); });
} }
pub fn initTexture() !Texture.Id { pub fn initTexture() Texture.Id {
const self = &g_state; const self = &g_state;
const id = try self.textures.insertBounded(); const id = self.textures.insertBounded() catch {
log.warn("Failed to create texture, limit reached! limit: {}", .{self.textures.slots.capacity});
return self.nil_texture;
};
const image = sg.allocImage(); const image = sg.allocImage();
assert(image.id != sg.invalid_id); if (image.id == sg.invalid_id) {
log.warn("allocImage() failed!", .{});
return self.nil_texture;
}
const view = sg.allocView(); const view = sg.allocView();
assert(view.id != sg.invalid_id); if (view.id == sg.invalid_id) {
log.warn("allocView() failed!", .{});
return self.nil_texture;
}
const texture = self.textures.getAssumeExists(id); const texture = self.textures.getAssumeExists(id);
texture.* = .{ texture.* = .{
@ -363,6 +410,10 @@ pub fn initTexture() !Texture.Id {
pub fn deinitTexture(id: Texture.Id) void { pub fn deinitTexture(id: Texture.Id) void {
const self = &g_state; const self = &g_state;
if (id == self.nil_texture) {
return;
}
if (self.textures.remove(id)) { if (self.textures.remove(id)) {
const texture = self.textures_buffer[id.index]; const texture = self.textures_buffer[id.index];
const bound_view = &self.bindings.views[shd.VIEW_tex]; const bound_view = &self.bindings.views[shd.VIEW_tex];
@ -378,11 +429,14 @@ pub fn deinitTexture(id: Texture.Id) void {
pub fn setTexture(id: Texture.Id, texture_data: ImageData) void { pub fn setTexture(id: Texture.Id, texture_data: ImageData) void {
const self = &g_state; const self = &g_state;
if (!self.textures.exists(id)) { if (id == self.nil_texture) {
log.warn("Attempt to set data for texture that doesn't exist: {}", .{id});
return; return;
} }
const texture = self.textures.getAssumeExists(id);
const texture = self.textures.get(id) orelse {
log.warn("Attempt to set texture that doesn't exist: {}", .{id});
return;
};
if (sg.queryImageState(texture.image) == .VALID) { if (sg.queryImageState(texture.image) == .VALID) {
sg.uninitImage(texture.image); sg.uninitImage(texture.image);
@ -400,13 +454,19 @@ pub fn setTexture(id: Texture.Id, texture_data: ImageData) void {
.pixel_format = .RGBA8, .pixel_format = .RGBA8,
.data = image_data, .data = image_data,
}); });
assert(sg.queryImageState(texture.image) == .VALID); if (sg.queryImageState(texture.image) != .VALID) {
log.warn("initImage() failed", .{});
return;
}
if (sg.queryViewState(texture.view) == .ALLOC) { if (sg.queryViewState(texture.view) == .ALLOC) {
sg.initView(texture.view, .{ sg.initView(texture.view, .{
.texture = .{ .image = texture.image } .texture = .{ .image = texture.image }
}); });
assert(sg.queryViewState(texture.view) == .VALID); if (sg.queryViewState(texture.view) != .VALID) {
log.warn("initView() failed", .{});
return;
}
} }
} }
@ -431,22 +491,31 @@ pub fn draw(opts: DrawOptions) void {
const self = &g_state; const self = &g_state;
var quad = opts.quad; var quad = opts.quad;
var texture = opts.texture;
if (!self.textures.exists(texture)) {
if (getSpriteUVRect(self.error_sprite)) |error_uv| {
quad.setUVRect(error_uv);
quad.setColor(.white);
const sprite = self.sprites.getAssumeExists(self.error_sprite); var view: sg.View = .{ .id = sg.invalid_id };
const spritesheet = self.spritesheets.getAssumeExists(sprite.spritesheet); if (opts.texture != self.nil_texture and self.textures.exists(opts.texture)) {
texture = spritesheet.texture; const texture = self.textures.getAssumeExists(opts.texture);
} else { view = texture.view;
log.err("Failed to draw error texture", .{}); }
if (sg.queryViewState(view) != .VALID) {
if (self.sprites.get(self.error_sprite)) |sprite| {
if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| {
if (self.textures.get(spritesheet.texture)) |texture| {
if (getSpriteUVRect(self.error_sprite)) |uv| {
quad.setUVRect(uv);
quad.setColor(.white);
view = texture.view;
}
}
}
}
}
if (sg.queryViewState(view) != .VALID) {
log.warn("Failed to draw error image", .{});
return; return;
} }
}
const view = self.textures.getAssumeExists(texture).view;
const bound_view = &self.bindings.views[shd.VIEW_tex]; const bound_view = &self.bindings.views[shd.VIEW_tex];
if (bound_view.id != view.id) { if (bound_view.id != view.id) {
@ -461,9 +530,12 @@ pub fn draw(opts: DrawOptions) void {
self.quads.appendAssumeCapacity(quad); self.quads.appendAssumeCapacity(quad);
} }
pub fn initSprite() !Sprite.Id { pub fn initSprite() Sprite.Id {
const self = &g_state; const self = &g_state;
const id = try self.sprites.insertBounded(); const id = self.sprites.insertBounded() catch {
log.warn("Failed to create sprite, limit reached! limit: {}", .{self.sprites.slots.capacity});
return self.nil_sprite;
};
const sprite = self.sprites.getAssumeExists(id); const sprite = self.sprites.getAssumeExists(id);
sprite.* = .{ sprite.* = .{
@ -477,6 +549,10 @@ pub fn initSprite() !Sprite.Id {
pub fn deinitSprite(id: Sprite.Id) void { pub fn deinitSprite(id: Sprite.Id) void {
const self = &g_state; const self = &g_state;
if (id == self.nil_sprite) {
return;
}
if (self.sprites.exists(id)) { if (self.sprites.exists(id)) {
const sprite = self.sprites.getAssumeExists(id); const sprite = self.sprites.getAssumeExists(id);
if (sprite.data) |data| { if (sprite.data) |data| {
@ -484,7 +560,6 @@ pub fn deinitSprite(id: Sprite.Id) void {
} }
self.sprites.removeAssumeExists(id); self.sprites.removeAssumeExists(id);
if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| { if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| {
spritesheet.stale = true; spritesheet.stale = true;
} }
@ -498,12 +573,16 @@ const SetSpriteOptions = struct {
pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void { pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void {
const self = &g_state; const self = &g_state;
if (!self.sprites.exists(id)) { if (id == self.nil_sprite) {
log.warn("Attempt to set data for sprite that doesn't exist: {}", .{id});
return; return;
} }
var sprite_data: ImageData = .empty; const sprite = self.sprites.get(id) orelse {
log.warn("Attempt to set sprite that doesn't exist: {}", .{id});
return;
};
var sprite_data: ImageData = .none;
errdefer sprite_data.deinit(self.gpa); errdefer sprite_data.deinit(self.gpa);
if (opts.rect) |rect| { if (opts.rect) |rect| {
@ -525,7 +604,6 @@ pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void {
}; };
} }
const sprite = self.sprites.getAssumeExists(id);
if (sprite.data) |old_sprite_data| { if (sprite.data) |old_sprite_data| {
old_sprite_data.deinit(self.gpa); old_sprite_data.deinit(self.gpa);
} }
@ -537,8 +615,12 @@ pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void {
} }
} }
pub fn packSprites(id: Spritesheet.Id) !void { pub fn packSpritesheet(id: Spritesheet.Id) !void {
const self = &g_state; const self = &g_state;
if (id == self.nil_spritesheet) {
return;
}
const spritesheet = self.spritesheets.get(id) orelse { const spritesheet = self.spritesheets.get(id) orelse {
log.warn("Attempt to pack spritesheet that doesn't exist: {}", .{id}); log.warn("Attempt to pack spritesheet that doesn't exist: {}", .{id});
return; return;
@ -555,28 +637,32 @@ pub fn packSprites(id: Spritesheet.Id) !void {
}; };
var nodes: [32]STBRectPack.Node = undefined; var nodes: [32]STBRectPack.Node = undefined;
var rects: [Sprite.max_sprites]STBRectPack.Rect = undefined;
for (&texture_sizes) |texture_size| { var sprites_buffer: [Sprite.max_sprites]*Sprite = undefined;
var ctx: STBRectPack = undefined; var sprites = std.ArrayList(*Sprite).initBuffer(&sprites_buffer);
ctx.init(texture_size, texture_size, &nodes);
ctx.setupAllowOutOfMem(true);
@memset(&rects, .{});
{
var i: usize = 0;
var sprite_iter = self.sprites.iterator(); var sprite_iter = self.sprites.iterator();
while (sprite_iter.next()) |sprite_id| : (i += 1) { while (sprite_iter.next()) |sprite_id| {
const sprite = self.sprites.getAssumeExists(sprite_id); const sprite = self.sprites.getAssumeExists(sprite_id);
const sprite_data = sprite.data orelse continue; if (sprite.data != null and sprite.spritesheet == id) {
sprites.appendAssumeCapacity(sprite);
}
}
var rects: [Sprite.max_sprites]STBRectPack.Rect = undefined;
@memset(&rects, .{});
for (0.., sprites.items) |i, sprite| {
const sprite_data = sprite.data.?;
rects[i] = .{ rects[i] = .{
.w = @intCast(sprite_data.width), .w = @intCast(sprite_data.width),
.h = @intCast(sprite_data.height), .h = @intCast(sprite_data.height),
}; };
} }
}
for (&texture_sizes) |texture_size| {
var ctx: STBRectPack = undefined;
ctx.init(texture_size, texture_size, &nodes);
ctx.setupAllowOutOfMem(true);
const success = ctx.packRects(&rects); const success = ctx.packRects(&rects);
std.debug.print("success {} {}\n", .{texture_size, success}); std.debug.print("success {} {}\n", .{texture_size, success});
@ -587,13 +673,8 @@ pub fn packSprites(id: Spritesheet.Id) !void {
const image_data = try ImageData.initRGBA8(self.gpa, texture_size, texture_size); const image_data = try ImageData.initRGBA8(self.gpa, texture_size, texture_size);
defer image_data.deinit(self.gpa); defer image_data.deinit(self.gpa);
{ for (0.., sprites.items) |i, sprite| {
var i: usize = 0; const sprite_data = sprite.data.?;
var sprite_iter = self.sprites.iterator();
while (sprite_iter.next()) |sprite_id| : (i += 1) {
const sprite = self.sprites.getAssumeExists(sprite_id);
const sprite_data = sprite.data orelse continue;
assert(rects[i].was_packed == 1); assert(rects[i].was_packed == 1);
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);
@ -608,7 +689,6 @@ pub fn packSprites(id: Spritesheet.Id) !void {
sprite_data.height sprite_data.height
); );
} }
}
setTexture(spritesheet.texture, image_data); setTexture(spritesheet.texture, image_data);
@ -617,32 +697,30 @@ pub fn packSprites(id: Spritesheet.Id) !void {
return; return;
} }
const max_texture_size = texture_sizes[texture_sizes.len-1]; const max_texture_size = texture_sizes[texture_sizes.len-1];
log.warn("Failed to pack sprites, couldn't create a texture that is large enough. Max size tried: {}x{}", .{max_texture_size, max_texture_size}); log.warn("Failed to pack sprites, couldn't create a texture that is large enough. Max size tried: {}x{}", .{max_texture_size, max_texture_size});
return error.TooManySprites;
} }
pub fn getSpriteUVRect(id: Sprite.Id) ?Rect { pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
const self = &g_state; const self = &g_state;
if (id == self.nil_sprite) {
return null;
}
if (!self.sprites.exists(id)) { const sprite = self.sprites.get(id) orelse return null;
return null; const sprite_data = sprite.data orelse return null;
}
const sprite = self.sprites.getAssumeExists(id);
if (sprite.data == null) {
return null;
}
const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return null; const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return null;
packSprites(sprite.spritesheet) catch |e| { packSpritesheet(sprite.spritesheet) catch |e| {
log.warn("Failed to pack sprites: {}", .{e}); log.warn("Failed to pack sprites: {}", .{e});
return null; 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(spritesheet.size), .pos = sprite.position.?.divide(spritesheet.size),
.size = sprite_size.divide(spritesheet.size), .size = sprite_size.divide(spritesheet.size),
@ -657,6 +735,7 @@ pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
quad.setRect(.{ .pos = pos, .size = size }); quad.setRect(.{ .pos = pos, .size = size });
var texture: ?Texture.Id = null; var texture: ?Texture.Id = null;
if (id != self.nil_sprite) {
if (getSpriteUVRect(id)) |sprite_uv| { if (getSpriteUVRect(id)) |sprite_uv| {
quad.setUVRect(sprite_uv); quad.setUVRect(sprite_uv);
if (self.sprites.get(id)) |sprite| { if (self.sprites.get(id)) |sprite| {
@ -664,39 +743,25 @@ pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
texture = spritesheet.texture; 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(.{ draw(.{
.texture = texture.?, .texture = texture orelse self.nil_texture,
.quad = quad .quad = quad
}); });
} }
pub fn initSpritesheet() !Spritesheet.Id { pub fn initSpritesheet() Spritesheet.Id {
// TODO: self.spritesheet_texture = try initTexture();
const self = &g_state; const self = &g_state;
const id = try self.spritesheets.insertBounded(); const id = self.spritesheets.insertBounded() catch {
log.warn("Failed to create spritesheet, limit reached! limit: {}", .{self.spritesheets.slots.capacity});
return self.nil_spritesheet;
};
const spritesheet = self.spritesheets.getAssumeExists(id); const spritesheet = self.spritesheets.getAssumeExists(id);
spritesheet.* = .{ spritesheet.* = .{
.texture = try initTexture(), .texture = initTexture(),
.size = .init(0, 0), .size = .init(0, 0),
.stale = false, .stale = false,
}; };
@ -705,7 +770,16 @@ pub fn initSpritesheet() !Spritesheet.Id {
} }
pub fn deinitSpritesheet(id: Spritesheet.Id) void { pub fn deinitSpritesheet(id: Spritesheet.Id) void {
_ = id; // autofix const self = &g_state;
if (id == self.nil_spritesheet) {
return;
}
if (self.spritesheets.get(id)) {
const spritesheet = self.spritesheets.getAssumeExists(id);
deinitTexture(spritesheet.texture);
self.spritesheets.removeAssumeExists(id);
}
} }
pub const ImageData = struct { pub const ImageData = struct {
@ -718,7 +792,7 @@ pub const ImageData = struct {
width: u32, width: u32,
height: u32, height: u32,
pub const empty = ImageData{ pub const none = ImageData{
.pixels = .none, .pixels = .none,
.width = 0, .width = 0,
.height = 0, .height = 0,

View File

@ -281,18 +281,38 @@ fn PlatformType(App: type) type {
} }
} }
const input = &self.input;
switch (e.type) { switch (e.type) {
.MOUSE_DOWN => { .MOUSE_DOWN => {
if (input.mouse_position == null) {
self.pushEvent(.{
.mouse_enter = .init(e.mouse_x, e.mouse_y)
});
}
self.pushEvent(.{ self.pushEvent(.{
.mouse_pressed = Input.MouseButton.fromSokol(e.mouse_button) orelse return false, .mouse_pressed = Input.MouseButton.fromSokol(e.mouse_button) orelse return false,
}); });
}, },
.MOUSE_UP => { .MOUSE_UP => {
if (input.mouse_position == null) {
self.pushEvent(.{
.mouse_enter = .init(e.mouse_x, e.mouse_y)
});
}
self.pushEvent(.{ self.pushEvent(.{
.mouse_released = Input.MouseButton.fromSokol(e.mouse_button) orelse return false, .mouse_released = Input.MouseButton.fromSokol(e.mouse_button) orelse return false,
}); });
}, },
.MOUSE_MOVE => { .MOUSE_MOVE => {
if (input.mouse_position == null) {
self.pushEvent(.{
.mouse_enter = .init(e.mouse_x, e.mouse_y)
});
}
self.pushEvent(.{ self.pushEvent(.{
.mouse_move = .init(e.mouse_x, e.mouse_y) .mouse_move = .init(e.mouse_x, e.mouse_y)
}); });