implement mipmaps support on spritesheets
This commit is contained in:
parent
f4ca77194e
commit
ddb012d3df
@ -8,6 +8,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const assets_dir = "assets";
|
const assets_dir = "assets";
|
||||||
const asset_files = [_][]const u8{
|
const asset_files = [_][]const u8{
|
||||||
"sokoban/sokoban_tilesheet.png",
|
"sokoban/sokoban_tilesheet.png",
|
||||||
|
"sokoban/sokoban_tilesheet@2.png",
|
||||||
"roboto-font/Roboto-Regular.ttf",
|
"roboto-font/Roboto-Regular.ttf",
|
||||||
"tiled/map.tmx",
|
"tiled/map.tmx",
|
||||||
"tiled/tileset.tsx",
|
"tiled/tileset.tsx",
|
||||||
|
|||||||
25
src/app.zig
25
src/app.zig
@ -188,15 +188,26 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
.ttf_data = plt.assets.readFile("roboto-font/Roboto-Regular.ttf")
|
.ttf_data = plt.assets.readFile("roboto-font/Roboto-Regular.ttf")
|
||||||
});
|
});
|
||||||
|
|
||||||
const tilesheet_png = try STBImage.load(plt.assets.readFile("sokoban/sokoban_tilesheet.png"));
|
const tilesheet1_png = try STBImage.load(plt.assets.readFile("sokoban/sokoban_tilesheet.png"));
|
||||||
|
const tilesheet2_png = try STBImage.load(plt.assets.readFile("sokoban/sokoban_tilesheet@2.png"));
|
||||||
|
assert(tilesheet2_png.width == tilesheet1_png.width*2);
|
||||||
|
assert(tilesheet2_png.height == tilesheet1_png.height*2);
|
||||||
|
|
||||||
|
var levels = [_][*]u8{ tilesheet2_png.rgba8_pixels, tilesheet1_png.rgba8_pixels };
|
||||||
|
|
||||||
const tilesheet = try Tilesheet.init(
|
const tilesheet = try Tilesheet.init(
|
||||||
plt.arena,
|
plt.arena,
|
||||||
Gfx.ImageData{
|
.init(.{
|
||||||
.width = tilesheet_png.width,
|
.width = tilesheet2_png.width,
|
||||||
.height = tilesheet_png.height,
|
.height = tilesheet2_png.height,
|
||||||
.pixels = .{ .rgba8 = tilesheet_png.rgba8_pixels }
|
.format = .rgba8,
|
||||||
},
|
.levels = &levels,
|
||||||
.init(64, 64)
|
}),
|
||||||
|
.init(128, 128),
|
||||||
|
.{
|
||||||
|
.padding = 1,
|
||||||
|
.spritesheet = Gfx.initSpritesheet(.{ .mipmaps = levels.len })
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var player_sprites: PlayerSprites = undefined;
|
var player_sprites: PlayerSprites = undefined;
|
||||||
|
|||||||
@ -492,12 +492,12 @@ pub fn showDebug() void {
|
|||||||
}
|
}
|
||||||
if (ImGUI.beginTabItem("Buses")) {
|
if (ImGUI.beginTabItem("Buses")) {
|
||||||
defer ImGUI.endTabItem();
|
defer ImGUI.endTabItem();
|
||||||
if(ImGUI.beginTable("buses", 3, 0)) {
|
if(ImGUI.beginTable("buses", 3, .{})) {
|
||||||
defer ImGUI.endTable();
|
defer ImGUI.endTable();
|
||||||
|
|
||||||
ImGUI.tableSetupColumn("ID (Label)", 0);
|
ImGUI.tableSetupColumn("ID (Label)", .{});
|
||||||
ImGUI.tableSetupColumn("Volume", 0);
|
ImGUI.tableSetupColumn("Volume", .{});
|
||||||
ImGUI.tableSetupColumn("Multiplied volume", 0);
|
ImGUI.tableSetupColumn("Multiplied volume", .{});
|
||||||
ImGUI.tableHeadersRow();
|
ImGUI.tableHeadersRow();
|
||||||
|
|
||||||
for (0.., buses.items) |i, *bus| {
|
for (0.., buses.items) |i, *bus| {
|
||||||
|
|||||||
@ -193,11 +193,12 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
|||||||
|
|
||||||
self.default_sprite = initSprite(.{ .padding = 1 });
|
self.default_sprite = initSprite(.{ .padding = 1 });
|
||||||
setSprite(self.default_sprite, .{
|
setSprite(self.default_sprite, .{
|
||||||
.image = ImageData{
|
.mipchain = .initSingle(Image{
|
||||||
.width = width,
|
.width = width,
|
||||||
.height = height,
|
.height = height,
|
||||||
.pixels = .{ .rgba8 = &pixels }
|
.format = .rgba8,
|
||||||
}
|
.pixels = &pixels,
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,11 +228,12 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
|||||||
|
|
||||||
self.error_sprite = initSprite(.{ .padding = 1 });
|
self.error_sprite = initSprite(.{ .padding = 1 });
|
||||||
setSprite(self.error_sprite, .{
|
setSprite(self.error_sprite, .{
|
||||||
.image = .{
|
.mipchain = .initSingle(.{
|
||||||
.width = width,
|
.width = width,
|
||||||
.height = height,
|
.height = height,
|
||||||
.pixels = .{ .rgba8 = &pixels }
|
.format = .rgba8,
|
||||||
}
|
.pixels = &pixels,
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,9 +244,7 @@ pub fn deinit() void {
|
|||||||
var sprite_iter = self.sprites.iterator();
|
var sprite_iter = self.sprites.iterator();
|
||||||
while (sprite_iter.next()) |sprite_id| {
|
while (sprite_iter.next()) |sprite_id| {
|
||||||
const sprite = self.sprites.getAssumeExists(sprite_id);
|
const sprite = self.sprites.getAssumeExists(sprite_id);
|
||||||
if (sprite.data) |data| {
|
sprite.arena.deinit();
|
||||||
data.deinit(self.gpa);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sg.shutdown();
|
sg.shutdown();
|
||||||
@ -337,11 +337,11 @@ pub fn showDebug() void {
|
|||||||
if (ImGUI.beginTabItem("Spritesheets")) {
|
if (ImGUI.beginTabItem("Spritesheets")) {
|
||||||
defer ImGUI.endTabItem();
|
defer ImGUI.endTabItem();
|
||||||
|
|
||||||
if(ImGUI.beginTable("spritesheets", 2, 0)) {
|
if(ImGUI.beginTable("spritesheets", 2, .{})) {
|
||||||
defer ImGUI.endTable();
|
defer ImGUI.endTable();
|
||||||
|
|
||||||
ImGUI.tableSetupColumn("ID", 0);
|
ImGUI.tableSetupColumn("ID", .{});
|
||||||
ImGUI.tableSetupColumn("Size", 0);
|
ImGUI.tableSetupColumn("Size", .{});
|
||||||
ImGUI.tableHeadersRow();
|
ImGUI.tableHeadersRow();
|
||||||
|
|
||||||
var spritesheet_iter = self.spritesheets.iterator();
|
var spritesheet_iter = self.spritesheets.iterator();
|
||||||
@ -365,11 +365,11 @@ pub fn showDebug() void {
|
|||||||
if (ImGUI.beginTabItem("Sprites")) {
|
if (ImGUI.beginTabItem("Sprites")) {
|
||||||
defer ImGUI.endTabItem();
|
defer ImGUI.endTabItem();
|
||||||
|
|
||||||
if(ImGUI.beginTable("sprites", 2, 0)) {
|
if(ImGUI.beginTable("sprites", 2, .{})) {
|
||||||
defer ImGUI.endTable();
|
defer ImGUI.endTable();
|
||||||
|
|
||||||
ImGUI.tableSetupColumn("ID", 0);
|
ImGUI.tableSetupColumn("ID", .{});
|
||||||
ImGUI.tableSetupColumn("Position", 0);
|
ImGUI.tableSetupColumn("Position", .{});
|
||||||
ImGUI.tableHeadersRow();
|
ImGUI.tableHeadersRow();
|
||||||
|
|
||||||
var sprite_iter = self.sprites.iterator();
|
var sprite_iter = self.sprites.iterator();
|
||||||
@ -649,7 +649,7 @@ pub fn deinitTexture(id: Texture.Id) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SetTextureOptions = struct {
|
const SetTextureOptions = struct {
|
||||||
data: ImageData,
|
mipchain: Mipchain,
|
||||||
premultiplied: bool = false
|
premultiplied: bool = false
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -660,34 +660,27 @@ pub fn setTexture(id: Texture.Id, opts: SetTextureOptions) void {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const texture_data = opts.data;
|
|
||||||
const pixel_count = texture_data.width*texture_data.height;
|
|
||||||
|
|
||||||
var image_data: sg.ImageData = .{};
|
|
||||||
var pixel_format: sg.PixelFormat = .DEFAULT;
|
|
||||||
|
|
||||||
if (texture_data.pixels) |pixels| {
|
|
||||||
switch (pixels) {
|
|
||||||
.rgba8 => |rgba8| {
|
|
||||||
image_data.mip_levels[0] = sg.asRange(rgba8[0..(pixel_count*4)]);
|
|
||||||
pixel_format = .RGBA8;
|
|
||||||
},
|
|
||||||
.r8 => |r8| {
|
|
||||||
image_data.mip_levels[0] = sg.asRange(r8[0..pixel_count]);
|
|
||||||
pixel_format = .R8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Apply premultiply, will need scratch buffer
|
// TODO: Apply premultiply, will need scratch buffer
|
||||||
assert(opts.premultiplied);
|
assert(opts.premultiplied);
|
||||||
|
|
||||||
|
var mipchain = opts.mipchain;
|
||||||
|
|
||||||
|
var image_data: sg.ImageData = .{};
|
||||||
|
assert(mipchain.length() <= image_data.mip_levels.len);
|
||||||
|
for (0..mipchain.length()) |index| {
|
||||||
|
const image = mipchain.getLevel(@intCast(index));
|
||||||
|
image_data.mip_levels[index] = sg.asRange(image.pixelsSlice());
|
||||||
|
}
|
||||||
|
|
||||||
var new_image_desc = sg.ImageDesc{
|
var new_image_desc = sg.ImageDesc{
|
||||||
.type = ._2D,
|
.type = ._2D,
|
||||||
.width = @intCast(texture_data.width),
|
.width = @intCast(mipchain.width),
|
||||||
.height = @intCast(texture_data.height),
|
.height = @intCast(mipchain.height),
|
||||||
.num_mipmaps = 1,
|
.num_mipmaps = @intCast(@max(1, mipchain.length())),
|
||||||
.pixel_format = pixel_format,
|
.pixel_format = switch (mipchain.format) {
|
||||||
|
.rgba8 => .RGBA8,
|
||||||
|
.r8 => .R8,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (texture.dynamic) {
|
if (texture.dynamic) {
|
||||||
new_image_desc.usage.dynamic_update = true;
|
new_image_desc.usage.dynamic_update = true;
|
||||||
@ -841,7 +834,7 @@ pub fn draw(opts: DrawOptions) void {
|
|||||||
self.frame.quads.appendAssumeCapacity(quad);
|
self.frame.quads.appendAssumeCapacity(quad);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpriteOptions = struct {
|
pub const SpriteOptions = struct {
|
||||||
spritesheet: ?Spritesheet.Id = null,
|
spritesheet: ?Spritesheet.Id = null,
|
||||||
padding: u32 = 0,
|
padding: u32 = 0,
|
||||||
};
|
};
|
||||||
@ -855,10 +848,11 @@ pub fn initSprite(opts: SpriteOptions) Sprite.Id {
|
|||||||
|
|
||||||
const sprite = self.sprites.getAssumeExists(id);
|
const sprite = self.sprites.getAssumeExists(id);
|
||||||
sprite.* = .{
|
sprite.* = .{
|
||||||
|
.arena = .init(self.gpa),
|
||||||
.spritesheet = opts.spritesheet orelse self.default_spritesheet,
|
.spritesheet = opts.spritesheet orelse self.default_spritesheet,
|
||||||
.padding = opts.padding,
|
.padding = opts.padding,
|
||||||
.data = null,
|
.mipchain = null,
|
||||||
.position = null
|
.position = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
@ -870,11 +864,8 @@ pub fn deinitSprite(id: Sprite.Id) void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.sprites.exists(id)) {
|
if (self.sprites.get(id)) |sprite| {
|
||||||
const sprite = self.sprites.getAssumeExists(id);
|
sprite.arena.deinit();
|
||||||
if (sprite.data) |data| {
|
|
||||||
data.deinit(self.gpa);
|
|
||||||
}
|
|
||||||
self.sprites.removeAssumeExists(id);
|
self.sprites.removeAssumeExists(id);
|
||||||
|
|
||||||
if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| {
|
if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| {
|
||||||
@ -884,7 +875,7 @@ pub fn deinitSprite(id: Sprite.Id) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SetSpriteOptions = struct {
|
const SetSpriteOptions = struct {
|
||||||
image: ImageData,
|
mipchain: Mipchain,
|
||||||
rect: ?Rect = null,
|
rect: ?Rect = null,
|
||||||
premultiplied: bool = false
|
premultiplied: bool = false
|
||||||
};
|
};
|
||||||
@ -902,57 +893,62 @@ pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
var sprite_data: ImageData = .none;
|
if (opts.mipchain.format != spritesheet.format) {
|
||||||
errdefer sprite_data.deinit(self.gpa);
|
log.warn("Attempt to use image of format '{}' on a spritesheet with '{}' format", .{opts.mipchain.format, spritesheet.format});
|
||||||
|
|
||||||
if (opts.image.pixels) |pixels| {
|
|
||||||
const format = @as(ImageData.Format, pixels);
|
|
||||||
if (format != spritesheet.format) {
|
|
||||||
log.warn("Attempt to use image of format '{}' on a spritesheet with '{}' format", .{format, spritesheet.format});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (opts.mipchain.length() != spritesheet.mipmaps) {
|
||||||
|
log.warn("Attempt to use image with '{}' mipmaps on a spritesheet with '{}' mipmaps", .{opts.mipchain.length(), spritesheet.mipmaps});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var next_mipchain: Mipchain = .none;
|
||||||
|
var next_arena = std.heap.ArenaAllocator.init(self.gpa);
|
||||||
|
errdefer next_arena.deinit();
|
||||||
|
|
||||||
if (opts.rect) |rect| {
|
if (opts.rect) |rect| {
|
||||||
var format: ?ImageData.Format = null;
|
const image_opts = Image.InitOptions{
|
||||||
if (opts.image.pixels) |pixels| {
|
.width = @trunc(rect.size.x),
|
||||||
format = @as(ImageData.Format, pixels);
|
.height = @trunc(rect.size.y),
|
||||||
}
|
.format = opts.mipchain.format
|
||||||
|
};
|
||||||
sprite_data = ImageData.init(self.gpa, @trunc(rect.size.x), @trunc(rect.size.y), format) catch |e| {
|
next_mipchain = Mipchain.initAlloc(next_arena.allocator(), image_opts, opts.mipchain.length()) catch |e| {
|
||||||
log.warn("Failed to create texture data: {}", .{e});
|
log.warn("Failed to create texture data: {}", .{e});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
sprite_data.copyFrom(
|
next_mipchain.copyFromRect(
|
||||||
opts.image,
|
opts.mipchain,
|
||||||
0, 0,
|
0, 0,
|
||||||
@trunc(rect.pos.x), @trunc(rect.pos.y),
|
@trunc(rect.pos.x), @trunc(rect.pos.y),
|
||||||
@trunc(rect.size.x), @trunc(rect.size.y)
|
@trunc(rect.size.x), @trunc(rect.size.y)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
sprite_data = opts.image.clone(self.gpa) catch |e| {
|
next_mipchain = opts.mipchain.clone(next_arena.allocator()) catch |e| {
|
||||||
log.warn("Failed to clone image: {}", .{e});
|
log.warn("Failed to clone mipchain: {}", .{e});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts.premultiplied) {
|
if (!opts.premultiplied) {
|
||||||
sprite_data.premultiplyAlpha();
|
for (0..next_mipchain.length()) |index| {
|
||||||
|
next_mipchain.getLevel(index).premultiplyAlpha();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var old_size: ?Vec2 = null;
|
var prev_size: ?Vec2 = null;
|
||||||
if (sprite.data) |old_sprite_data| {
|
if (sprite.mipchain) |prev_mipchain| {
|
||||||
old_size = Vec2.initFromInt(u32, old_sprite_data.width, old_sprite_data.height);
|
prev_size = Vec2.initFromInt(u32, prev_mipchain.width, prev_mipchain.height);
|
||||||
old_sprite_data.deinit(self.gpa);
|
|
||||||
}
|
}
|
||||||
sprite.data = sprite_data;
|
sprite.arena.deinit();
|
||||||
|
sprite.mipchain = next_mipchain;
|
||||||
|
sprite.arena = next_arena;
|
||||||
|
|
||||||
const new_size = Vec2.initFromInt(u32, sprite_data.width, sprite_data.height);
|
const new_size = Vec2.initFromInt(u32, next_mipchain.width, next_mipchain.height);
|
||||||
|
|
||||||
log.debug("set sprite on {f}", .{sprite.spritesheet});
|
log.debug("set sprite on {f}", .{sprite.spritesheet});
|
||||||
|
|
||||||
if (old_size == null or !old_size.?.eql(new_size) or sprite.position == null) {
|
if (prev_size == null or !prev_size.?.eql(new_size) or sprite.position == null) {
|
||||||
sprite.position = null;
|
sprite.position = null;
|
||||||
spritesheet.needs_repack = true;
|
spritesheet.needs_repack = true;
|
||||||
}
|
}
|
||||||
@ -984,7 +980,7 @@ fn repackSpritesheet(id: Spritesheet.Id) void {
|
|||||||
var sprite_iter = self.sprites.iterator();
|
var sprite_iter = self.sprites.iterator();
|
||||||
while (sprite_iter.next()) |sprite_id| {
|
while (sprite_iter.next()) |sprite_id| {
|
||||||
const sprite = self.sprites.getAssumeExists(sprite_id);
|
const sprite = self.sprites.getAssumeExists(sprite_id);
|
||||||
if (sprite.data != null and sprite.spritesheet == id) {
|
if (sprite.mipchain != null and sprite.spritesheet == id) {
|
||||||
sprites.appendAssumeCapacity(sprite);
|
sprites.appendAssumeCapacity(sprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -992,10 +988,11 @@ fn repackSpritesheet(id: Spritesheet.Id) void {
|
|||||||
var rects: [Sprite.max_sprites]STBRectPack.Rect = undefined;
|
var rects: [Sprite.max_sprites]STBRectPack.Rect = undefined;
|
||||||
@memset(&rects, .{});
|
@memset(&rects, .{});
|
||||||
for (0.., sprites.items) |i, sprite| {
|
for (0.., sprites.items) |i, sprite| {
|
||||||
const sprite_data = sprite.data.?;
|
const sprite_mipchain = sprite.mipchain.?;
|
||||||
|
const padding = sprite.padding << @intCast(sprite_mipchain.length()-1);
|
||||||
rects[i] = .{
|
rects[i] = .{
|
||||||
.w = @intCast(sprite_data.width + sprite.padding * 2),
|
.w = @intCast(sprite_mipchain.width + padding * 2),
|
||||||
.h = @intCast(sprite_data.height + sprite.padding * 2),
|
.h = @intCast(sprite_mipchain.height + padding * 2),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1012,8 +1009,9 @@ fn repackSpritesheet(id: Spritesheet.Id) void {
|
|||||||
|
|
||||||
for (0.., sprites.items) |i, sprite| {
|
for (0.., sprites.items) |i, sprite| {
|
||||||
assert(rects[i].was_packed == 1);
|
assert(rects[i].was_packed == 1);
|
||||||
const x: u32 = @as(u32, @intCast(rects[i].x)) + sprite.padding;
|
const padding = sprite.padding << @intCast(sprite.mipchain.?.length()-1);
|
||||||
const y: u32 = @as(u32, @intCast(rects[i].y)) + sprite.padding;
|
const x: u32 = @as(u32, @intCast(rects[i].x)) + padding;
|
||||||
|
const y: u32 = @as(u32, @intCast(rects[i].y)) + padding;
|
||||||
sprite.position = .initFromInt(u32, x, y);
|
sprite.position = .initFromInt(u32, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1053,16 +1051,20 @@ fn rebuildSpritesheetTextureIfNeeded(id: Spritesheet.Id) void {
|
|||||||
|
|
||||||
log.debug("[{}] Rebuild spritesheet texture: {f}", .{self.frame.index, id});
|
log.debug("[{}] Rebuild spritesheet texture: {f}", .{self.frame.index, id});
|
||||||
|
|
||||||
const image_data = ImageData.init(
|
var mipchain_arena = std.heap.ArenaAllocator.init(self.gpa);
|
||||||
self.gpa,
|
defer mipchain_arena.deinit();
|
||||||
@intFromFloat(spritesheet.size.x),
|
const mipchain = Mipchain.initAlloc(
|
||||||
@intFromFloat(spritesheet.size.y),
|
mipchain_arena.allocator(),
|
||||||
spritesheet.format
|
.{
|
||||||
|
.width = @intFromFloat(spritesheet.size.x),
|
||||||
|
.height = @intFromFloat(spritesheet.size.y),
|
||||||
|
.format = spritesheet.format
|
||||||
|
},
|
||||||
|
spritesheet.mipmaps
|
||||||
) catch |e| {
|
) catch |e| {
|
||||||
log.err("ImageData allocation failed: {}", .{e});
|
log.err("Mipchain allocation failed: {}", .{e});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer image_data.deinit(self.gpa);
|
|
||||||
|
|
||||||
var sprite_iter = self.sprites.iterator();
|
var sprite_iter = self.sprites.iterator();
|
||||||
while (sprite_iter.next()) |sprite_id| {
|
while (sprite_iter.next()) |sprite_id| {
|
||||||
@ -1071,60 +1073,64 @@ fn rebuildSpritesheetTextureIfNeeded(id: Spritesheet.Id) void {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sprite_data = sprite.data orelse continue;
|
const sprite_mipchain = sprite.mipchain orelse continue;
|
||||||
const sprite_position = sprite.position orelse continue;
|
const sprite_position = sprite.position orelse continue;
|
||||||
const x: u32 = @intFromFloat(sprite_position.x);
|
assert(sprite_mipchain.length() == spritesheet.mipmaps);
|
||||||
const y: u32 = @intFromFloat(sprite_position.y);
|
|
||||||
image_data.setRectZero(
|
for (0..spritesheet.mipmaps) |index| {
|
||||||
x - sprite.padding,
|
const level = mipchain.getLevel(index);
|
||||||
y - sprite.padding,
|
|
||||||
sprite_data.width + sprite.padding * 2,
|
const sprite_level = sprite_mipchain.getLevel(index);
|
||||||
sprite_data.height + sprite.padding * 2
|
const padding = sprite.padding << @intCast(sprite_mipchain.length()-1 - index);
|
||||||
|
var x: u32 = @intFromFloat(sprite_position.x);
|
||||||
|
x = x >> @intCast(index);
|
||||||
|
var y: u32 = @intFromFloat(sprite_position.y);
|
||||||
|
y = y >> @intCast(index);
|
||||||
|
|
||||||
|
level.setRectZero(
|
||||||
|
x - padding,
|
||||||
|
y - padding,
|
||||||
|
sprite_level.width + padding * 2,
|
||||||
|
sprite_level.height + padding * 2
|
||||||
);
|
);
|
||||||
image_data.copyFrom(
|
level.copyFrom(sprite_level, x, y);
|
||||||
sprite_data,
|
|
||||||
x,
|
if (padding > 0) {
|
||||||
y,
|
for (0..sprite_level.width) |ox| {
|
||||||
0,
|
const pixel = level.getPixel(x+ox, y);
|
||||||
0,
|
for (0..padding) |i| {
|
||||||
sprite_data.width,
|
@memcpy(level.getPixel(x+ox, y-(i+1)), pixel);
|
||||||
sprite_data.height
|
|
||||||
);
|
|
||||||
if (sprite.padding > 0) {
|
|
||||||
for (0..sprite_data.width) |ox| {
|
|
||||||
const pixel = image_data.getPixel(x+ox, y);
|
|
||||||
for (0..sprite.padding) |i| {
|
|
||||||
@memcpy(image_data.getPixel(x+ox, y-(i+1)), pixel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (0..sprite_data.width) |ox| {
|
for (0..sprite_level.width) |ox| {
|
||||||
const pixel = image_data.getPixel(x+ox, y+sprite_data.height-1);
|
const pixel = level.getPixel(x+ox, y+sprite_level.height-1);
|
||||||
for (0..sprite.padding) |i| {
|
for (0..padding) |i| {
|
||||||
@memcpy(image_data.getPixel(x+ox, y+sprite_data.height+i), pixel);
|
@memcpy(level.getPixel(x+ox, y+sprite_level.height+i), pixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (0..sprite_data.height) |oy| {
|
for (0..sprite_level.height) |oy| {
|
||||||
const pixel = image_data.getPixel(x, y+oy);
|
const pixel = level.getPixel(x, y+oy);
|
||||||
for (0..sprite.padding) |i| {
|
for (0..padding) |i| {
|
||||||
@memcpy(image_data.getPixel(x-(i+1), y+oy), pixel);
|
@memcpy(level.getPixel(x-(i+1), y+oy), pixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (0..sprite_data.height) |oy| {
|
for (0..sprite_level.height) |oy| {
|
||||||
const pixel = image_data.getPixel(x+sprite_data.width-1, y+oy);
|
const pixel = level.getPixel(x+sprite_level.width-1, y+oy);
|
||||||
for (0..sprite.padding) |i| {
|
for (0..padding) |i| {
|
||||||
@memcpy(image_data.getPixel(x+sprite_data.width+i, y+oy), pixel);
|
@memcpy(level.getPixel(x+sprite_level.width+i, y+oy), pixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add padding to corners.
|
// TODO: Add padding to corners.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTexture(spritesheet.texture, .{
|
setTexture(spritesheet.texture, .{
|
||||||
.data = image_data,
|
.mipchain = mipchain,
|
||||||
.premultiplied = true
|
.premultiplied = true
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1137,20 +1143,23 @@ fn ensureSpritePacked(id: Sprite.Id) bool {
|
|||||||
|
|
||||||
if (sprite.position == null) {
|
if (sprite.position == null) {
|
||||||
const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return false;
|
const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return false;
|
||||||
const sprite_data = sprite.data orelse return false;
|
const sprite_mipchain = sprite.mipchain orelse return false;
|
||||||
|
|
||||||
if (spritesheet.packer) |*packer| {
|
if (spritesheet.packer) |*packer| {
|
||||||
|
assert(sprite_mipchain.length() > 0);
|
||||||
|
const padding = sprite.padding << @intCast(sprite_mipchain.length()-1);
|
||||||
|
|
||||||
var rects: [1]STBRectPack.Rect = .{
|
var rects: [1]STBRectPack.Rect = .{
|
||||||
.{
|
.{
|
||||||
.w = @intCast(sprite_data.width + sprite.padding * 2),
|
.w = @intCast(sprite_mipchain.width + padding * 2),
|
||||||
.h = @intCast(sprite_data.height + sprite.padding * 2),
|
.h = @intCast(sprite_mipchain.height + padding * 2),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (packer.packRects(&rects)) {
|
if (packer.packRects(&rects)) {
|
||||||
// Suboptimal sprite packing, will repack at end of frame
|
// Suboptimal sprite packing, will repack at end of frame
|
||||||
assert(rects[0].was_packed == 1);
|
assert(rects[0].was_packed == 1);
|
||||||
const x: u32 = @as(u32, @intCast(rects[0].x)) + sprite.padding;
|
const x: u32 = @as(u32, @intCast(rects[0].x)) + padding;
|
||||||
const y: u32 = @as(u32, @intCast(rects[0].y)) + sprite.padding;
|
const y: u32 = @as(u32, @intCast(rects[0].y)) + padding;
|
||||||
sprite.position = .initFromInt(u32, x, y);
|
sprite.position = .initFromInt(u32, x, y);
|
||||||
spritesheet.needs_repack = true;
|
spritesheet.needs_repack = true;
|
||||||
} else {
|
} else {
|
||||||
@ -1167,7 +1176,7 @@ fn ensureSpritePacked(id: Sprite.Id) bool {
|
|||||||
pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
|
pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
const sprite = self.sprites.get(id) orelse return null;
|
const sprite = self.sprites.get(id) orelse return null;
|
||||||
const sprite_data = sprite.data orelse return null;
|
const sprite_mipchain = sprite.mipchain orelse return null;
|
||||||
|
|
||||||
const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return null;
|
const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return null;
|
||||||
|
|
||||||
@ -1177,7 +1186,7 @@ pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
|
|||||||
|
|
||||||
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_mipchain.width, sprite_mipchain.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),
|
||||||
@ -1215,7 +1224,8 @@ pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const SpriteSheetOptions = struct {
|
pub const SpriteSheetOptions = struct {
|
||||||
format: ImageData.Format = .rgba8
|
mipmaps: u32 = 1,
|
||||||
|
format: Image.Format = .rgba8
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn initSpritesheet(opts: SpriteSheetOptions) Spritesheet.Id {
|
pub fn initSpritesheet(opts: SpriteSheetOptions) Spritesheet.Id {
|
||||||
@ -1225,11 +1235,15 @@ pub fn initSpritesheet(opts: SpriteSheetOptions) Spritesheet.Id {
|
|||||||
return .nil;
|
return .nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
assert(opts.mipmaps <= sg.max_mipmaps);
|
||||||
|
assert(opts.mipmaps > 0);
|
||||||
|
|
||||||
const spritesheet = self.spritesheets.getAssumeExists(id);
|
const spritesheet = self.spritesheets.getAssumeExists(id);
|
||||||
spritesheet.* = .{
|
spritesheet.* = .{
|
||||||
.texture = initTexture(.{ .dynamic = true }),
|
.texture = initTexture(.{ .dynamic = true }),
|
||||||
.size = .init(0, 0),
|
.size = .init(0, 0),
|
||||||
.format = opts.format,
|
.format = opts.format,
|
||||||
|
.mipmaps = opts.mipmaps,
|
||||||
.needs_repack = false,
|
.needs_repack = false,
|
||||||
.needs_texture_rebuild = false,
|
.needs_texture_rebuild = false,
|
||||||
.nodes = undefined,
|
.nodes = undefined,
|
||||||
@ -1247,105 +1261,86 @@ pub fn deinitSpritesheet(id: Spritesheet.Id) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ImageData = struct {
|
pub const Image = struct {
|
||||||
const Format = enum {
|
format: Image.Format,
|
||||||
rgba8,
|
pixels: [*]u8,
|
||||||
r8
|
|
||||||
};
|
|
||||||
|
|
||||||
const Pixels = union(Format) {
|
|
||||||
rgba8: [*]u8,
|
|
||||||
r8: [*]u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pixels: ?Pixels,
|
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
|
||||||
pub const none = ImageData{
|
pub const Format = enum {
|
||||||
.pixels = null,
|
rgba8,
|
||||||
|
r8,
|
||||||
|
|
||||||
|
pub fn pixelSize(self: Image.Format) usize {
|
||||||
|
return switch(self) {
|
||||||
|
.rgba8 => 4,
|
||||||
|
.r8 => 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maxPixelSize() usize {
|
||||||
|
var result: usize = 0;
|
||||||
|
for (@typeInfo(Image.Format).@"enum".fields) |field| {
|
||||||
|
const pixel_size = Image.Format.pixelSize(@enumFromInt(field.value));
|
||||||
|
result = @max(result, pixel_size);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const none = Image{
|
||||||
|
.format = .rgba8,
|
||||||
|
.pixels = &.{},
|
||||||
.width = 0,
|
.width = 0,
|
||||||
.height = 0,
|
.height = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(gpa: Allocator, width: u32, height: u32, format: ?Format) !ImageData {
|
const InitOptions = struct {
|
||||||
const pixel_count = width * height;
|
format: Image.Format,
|
||||||
var pixels: ?Pixels = null;
|
width: u32,
|
||||||
if (format != null) {
|
height: u32,
|
||||||
pixels = switch (format.?) {
|
};
|
||||||
.rgba8 => Pixels{
|
|
||||||
.rgba8 = (try gpa.alloc(u8, pixel_count * 4)).ptr
|
pub fn initAlloc(gpa: Allocator, opts: InitOptions) !Image {
|
||||||
},
|
const pixel_count = opts.width * opts.height;
|
||||||
.r8 => Pixels{
|
const pixel_size = opts.format.pixelSize();
|
||||||
.r8 = (try gpa.alloc(u8, pixel_count)).ptr
|
|
||||||
}
|
return Image{
|
||||||
|
.format = opts.format,
|
||||||
|
.pixels = (try gpa.alloc(u8, pixel_count * pixel_size)).ptr,
|
||||||
|
.width = opts.width,
|
||||||
|
.height = opts.height
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImageData{
|
fn pixelCount(self: Image) usize {
|
||||||
.pixels = pixels,
|
return self.width * self.height;
|
||||||
.width = width,
|
|
||||||
.height = height
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initRGBA8(gpa: Allocator, width: u32, height: u32) !ImageData {
|
fn pixelsSlice(self: Image) []u8 {
|
||||||
return ImageData.init(gpa, width, height, .rgba8);
|
return self.pixels[0..(self.pixelCount() * self.format.pixelSize())];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initR8(gpa: Allocator, width: u32, height: u32) !ImageData {
|
pub fn clone(self: Image, arena: Allocator) !Image {
|
||||||
return ImageData.init(gpa, width, height, .r8);
|
return Image{
|
||||||
}
|
.pixels = (try arena.dupe(u8, self.pixelsSlice())).ptr,
|
||||||
|
.format = self.format,
|
||||||
pub fn clone(self: ImageData, gpa: Allocator) !ImageData {
|
|
||||||
const pixel_count = self.width * self.height;
|
|
||||||
|
|
||||||
var pixels: ?Pixels = null;
|
|
||||||
if (self.pixels) |self_pixels| {
|
|
||||||
pixels = switch (self_pixels) {
|
|
||||||
.rgba8 => |rgba8| Pixels{
|
|
||||||
.rgba8 = (try gpa.dupe(u8, rgba8[0..(pixel_count * 4)])).ptr
|
|
||||||
},
|
|
||||||
.r8 => |r8| Pixels{
|
|
||||||
.r8 = (try gpa.dupe(u8, r8[0..pixel_count])).ptr
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ImageData{
|
|
||||||
.pixels = pixels,
|
|
||||||
.width = self.width,
|
.width = self.width,
|
||||||
.height = self.height
|
.height = self.height
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: ImageData, gpa: Allocator) void {
|
pub fn deinit(self: Image, gpa: Allocator) void {
|
||||||
const pixel_count = self.width * self.height;
|
gpa.free(self.pixelsSlice());
|
||||||
if (self.pixels) |pixels| {
|
|
||||||
switch (pixels) {
|
|
||||||
.rgba8 => |rgba8| gpa.free(rgba8[0..(pixel_count*4)]),
|
|
||||||
.r8 => |r8| gpa.free(r8[0..pixel_count])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getPixel(self: ImageData, x: usize, y: usize) []u8 {
|
pub fn getPixel(self: Image, x: usize, y: usize) []u8 {
|
||||||
const pixel_index = y * self.width + x;
|
const pixel_index = y * self.width + x;
|
||||||
if (self.pixels) |pixels| {
|
const pixel_size = self.format.pixelSize();
|
||||||
return switch (pixels) {
|
return self.pixels[(pixel_size*pixel_index)..][0..pixel_size];
|
||||||
.rgba8 => |rgba8| rgba8[(4*pixel_index)..][0..4],
|
|
||||||
.r8 => |r8| r8[pixel_index..][0..1]
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return &.{};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setRect(self: ImageData, x: u32, y: u32, width: u32, height: u32, pixel: []const u8) void {
|
|
||||||
if (self.pixels == null) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setRect(self: Image, x: u32, y: u32, width: u32, height: u32, pixel: []const u8) void {
|
||||||
for (0..height) |oy| {
|
for (0..height) |oy| {
|
||||||
const iy = y + oy;
|
const iy = y + oy;
|
||||||
if (iy >= self.height) {
|
if (iy >= self.height) {
|
||||||
@ -1364,27 +1359,19 @@ pub const ImageData = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setRectZero(self: ImageData, x: u32, y: u32, width: u32, height: u32) void {
|
pub fn setRectZero(self: Image, x: u32, y: u32, width: u32, height: u32) void {
|
||||||
if (self.pixels == null) {
|
const zero_pixel_buff: [Image.Format.maxPixelSize()]u8 = @splat(0);
|
||||||
return;
|
const zero_pixel = zero_pixel_buff[0..self.format.pixelSize()];
|
||||||
|
self.setRect(x, y, width, height, zero_pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pixel = switch (self.pixels.?) {
|
pub fn premultiplyAlpha(self: Image) void {
|
||||||
.rgba8 => &[_]u8{ 0, 0, 0, 0 },
|
if (self.format != .rgba8) return;
|
||||||
.r8 => &[_]u8{ 0 }
|
|
||||||
};
|
|
||||||
self.setRect(x, y, width, height, pixel);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn premultiplyAlpha(self: ImageData) void {
|
|
||||||
const pixels = self.pixels orelse return;
|
|
||||||
if (pixels != .rgba8) return;
|
|
||||||
const rgba8_pixels = pixels.rgba8;
|
|
||||||
|
|
||||||
for (0..self.height) |y| {
|
for (0..self.height) |y| {
|
||||||
for (0..self.width) |x| {
|
for (0..self.width) |x| {
|
||||||
const pixel_index = y * self.width + x;
|
const pixel_index = y * self.width + x;
|
||||||
const rgba8_pixel = rgba8_pixels[(4*pixel_index)..][0..4];
|
const rgba8_pixel = self.pixels[(4*pixel_index)..][0..4];
|
||||||
|
|
||||||
inline for (0..3) |i| {
|
inline for (0..3) |i| {
|
||||||
rgba8_pixel[i] = @intCast(@as(u16, rgba8_pixel[i]) * rgba8_pixel[3] / 255);
|
rgba8_pixel[i] = @intCast(@as(u16, rgba8_pixel[i]) * rgba8_pixel[3] / 255);
|
||||||
@ -1393,9 +1380,9 @@ pub const ImageData = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copyFrom(
|
pub fn copyFromRect(
|
||||||
self: ImageData,
|
self: Image,
|
||||||
source: ImageData,
|
source: Image,
|
||||||
cx: u32,
|
cx: u32,
|
||||||
cy: u32,
|
cy: u32,
|
||||||
source_cx: u32,
|
source_cx: u32,
|
||||||
@ -1403,9 +1390,7 @@ pub const ImageData = struct {
|
|||||||
source_width: u32,
|
source_width: u32,
|
||||||
source_height: u32,
|
source_height: u32,
|
||||||
) void {
|
) void {
|
||||||
if (source.pixels == null or self.pixels == null) {
|
assert(source.format == self.format);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0..source_height) |oy| {
|
for (0..source_height) |oy| {
|
||||||
const source_y = source_cy+oy;
|
const source_y = source_cy+oy;
|
||||||
@ -1434,6 +1419,170 @@ pub const ImageData = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn copyFrom(
|
||||||
|
self: Image,
|
||||||
|
source: Image,
|
||||||
|
cx: u32,
|
||||||
|
cy: u32,
|
||||||
|
) void {
|
||||||
|
self.copyFromRect(source, cx, cy, 0, 0, source.width, source.height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Mipchain = struct {
|
||||||
|
format: Image.Format,
|
||||||
|
level0: [*]u8, // level0 is stored inline, so that we could cast a `Image` into a `Mipchain` without allocations
|
||||||
|
next_levels: [][*]u8, // Each level after `level0` must be half the size
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
|
||||||
|
pub const none = Mipchain{
|
||||||
|
.format = .rgba8,
|
||||||
|
.level0 = &.{},
|
||||||
|
.next_levels = &.{},
|
||||||
|
.width = 0,
|
||||||
|
.height = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InitOptions = struct {
|
||||||
|
format: Image.Format,
|
||||||
|
levels: [][*]u8,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(opts: InitOptions) Mipchain {
|
||||||
|
var mipchain: Mipchain = .{
|
||||||
|
.format = opts.format,
|
||||||
|
.level0 = &.{},
|
||||||
|
.next_levels = &.{},
|
||||||
|
.width = opts.width,
|
||||||
|
.height = opts.height,
|
||||||
|
};
|
||||||
|
if (opts.levels.len > 0) {
|
||||||
|
mipchain.level0 = opts.levels[0];
|
||||||
|
mipchain.next_levels = opts.levels[1..];
|
||||||
|
}
|
||||||
|
return mipchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initSingle(image: Image) Mipchain {
|
||||||
|
return Mipchain{
|
||||||
|
.format = image.format,
|
||||||
|
.level0 = image.pixels,
|
||||||
|
.next_levels = &.{},
|
||||||
|
.width = image.width,
|
||||||
|
.height = image.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone(self: Mipchain, arena: Allocator) !Mipchain {
|
||||||
|
var mipchain = Mipchain{
|
||||||
|
.format = self.format,
|
||||||
|
.width = self.width,
|
||||||
|
.height = self.height,
|
||||||
|
.level0 = &.{},
|
||||||
|
.next_levels = &.{}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (self.length() >= 2) {
|
||||||
|
mipchain.next_levels = try arena.alloc([*]u8, self.length()-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..mipchain.length()) |index| {
|
||||||
|
var level = self.getLevel(index);
|
||||||
|
const pixels = try arena.dupe(u8, level.pixelsSlice());
|
||||||
|
mipchain.getLevelPixelsPtr(index).* = pixels.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mipchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initAlloc(arena: Allocator, image_opts: Image.InitOptions, levels: usize) !Mipchain {
|
||||||
|
var mipchain = Mipchain{
|
||||||
|
.width = image_opts.width,
|
||||||
|
.height = image_opts.height,
|
||||||
|
.format = image_opts.format,
|
||||||
|
.level0 = &.{},
|
||||||
|
.next_levels = &.{}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (levels >= 2) {
|
||||||
|
mipchain.next_levels = try arena.alloc([*]u8, levels-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..mipchain.length()) |index| {
|
||||||
|
var level = mipchain.getLevel(index);
|
||||||
|
const pixels = try arena.alloc(u8, level.pixelCount() * level.format.pixelSize());
|
||||||
|
mipchain.getLevelPixelsPtr(index).* = pixels.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(mipchain.length() == levels);
|
||||||
|
|
||||||
|
return mipchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(self: Mipchain) usize {
|
||||||
|
if (self.width == 0 or self.height == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1 + self.next_levels.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getLevelPixels(self: Mipchain, index: usize) [*]u8 {
|
||||||
|
if (index == 0) {
|
||||||
|
return self.level0;
|
||||||
|
} else {
|
||||||
|
return self.next_levels[index-1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Don't like that this is redundant with `getLevelPixels`
|
||||||
|
pub fn getLevelPixelsPtr(self: *Mipchain, index: usize) *[*]u8 {
|
||||||
|
if (index == 0) {
|
||||||
|
return &self.level0;
|
||||||
|
} else {
|
||||||
|
return &self.next_levels[index-1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getLevel(self: Mipchain, index: usize) Image {
|
||||||
|
return Image{
|
||||||
|
.format = self.format,
|
||||||
|
.pixels = self.getLevelPixels(index),
|
||||||
|
.width = @max(self.width >> @intCast(index), 1),
|
||||||
|
.height = @max(self.height >> @intCast(index), 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copyFromRect(
|
||||||
|
self: Mipchain,
|
||||||
|
source: Mipchain,
|
||||||
|
cx: u32,
|
||||||
|
cy: u32,
|
||||||
|
source_cx: u32,
|
||||||
|
source_cy: u32,
|
||||||
|
source_width: u32,
|
||||||
|
source_height: u32,
|
||||||
|
) void {
|
||||||
|
assert(self.length() == source.length());
|
||||||
|
|
||||||
|
for (0..self.length()) |index| {
|
||||||
|
const self_level = self.getLevel(index);
|
||||||
|
const source_level = source.getLevel(index);
|
||||||
|
self_level.copyFromRect(
|
||||||
|
source_level,
|
||||||
|
cx >> @intCast(index),
|
||||||
|
cy >> @intCast(index),
|
||||||
|
source_cx >> @intCast(index),
|
||||||
|
source_cy >> @intCast(index),
|
||||||
|
source_width >> @intCast(index),
|
||||||
|
source_height >> @intCast(index),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Texture = struct {
|
pub const Texture = struct {
|
||||||
@ -1448,8 +1597,9 @@ pub const Texture = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Sprite = struct {
|
pub const Sprite = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
spritesheet: Spritesheet.Id,
|
spritesheet: Spritesheet.Id,
|
||||||
data: ?ImageData,
|
mipchain: ?Mipchain,
|
||||||
position: ?Vec2,
|
position: ?Vec2,
|
||||||
padding: u32,
|
padding: u32,
|
||||||
|
|
||||||
@ -1460,8 +1610,9 @@ pub const Sprite = struct {
|
|||||||
|
|
||||||
pub const Spritesheet = struct {
|
pub const Spritesheet = struct {
|
||||||
texture: Texture.Id,
|
texture: Texture.Id,
|
||||||
format: ImageData.Format,
|
format: Image.Format,
|
||||||
size: Vec2,
|
size: Vec2,
|
||||||
|
mipmaps: u32,
|
||||||
|
|
||||||
needs_repack: bool,
|
needs_repack: bool,
|
||||||
needs_texture_rebuild: bool,
|
needs_texture_rebuild: bool,
|
||||||
|
|||||||
@ -504,12 +504,24 @@ pub fn collapsingHeader(label: []const u8) bool {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn beginTable(id: [*c]const u8, columns: u32, flags: ig.ImGuiTableFlags) bool {
|
pub const TableFlags = struct {
|
||||||
|
sortable: bool = false,
|
||||||
|
|
||||||
|
fn toInt(self: TableFlags) ig.ImGuiTableFlags {
|
||||||
|
var result: ig.ImGuiTableFlags = 0;
|
||||||
|
if (self.sortable) {
|
||||||
|
result |= ig.ImGuiTableFlags_Sortable;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn beginTable(id: [*c]const u8, columns: u32, flags: TableFlags) bool {
|
||||||
if (isDisabled()) {
|
if (isDisabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ig.igBeginTable(id, @intCast(columns), flags);
|
return ig.igBeginTable(id, @intCast(columns), flags.toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn endTable() void {
|
pub fn endTable() void {
|
||||||
@ -544,12 +556,22 @@ pub fn tableSetColumnIndex(index: usize) void {
|
|||||||
_ = ig.igTableSetColumnIndex(@intCast(index));
|
_ = ig.igTableSetColumnIndex(@intCast(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tableSetupColumn(label: [*c]const u8, flags: ig.ImGuiTableColumnFlags) void {
|
pub const TableColumnFlags = struct {
|
||||||
|
|
||||||
|
fn toInt(self: TableColumnFlags) ig.ImGuiTableColumnFlags {
|
||||||
|
_ = self; // autofix
|
||||||
|
const result: ig.ImGuiTableColumnFlags = 0;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn tableSetupColumn(label: [*c]const u8, flags: TableColumnFlags) void {
|
||||||
if (isDisabled()) {
|
if (isDisabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ig.igTableSetupColumn(label, flags);
|
ig.igTableSetupColumn(label, flags.toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tableHeadersRow() void {
|
pub fn tableHeadersRow() void {
|
||||||
|
|||||||
@ -149,6 +149,11 @@ fn PlatformType(App: type) type {
|
|||||||
_ = ImGUI.beginTabBar("platform tab bar");
|
_ = ImGUI.beginTabBar("platform tab bar");
|
||||||
defer ImGUI.endTabBar();
|
defer ImGUI.endTabBar();
|
||||||
|
|
||||||
|
if (ImGUI.beginTabItem("General")) {
|
||||||
|
defer ImGUI.endTabItem();
|
||||||
|
ImGUI.text("Embedded assets: {B}", .{self.assets.totalSize()});
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGUI.beginTabItem("Graphics")) {
|
if (ImGUI.beginTabItem("Graphics")) {
|
||||||
defer ImGUI.endTabItem();
|
defer ImGUI.endTabItem();
|
||||||
Gfx.showDebug();
|
Gfx.showDebug();
|
||||||
@ -543,6 +548,14 @@ pub const Assets = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn totalSize(self: *Assets) usize {
|
||||||
|
var total: usize = 0;
|
||||||
|
for (self.files.values()) |file| {
|
||||||
|
total += file.contents.len;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
fn insertStaticFile(self: *Assets, filename: []const u8, contents: [:0]const u8) !void {
|
fn insertStaticFile(self: *Assets, filename: []const u8, contents: [:0]const u8) !void {
|
||||||
try self.files.put(self.gpa, filename, File{
|
try self.files.put(self.gpa, filename, File{
|
||||||
.contents = contents
|
.contents = contents
|
||||||
|
|||||||
@ -9,25 +9,27 @@ const Rect = Platform.Rect;
|
|||||||
|
|
||||||
const Tilesheet = @This();
|
const Tilesheet = @This();
|
||||||
|
|
||||||
image: Gfx.ImageData,
|
sprite_options: Gfx.SpriteOptions,
|
||||||
|
mipchain: Gfx.Mipchain,
|
||||||
sprites: []Gfx.Sprite.Id,
|
sprites: []Gfx.Sprite.Id,
|
||||||
tile_size: Vec2,
|
tile_size: Vec2,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
|
||||||
pub fn init(gpa: Allocator, image: Gfx.ImageData, tile_size: Vec2) !Tilesheet {
|
pub fn init(gpa: Allocator, mipchain: Gfx.Mipchain, tile_size: Vec2, sprite_options: Gfx.SpriteOptions) !Tilesheet {
|
||||||
const width = @divFloor(image.width, @as(u32, @intFromFloat(tile_size.x)));
|
const width = @divFloor(mipchain.width, @as(u32, @intFromFloat(tile_size.x)));
|
||||||
const height = @divFloor(image.height, @as(u32, @intFromFloat(tile_size.y)));
|
const height = @divFloor(mipchain.height, @as(u32, @intFromFloat(tile_size.y)));
|
||||||
const sprites = try gpa.alloc(Gfx.Sprite.Id, width * height);
|
const sprites = try gpa.alloc(Gfx.Sprite.Id, width * height);
|
||||||
errdefer gpa.free(sprites);
|
errdefer gpa.free(sprites);
|
||||||
@memset(sprites, .nil);
|
@memset(sprites, .nil);
|
||||||
|
|
||||||
return Tilesheet{
|
return Tilesheet{
|
||||||
.sprites = sprites,
|
.sprites = sprites,
|
||||||
.image = image,
|
.mipchain = mipchain,
|
||||||
.width = width,
|
.width = width,
|
||||||
.height = height,
|
.height = height,
|
||||||
.tile_size = tile_size
|
.tile_size = tile_size,
|
||||||
|
.sprite_options = sprite_options
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,9 +47,9 @@ pub fn get(self: Tilesheet, x: usize, y: usize) Gfx.Sprite.Id {
|
|||||||
|
|
||||||
const sprite_index = y * self.width + x;
|
const sprite_index = y * self.width + x;
|
||||||
if (self.sprites[sprite_index] == Gfx.Sprite.Id.nil) {
|
if (self.sprites[sprite_index] == Gfx.Sprite.Id.nil) {
|
||||||
const sprite = Gfx.initSprite(.{ .padding = 1 });
|
const sprite = Gfx.initSprite(self.sprite_options);
|
||||||
Gfx.setSprite(sprite, .{
|
Gfx.setSprite(sprite, .{
|
||||||
.image = self.image,
|
.mipchain = self.mipchain,
|
||||||
.rect = Rect.init(@floatFromInt(x), @floatFromInt(y), 1, 1).multiply(self.tile_size)
|
.rect = Rect.init(@floatFromInt(x), @floatFromInt(y), 1, 1).multiply(self.tile_size)
|
||||||
});
|
});
|
||||||
self.sprites[sprite_index] = sprite;
|
self.sprites[sprite_index] = sprite;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user