GPU-based font rendering (keming edition)

This commit is contained in:
2026-09-19 23:17:48 +02:00
parent f7576a941d
commit 183e825827
12 changed files with 966 additions and 35 deletions

View File

@@ -16,6 +16,7 @@ const Chunk = @import("Chunk.zig");
const Chunks = @import("Chunks.zig");
const CommandBuffer = @import("engine/CommandBuffer.zig");
const Engine = @import("engine/Engine.zig");
const Fonts = @import("engine/Fonts.zig");
const Iterator2 = math.Iterator2;
const Materials = @import("engine/Materials.zig");
const Player = @import("Player.zig");
@@ -47,6 +48,7 @@ blocks: Blocks,
chunks: Chunks,
skybox: Skybox,
player: Player,
font: Fonts.Id,
const max_textures = 1024;
const max_point_lights = 1024;
@@ -70,6 +72,7 @@ pub fn init() !Game {
const swapchain = ctx.swapchain;
const textures = ctx.textures;
const materials = ctx.materials;
const fonts = ctx.fonts;
const gui = ctx.gui;
var blocks = try Blocks.init();
@@ -137,7 +140,7 @@ pub fn init() !Game {
.descriptor_type = .sampled_image,
.descriptor_count = max_textures,
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
.flags = .{ .variable_descriptor_count_bit = true },
.flags = .{ .variable_descriptor_count_bit = true }, // TODO Get rid of this AND somehow integrate with Textures module?
},
},
});
@@ -442,6 +445,13 @@ pub fn init() !Game {
const block_stone = try blocks.getOrLoad("Stone.json");
const block_bedrock = try blocks.getOrLoad("Bedrock.json");
const font_id = try fonts.load("FiraMono-Regular.ttf", .{
.codepoint_ranges = &.{
.{ .start = 32, .end = 127 },
},
.name = "EB Garamond Regular",
});
// VOLATILE Load all assets before this point
const descriptor_images = try allocator_frame.alloc(vk.DescriptorImageInfo, textures.array.items.len);
@@ -619,6 +629,8 @@ pub fn init() !Game {
var skybox = try Skybox.load("skybox.hdr", 512, global_uniforms.buffer);
errdefer skybox.deinit();
const font = &fonts.array.items[@intFromEnum(font_id)];
// TODO This coupling is not elegant - the game must provide the GUI with
// uniform buffer for globals. Maybe GUI should just use their own global
// uniform buffer not for any other reason than decoupling. After all, GUI
@@ -641,6 +653,66 @@ pub fn init() !Game {
},
},
},
.{
.dst_set = gui.text_descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .uniform_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = global_uniforms.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.text_descriptor_set,
.dst_binding = 2,
.dst_array_element = @intFromEnum(font_id),
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = font.glyph_data.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.text_descriptor_set,
.dst_binding = 3,
.dst_array_element = @intFromEnum(font_id),
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = font.control_points.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.text_descriptor_set,
.dst_binding = 4,
.dst_array_element = @intFromEnum(font_id),
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = font.band_data.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
},
});
@@ -667,6 +739,7 @@ pub fn init() !Game {
.chunks = .{ .chunks = chunks },
.skybox = skybox,
.player = .init(player_position_sv, 0, 0),
.font = font_id,
};
}
@@ -741,6 +814,43 @@ pub fn update(self: *Game, dt: f32) void {
.border_radius_px = 0,
});
{
const margin: f16 = 8;
const font_size: f32 = 16;
const player_position_sv = self.player.position_sv;
gui.pushBox(.{
.background_color = .init(0, 0, 0, 0.75),
.border_color = .init(0, 0, 0, 1),
.position_sspx = .init(0, 0),
.size_px = .init(157, 4 * margin + 3 * font_size),
.border_width_px = 0,
.border_radius_px = 0,
});
gui.printLine("X {X:0>8}", .{@as(u32, @bitCast(player_position_sv.x))}, .{
.position_sspx = .init(margin, margin + font_size),
.font_id = self.font,
.cap_height_px = font_size,
.color = .init(1, 1, 1, 1),
});
gui.printLine("Y {X:0>8}", .{@as(u32, @bitCast(player_position_sv.y))}, .{
.position_sspx = .init(margin, 2 * (margin + font_size)),
.font_id = self.font,
.cap_height_px = font_size,
.color = .init(1, 1, 1, 1),
});
gui.printLine("Z {X:0>8}", .{@as(u32, @bitCast(player_position_sv.z))}, .{
.position_sspx = .init(margin, 3 * (margin + font_size)),
.font_id = self.font,
.cap_height_px = font_size,
.color = .init(1, 1, 1, 1),
});
}
self.render() catch |err| {
std.log.err("Failed to render: {s}", .{@errorName(err)});
@panic("Frame update failed");