add configurable padding to sprites
This commit is contained in:
parent
e2b15895c2
commit
1e072c5607
17
build.zig
17
build.zig
@ -52,16 +52,21 @@ pub fn build(b: *std.Build) !void {
|
||||
exe_mod.addImport("sokol", mod_sokol);
|
||||
|
||||
const dep_shdc = dep_sokol.builder.dependency("shdc", .{});
|
||||
|
||||
var slang = sokol.shdc.Slang{};
|
||||
if (is_wasm) {
|
||||
slang.wgsl = true;
|
||||
} else {
|
||||
slang.glsl410 = true;
|
||||
slang.hlsl4 = true;
|
||||
slang.metal_macos = true;
|
||||
slang.glsl300es = true;
|
||||
}
|
||||
const mod_shader = try sokol.shdc.createModule(b, "shader", mod_sokol, .{
|
||||
.shdc_dep = dep_shdc,
|
||||
.input = "src/shader.glsl",
|
||||
.output = "src/shader.zig",
|
||||
.slang = .{
|
||||
.glsl410 = true,
|
||||
.hlsl4 = true,
|
||||
.metal_macos = true,
|
||||
.glsl300es = true,
|
||||
},
|
||||
.slang = slang
|
||||
});
|
||||
mod_shader.addImport("math", math_mod);
|
||||
exe_mod.addImport("shader", mod_shader);
|
||||
|
||||
@ -109,8 +109,8 @@ pub const Font = struct {
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn getGlyphKernAdvance(self: Font, prev_glyph: u32, next_glyph: u21) i32 {
|
||||
return c.stbtt_GetGlyphKernAdvance(&self.info, prev_glyph, next_glyph);
|
||||
pub fn getGlyphKernAdvance(self: Font, prev_glyph: u32, next_glyph: u32) i32 {
|
||||
return c.stbtt_GetGlyphKernAdvance(&self.info, @intCast(prev_glyph), @intCast(next_glyph));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
135
src/graphics.zig
135
src/graphics.zig
@ -71,9 +71,10 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
const shader = sg.makeShader(shd.mainShaderDesc(sg.queryBackend()));
|
||||
assert(shader.id != sg.invalid_id);
|
||||
|
||||
// TODO: Make sampler configurable.
|
||||
const default_sampler = sg.makeSampler(.{
|
||||
.min_filter = .NEAREST,
|
||||
.mag_filter = .NEAREST,
|
||||
.min_filter = .LINEAR,
|
||||
.mag_filter = .LINEAR,
|
||||
.label = "default-sampler"
|
||||
});
|
||||
assert(default_sampler.id != sg.invalid_id);
|
||||
@ -200,7 +201,8 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
nil_sprite.* = Sprite{
|
||||
.spritesheet = self.nil_spritesheet,
|
||||
.data = .none,
|
||||
.position = null
|
||||
.position = null,
|
||||
.padding = 0
|
||||
};
|
||||
}
|
||||
|
||||
@ -710,7 +712,8 @@ pub fn draw(opts: DrawOptions) void {
|
||||
}
|
||||
|
||||
const SpriteOptions = struct {
|
||||
spritesheet: ?Spritesheet.Id = null
|
||||
spritesheet: ?Spritesheet.Id = null,
|
||||
padding: u32 = 0,
|
||||
};
|
||||
|
||||
pub fn initSprite(opts: SpriteOptions) Sprite.Id {
|
||||
@ -723,6 +726,7 @@ pub fn initSprite(opts: SpriteOptions) Sprite.Id {
|
||||
const sprite = self.sprites.getAssumeExists(id);
|
||||
sprite.* = .{
|
||||
.spritesheet = opts.spritesheet orelse self.default_spritesheet,
|
||||
.padding = opts.padding,
|
||||
.data = null,
|
||||
.position = null
|
||||
};
|
||||
@ -856,8 +860,8 @@ fn repackSpritesheet(id: Spritesheet.Id) void {
|
||||
for (0.., sprites.items) |i, sprite| {
|
||||
const sprite_data = sprite.data.?;
|
||||
rects[i] = .{
|
||||
.w = @intCast(sprite_data.width),
|
||||
.h = @intCast(sprite_data.height),
|
||||
.w = @intCast(sprite_data.width + sprite.padding * 2),
|
||||
.h = @intCast(sprite_data.height + sprite.padding * 2),
|
||||
};
|
||||
}
|
||||
|
||||
@ -874,8 +878,8 @@ fn repackSpritesheet(id: Spritesheet.Id) void {
|
||||
|
||||
for (0.., sprites.items) |i, sprite| {
|
||||
assert(rects[i].was_packed == 1);
|
||||
const x: u32 = @intCast(rects[i].x);
|
||||
const y: u32 = @intCast(rects[i].y);
|
||||
const x: u32 = @as(u32, @intCast(rects[i].x)) + sprite.padding;
|
||||
const y: u32 = @as(u32, @intCast(rects[i].y)) + sprite.padding;
|
||||
sprite.position = .initFromInt(u32, x, y);
|
||||
}
|
||||
|
||||
@ -941,10 +945,18 @@ fn rebuildSpritesheetTextureIfNeeded(id: Spritesheet.Id) void {
|
||||
|
||||
const sprite_data = sprite.data orelse continue;
|
||||
const sprite_position = sprite.position orelse continue;
|
||||
const x: u32 = @intFromFloat(sprite_position.x);
|
||||
const y: u32 = @intFromFloat(sprite_position.y);
|
||||
image_data.setRectZero(
|
||||
x - sprite.padding,
|
||||
y - sprite.padding,
|
||||
sprite_data.width + sprite.padding * 2,
|
||||
sprite_data.height + sprite.padding * 2
|
||||
);
|
||||
image_data.copyFrom(
|
||||
sprite_data,
|
||||
@intFromFloat(sprite_position.x),
|
||||
@intFromFloat(sprite_position.y),
|
||||
x,
|
||||
y,
|
||||
0,
|
||||
0,
|
||||
sprite_data.width,
|
||||
@ -968,14 +980,16 @@ fn ensureSpritePacked(id: Sprite.Id) bool {
|
||||
if (spritesheet.packer) |*packer| {
|
||||
var rects: [1]STBRectPack.Rect = .{
|
||||
.{
|
||||
.w = @intCast(sprite_data.width),
|
||||
.h = @intCast(sprite_data.height),
|
||||
.w = @intCast(sprite_data.width + sprite.padding * 2),
|
||||
.h = @intCast(sprite_data.height + sprite.padding * 2),
|
||||
}
|
||||
};
|
||||
if (packer.packRects(&rects)) {
|
||||
// Suboptimal sprite packing, will repack at end of frame
|
||||
assert(rects[0].was_packed == 1);
|
||||
sprite.position = .initFromInt(i32, rects[0].x, rects[0].y);
|
||||
const x: u32 = @as(u32, @intCast(rects[0].x)) + sprite.padding;
|
||||
const y: u32 = @as(u32, @intCast(rects[0].y)) + sprite.padding;
|
||||
sprite.position = .initFromInt(u32, x, y);
|
||||
spritesheet.needs_repack = true;
|
||||
} else {
|
||||
log.debug("Failed to pack a single sprite to spritesheet", .{});
|
||||
@ -1168,6 +1182,7 @@ pub fn drawText(id: Font.Id, opts: DrawTextOptions) void {
|
||||
var current_point = opts.pos;
|
||||
current_point.y += baseline;
|
||||
|
||||
var prev_glyph: ?u32 = null;
|
||||
var cursor: usize = 0;
|
||||
while (cursor < opts.text.len) {
|
||||
const codepoint_len = std.unicode.utf8ByteSequenceLength(opts.text[cursor]) catch |e| {
|
||||
@ -1194,39 +1209,49 @@ pub fn drawText(id: Font.Id, opts: DrawTextOptions) void {
|
||||
glyph = font.cache.get(glyph_id);
|
||||
glyph.box = stb_font.getGlyphBitmapBox(glyph_key.index, glyph_key.scale_x, glyph_key.scale_y);
|
||||
|
||||
assert(glyph.sprite == self.nil_sprite);
|
||||
glyph.sprite = initSprite(.{ .spritesheet = font.spritesheet });
|
||||
|
||||
const width: u32 = @intCast(glyph.box.x1 - glyph.box.x0);
|
||||
const height: u32 = @intCast(glyph.box.y1 - glyph.box.y0);
|
||||
if (width > 0 and height > 0) {
|
||||
assert(glyph.sprite == self.nil_sprite);
|
||||
glyph.sprite = initSprite(.{ .spritesheet = font.spritesheet, .padding = 1 });
|
||||
|
||||
assert(width < Font.Glyph.max_width);
|
||||
assert(height < Font.Glyph.max_height);
|
||||
assert(width < Font.Glyph.max_width);
|
||||
assert(height < Font.Glyph.max_height);
|
||||
|
||||
var bitmap_buffer: [Font.Glyph.max_width * Font.Glyph.max_height]u8 = undefined;
|
||||
const bitmap = ImageData{
|
||||
.pixels = .{ .r8 = (&bitmap_buffer).ptr },
|
||||
.width = width,
|
||||
.height = height
|
||||
};
|
||||
stb_font.makeGlyphBitmap(
|
||||
bitmap.pixels.?.r8,
|
||||
width, height,
|
||||
width,
|
||||
glyph_key.scale_x, glyph_key.scale_y,
|
||||
glyph_key.index
|
||||
);
|
||||
setSprite(glyph.sprite, .{ .image = bitmap });
|
||||
var bitmap_buffer: [Font.Glyph.max_width * Font.Glyph.max_height]u8 = undefined;
|
||||
const bitmap = ImageData{
|
||||
.pixels = .{ .r8 = (&bitmap_buffer).ptr },
|
||||
.width = width,
|
||||
.height = height
|
||||
};
|
||||
stb_font.makeGlyphBitmap(
|
||||
bitmap.pixels.?.r8,
|
||||
width, height,
|
||||
width,
|
||||
glyph_key.scale_x, glyph_key.scale_y,
|
||||
glyph_key.index
|
||||
);
|
||||
setSprite(glyph.sprite, .{ .image = bitmap });
|
||||
} else {
|
||||
glyph.sprite = self.nil_sprite;
|
||||
}
|
||||
}
|
||||
glyph.used_this_frame = true;
|
||||
|
||||
if (prev_glyph != null) {
|
||||
const kerning = stb_font.getGlyphKernAdvance(prev_glyph.?, glyph_index);
|
||||
current_point.x += @as(f32, @floatFromInt(kerning)) * scale;
|
||||
}
|
||||
|
||||
if (glyph.sprite != self.nil_sprite) {
|
||||
const glyph_size = Vec2.initFromInt(i32, glyph.box.x1 - glyph.box.x0, glyph.box.y1 - glyph.box.y0);
|
||||
const glyph_pos = current_point.sub(.initFromInt(i32, glyph.box.x0, -glyph.box.y0));
|
||||
drawSprite(glyph.sprite, glyph_pos, glyph_size, opts.color);
|
||||
}
|
||||
|
||||
const hmetrics = stb_font.getGlyphHMetrics(glyph_index);
|
||||
|
||||
const glyph_size = Vec2.initFromInt(i32, glyph.box.x1 - glyph.box.x0, glyph.box.y1 - glyph.box.y0);
|
||||
const glyph_pos = current_point.sub(.initFromInt(i32, glyph.box.x0, -glyph.box.y0));
|
||||
drawSprite(glyph.sprite, glyph_pos, glyph_size, opts.color);
|
||||
|
||||
current_point.x += @as(f32, @floatFromInt(hmetrics.advance_width)) * scale;
|
||||
prev_glyph = glyph_index;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1324,6 +1349,41 @@ pub const ImageData = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setRect(self: ImageData, x: u32, y: u32, width: u32, height: u32, pixel: []const u8) void {
|
||||
if (self.pixels == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (0..height) |oy| {
|
||||
const iy = y + oy;
|
||||
if (iy >= self.height) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (0..width) |ox| {
|
||||
const ix = x + ox;
|
||||
if (ix >= self.width) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dest_pixel = self.getPixel(@intCast(ix), @intCast(iy));
|
||||
@memcpy(dest_pixel, pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setRectZero(self: ImageData, x: u32, y: u32, width: u32, height: u32) void {
|
||||
if (self.pixels == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pixel = switch (self.pixels.?) {
|
||||
.rgba8 => &[_]u8{ 0, 0, 0, 0 },
|
||||
.r8 => &[_]u8{ 0 }
|
||||
};
|
||||
self.setRect(x, y, width, height, pixel);
|
||||
}
|
||||
|
||||
pub fn copyFrom(
|
||||
self: ImageData,
|
||||
source: ImageData,
|
||||
@ -1382,6 +1442,7 @@ pub const Sprite = struct {
|
||||
spritesheet: Spritesheet.Id,
|
||||
data: ?ImageData,
|
||||
position: ?Vec2,
|
||||
padding: u32,
|
||||
|
||||
const max_sprites = 128;
|
||||
const SlotMap = SlotMapType(std.math.IntFittingRange(0, max_sprites-1), u8, Sprite);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user