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", .{});
|
const dep_stb = b.dependency("stb", .{});
|
||||||
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
|
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
|
||||||
exe_mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
|
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", .{
|
const dep_tracy = b.dependency("tracy", .{
|
||||||
.target = target,
|
.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,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.root_source_file = b.path("src/stb_vorbis.zig"),
|
.root_source_file = b.path("src/stb_vorbis.zig"),
|
||||||
.link_libc = true,
|
.link_libc = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
mod_stb_image.addIncludePath(stb_dependency.path("."));
|
mod_stb_vorbis.addIncludePath(stb_dependency.path("."));
|
||||||
mod_stb_image.addCSourceFile(.{
|
mod_stb_vorbis.addCSourceFile(.{
|
||||||
.file = b.path("src/stb_vorbis_impl.c"),
|
.file = b.path("src/stb_vorbis_impl.c"),
|
||||||
.flags = &.{}
|
.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 Platform = @import("./platform.zig");
|
||||||
const Gfx = Platform.Gfx;
|
const Gfx = Platform.Gfx;
|
||||||
const ImGUI = Platform.ImGUI;
|
const ImGUI = Platform.ImGUI;
|
||||||
|
const Rect = Platform.Rect;
|
||||||
|
|
||||||
const App = @This();
|
const App = @This();
|
||||||
|
|
||||||
@ -17,13 +18,14 @@ bg: Platform.Color,
|
|||||||
show_first_window: bool,
|
show_first_window: bool,
|
||||||
check: bool,
|
check: bool,
|
||||||
player_pos: Vec2,
|
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 {
|
pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||||
const cwd = std.Io.Dir.cwd();
|
const cwd = std.Io.Dir.cwd();
|
||||||
const sokoban_spritesheet = try cwd.readFileAlloc(
|
const sokoban_spritesheet = try cwd.readFileAlloc(
|
||||||
plt.io,
|
plt.io,
|
||||||
"assets/sokoban/Spritesheet/sokoban_spritesheet.png",
|
"assets/sokoban/sokoban_tilesheet.png",
|
||||||
plt.gpa,
|
plt.gpa,
|
||||||
.unlimited
|
.unlimited
|
||||||
);
|
);
|
||||||
@ -32,15 +34,35 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
var image = try STBImage.load(sokoban_spritesheet);
|
var image = try STBImage.load(sokoban_spritesheet);
|
||||||
defer image.deinit();
|
defer image.deinit();
|
||||||
|
|
||||||
const tex = try Gfx.initTexture();
|
const spritesheet = Gfx.ImageData{
|
||||||
try Gfx.setTexture(tex, image.rgba8_pixels, image.width, image.height);
|
.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{
|
self.* = App{
|
||||||
.bg = .black,
|
.bg = .black,
|
||||||
.player_pos = .init(100, 100),
|
.player_pos = .init(100, 100),
|
||||||
.show_first_window = true,
|
.show_first_window = true,
|
||||||
.check = false,
|
.check = false,
|
||||||
.tex = tex
|
.sprite_sprite = player_sprite,
|
||||||
|
.wall_sprite = wall_sprite
|
||||||
};
|
};
|
||||||
|
|
||||||
return null;
|
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));
|
self.player_pos = self.player_pos.add(dir.multiplyScalar(50 * dt));
|
||||||
|
|
||||||
Gfx.setClearColor(self.bg);
|
Gfx.setClearColor(self.bg);
|
||||||
Gfx.drawTexture(self.tex, self.player_pos, .init(100, 100), .rgb(200, 0, 0));
|
Gfx.drawSprite(self.sprite_sprite, self.player_pos, .init(100, 100), .white);
|
||||||
Gfx.drawRectangle(self.player_pos.add(.init(0, 200)), .init(100, 100), .rgb(255, 255, 255));
|
Gfx.drawSprite(self.wall_sprite, self.player_pos.add(.init(0, 200)), .init(100, 100), .white);
|
||||||
|
|
||||||
if (ImGUI.beginWindow(.{
|
if (ImGUI.beginWindow(.{
|
||||||
.name = "Hello",
|
.name = "Hello",
|
||||||
|
|||||||
438
src/graphics.zig
438
src/graphics.zig
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const log = std.log.scoped(.graphics);
|
const log = std.log.scoped(.graphics);
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
const sokol = @import("sokol");
|
||||||
const sg = sokol.gfx;
|
const sg = sokol.gfx;
|
||||||
@ -11,7 +12,9 @@ const Math = @import("math");
|
|||||||
const Vec2 = Math.Vec2;
|
const Vec2 = Math.Vec2;
|
||||||
const Vec4 = Math.Vec4;
|
const Vec4 = Math.Vec4;
|
||||||
const Mat4 = Math.Mat4;
|
const Mat4 = Math.Mat4;
|
||||||
|
const Rect = Math.Rect;
|
||||||
|
|
||||||
|
const STBRectPack = @import("stb_rect_pack");
|
||||||
const Color = @import("./color.zig");
|
const Color = @import("./color.zig");
|
||||||
const shd = @import("shader");
|
const shd = @import("shader");
|
||||||
const SlotMapType = @import("slot_map.zig").SlotMapType;
|
const SlotMapType = @import("slot_map.zig").SlotMapType;
|
||||||
@ -23,8 +26,8 @@ const State = struct {
|
|||||||
pipeline: sg.Pipeline,
|
pipeline: sg.Pipeline,
|
||||||
bindings: sg.Bindings,
|
bindings: sg.Bindings,
|
||||||
|
|
||||||
default_texture: TextureId,
|
default_sprite: Sprite.Id,
|
||||||
error_texture: TextureId,
|
error_sprite: Sprite.Id,
|
||||||
default_sampler: sg.Sampler,
|
default_sampler: sg.Sampler,
|
||||||
|
|
||||||
quads_buffer: [2048]Quad,
|
quads_buffer: [2048]Quad,
|
||||||
@ -32,13 +35,15 @@ const State = struct {
|
|||||||
|
|
||||||
clear_color: Color,
|
clear_color: Color,
|
||||||
|
|
||||||
textures_buffer: [max_textures]Texture,
|
textures_buffer: [Texture.max_textures]Texture.SlotMap.Slot,
|
||||||
textures_slotmap: TextureSlotMap,
|
textures: Texture.SlotMap,
|
||||||
};
|
|
||||||
|
|
||||||
const max_textures = 64;
|
spritesheet_texture: Texture.Id,
|
||||||
const TextureSlotMap = SlotMapType(std.math.IntFittingRange(0, max_textures-1), u8);
|
spritesheet_size: Vec2,
|
||||||
pub const TextureId = TextureSlotMap.Id;
|
spritesheet_stale: bool,
|
||||||
|
sprites_buffer: [Sprite.max_sprites]Sprite.SlotMap.Slot,
|
||||||
|
sprites: Sprite.SlotMap,
|
||||||
|
};
|
||||||
|
|
||||||
var g_state: State = undefined;
|
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),
|
.quads = .initBuffer(&self.quads_buffer),
|
||||||
.clear_color = .black,
|
.clear_color = .black,
|
||||||
.textures_buffer = undefined,
|
.textures_buffer = undefined,
|
||||||
.textures_slotmap = .init(try .initCapacity(gpa, max_textures)),
|
.textures = .init(.initBuffer(&self.textures_buffer)),
|
||||||
.default_texture = undefined,
|
.default_sprite = undefined,
|
||||||
.error_texture = 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;
|
var pixels: [width * height * 4]u8 = undefined;
|
||||||
@memset(&pixels, 0xFF);
|
@memset(&pixels, 0xFF);
|
||||||
|
|
||||||
self.default_texture = try initTexture();
|
self.default_sprite = try initSprite();
|
||||||
try setTexture(self.default_texture, &pixels, width, height);
|
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();
|
self.error_sprite = try initSprite();
|
||||||
try setTexture(self.error_texture, &pixels, width, height);
|
setSprite(self.error_sprite, .{
|
||||||
|
.image = .{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.pixels = .{ .rgba8 = &pixels }
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.spritesheet_texture = try initTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit() void {
|
pub fn deinit() void {
|
||||||
const self = &g_state;
|
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.destroyPipeline(self.pipeline);
|
||||||
sg.destroyShader(self.shader);
|
sg.destroyShader(self.shader);
|
||||||
@ -227,9 +257,8 @@ pub fn beginFrame() void {
|
|||||||
.swapchain = sglue.swapchain()
|
.swapchain = sglue.swapchain()
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(self.textures_slotmap.exists(self.default_texture));
|
const spritesheet_texture = self.textures.getAssumeExists(self.spritesheet_texture);
|
||||||
const default_texture = self.textures_buffer[self.default_texture.index];
|
self.bindings.views[shd.VIEW_tex] = spritesheet_texture.view;
|
||||||
self.bindings.views[shd.VIEW_tex] = default_texture.view;
|
|
||||||
self.bindings.samplers[shd.SMP_smp] = self.default_sampler;
|
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 {
|
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
|
||||||
const self = &g_state;
|
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 {
|
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;
|
var quad: Quad = undefined;
|
||||||
quad.setColor(color);
|
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[0] = top_right;
|
||||||
quad.vertices[1] = top_left;
|
quad.vertices[1] = top_left;
|
||||||
quad.vertices[2] = bottom_right;
|
quad.vertices[2] = bottom_right;
|
||||||
quad.vertices[3] = bottom_left;
|
quad.vertices[3] = bottom_left;
|
||||||
|
|
||||||
draw(.{
|
draw(.{
|
||||||
.texture = self.default_texture,
|
.texture = self.default_sprite,
|
||||||
.quad = quad
|
.quad = quad
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initTexture() !TextureId {
|
pub fn initTexture() !Texture.Id {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
const id = try self.textures_slotmap.insertBounded();
|
const id = try self.textures.insertBounded();
|
||||||
|
|
||||||
const image = sg.allocImage();
|
const image = sg.allocImage();
|
||||||
assert(image.id != sg.invalid_id);
|
assert(image.id != sg.invalid_id);
|
||||||
@ -322,7 +351,7 @@ pub fn initTexture() !TextureId {
|
|||||||
const view = sg.allocView();
|
const view = sg.allocView();
|
||||||
assert(view.id != sg.invalid_id);
|
assert(view.id != sg.invalid_id);
|
||||||
|
|
||||||
const texture = &self.textures_buffer[id.index];
|
const texture = self.textures.getAssumeExists(id);
|
||||||
texture.* = .{
|
texture.* = .{
|
||||||
.image = image,
|
.image = image,
|
||||||
.view = view
|
.view = view
|
||||||
@ -330,14 +359,14 @@ pub fn initTexture() !TextureId {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinitTexture(id: TextureId) void {
|
pub fn deinitTexture(id: Texture.Id) void {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
if (self.textures_slotmap.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];
|
||||||
if (texture.view.id == bound_view.id) {
|
if (texture.view.id == bound_view.id) {
|
||||||
assert(self.textures_slotmap.exists(self.default_texture));
|
assert(self.textures.exists(self.default_sprite));
|
||||||
bound_view.* = self.textures_buffer[self.default_texture.index].view;
|
bound_view.* = self.textures_buffer[self.default_sprite.index].view;
|
||||||
}
|
}
|
||||||
|
|
||||||
sg.destroyView(texture.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;
|
const self = &g_state;
|
||||||
if (!self.textures_slotmap.exists(id)) {
|
if (!self.textures.exists(id)) {
|
||||||
return error.NotFound;
|
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) {
|
if (sg.queryImageState(texture.image) == .VALID) {
|
||||||
sg.uninitImage(texture.image);
|
sg.uninitImage(texture.image);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pixel_count = width*height;
|
const pixel_count = texture_data.width*texture_data.height;
|
||||||
|
|
||||||
var image_data: sg.ImageData = .{};
|
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, .{
|
sg.initImage(texture.image, .{
|
||||||
.type = ._2D,
|
.type = ._2D,
|
||||||
.width = @intCast(width),
|
.width = @intCast(texture_data.width),
|
||||||
.height = @intCast(height),
|
.height = @intCast(texture_data.height),
|
||||||
.num_mipmaps = 1,
|
.num_mipmaps = 1,
|
||||||
.pixel_format = .RGBA8,
|
.pixel_format = .RGBA8,
|
||||||
.data = image_data,
|
.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;
|
var quad: Quad = undefined;
|
||||||
quad.setColor(color);
|
quad.setColor(color);
|
||||||
quad.setRect(pos, size);
|
quad.setRect(.{ .pos = pos, .size = size });
|
||||||
quad.setUVRect(.init(0, 0), .init(1, 1));
|
quad.setUVRect(.init(0, 0, 1, 1));
|
||||||
|
|
||||||
draw(.{
|
draw(.{
|
||||||
.texture = id,
|
.texture = id,
|
||||||
@ -391,7 +421,7 @@ pub fn drawTexture(id: TextureId, pos: Vec2, size: Vec2, color: Color) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DrawOptions = struct {
|
const DrawOptions = struct {
|
||||||
texture: TextureId,
|
texture: Texture.Id,
|
||||||
quad: Quad
|
quad: Quad
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -400,12 +430,18 @@ pub fn draw(opts: DrawOptions) void {
|
|||||||
|
|
||||||
var quad = opts.quad;
|
var quad = opts.quad;
|
||||||
var texture = opts.texture;
|
var texture = opts.texture;
|
||||||
if (!self.textures_slotmap.exists(texture)) {
|
if (!self.textures.exists(texture)) {
|
||||||
texture = self.error_texture;
|
if (getSpriteUVRect(self.error_sprite)) |error_uv| {
|
||||||
quad.setColor(.white);
|
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];
|
const bound_view = &self.bindings.views[shd.VIEW_tex];
|
||||||
if (bound_view.id != view.id) {
|
if (bound_view.id != view.id) {
|
||||||
@ -420,9 +456,311 @@ pub fn draw(opts: DrawOptions) void {
|
|||||||
self.quads.appendAssumeCapacity(quad);
|
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,
|
image: sg.Image,
|
||||||
view: sg.View,
|
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 {
|
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[0].texcoord = pos;
|
||||||
self.vertices[1].texcoord = pos.add(.init(size.x, 0));
|
self.vertices[1].texcoord = pos.add(.init(size.x, 0));
|
||||||
self.vertices[2].texcoord = pos.add(.init(0, size.y));
|
self.vertices[2].texcoord = pos.add(.init(0, size.y));
|
||||||
self.vertices[3].texcoord = pos.add(size);
|
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[0].position = pos;
|
||||||
self.vertices[1].position = pos.add(.init(size.x, 0));
|
self.vertices[1].position = pos.add(.init(size.x, 0));
|
||||||
self.vertices[2].position = pos.add(.init(0, size.y));
|
self.vertices[2].position = pos.add(.init(0, size.y));
|
||||||
|
|||||||
@ -12,10 +12,11 @@ const sg = sokol.gfx;
|
|||||||
const sglue = sokol.glue;
|
const sglue = sokol.glue;
|
||||||
const sgl = sokol.gl;
|
const sgl = sokol.gl;
|
||||||
|
|
||||||
const Math = @import("math");
|
pub const Math = @import("math");
|
||||||
const Vec2 = Math.Vec2;
|
pub const Vec2 = Math.Vec2;
|
||||||
const Vec4 = Math.Vec4;
|
pub const Vec4 = Math.Vec4;
|
||||||
const Mat4 = Math.Mat4;
|
pub const Mat4 = Math.Mat4;
|
||||||
|
pub const Rect = Math.Rect;
|
||||||
|
|
||||||
const tracy = @import("tracy");
|
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:
|
/// For more details about this data structure checkout this podcast episode from Wookash:
|
||||||
/// * https://www.youtube.com/watch?v=ShSGHb65f3M
|
/// * 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(Generation) == .int);
|
||||||
assert(@typeInfo(Index) == .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.
|
// So in the end idk if it's worth it.
|
||||||
pub const Slot = struct {
|
pub const Slot = struct {
|
||||||
|
value: Value,
|
||||||
generation: Generation,
|
generation: Generation,
|
||||||
next_hole: ?Index,
|
next_hole: ?Index,
|
||||||
used: bool
|
used: bool
|
||||||
@ -109,6 +110,7 @@ pub fn SlotMapType(Index: type, Generation: type) type {
|
|||||||
const index: Index = @intCast(self.slots.items.len);
|
const index: Index = @intCast(self.slots.items.len);
|
||||||
self.slots.items.len += 1;
|
self.slots.items.len += 1;
|
||||||
self.slots.items[index] = Slot{
|
self.slots.items[index] = Slot{
|
||||||
|
.value = undefined,
|
||||||
.generation = 0,
|
.generation = 0,
|
||||||
.next_hole = null,
|
.next_hole = null,
|
||||||
.used = false
|
.used = false
|
||||||
@ -171,6 +173,11 @@ pub fn SlotMapType(Index: type, Generation: type) type {
|
|||||||
return slot.used and slot.generation == id.generation;
|
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 {
|
pub fn removeAssumeExists(self: *Self, id: Id) void {
|
||||||
assert(self.exists(id));
|
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.
|
// TODO: Add more rigorous test for check if generation usage is nicely distributed.
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user