add spritesheets
This commit is contained in:
parent
f685b69bd9
commit
c09ff2aad0
Binary file not shown.
|
Before Width: | Height: | Size: 85 KiB |
BIN
assets/sokoban/sokoban_tilesheet.png
Normal file
BIN
assets/sokoban/sokoban_tilesheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
BIN
assets/sokoban/sokoban_tilesheet@2.png
Normal file
BIN
assets/sokoban/sokoban_tilesheet@2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
@ -69,6 +69,7 @@ pub fn build(b: *std.Build) !void {
|
||||
const dep_stb = b.dependency("stb", .{});
|
||||
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
|
||||
exe_mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
|
||||
exe_mod.addImport("stb_rect_pack", dep_stb.module("stb_rect_pack"));
|
||||
|
||||
const dep_tracy = b.dependency("tracy", .{
|
||||
.target = target,
|
||||
|
||||
@ -22,17 +22,32 @@ pub fn build(b: *std.Build) void {
|
||||
}
|
||||
|
||||
{
|
||||
const mod_stb_image = b.addModule("stb_vorbis", .{
|
||||
const mod_stb_vorbis = b.addModule("stb_vorbis", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/stb_vorbis.zig"),
|
||||
.link_libc = true,
|
||||
});
|
||||
|
||||
mod_stb_image.addIncludePath(stb_dependency.path("."));
|
||||
mod_stb_image.addCSourceFile(.{
|
||||
mod_stb_vorbis.addIncludePath(stb_dependency.path("."));
|
||||
mod_stb_vorbis.addCSourceFile(.{
|
||||
.file = b.path("src/stb_vorbis_impl.c"),
|
||||
.flags = &.{}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const mod_stb_rect_pack = b.addModule("stb_rect_pack", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/stb_rect_pack.zig"),
|
||||
.link_libc = true,
|
||||
});
|
||||
|
||||
mod_stb_rect_pack.addIncludePath(stb_dependency.path("."));
|
||||
mod_stb_rect_pack.addCSourceFile(.{
|
||||
.file = b.path("src/stb_rect_pack_impl.c"),
|
||||
.flags = &.{}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
36
libs/stb/src/stb_rect_pack.zig
Normal file
36
libs/stb/src/stb_rect_pack.zig
Normal file
@ -0,0 +1,36 @@
|
||||
const std = @import("std");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("stb_rect_pack.h");
|
||||
});
|
||||
|
||||
const STBRectPack = @This();
|
||||
|
||||
ctx: c.stbrp_context,
|
||||
|
||||
pub fn init(self: *STBRectPack, width: u32, height: u32, nodes: []Node) void {
|
||||
c.stbrp_init_target(&self.ctx, @intCast(width), @intCast(height), nodes.ptr, @intCast(nodes.len));
|
||||
}
|
||||
|
||||
pub fn setupAllowOutOfMem(self: *STBRectPack, enabled: bool) void {
|
||||
c.stbrp_setup_heuristic(&self.ctx, if (enabled) 1 else 0);
|
||||
}
|
||||
|
||||
pub fn setupHeuristic(self: *STBRectPack, heuristic: Heuristic) void {
|
||||
c.stbrp_setup_heuristic(&self.ctx, heuristic);
|
||||
}
|
||||
|
||||
pub fn packRects(self: *STBRectPack, rects: []Rect) bool {
|
||||
return c.stbrp_pack_rects(&self.ctx, rects.ptr, @intCast(rects.len)) == 1;
|
||||
}
|
||||
|
||||
pub const Heuristic = enum(i32) {
|
||||
Skyline_BL_sortHeight = c.STBRP_HEURISTIC_Skyline_BL_sortHeight,
|
||||
Skyline_BF_sortHeight = c.STBRP_HEURISTIC_Skyline_BF_sortHeight,
|
||||
_,
|
||||
|
||||
pub const default = Heuristic.Skyline_BL_sortHeight;
|
||||
};
|
||||
|
||||
pub const Node = c.stbrp_node;
|
||||
pub const Rect = c.stbrp_rect;
|
||||
2
libs/stb/src/stb_rect_pack_impl.c
Normal file
2
libs/stb/src/stb_rect_pack_impl.c
Normal file
@ -0,0 +1,2 @@
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#include "stb_rect_pack.h"
|
||||
36
src/app.zig
36
src/app.zig
@ -10,6 +10,7 @@ const STBImage = @import("stb_image");
|
||||
const Platform = @import("./platform.zig");
|
||||
const Gfx = Platform.Gfx;
|
||||
const ImGUI = Platform.ImGUI;
|
||||
const Rect = Platform.Rect;
|
||||
|
||||
const App = @This();
|
||||
|
||||
@ -17,13 +18,14 @@ bg: Platform.Color,
|
||||
show_first_window: bool,
|
||||
check: bool,
|
||||
player_pos: Vec2,
|
||||
tex: Gfx.TextureId,
|
||||
sprite_sprite: Gfx.Sprite.Id,
|
||||
wall_sprite: Gfx.Sprite.Id,
|
||||
|
||||
pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
const cwd = std.Io.Dir.cwd();
|
||||
const sokoban_spritesheet = try cwd.readFileAlloc(
|
||||
plt.io,
|
||||
"assets/sokoban/Spritesheet/sokoban_spritesheet.png",
|
||||
"assets/sokoban/sokoban_tilesheet.png",
|
||||
plt.gpa,
|
||||
.unlimited
|
||||
);
|
||||
@ -32,15 +34,35 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
var image = try STBImage.load(sokoban_spritesheet);
|
||||
defer image.deinit();
|
||||
|
||||
const tex = try Gfx.initTexture();
|
||||
try Gfx.setTexture(tex, image.rgba8_pixels, image.width, image.height);
|
||||
const spritesheet = Gfx.ImageData{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.pixels = .{ .rgba8 = image.rgba8_pixels }
|
||||
};
|
||||
|
||||
const tile_size = Vec2.init(64, 64);
|
||||
|
||||
const player_sprite = try Gfx.initSprite();
|
||||
Gfx.setSprite(player_sprite, .{
|
||||
.image = spritesheet,
|
||||
.rect = Rect.init(0, 4, 1, 1).multiply(tile_size)
|
||||
});
|
||||
|
||||
const wall_sprite = try Gfx.initSprite();
|
||||
Gfx.setSprite(wall_sprite, .{
|
||||
.image = spritesheet,
|
||||
.rect = Rect.init(1, 0, 1, 1).multiply(tile_size)
|
||||
});
|
||||
|
||||
try Gfx.packSprites();
|
||||
|
||||
self.* = App{
|
||||
.bg = .black,
|
||||
.player_pos = .init(100, 100),
|
||||
.show_first_window = true,
|
||||
.check = false,
|
||||
.tex = tex
|
||||
.sprite_sprite = player_sprite,
|
||||
.wall_sprite = wall_sprite
|
||||
};
|
||||
|
||||
return null;
|
||||
@ -68,8 +90,8 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||
self.player_pos = self.player_pos.add(dir.multiplyScalar(50 * dt));
|
||||
|
||||
Gfx.setClearColor(self.bg);
|
||||
Gfx.drawTexture(self.tex, self.player_pos, .init(100, 100), .rgb(200, 0, 0));
|
||||
Gfx.drawRectangle(self.player_pos.add(.init(0, 200)), .init(100, 100), .rgb(255, 255, 255));
|
||||
Gfx.drawSprite(self.sprite_sprite, self.player_pos, .init(100, 100), .white);
|
||||
Gfx.drawSprite(self.wall_sprite, self.player_pos.add(.init(0, 200)), .init(100, 100), .white);
|
||||
|
||||
if (ImGUI.beginWindow(.{
|
||||
.name = "Hello",
|
||||
|
||||
438
src/graphics.zig
438
src/graphics.zig
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const log = std.log.scoped(.graphics);
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const sokol = @import("sokol");
|
||||
const sg = sokol.gfx;
|
||||
@ -11,7 +12,9 @@ const Math = @import("math");
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec4 = Math.Vec4;
|
||||
const Mat4 = Math.Mat4;
|
||||
const Rect = Math.Rect;
|
||||
|
||||
const STBRectPack = @import("stb_rect_pack");
|
||||
const Color = @import("./color.zig");
|
||||
const shd = @import("shader");
|
||||
const SlotMapType = @import("slot_map.zig").SlotMapType;
|
||||
@ -23,8 +26,8 @@ const State = struct {
|
||||
pipeline: sg.Pipeline,
|
||||
bindings: sg.Bindings,
|
||||
|
||||
default_texture: TextureId,
|
||||
error_texture: TextureId,
|
||||
default_sprite: Sprite.Id,
|
||||
error_sprite: Sprite.Id,
|
||||
default_sampler: sg.Sampler,
|
||||
|
||||
quads_buffer: [2048]Quad,
|
||||
@ -32,13 +35,15 @@ const State = struct {
|
||||
|
||||
clear_color: Color,
|
||||
|
||||
textures_buffer: [max_textures]Texture,
|
||||
textures_slotmap: TextureSlotMap,
|
||||
};
|
||||
textures_buffer: [Texture.max_textures]Texture.SlotMap.Slot,
|
||||
textures: Texture.SlotMap,
|
||||
|
||||
const max_textures = 64;
|
||||
const TextureSlotMap = SlotMapType(std.math.IntFittingRange(0, max_textures-1), u8);
|
||||
pub const TextureId = TextureSlotMap.Id;
|
||||
spritesheet_texture: Texture.Id,
|
||||
spritesheet_size: Vec2,
|
||||
spritesheet_stale: bool,
|
||||
sprites_buffer: [Sprite.max_sprites]Sprite.SlotMap.Slot,
|
||||
sprites: Sprite.SlotMap,
|
||||
};
|
||||
|
||||
var g_state: State = undefined;
|
||||
|
||||
@ -129,9 +134,14 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
.quads = .initBuffer(&self.quads_buffer),
|
||||
.clear_color = .black,
|
||||
.textures_buffer = undefined,
|
||||
.textures_slotmap = .init(try .initCapacity(gpa, max_textures)),
|
||||
.default_texture = undefined,
|
||||
.error_texture = undefined
|
||||
.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)
|
||||
};
|
||||
|
||||
{
|
||||
@ -141,8 +151,14 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
var pixels: [width * height * 4]u8 = undefined;
|
||||
@memset(&pixels, 0xFF);
|
||||
|
||||
self.default_texture = try initTexture();
|
||||
try setTexture(self.default_texture, &pixels, width, height);
|
||||
self.default_sprite = try initSprite();
|
||||
setSprite(self.default_sprite, .{
|
||||
.image = ImageData{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.pixels = .{ .rgba8 = &pixels }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@ -169,15 +185,29 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
}
|
||||
}
|
||||
|
||||
self.error_texture = try initTexture();
|
||||
try setTexture(self.error_texture, &pixels, width, height);
|
||||
self.error_sprite = try initSprite();
|
||||
setSprite(self.error_sprite, .{
|
||||
.image = .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.pixels = .{ .rgba8 = &pixels }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.spritesheet_texture = try initTexture();
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
const self = &g_state;
|
||||
|
||||
self.textures_slotmap.deinit(self.gpa);
|
||||
var sprite_iter = self.sprites.iterator();
|
||||
while (sprite_iter.next()) |sprite_id| {
|
||||
const sprite = self.sprites.getAssumeExists(sprite_id);
|
||||
if (sprite.data) |data| {
|
||||
data.deinit(self.gpa);
|
||||
}
|
||||
}
|
||||
|
||||
sg.destroyPipeline(self.pipeline);
|
||||
sg.destroyShader(self.shader);
|
||||
@ -227,9 +257,8 @@ pub fn beginFrame() void {
|
||||
.swapchain = sglue.swapchain()
|
||||
});
|
||||
|
||||
assert(self.textures_slotmap.exists(self.default_texture));
|
||||
const default_texture = self.textures_buffer[self.default_texture.index];
|
||||
self.bindings.views[shd.VIEW_tex] = default_texture.view;
|
||||
const spritesheet_texture = self.textures.getAssumeExists(self.spritesheet_texture);
|
||||
self.bindings.views[shd.VIEW_tex] = spritesheet_texture.view;
|
||||
self.bindings.samplers[shd.SMP_smp] = self.default_sampler;
|
||||
}
|
||||
|
||||
@ -285,7 +314,7 @@ pub fn endFrame() void {
|
||||
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
|
||||
const self = &g_state;
|
||||
|
||||
drawTexture(self.default_texture, pos, size, color);
|
||||
drawSprite(self.default_sprite, pos, size, color);
|
||||
}
|
||||
|
||||
pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
@ -300,21 +329,21 @@ pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
|
||||
var quad: Quad = undefined;
|
||||
quad.setColor(color);
|
||||
quad.setUVRect(.init(0, 0), .init(1, 1));
|
||||
quad.setUVRect(.init(0, 0, 1, 1));
|
||||
quad.vertices[0] = top_right;
|
||||
quad.vertices[1] = top_left;
|
||||
quad.vertices[2] = bottom_right;
|
||||
quad.vertices[3] = bottom_left;
|
||||
|
||||
draw(.{
|
||||
.texture = self.default_texture,
|
||||
.texture = self.default_sprite,
|
||||
.quad = quad
|
||||
});
|
||||
}
|
||||
|
||||
pub fn initTexture() !TextureId {
|
||||
pub fn initTexture() !Texture.Id {
|
||||
const self = &g_state;
|
||||
const id = try self.textures_slotmap.insertBounded();
|
||||
const id = try self.textures.insertBounded();
|
||||
|
||||
const image = sg.allocImage();
|
||||
assert(image.id != sg.invalid_id);
|
||||
@ -322,7 +351,7 @@ pub fn initTexture() !TextureId {
|
||||
const view = sg.allocView();
|
||||
assert(view.id != sg.invalid_id);
|
||||
|
||||
const texture = &self.textures_buffer[id.index];
|
||||
const texture = self.textures.getAssumeExists(id);
|
||||
texture.* = .{
|
||||
.image = image,
|
||||
.view = view
|
||||
@ -330,14 +359,14 @@ pub fn initTexture() !TextureId {
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn deinitTexture(id: TextureId) void {
|
||||
pub fn deinitTexture(id: Texture.Id) void {
|
||||
const self = &g_state;
|
||||
if (self.textures_slotmap.remove(id)) {
|
||||
if (self.textures.remove(id)) {
|
||||
const texture = self.textures_buffer[id.index];
|
||||
const bound_view = &self.bindings.views[shd.VIEW_tex];
|
||||
if (texture.view.id == bound_view.id) {
|
||||
assert(self.textures_slotmap.exists(self.default_texture));
|
||||
bound_view.* = self.textures_buffer[self.default_texture.index].view;
|
||||
assert(self.textures.exists(self.default_sprite));
|
||||
bound_view.* = self.textures_buffer[self.default_sprite.index].view;
|
||||
}
|
||||
|
||||
sg.destroyView(texture.view);
|
||||
@ -345,25 +374,26 @@ pub fn deinitTexture(id: TextureId) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setTexture(id: TextureId, rgba8_pixels: [*]const u8, width: u32, height: u32) !void {
|
||||
pub fn setTexture(id: Texture.Id, texture_data: ImageData) void {
|
||||
const self = &g_state;
|
||||
if (!self.textures_slotmap.exists(id)) {
|
||||
return error.NotFound;
|
||||
if (!self.textures.exists(id)) {
|
||||
log.warn("Attempt to set data for texture that doesn't exist: {}", .{id});
|
||||
return;
|
||||
}
|
||||
const texture = &self.textures_buffer[id.index];
|
||||
const texture = self.textures.getAssumeExists(id);
|
||||
|
||||
if (sg.queryImageState(texture.image) == .VALID) {
|
||||
sg.uninitImage(texture.image);
|
||||
}
|
||||
|
||||
const pixel_count = width*height;
|
||||
const pixel_count = texture_data.width*texture_data.height;
|
||||
|
||||
var image_data: sg.ImageData = .{};
|
||||
image_data.mip_levels[0] = sg.asRange(rgba8_pixels[0..(pixel_count*4)]);
|
||||
image_data.mip_levels[0] = sg.asRange(texture_data.pixels.rgba8[0..(pixel_count*4)]);
|
||||
sg.initImage(texture.image, .{
|
||||
.type = ._2D,
|
||||
.width = @intCast(width),
|
||||
.height = @intCast(height),
|
||||
.width = @intCast(texture_data.width),
|
||||
.height = @intCast(texture_data.height),
|
||||
.num_mipmaps = 1,
|
||||
.pixel_format = .RGBA8,
|
||||
.data = image_data,
|
||||
@ -378,11 +408,11 @@ pub fn setTexture(id: TextureId, rgba8_pixels: [*]const u8, width: u32, height:
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawTexture(id: TextureId, pos: Vec2, size: Vec2, color: Color) void {
|
||||
pub fn drawTexture(id: Texture.Id, pos: Vec2, size: Vec2, color: Color) void {
|
||||
var quad: Quad = undefined;
|
||||
quad.setColor(color);
|
||||
quad.setRect(pos, size);
|
||||
quad.setUVRect(.init(0, 0), .init(1, 1));
|
||||
quad.setRect(.{ .pos = pos, .size = size });
|
||||
quad.setUVRect(.init(0, 0, 1, 1));
|
||||
|
||||
draw(.{
|
||||
.texture = id,
|
||||
@ -391,7 +421,7 @@ pub fn drawTexture(id: TextureId, pos: Vec2, size: Vec2, color: Color) void {
|
||||
}
|
||||
|
||||
const DrawOptions = struct {
|
||||
texture: TextureId,
|
||||
texture: Texture.Id,
|
||||
quad: Quad
|
||||
};
|
||||
|
||||
@ -400,12 +430,18 @@ pub fn draw(opts: DrawOptions) void {
|
||||
|
||||
var quad = opts.quad;
|
||||
var texture = opts.texture;
|
||||
if (!self.textures_slotmap.exists(texture)) {
|
||||
texture = self.error_texture;
|
||||
quad.setColor(.white);
|
||||
if (!self.textures.exists(texture)) {
|
||||
if (getSpriteUVRect(self.error_sprite)) |error_uv| {
|
||||
texture = self.spritesheet_texture;
|
||||
quad.setUVRect(error_uv);
|
||||
quad.setColor(.white);
|
||||
} else {
|
||||
log.err("Failed to draw error texture", .{});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const view = self.textures_buffer[texture.index].view;
|
||||
const view = self.textures.getAssumeExists(texture).view;
|
||||
|
||||
const bound_view = &self.bindings.views[shd.VIEW_tex];
|
||||
if (bound_view.id != view.id) {
|
||||
@ -420,9 +456,311 @@ pub fn draw(opts: DrawOptions) void {
|
||||
self.quads.appendAssumeCapacity(quad);
|
||||
}
|
||||
|
||||
const Texture = struct {
|
||||
pub fn initSprite() !Sprite.Id {
|
||||
const self = &g_state;
|
||||
const id = try self.sprites.insertBounded();
|
||||
|
||||
const sprite = self.sprites.getAssumeExists(id);
|
||||
sprite.* = .{
|
||||
.data = null,
|
||||
.position = null
|
||||
};
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn deinitSprite(id: Sprite.Id) void {
|
||||
const self = &g_state;
|
||||
if (self.sprites.exists(id)) {
|
||||
const sprite = self.sprites.getAssumeExists(id);
|
||||
if (sprite.data) |data| {
|
||||
data.deinit(self.gpa);
|
||||
}
|
||||
self.sprites.removeAssumeExists(id);
|
||||
self.spritesheet_stale = true;
|
||||
}
|
||||
}
|
||||
|
||||
const SetSpriteOptions = struct {
|
||||
image: ImageData,
|
||||
rect: ?Rect = null
|
||||
};
|
||||
|
||||
pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void {
|
||||
const self = &g_state;
|
||||
if (!self.sprites.exists(id)) {
|
||||
log.warn("Attempt to set data for sprite that doesn't exist: {}", .{id});
|
||||
return;
|
||||
}
|
||||
|
||||
var sprite_data: ImageData = .empty;
|
||||
errdefer sprite_data.deinit(self.gpa);
|
||||
|
||||
if (opts.rect) |rect| {
|
||||
sprite_data = ImageData.initRGBA8(self.gpa, @trunc(rect.size.x), @trunc(rect.size.y)) catch |e| {
|
||||
log.warn("Failed to create texture data: {}", .{e});
|
||||
return;
|
||||
};
|
||||
|
||||
sprite_data.copyFrom(
|
||||
opts.image,
|
||||
0, 0,
|
||||
@trunc(rect.pos.x), @trunc(rect.pos.y),
|
||||
@trunc(rect.size.x), @trunc(rect.size.y)
|
||||
);
|
||||
} else {
|
||||
sprite_data = opts.image.clone(self.gpa) catch |e| {
|
||||
log.warn("Failed to clone image: {}", .{e});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
const sprite = self.sprites.getAssumeExists(id);
|
||||
if (sprite.data) |old_sprite_data| {
|
||||
old_sprite_data.deinit(self.gpa);
|
||||
}
|
||||
|
||||
sprite.data = sprite_data;
|
||||
sprite.position = null;
|
||||
self.spritesheet_stale = true;
|
||||
}
|
||||
|
||||
pub fn packSprites() !void {
|
||||
const self = &g_state;
|
||||
|
||||
// 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.
|
||||
const texture_sizes = [_]u32{
|
||||
64, 128, 256, 512, 1024, 2048, 4096
|
||||
};
|
||||
|
||||
var nodes: [32]STBRectPack.Node = undefined;
|
||||
var rects: [Sprite.max_sprites]STBRectPack.Rect = undefined;
|
||||
|
||||
for (&texture_sizes) |texture_size| {
|
||||
var ctx: STBRectPack = undefined;
|
||||
ctx.init(texture_size, texture_size, &nodes);
|
||||
ctx.setupAllowOutOfMem(true);
|
||||
|
||||
@memset(&rects, .{});
|
||||
|
||||
|
||||
{
|
||||
var i: usize = 0;
|
||||
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;
|
||||
rects[i] = .{
|
||||
.w = @intCast(sprite_data.width),
|
||||
.h = @intCast(sprite_data.height),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const success = ctx.packRects(&rects);
|
||||
std.debug.print("success {} {}\n", .{texture_size, success});
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const spritesheet = try ImageData.initRGBA8(self.gpa, texture_size, texture_size);
|
||||
defer spritesheet.deinit(self.gpa);
|
||||
|
||||
{
|
||||
var i: usize = 0;
|
||||
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);
|
||||
const x: u32 = @intCast(rects[i].x);
|
||||
const y: u32 = @intCast(rects[i].y);
|
||||
sprite.position = .initFromInt(u32, x, y);
|
||||
spritesheet.copyFrom(
|
||||
sprite_data,
|
||||
x,
|
||||
y,
|
||||
0,
|
||||
0,
|
||||
sprite_data.width,
|
||||
sprite_data.height
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setTexture(self.spritesheet_texture, spritesheet);
|
||||
|
||||
self.spritesheet_size = .initFromInt(u32, texture_size, texture_size);
|
||||
self.spritesheet_stale = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
|
||||
const self = &g_state;
|
||||
|
||||
if (!self.sprites.exists(id)) {
|
||||
return null;
|
||||
}
|
||||
const sprite = self.sprites.getAssumeExists(id);
|
||||
if (sprite.data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (self.spritesheet_stale) {
|
||||
packSprites() catch |e| {
|
||||
log.warn("Failed to pack sprites: {}", .{e});
|
||||
};
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
|
||||
const self = &g_state;
|
||||
|
||||
var quad: Quad = undefined;
|
||||
quad.setColor(color);
|
||||
quad.setRect(.{ .pos = pos, .size = size });
|
||||
|
||||
if (getSpriteUVRect(id)) |sprite_uv| {
|
||||
quad.setUVRect(sprite_uv);
|
||||
} else if (getSpriteUVRect(self.error_sprite)) |error_uv| {
|
||||
quad.setUVRect(error_uv);
|
||||
quad.setColor(.white);
|
||||
}
|
||||
|
||||
draw(.{
|
||||
.texture = self.spritesheet_texture,
|
||||
.quad = quad
|
||||
});
|
||||
}
|
||||
|
||||
pub const ImageData = struct {
|
||||
const Pixels = union(enum) {
|
||||
none,
|
||||
rgba8: [*]u8
|
||||
};
|
||||
|
||||
pixels: Pixels,
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
pub const empty = ImageData{
|
||||
.pixels = .none,
|
||||
.width = 0,
|
||||
.height = 0,
|
||||
};
|
||||
|
||||
pub fn initRGBA8(gpa: Allocator, width: u32, height: u32) !ImageData {
|
||||
const pixel_count = width * height;
|
||||
const pixels = try gpa.alloc(u8, pixel_count * 4);
|
||||
|
||||
return ImageData{
|
||||
.pixels = .{ .rgba8 = pixels.ptr },
|
||||
.width = width,
|
||||
.height = height
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clone(self: ImageData, gpa: Allocator) !ImageData {
|
||||
const pixel_count = self.width * self.height;
|
||||
const pixels = switch (self.pixels) {
|
||||
.none => Pixels{ .none = {} },
|
||||
.rgba8 => |pixels| Pixels{
|
||||
.rgba8 = (try gpa.dupe(u8, pixels[0..(pixel_count * 4)])).ptr
|
||||
}
|
||||
};
|
||||
|
||||
return ImageData{
|
||||
.pixels = pixels,
|
||||
.width = self.width,
|
||||
.height = self.height
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: ImageData, gpa: Allocator) void {
|
||||
const pixel_count = self.width * self.height;
|
||||
switch (self.pixels) {
|
||||
.none => {},
|
||||
.rgba8 => |pixels| gpa.free(pixels[0..(pixel_count*4)])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copyFrom(
|
||||
self: ImageData,
|
||||
source: ImageData,
|
||||
cx: u32,
|
||||
cy: u32,
|
||||
source_cx: u32,
|
||||
source_cy: u32,
|
||||
source_width: u32,
|
||||
source_height: u32,
|
||||
) void {
|
||||
if (source.pixels == .none or self.pixels == .none) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (0..source_height) |oy| {
|
||||
const source_y = source_cy+oy;
|
||||
const dest_y = cy+oy;
|
||||
if (dest_y >= self.height) {
|
||||
continue;
|
||||
}
|
||||
if (source_y >= source.height) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (0..source_width) |ox| {
|
||||
const source_x = source_cx+ox;
|
||||
const dest_x = cx+ox;
|
||||
if (dest_x >= self.width) {
|
||||
continue;
|
||||
}
|
||||
if (source_x >= source.width) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const source_pixel_index = source_y * source.width + source_x;
|
||||
const source_pixel = source.pixels.rgba8[(4*source_pixel_index)..][0..4];
|
||||
|
||||
const dest_pixel_index = dest_y * self.width + dest_x;
|
||||
const dest_pixel = self.pixels.rgba8[(4*dest_pixel_index)..][0..4];
|
||||
|
||||
@memcpy(dest_pixel, source_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Texture = struct {
|
||||
image: sg.Image,
|
||||
view: sg.View,
|
||||
|
||||
const max_textures = 64;
|
||||
const SlotMap = SlotMapType(std.math.IntFittingRange(0, max_textures-1), u8, Texture);
|
||||
pub const Id = SlotMap.Id;
|
||||
};
|
||||
|
||||
pub const Sprite = struct {
|
||||
data: ?ImageData,
|
||||
position: ?Vec2,
|
||||
|
||||
const max_sprites = 128;
|
||||
const SlotMap = SlotMapType(std.math.IntFittingRange(0, max_sprites-1), u8, Sprite);
|
||||
pub const Id = SlotMap.Id;
|
||||
};
|
||||
|
||||
pub const Vertex = extern struct {
|
||||
@ -451,14 +789,18 @@ pub const Quad = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setUVRect(self: *Quad, pos: Vec2, size: Vec2) void {
|
||||
pub fn setUVRect(self: *Quad, rect: Rect) void {
|
||||
const pos = rect.pos;
|
||||
const size = rect.size;
|
||||
self.vertices[0].texcoord = pos;
|
||||
self.vertices[1].texcoord = pos.add(.init(size.x, 0));
|
||||
self.vertices[2].texcoord = pos.add(.init(0, size.y));
|
||||
self.vertices[3].texcoord = pos.add(size);
|
||||
}
|
||||
|
||||
pub fn setRect(self: *Quad, pos: Vec2, size: Vec2) void {
|
||||
pub fn setRect(self: *Quad, rect: Rect) void {
|
||||
const pos = rect.pos;
|
||||
const size = rect.size;
|
||||
self.vertices[0].position = pos;
|
||||
self.vertices[1].position = pos.add(.init(size.x, 0));
|
||||
self.vertices[2].position = pos.add(.init(0, size.y));
|
||||
|
||||
@ -12,10 +12,11 @@ const sg = sokol.gfx;
|
||||
const sglue = sokol.glue;
|
||||
const sgl = sokol.gl;
|
||||
|
||||
const Math = @import("math");
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec4 = Math.Vec4;
|
||||
const Mat4 = Math.Mat4;
|
||||
pub const Math = @import("math");
|
||||
pub const Vec2 = Math.Vec2;
|
||||
pub const Vec4 = Math.Vec4;
|
||||
pub const Mat4 = Math.Mat4;
|
||||
pub const Rect = Math.Rect;
|
||||
|
||||
const tracy = @import("tracy");
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ const Allocator = std.mem.Allocator;
|
||||
///
|
||||
/// For more details about this data structure checkout this podcast episode from Wookash:
|
||||
/// * https://www.youtube.com/watch?v=ShSGHb65f3M
|
||||
pub fn SlotMapType(Index: type, Generation: type) type {
|
||||
pub fn SlotMapType(Index: type, Generation: type, Value: type) type {
|
||||
assert(@typeInfo(Generation) == .int);
|
||||
assert(@typeInfo(Index) == .int);
|
||||
|
||||
@ -28,6 +28,7 @@ pub fn SlotMapType(Index: type, Generation: type) type {
|
||||
//
|
||||
// So in the end idk if it's worth it.
|
||||
pub const Slot = struct {
|
||||
value: Value,
|
||||
generation: Generation,
|
||||
next_hole: ?Index,
|
||||
used: bool
|
||||
@ -109,6 +110,7 @@ pub fn SlotMapType(Index: type, Generation: type) type {
|
||||
const index: Index = @intCast(self.slots.items.len);
|
||||
self.slots.items.len += 1;
|
||||
self.slots.items[index] = Slot{
|
||||
.value = undefined,
|
||||
.generation = 0,
|
||||
.next_hole = null,
|
||||
.used = false
|
||||
@ -171,6 +173,11 @@ pub fn SlotMapType(Index: type, Generation: type) type {
|
||||
return slot.used and slot.generation == id.generation;
|
||||
}
|
||||
|
||||
pub fn getAssumeExists(self: *Self, id: Id) *Value {
|
||||
assert(self.exists(id));
|
||||
return &self.slots.items[id.index].value;
|
||||
}
|
||||
|
||||
pub fn removeAssumeExists(self: *Self, id: Id) void {
|
||||
assert(self.exists(id));
|
||||
|
||||
@ -209,7 +216,7 @@ pub fn SlotMapType(Index: type, Generation: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
const TestMap = SlotMapType(u24, u8);
|
||||
const TestMap = SlotMapType(u24, u8, void);
|
||||
|
||||
// TODO: Add more rigorous test for check if generation usage is nicely distributed.
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user