GPU-based font rendering (keming edition)
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,2 +1,3 @@
|
|||||||
*.hdr filter=lfs diff=lfs merge=lfs -text
|
*.hdr filter=lfs diff=lfs merge=lfs -text
|
||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
BIN
assets/fonts/FiraMono-Regular.ttf
(Stored with Git LFS)
Normal file
BIN
assets/fonts/FiraMono-Regular.ttf
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -3,15 +3,17 @@ import "includes/structs";
|
|||||||
struct Varyings {
|
struct Varyings {
|
||||||
float4 positionCS : SV_Position;
|
float4 positionCS : SV_Position;
|
||||||
nointerpolation uint instance;
|
nointerpolation uint instance;
|
||||||
float2 texCoordEM : U_TEXCOORD;
|
float2 texCoordEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GuiText {
|
struct GuiText {
|
||||||
float4 color;
|
float4 color;
|
||||||
// Index into _GlyphData
|
|
||||||
uint glyph;
|
|
||||||
float2 bottomLeftSSPX;
|
float2 bottomLeftSSPX;
|
||||||
float fontSizePX;
|
float fontSizePX;
|
||||||
|
// Index into _GlyphData[font]
|
||||||
|
uint16_t glyph;
|
||||||
|
// Index into _GlyphData, _ControlPoints and _BandData
|
||||||
|
uint16_t font;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GlyphData {
|
struct GlyphData {
|
||||||
@@ -28,10 +30,9 @@ struct GlyphData {
|
|||||||
|
|
||||||
[[vk::binding(0, 0)]] ConstantBuffer<GlobalUniforms> _Global;
|
[[vk::binding(0, 0)]] ConstantBuffer<GlobalUniforms> _Global;
|
||||||
[[vk::binding(1, 0)]] StructuredBuffer<GuiText> _Instances;
|
[[vk::binding(1, 0)]] StructuredBuffer<GuiText> _Instances;
|
||||||
|
[[vk::binding(2, 0)]] StructuredBuffer<GlyphData> _GlyphData[];
|
||||||
[[vk::binding(0, 1)]] StructuredBuffer<GlyphData> _GlyphData;
|
[[vk::binding(3, 0)]] StructuredBuffer<half2> _ControlPoints[];
|
||||||
[[vk::binding(1, 1)]] StructuredBuffer<half2> _ControlPoints;
|
[[vk::binding(4, 0)]] StructuredBuffer<uint> _BandData[];
|
||||||
[[vk::binding(2, 1)]] StructuredBuffer<uint> _BandData;
|
|
||||||
|
|
||||||
static const float2 VERTICES[4] = {
|
static const float2 VERTICES[4] = {
|
||||||
float2(0, 1),
|
float2(0, 1),
|
||||||
@@ -70,7 +71,7 @@ uint classifyQuadCurve(float y0, float y1, float y2)
|
|||||||
let class = (sign0 << 0) | (sign1 << 1) | (sign2 << 2);
|
let class = (sign0 << 0) | (sign1 << 1) | (sign2 << 2);
|
||||||
let shift = 2 * class;
|
let shift = 2 * class;
|
||||||
|
|
||||||
return (0x2E74U >> shift) & 0b11U;
|
return (0x1DB8 >> shift) & 0b11U;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find intersections between a quadratic Bézier curve and `y = 0` line by
|
// Find intersections between a quadratic Bézier curve and `y = 0` line by
|
||||||
@@ -156,12 +157,12 @@ Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_Vulk
|
|||||||
|
|
||||||
let vertex = VERTICES[vertexID];
|
let vertex = VERTICES[vertexID];
|
||||||
let instance = _Instances[instanceID];
|
let instance = _Instances[instanceID];
|
||||||
let glyph = _GlyphData[instance.glyph];
|
let glyph = _GlyphData[instance.font][instance.glyph];
|
||||||
let normal = 2.0 * vertex - 1.0;
|
let normal = 2.0 * vertex - 1.0;
|
||||||
|
|
||||||
let sizePX = instance.fontSizePX * glyph.sizeEM;
|
let sizePX = instance.fontSizePX * glyph.sizeEM;
|
||||||
|
|
||||||
let positionSSPX = vertex * sizePX + instance.bottomLeftSSPX;
|
let positionSSPX = vertex * float2(sizePX.x, -sizePX.y) + instance.bottomLeftSSPX;
|
||||||
let dilatedPositionSSPX = positionSSPX + 0.5 * normal;
|
let dilatedPositionSSPX = positionSSPX + 0.5 * normal;
|
||||||
let dilatedPositionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0);
|
let dilatedPositionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0);
|
||||||
|
|
||||||
@@ -177,7 +178,9 @@ Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_Vulk
|
|||||||
[shader("fragment")]
|
[shader("fragment")]
|
||||||
float4 fragmentMain(Varyings frag) : SV_Target {
|
float4 fragmentMain(Varyings frag) : SV_Target {
|
||||||
let instance = _Instances[frag.instance];
|
let instance = _Instances[frag.instance];
|
||||||
let glyph = _GlyphData[instance.glyph];
|
let glyph = _GlyphData[instance.font][instance.glyph];
|
||||||
|
let bandData = _BandData[instance.font];
|
||||||
|
let controlPoints = _ControlPoints[instance.font];
|
||||||
|
|
||||||
let emPerPX = fwidth(frag.texCoordEM);
|
let emPerPX = fwidth(frag.texCoordEM);
|
||||||
let pxPerEM = 1.0 / emPerPX;
|
let pxPerEM = 1.0 / emPerPX;
|
||||||
@@ -191,17 +194,17 @@ float4 fragmentMain(Varyings frag) : SV_Target {
|
|||||||
var xWeight = 0.0;
|
var xWeight = 0.0;
|
||||||
|
|
||||||
let horizontalBandPtr = glyph.bandPtr.y + band.y;
|
let horizontalBandPtr = glyph.bandPtr.y + band.y;
|
||||||
let horizontalBandData = _BandData[horizontalBandPtr];
|
let horizontalBandData = bandData[horizontalBandPtr];
|
||||||
let horizontalBandCurvePtr = horizontalBandPtr + (horizontalBandData >> 16);
|
let horizontalBandCurvePtr = horizontalBandPtr + (horizontalBandData >> 16);
|
||||||
let horizontalBandCurveCount = horizontalBandData & 0xFFFF;
|
let horizontalBandCurveCount = horizontalBandData & 0xFFFF;
|
||||||
|
|
||||||
for (uint horizontalBandCurveID = 0; horizontalBandCurveID < horizontalBandCurveCount; horizontalBandCurveID++) {
|
for (uint horizontalBandCurveID = 0; horizontalBandCurveID < horizontalBandCurveCount; horizontalBandCurveID++) {
|
||||||
let cpPtr = _BandData[horizontalBandCurvePtr + horizontalBandCurveID];
|
let cpPtr = bandData[horizontalBandCurvePtr + horizontalBandCurveID];
|
||||||
let cp0 = float2(_ControlPoints[cpPtr]) - frag.texCoordEM;
|
let cp0 = float2(controlPoints[cpPtr + 0]) - frag.texCoordEM;
|
||||||
let cp1 = float2(_ControlPoints[cpPtr + 1]) - frag.texCoordEM;
|
let cp1 = float2(controlPoints[cpPtr + 1]) - frag.texCoordEM;
|
||||||
let cp2 = float2(_ControlPoints[cpPtr + 2]) - frag.texCoordEM;
|
let cp2 = float2(controlPoints[cpPtr + 2]) - frag.texCoordEM;
|
||||||
|
|
||||||
if (max(max(cp0.x, cp1.x), cp2.x) * pxPerEM.x < -0.5) break;
|
if (max(max(cp0.x, cp1.x), cp2.x) * pxPerEM.x < -0.5) break; // XXX
|
||||||
|
|
||||||
let rootFlags = classifyQuadCurve(cp0.y, cp1.y, cp2.y);
|
let rootFlags = classifyQuadCurve(cp0.y, cp1.y, cp2.y);
|
||||||
if (rootFlags != 0U) {
|
if (rootFlags != 0U) {
|
||||||
@@ -228,17 +231,17 @@ float4 fragmentMain(Varyings frag) : SV_Target {
|
|||||||
var yWeight = 0.0;
|
var yWeight = 0.0;
|
||||||
|
|
||||||
let verticalBandPtr = glyph.bandPtr.x + band.x;
|
let verticalBandPtr = glyph.bandPtr.x + band.x;
|
||||||
let verticalBandData = _BandData[verticalBandPtr];
|
let verticalBandData = bandData[verticalBandPtr];
|
||||||
let verticalBandCurvePtr = verticalBandPtr + (verticalBandData >> 16);
|
let verticalBandCurvePtr = verticalBandPtr + (verticalBandData >> 16);
|
||||||
let verticalBandCurveCount = verticalBandData & 0xFFFF;
|
let verticalBandCurveCount = verticalBandData & 0xFFFF;
|
||||||
|
|
||||||
for (uint verticalBandCurveID = 0; verticalBandCurveID < verticalBandCurveCount; verticalBandCurveID++) {
|
for (uint verticalBandCurveID = 0; verticalBandCurveID < verticalBandCurveCount; verticalBandCurveID++) {
|
||||||
let cpPtr = _BandData[verticalBandCurvePtr + verticalBandCurveID];
|
let cpPtr = bandData[verticalBandCurvePtr + verticalBandCurveID];
|
||||||
let cp0 = float2(_ControlPoints[cpPtr]) - frag.texCoordEM;
|
let cp0 = float2(controlPoints[cpPtr + 0]) - frag.texCoordEM;
|
||||||
let cp1 = float2(_ControlPoints[cpPtr + 1]) - frag.texCoordEM;
|
let cp1 = float2(controlPoints[cpPtr + 1]) - frag.texCoordEM;
|
||||||
let cp2 = float2(_ControlPoints[cpPtr + 2]) - frag.texCoordEM;
|
let cp2 = float2(controlPoints[cpPtr + 2]) - frag.texCoordEM;
|
||||||
|
|
||||||
if (max(max(cp0.y, cp1.y), cp2.y) * pxPerEM.y < -0.5) break;
|
if (max(max(cp0.y, cp1.y), cp2.y) * pxPerEM.y < -0.5) break; // XXX
|
||||||
|
|
||||||
let rootFlags = classifyQuadCurve(cp0.x, cp1.x, cp2.x);
|
let rootFlags = classifyQuadCurve(cp0.x, cp1.x, cp2.x);
|
||||||
if (rootFlags != 0U) {
|
if (rootFlags != 0U) {
|
||||||
|
|||||||
2
castle
2
castle
Submodule castle updated: dc839e098c...af8d725e0c
@@ -10,6 +10,7 @@ const media = @import("media");
|
|||||||
const glfw = @import("zglfw");
|
const glfw = @import("zglfw");
|
||||||
|
|
||||||
const Atoms = @import("engine/Atoms.zig");
|
const Atoms = @import("engine/Atoms.zig");
|
||||||
|
const Fonts = @import("engine/Fonts.zig");
|
||||||
const Engine = @import("engine/Engine.zig");
|
const Engine = @import("engine/Engine.zig");
|
||||||
const Gui = @import("engine/Gui.zig");
|
const Gui = @import("engine/Gui.zig");
|
||||||
const Materials = @import("engine/Materials.zig");
|
const Materials = @import("engine/Materials.zig");
|
||||||
@@ -23,6 +24,7 @@ pub var allocator_persistent: std.mem.Allocator = undefined;
|
|||||||
pub var io: std.Io = undefined;
|
pub var io: std.Io = undefined;
|
||||||
pub var vk_allocator: *VkAllocator = undefined;
|
pub var vk_allocator: *VkAllocator = undefined;
|
||||||
pub var stbi: *media.stbi = undefined;
|
pub var stbi: *media.stbi = undefined;
|
||||||
|
pub var stbtt: *media.stbtt = undefined;
|
||||||
|
|
||||||
pub var window: *glfw.Window = undefined;
|
pub var window: *glfw.Window = undefined;
|
||||||
pub var atoms: *Atoms = undefined;
|
pub var atoms: *Atoms = undefined;
|
||||||
@@ -31,4 +33,5 @@ pub var swapchain: *Swapchain = undefined;
|
|||||||
|
|
||||||
pub var textures: *Textures = undefined;
|
pub var textures: *Textures = undefined;
|
||||||
pub var materials: *Materials = undefined;
|
pub var materials: *Materials = undefined;
|
||||||
|
pub var fonts: *Fonts = undefined;
|
||||||
pub var gui: *Gui = undefined;
|
pub var gui: *Gui = undefined;
|
||||||
|
|||||||
112
src/Game.zig
112
src/Game.zig
@@ -16,6 +16,7 @@ const Chunk = @import("Chunk.zig");
|
|||||||
const Chunks = @import("Chunks.zig");
|
const Chunks = @import("Chunks.zig");
|
||||||
const CommandBuffer = @import("engine/CommandBuffer.zig");
|
const CommandBuffer = @import("engine/CommandBuffer.zig");
|
||||||
const Engine = @import("engine/Engine.zig");
|
const Engine = @import("engine/Engine.zig");
|
||||||
|
const Fonts = @import("engine/Fonts.zig");
|
||||||
const Iterator2 = math.Iterator2;
|
const Iterator2 = math.Iterator2;
|
||||||
const Materials = @import("engine/Materials.zig");
|
const Materials = @import("engine/Materials.zig");
|
||||||
const Player = @import("Player.zig");
|
const Player = @import("Player.zig");
|
||||||
@@ -47,6 +48,7 @@ blocks: Blocks,
|
|||||||
chunks: Chunks,
|
chunks: Chunks,
|
||||||
skybox: Skybox,
|
skybox: Skybox,
|
||||||
player: Player,
|
player: Player,
|
||||||
|
font: Fonts.Id,
|
||||||
|
|
||||||
const max_textures = 1024;
|
const max_textures = 1024;
|
||||||
const max_point_lights = 1024;
|
const max_point_lights = 1024;
|
||||||
@@ -70,6 +72,7 @@ pub fn init() !Game {
|
|||||||
const swapchain = ctx.swapchain;
|
const swapchain = ctx.swapchain;
|
||||||
const textures = ctx.textures;
|
const textures = ctx.textures;
|
||||||
const materials = ctx.materials;
|
const materials = ctx.materials;
|
||||||
|
const fonts = ctx.fonts;
|
||||||
const gui = ctx.gui;
|
const gui = ctx.gui;
|
||||||
|
|
||||||
var blocks = try Blocks.init();
|
var blocks = try Blocks.init();
|
||||||
@@ -137,7 +140,7 @@ pub fn init() !Game {
|
|||||||
.descriptor_type = .sampled_image,
|
.descriptor_type = .sampled_image,
|
||||||
.descriptor_count = max_textures,
|
.descriptor_count = max_textures,
|
||||||
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
.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_stone = try blocks.getOrLoad("Stone.json");
|
||||||
const block_bedrock = try blocks.getOrLoad("Bedrock.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
|
// VOLATILE Load all assets before this point
|
||||||
|
|
||||||
const descriptor_images = try allocator_frame.alloc(vk.DescriptorImageInfo, textures.array.items.len);
|
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);
|
var skybox = try Skybox.load("skybox.hdr", 512, global_uniforms.buffer);
|
||||||
errdefer skybox.deinit();
|
errdefer skybox.deinit();
|
||||||
|
|
||||||
|
const font = &fonts.array.items[@intFromEnum(font_id)];
|
||||||
|
|
||||||
// TODO This coupling is not elegant - the game must provide the GUI with
|
// 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 for globals. Maybe GUI should just use their own global
|
||||||
// uniform buffer not for any other reason than decoupling. After all, GUI
|
// 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 },
|
.chunks = .{ .chunks = chunks },
|
||||||
.skybox = skybox,
|
.skybox = skybox,
|
||||||
.player = .init(player_position_sv, 0, 0),
|
.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,
|
.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| {
|
self.render() catch |err| {
|
||||||
std.log.err("Failed to render: {s}", .{@errorName(err)});
|
std.log.err("Failed to render: {s}", .{@errorName(err)});
|
||||||
@panic("Frame update failed");
|
@panic("Frame update failed");
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ pub fn init() !*Engine {
|
|||||||
|
|
||||||
var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{
|
var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{
|
||||||
.p_next = &enabled_features_vulkan11,
|
.p_next = &enabled_features_vulkan11,
|
||||||
|
.shader_float_16 = .true,
|
||||||
.descriptor_indexing = .true,
|
.descriptor_indexing = .true,
|
||||||
.descriptor_binding_partially_bound = .true,
|
.descriptor_binding_partially_bound = .true,
|
||||||
.descriptor_binding_variable_descriptor_count = .true,
|
.descriptor_binding_variable_descriptor_count = .true,
|
||||||
|
|||||||
432
src/engine/Font.zig
Normal file
432
src/engine/Font.zig
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
const Font = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const ctx = @import("../AppContext.zig");
|
||||||
|
const shaders = @import("../shaders.zig");
|
||||||
|
const vm = @import("vecmath");
|
||||||
|
|
||||||
|
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
||||||
|
|
||||||
|
pub const CodePointRange = struct {
|
||||||
|
/// inclusive
|
||||||
|
start: u21,
|
||||||
|
/// exclusive
|
||||||
|
end: u21,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InitInfo = struct {
|
||||||
|
codepoint_ranges: []const CodePointRange = &.{
|
||||||
|
.{ .start = 32, .end = 127 },
|
||||||
|
},
|
||||||
|
/// [vertical, horizontal]
|
||||||
|
bands_per_em: vm.Vector2 = .init(8, 8),
|
||||||
|
name: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const GlyphCpuData = struct {
|
||||||
|
/// `undefined` when `gpu_glyph_id == null`
|
||||||
|
bottom_left_em: vm.Vector2,
|
||||||
|
advance_em: f32,
|
||||||
|
left_side_bearing_em: f32,
|
||||||
|
/// If `null`, the glyph is empty (like a space). In such case, we only need
|
||||||
|
/// to advance the cursor by given `advance_em` and we must not emit any gpu
|
||||||
|
/// glyph instances.
|
||||||
|
gpu_glyph_id: ?u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
cpu_glyph_data: []GlyphCpuData,
|
||||||
|
codepoint_glyph_id: std.hash_map.AutoHashMapUnmanaged(u21, u16),
|
||||||
|
kerning_em: std.hash_map.AutoHashMapUnmanaged([2]u16, f32),
|
||||||
|
cap_height_em: f32,
|
||||||
|
|
||||||
|
glyph_data: GenericBuffer(void, shaders.GlyphData),
|
||||||
|
control_points: GenericBuffer(void, [2]f16),
|
||||||
|
band_data: GenericBuffer(void, u32),
|
||||||
|
|
||||||
|
const BandHelper = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
bands: []std.ArrayList(u32),
|
||||||
|
|
||||||
|
fn init(allocator: std.mem.Allocator) BandHelper {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.bands = &.{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensureBandCount(self: *BandHelper, desired_band_count: usize) !void {
|
||||||
|
std.debug.assert(desired_band_count > 0);
|
||||||
|
|
||||||
|
const old_band_count = self.bands.len;
|
||||||
|
if (old_band_count >= desired_band_count) return;
|
||||||
|
|
||||||
|
const new_band_count = std.math.ceilPowerOfTwo(usize, desired_band_count) catch |err| switch (err) {
|
||||||
|
error.Overflow => return error.OutOfMemory,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.bands = try self.allocator.realloc(self.bands, new_band_count);
|
||||||
|
for (old_band_count..new_band_count) |i| {
|
||||||
|
self.bands[i] = .empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clearBands(self: *BandHelper, band_count: usize) void {
|
||||||
|
for (self.bands[0..band_count]) |*array| {
|
||||||
|
array.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insertControlPointPtr(self: *BandHelper, band: usize, ptr: u32) !void {
|
||||||
|
const array = &self.bands[band];
|
||||||
|
try array.append(self.allocator, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sortBands(self: *BandHelper, coordinate: u1, band_count: usize, control_points: []const [2]f16) void {
|
||||||
|
for (self.bands[0..band_count]) |*array| {
|
||||||
|
const sort_ctx = SortContext{
|
||||||
|
.coordinate = coordinate,
|
||||||
|
.control_points = control_points,
|
||||||
|
};
|
||||||
|
|
||||||
|
std.mem.sortUnstable(u32, array.items, &sort_ctx, SortContext.lessThan);
|
||||||
|
|
||||||
|
// std.debug.print(" BAND {d} sorted by coordinate {d}\n", .{ array - self.bands.ptr, coordinate });
|
||||||
|
// for (array.items) |cp_ptr| {
|
||||||
|
// const cp0 = control_points[cp_ptr + 0];
|
||||||
|
// const cp1 = control_points[cp_ptr + 1];
|
||||||
|
// const cp2 = control_points[cp_ptr + 2];
|
||||||
|
// const max = @max(cp0[coordinate], cp1[coordinate], cp2[coordinate]);
|
||||||
|
// std.debug.print(" max {d} | ({d}, {d}) → ({d}, {d}) → ({d}, {d})\n", .{ max, cp0[0], cp0[1], cp1[0], cp1[1], cp2[0], cp2[1] });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self: *const BandHelper, band_count: usize, buffer: *std.ArrayList(u32), buffer_allocator: std.mem.Allocator) !void {
|
||||||
|
var band_data_ptr = buffer.items.len;
|
||||||
|
|
||||||
|
// --- HEADERS ---
|
||||||
|
|
||||||
|
band_data_ptr += band_count;
|
||||||
|
|
||||||
|
for (self.bands[0..band_count]) |*array| {
|
||||||
|
const band = array.items;
|
||||||
|
|
||||||
|
// Header is a self-relative forward-only pointer in high 16 bits
|
||||||
|
// and length in low 16 bits.
|
||||||
|
|
||||||
|
const ptr = std.math.cast(u16, band_data_ptr - buffer.items.len) orelse return error.Overflow;
|
||||||
|
const len = std.math.cast(u16, band.len) orelse return error.Overflow;
|
||||||
|
|
||||||
|
try buffer.append(buffer_allocator, (@as(u32, ptr) << 16) | len);
|
||||||
|
|
||||||
|
band_data_ptr += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- BAND DATA ---
|
||||||
|
|
||||||
|
for (self.bands[0..band_count]) |*array| {
|
||||||
|
const band = array.items;
|
||||||
|
try buffer.appendSlice(buffer_allocator, band);
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.assert(band_data_ptr == buffer.items.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *BandHelper) void {
|
||||||
|
for (self.bands) |*array| {
|
||||||
|
array.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.allocator.free(self.bands);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SortContext = struct {
|
||||||
|
coordinate: u1,
|
||||||
|
control_points: []const [2]f16,
|
||||||
|
|
||||||
|
fn lessThan(self: *const SortContext, lhs: u32, rhs: u32) bool {
|
||||||
|
const lhs_cp0 = self.control_points[lhs + 0];
|
||||||
|
const lhs_cp1 = self.control_points[lhs + 1];
|
||||||
|
const lhs_cp2 = self.control_points[lhs + 2];
|
||||||
|
|
||||||
|
const lhs_max = @max(
|
||||||
|
lhs_cp0[self.coordinate],
|
||||||
|
lhs_cp1[self.coordinate],
|
||||||
|
lhs_cp2[self.coordinate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const rhs_cp0 = self.control_points[rhs + 0];
|
||||||
|
const rhs_cp1 = self.control_points[rhs + 1];
|
||||||
|
const rhs_cp2 = self.control_points[rhs + 2];
|
||||||
|
|
||||||
|
const rhs_max = @max(
|
||||||
|
rhs_cp0[self.coordinate],
|
||||||
|
rhs_cp1[self.coordinate],
|
||||||
|
rhs_cp2[self.coordinate],
|
||||||
|
);
|
||||||
|
|
||||||
|
return lhs_max >= rhs_max;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(ttf_buffer: []const u8, init_info: InitInfo) !Font {
|
||||||
|
const allocator_general = ctx.allocator_general;
|
||||||
|
const allocator_frame = ctx.allocator_frame;
|
||||||
|
const stbtt = ctx.stbtt;
|
||||||
|
|
||||||
|
const ttf_font = try stbtt.initFont(ttf_buffer);
|
||||||
|
defer ttf_font.deinit();
|
||||||
|
|
||||||
|
const units_per_em = ttf_font.unitsPerEm();
|
||||||
|
const em_per_unit = 1 / @as(f32, @floatFromInt(units_per_em));
|
||||||
|
|
||||||
|
// std.debug.print("Units per EM: {d}\nEM per unit: {d}\n", .{ units_per_em, em_per_unit });
|
||||||
|
|
||||||
|
var glyph_count: u16 = 0;
|
||||||
|
for (init_info.codepoint_ranges) |range| {
|
||||||
|
const range_size = std.math.cast(u16, range.end - range.start) orelse return error.TooManyGlyphs;
|
||||||
|
glyph_count = std.math.add(u16, glyph_count, range_size) catch |err| switch (err) {
|
||||||
|
error.Overflow => return error.TooManyGlyphs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var codepoint_glyph_id: std.hash_map.AutoHashMapUnmanaged(u21, u16) = .empty;
|
||||||
|
errdefer codepoint_glyph_id.deinit(allocator_general);
|
||||||
|
try codepoint_glyph_id.ensureTotalCapacity(allocator_general, glyph_count);
|
||||||
|
|
||||||
|
var kerning_em: std.hash_map.AutoHashMapUnmanaged([2]u16, f32) = .empty;
|
||||||
|
errdefer kerning_em.deinit(allocator_general);
|
||||||
|
|
||||||
|
const cap_height = ttf_font.getCapHeight() orelse return error.FontError;
|
||||||
|
const cap_height_em = @as(f32, @floatFromInt(cap_height)) * em_per_unit;
|
||||||
|
|
||||||
|
// std.debug.print("Cap height: {d} U | {d} EM\n", .{ cap_height, cap_height_em });
|
||||||
|
|
||||||
|
var cpu_glyph_data: std.ArrayList(GlyphCpuData) = try .initCapacity(allocator_general, glyph_count);
|
||||||
|
errdefer cpu_glyph_data.deinit(allocator_general);
|
||||||
|
|
||||||
|
var gpu_glyph_data: std.ArrayList(shaders.GlyphData) = try .initCapacity(allocator_frame, glyph_count);
|
||||||
|
|
||||||
|
var control_points_array: std.ArrayList([2]f16) = .empty;
|
||||||
|
defer control_points_array.deinit(allocator_general);
|
||||||
|
|
||||||
|
var band_data_array: std.ArrayList(u32) = .empty;
|
||||||
|
defer band_data_array.deinit(allocator_general);
|
||||||
|
|
||||||
|
var vertical_band_helper: BandHelper = .init(allocator_general);
|
||||||
|
defer vertical_band_helper.deinit();
|
||||||
|
|
||||||
|
var horizontal_band_helper: BandHelper = .init(allocator_general);
|
||||||
|
defer horizontal_band_helper.deinit();
|
||||||
|
|
||||||
|
for (init_info.codepoint_ranges) |range| {
|
||||||
|
var codepoint: u21 = range.start;
|
||||||
|
while (codepoint < range.end) : (codepoint += 1) {
|
||||||
|
// std.debug.print("Processing codepoint 0x{X} '{u}'\n", .{ codepoint, codepoint });
|
||||||
|
if (ttf_font.findGlyphIndex(codepoint)) |external_glyph_id| {
|
||||||
|
const cpu_glyph_id = @as(u16, @intCast(cpu_glyph_data.items.len));
|
||||||
|
codepoint_glyph_id.putAssumeCapacityNoClobber(codepoint, cpu_glyph_id);
|
||||||
|
|
||||||
|
const glyph_metrics = ttf_font.getGlyphMetrics(external_glyph_id);
|
||||||
|
// std.debug.print(" Advance: {d} U\n", .{glyph_metrics.advance_width});
|
||||||
|
// std.debug.print(" LSB: {d} U\n", .{glyph_metrics.left_side_bearing});
|
||||||
|
|
||||||
|
if (ttf_font.isGlyphEmpty(external_glyph_id)) {
|
||||||
|
// std.debug.print(" Glyph is empty\n", .{});
|
||||||
|
cpu_glyph_data.appendAssumeCapacity(.{
|
||||||
|
.bottom_left_em = undefined,
|
||||||
|
.left_side_bearing_em = @as(f32, @floatFromInt(glyph_metrics.left_side_bearing)) * em_per_unit,
|
||||||
|
.advance_em = @as(f32, @floatFromInt(glyph_metrics.advance_width)) * em_per_unit,
|
||||||
|
.gpu_glyph_id = null,
|
||||||
|
});
|
||||||
|
} else if (ttf_font.getGlyphBox(external_glyph_id)) |box| {
|
||||||
|
const bottom_left_em = vm.Vector2.init(
|
||||||
|
@floatFromInt(box.x_min),
|
||||||
|
@floatFromInt(box.y_min),
|
||||||
|
).mulScalar(em_per_unit);
|
||||||
|
const size_em = vm.Vector2.init(
|
||||||
|
@as(f32, @floatFromInt(box.x_max)) - @as(f32, @floatFromInt(box.x_min)),
|
||||||
|
@as(f32, @floatFromInt(box.y_max)) - @as(f32, @floatFromInt(box.y_min)),
|
||||||
|
).mulScalar(em_per_unit);
|
||||||
|
|
||||||
|
const gpu_glyph_id = @as(u16, @intCast(gpu_glyph_data.items.len));
|
||||||
|
cpu_glyph_data.appendAssumeCapacity(.{
|
||||||
|
.bottom_left_em = bottom_left_em,
|
||||||
|
.left_side_bearing_em = @as(f32, @floatFromInt(glyph_metrics.left_side_bearing)) * em_per_unit,
|
||||||
|
.advance_em = @as(f32, @floatFromInt(glyph_metrics.advance_width)) * em_per_unit,
|
||||||
|
.gpu_glyph_id = gpu_glyph_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const band_count = vm.Vector2.max(.round(.mul(size_em, init_info.bands_per_em)), .initScalar(1));
|
||||||
|
|
||||||
|
const vertical_band_count: u32 = @trunc(band_count.x);
|
||||||
|
const horizontal_band_count: u32 = @trunc(band_count.y);
|
||||||
|
|
||||||
|
try vertical_band_helper.ensureBandCount(vertical_band_count);
|
||||||
|
try horizontal_band_helper.ensureBandCount(horizontal_band_count);
|
||||||
|
|
||||||
|
vertical_band_helper.clearBands(vertical_band_count);
|
||||||
|
horizontal_band_helper.clearBands(horizontal_band_count);
|
||||||
|
|
||||||
|
const vertices = ttf_font.getGlyphShape(external_glyph_id);
|
||||||
|
defer ttf_font.freeShape(vertices);
|
||||||
|
|
||||||
|
// std.debug.print(" bottom left: {f} [EM]\n", .{bottom_left_em});
|
||||||
|
// std.debug.print(" size: {f} [EM]\n", .{size_em});
|
||||||
|
// std.debug.print(" band count: {f}\n", .{band_count});
|
||||||
|
// std.debug.print(" vertices: {d}\n", .{vertices.len});
|
||||||
|
|
||||||
|
var cp0: vm.Vector2 = undefined;
|
||||||
|
for (vertices) |vertex| {
|
||||||
|
var cp1: vm.Vector2 = undefined;
|
||||||
|
var cp2: vm.Vector2 = undefined;
|
||||||
|
switch (vertex.type) {
|
||||||
|
.STBTT_vmove => {
|
||||||
|
cp0.x = @as(f32, @floatFromInt(vertex.x)) * em_per_unit;
|
||||||
|
cp0.y = @as(f32, @floatFromInt(vertex.y)) * em_per_unit;
|
||||||
|
try control_points_array.append(
|
||||||
|
allocator_general,
|
||||||
|
.{ @floatCast(cp0.x), @floatCast(cp0.y) },
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
.STBTT_vline => {
|
||||||
|
cp1.x = @as(f32, @floatFromInt(vertex.x)) * em_per_unit;
|
||||||
|
cp1.y = @as(f32, @floatFromInt(vertex.y)) * em_per_unit;
|
||||||
|
cp2 = cp1;
|
||||||
|
try control_points_array.appendNTimes(
|
||||||
|
allocator_general,
|
||||||
|
.{ @floatCast(cp1.x), @floatCast(cp1.y) },
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
.STBTT_vcurve => {
|
||||||
|
cp1.x = @as(f32, @floatFromInt(vertex.cx)) * em_per_unit;
|
||||||
|
cp1.y = @as(f32, @floatFromInt(vertex.cy)) * em_per_unit;
|
||||||
|
cp2.x = @as(f32, @floatFromInt(vertex.x)) * em_per_unit;
|
||||||
|
cp2.y = @as(f32, @floatFromInt(vertex.y)) * em_per_unit;
|
||||||
|
try control_points_array.append(allocator_general, .{ @floatCast(cp1.x), @floatCast(cp1.y) });
|
||||||
|
try control_points_array.append(allocator_general, .{ @floatCast(cp2.x), @floatCast(cp2.y) });
|
||||||
|
},
|
||||||
|
.STBTT_vcubic => unreachable,
|
||||||
|
}
|
||||||
|
defer cp0 = cp2;
|
||||||
|
|
||||||
|
const cp_min = cp0.min(cp1).min(cp2).sub(.initScalar(em_per_unit));
|
||||||
|
const cp_max = cp0.max(cp1).max(cp2).add(.initScalar(em_per_unit));
|
||||||
|
|
||||||
|
const min_band_f = band_count.mul(.sub(cp_min, bottom_left_em)).div(size_em)
|
||||||
|
.max(.initScalar(0))
|
||||||
|
.min(.sub(band_count, .initScalar(1)))
|
||||||
|
.floor();
|
||||||
|
const max_band_f = band_count.mul(.sub(cp_max, bottom_left_em)).div(size_em)
|
||||||
|
.max(.initScalar(0))
|
||||||
|
.min(.sub(band_count, .initScalar(1)))
|
||||||
|
.floor();
|
||||||
|
|
||||||
|
// std.debug.print(" {f} → {f} → {f} | bands: {f} → {f}\n", .{ cp0, cp1, cp2, min_band_f, max_band_f });
|
||||||
|
|
||||||
|
const vertical_band_start = @as(u32, @trunc(min_band_f.x));
|
||||||
|
const vertical_band_end = @as(u32, @trunc(max_band_f.x)) + 1;
|
||||||
|
const horizontal_band_start = @as(u32, @trunc(min_band_f.y));
|
||||||
|
const horizontal_band_end = @as(u32, @trunc(max_band_f.y)) + 1;
|
||||||
|
|
||||||
|
const cp_ptr = @as(u32, @intCast(control_points_array.items.len - 3));
|
||||||
|
|
||||||
|
for (vertical_band_start..vertical_band_end) |vband| {
|
||||||
|
try vertical_band_helper.insertControlPointPtr(vband, cp_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (horizontal_band_start..horizontal_band_end) |hband| {
|
||||||
|
try horizontal_band_helper.insertControlPointPtr(hband, cp_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
cp0 = cp2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertical_band_ptr = @as(u32, @intCast(band_data_array.items.len));
|
||||||
|
vertical_band_helper.sortBands(1, vertical_band_count, control_points_array.items);
|
||||||
|
try vertical_band_helper.write(vertical_band_count, &band_data_array, allocator_general);
|
||||||
|
|
||||||
|
const horizontal_band_ptr = @as(u32, @intCast(band_data_array.items.len));
|
||||||
|
horizontal_band_helper.sortBands(0, horizontal_band_count, control_points_array.items);
|
||||||
|
try horizontal_band_helper.write(horizontal_band_count, &band_data_array, allocator_general);
|
||||||
|
|
||||||
|
// std.debug.print(" vband: [{d}] @ {d}\n hband: [{d}] @ {d}\n", .{ vertical_band_count, vertical_band_ptr, horizontal_band_count, horizontal_band_ptr });
|
||||||
|
|
||||||
|
gpu_glyph_data.appendAssumeCapacity(.{
|
||||||
|
.bottomLeftEM = bottom_left_em,
|
||||||
|
.sizeEM = size_em,
|
||||||
|
.verticalBandPtr = vertical_band_ptr,
|
||||||
|
.horizontalBandPtr = horizontal_band_ptr,
|
||||||
|
.verticalBandCount = vertical_band_count,
|
||||||
|
.horizontalBandCount = horizontal_band_count,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return error.FontError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return error.MissingGlyph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var glyph_data: GenericBuffer(void, shaders.GlyphData) = try .init(.{
|
||||||
|
.usage = .storage,
|
||||||
|
.target_queue = .graphics,
|
||||||
|
.array_capacity = @intCast(gpu_glyph_data.items.len),
|
||||||
|
.name = "Glyph data",
|
||||||
|
});
|
||||||
|
errdefer glyph_data.deinit();
|
||||||
|
|
||||||
|
// std.debug.print("Glyph data size: {d} [B]\n", .{gpu_glyph_data.items.len * @sizeOf(shaders.GlyphData)});
|
||||||
|
|
||||||
|
var control_points: GenericBuffer(void, [2]f16) = try .init(.{
|
||||||
|
.usage = .storage,
|
||||||
|
.target_queue = .graphics,
|
||||||
|
.array_capacity = @intCast(control_points_array.items.len),
|
||||||
|
.name = "Control points",
|
||||||
|
});
|
||||||
|
errdefer control_points.deinit();
|
||||||
|
|
||||||
|
// std.debug.print("Control points size: {d} [B]\n", .{control_points_array.items.len * @sizeOf([2]f16)});
|
||||||
|
|
||||||
|
var band_data: GenericBuffer(void, u32) = try .init(.{
|
||||||
|
.usage = .storage,
|
||||||
|
.target_queue = .graphics,
|
||||||
|
.array_capacity = @intCast(band_data_array.items.len),
|
||||||
|
.name = "Band data",
|
||||||
|
});
|
||||||
|
errdefer band_data.deinit();
|
||||||
|
|
||||||
|
// std.debug.print("Band data size: {d} [B]\n", .{band_data_array.items.len * @sizeOf(u32)});
|
||||||
|
|
||||||
|
try glyph_data.write(.{ .elements = gpu_glyph_data.items });
|
||||||
|
try control_points.write(.{ .elements = control_points_array.items });
|
||||||
|
try band_data.write(.{ .elements = band_data_array.items });
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.cpu_glyph_data = cpu_glyph_data.toOwnedSliceAssert(),
|
||||||
|
.codepoint_glyph_id = codepoint_glyph_id,
|
||||||
|
.kerning_em = kerning_em,
|
||||||
|
.cap_height_em = cap_height_em,
|
||||||
|
|
||||||
|
.glyph_data = glyph_data,
|
||||||
|
.control_points = control_points,
|
||||||
|
.band_data = band_data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Font) void {
|
||||||
|
const allocator_general = ctx.allocator_general;
|
||||||
|
|
||||||
|
self.band_data.deinit();
|
||||||
|
self.control_points.deinit();
|
||||||
|
self.glyph_data.deinit();
|
||||||
|
|
||||||
|
self.codepoint_glyph_id.deinit(allocator_general);
|
||||||
|
allocator_general.free(self.cpu_glyph_data);
|
||||||
|
}
|
||||||
111
src/engine/Fonts.zig
Normal file
111
src/engine/Fonts.zig
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
const Fonts = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const ctx = @import("../AppContext.zig");
|
||||||
|
|
||||||
|
const Atom = @import("../engine/Atom.zig").Atom;
|
||||||
|
const Font = @import("Font.zig");
|
||||||
|
|
||||||
|
pub const Id = enum(u16) {
|
||||||
|
// VOLATILE Synchronize explicit values with `init` implementation.
|
||||||
|
|
||||||
|
_,
|
||||||
|
|
||||||
|
/// Cast an integer into an ID. This can produce an invalid ID.
|
||||||
|
pub fn fromInt(value: u16) Id {
|
||||||
|
return @enumFromInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast an index into an ID. This can produce an invalid ID. The caller
|
||||||
|
/// asserts that the index is not greater than the max ID value.
|
||||||
|
pub fn fromIndex(index: usize) Id {
|
||||||
|
std.debug.assert(index < max_fonts);
|
||||||
|
return @enumFromInt(@as(u16, @intCast(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast an index into an ID. This can produce an invalid ID. Returns an
|
||||||
|
/// error if the index is greater than the max ID value.
|
||||||
|
pub fn fromIndexSafe(index: usize) error{Overflow}!Id {
|
||||||
|
if (index >= max_fonts) return error.Overflow;
|
||||||
|
return @enumFromInt(@as(u16, @intCast(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast an ID into an integer.
|
||||||
|
pub fn toInt(self: Id) u16 {
|
||||||
|
return @intFromEnum(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Stores all `Font` structs and maps a font ID to a `Font` struct.
|
||||||
|
/// Preallocated with `allocator_persistent`.
|
||||||
|
array: std.ArrayList(Font),
|
||||||
|
|
||||||
|
pub const max_fonts = 256;
|
||||||
|
|
||||||
|
pub fn init() !*Fonts {
|
||||||
|
const allocator_persistent = ctx.allocator_persistent;
|
||||||
|
|
||||||
|
const fonts = try allocator_persistent.create(Fonts);
|
||||||
|
|
||||||
|
fonts.* = .{
|
||||||
|
.array = .empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
for (fonts.array.items) |*font| {
|
||||||
|
font.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try fonts.array.ensureTotalCapacityPrecise(allocator_persistent, max_fonts);
|
||||||
|
|
||||||
|
// VOLATILE Synchronize with explicit values on top of `Id` type.
|
||||||
|
|
||||||
|
return fonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Fonts) void {
|
||||||
|
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||||||
|
|
||||||
|
for (self.array.items) |*font| {
|
||||||
|
font.deinit();
|
||||||
|
}
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(
|
||||||
|
self: *Fonts,
|
||||||
|
filename: []const u8,
|
||||||
|
init_info: Font.InitInfo,
|
||||||
|
) !Id {
|
||||||
|
const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) {
|
||||||
|
error.Overflow => return error.OutOfFonts,
|
||||||
|
};
|
||||||
|
const font = try loadFont(filename, init_info);
|
||||||
|
|
||||||
|
self.array.appendAssumeCapacity(font);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadFont(
|
||||||
|
filename: []const u8,
|
||||||
|
init_info: Font.InitInfo,
|
||||||
|
) !Font {
|
||||||
|
const allocator_frame = ctx.allocator_frame;
|
||||||
|
const io = ctx.io;
|
||||||
|
|
||||||
|
std.log.debug("Loading font \"{s}\"...", .{filename});
|
||||||
|
|
||||||
|
const cwd = std.Io.Dir.cwd();
|
||||||
|
|
||||||
|
var dir = try cwd.openDir(io, "assets/fonts", .{});
|
||||||
|
defer dir.close(io);
|
||||||
|
|
||||||
|
const ttf_buffer = try dir.readFileAlloc(io, filename, allocator_frame, .unlimited);
|
||||||
|
|
||||||
|
var font = try Font.init(ttf_buffer, init_info);
|
||||||
|
errdefer font.deinit();
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ const vm = @import("vecmath");
|
|||||||
|
|
||||||
const CommandBuffer = @import("CommandBuffer.zig");
|
const CommandBuffer = @import("CommandBuffer.zig");
|
||||||
const Engine = @import("Engine.zig");
|
const Engine = @import("Engine.zig");
|
||||||
|
const Fonts = @import("Fonts.zig");
|
||||||
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
||||||
const Swapchain = @import("Swapchain.zig");
|
const Swapchain = @import("Swapchain.zig");
|
||||||
const Texture = @import("Texture.zig");
|
const Texture = @import("Texture.zig");
|
||||||
@@ -25,11 +26,10 @@ pub const Draw = struct {
|
|||||||
|
|
||||||
pub const Text = extern struct {
|
pub const Text = extern struct {
|
||||||
color: vm.Vector4,
|
color: vm.Vector4,
|
||||||
pos_min: vm.Vector2,
|
bottom_left_sspx: vm.Vector2,
|
||||||
pos_max: vm.Vector2,
|
font_size_px: f32,
|
||||||
uv_min: vm.Vector2,
|
glyph_id: u16,
|
||||||
uv_max: vm.Vector2,
|
font_id: Fonts.Id,
|
||||||
atlas_id: Textures.Id,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Image = extern struct {
|
pub const Image = extern struct {
|
||||||
@@ -65,8 +65,13 @@ box_descriptor_set_layout: vk.DescriptorSetLayout,
|
|||||||
box_pipeline_layout: vk.PipelineLayout,
|
box_pipeline_layout: vk.PipelineLayout,
|
||||||
box_pipeline: vk.Pipeline,
|
box_pipeline: vk.Pipeline,
|
||||||
|
|
||||||
|
text_descriptor_set_layout: vk.DescriptorSetLayout,
|
||||||
|
text_pipeline_layout: vk.PipelineLayout,
|
||||||
|
text_pipeline: vk.Pipeline,
|
||||||
|
|
||||||
descriptor_pool: vk.DescriptorPool,
|
descriptor_pool: vk.DescriptorPool,
|
||||||
box_descriptor_set: vk.DescriptorSet,
|
box_descriptor_set: vk.DescriptorSet,
|
||||||
|
text_descriptor_set: vk.DescriptorSet,
|
||||||
|
|
||||||
pub const max_box_draws = 1024;
|
pub const max_box_draws = 1024;
|
||||||
pub const max_image_draws = 1024;
|
pub const max_image_draws = 1024;
|
||||||
@@ -273,16 +278,169 @@ pub fn init() !*Gui {
|
|||||||
errdefer engine.destroyPipeline(box_pipeline);
|
errdefer engine.destroyPipeline(box_pipeline);
|
||||||
engine.setObjectName(box_pipeline, "P GUI Box", .{});
|
engine.setObjectName(box_pipeline, "P GUI Box", .{});
|
||||||
|
|
||||||
|
const text_descriptor_set_layout = try engine.createDescriptorSetLayout(.{
|
||||||
|
.bindings = &.{
|
||||||
|
.{
|
||||||
|
.binding = 0,
|
||||||
|
.descriptor_type = .uniform_buffer,
|
||||||
|
.descriptor_count = 1,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 1,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = 1,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 2,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = Fonts.max_fonts,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 3,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = Fonts.max_fonts,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 4,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = Fonts.max_fonts,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
errdefer engine.destroyDescriptorSetLayout(text_descriptor_set_layout);
|
||||||
|
engine.setObjectName(text_descriptor_set_layout, "DSL GUI Text", .{});
|
||||||
|
|
||||||
|
const text_pipeline_layout = try engine.createPipelineLayout(.{
|
||||||
|
.set_layouts = &.{
|
||||||
|
text_descriptor_set_layout,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
errdefer engine.destroyPipelineLayout(text_pipeline_layout);
|
||||||
|
engine.setObjectName(text_pipeline_layout, "PL GUI Text", .{});
|
||||||
|
|
||||||
|
const text_shader = try engine.createShaderModule(.{ .code = &shaders.gui_text_spv });
|
||||||
|
defer engine.destroyShaderModule(text_shader);
|
||||||
|
engine.setObjectName(text_shader, "SM gui_text", .{});
|
||||||
|
|
||||||
|
const text_pipeline = try engine.createGraphicsPipeline(.{
|
||||||
|
.stages = &.{
|
||||||
|
.{
|
||||||
|
.stage = .{ .vertex_bit = true },
|
||||||
|
.module = text_shader,
|
||||||
|
.name = "vertexMain",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.stage = .{ .fragment_bit = true },
|
||||||
|
.module = text_shader,
|
||||||
|
.name = "fragmentMain",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.vertex_input_state = .{},
|
||||||
|
.input_assembly_state = .{
|
||||||
|
.topology = .triangle_list,
|
||||||
|
.primitive_restart_enable = .false,
|
||||||
|
},
|
||||||
|
.viewport_state = .{
|
||||||
|
.viewports = &.{undefined}, // dynamic
|
||||||
|
.scissors = &.{undefined}, // dynamic
|
||||||
|
},
|
||||||
|
.rasterization_state = .{
|
||||||
|
.depth_clamp_enable = .false,
|
||||||
|
.rasterizer_discard_enable = .false,
|
||||||
|
.polygon_mode = .fill,
|
||||||
|
.cull_mode = .{},
|
||||||
|
.front_face = .counter_clockwise,
|
||||||
|
.depth_bias_enable = .false,
|
||||||
|
.depth_bias_constant_factor = 0,
|
||||||
|
.depth_bias_clamp = 0,
|
||||||
|
.depth_bias_slope_factor = 0,
|
||||||
|
.line_width = 1,
|
||||||
|
},
|
||||||
|
.multisample_state = .{
|
||||||
|
.rasterization_samples = .{ .@"1_bit" = true },
|
||||||
|
.sample_shading_enable = .false,
|
||||||
|
.min_sample_shading = 1,
|
||||||
|
.alpha_to_coverage_enable = .false,
|
||||||
|
.alpha_to_one_enable = .false,
|
||||||
|
},
|
||||||
|
.depth_stencil_state = .{
|
||||||
|
.depth_test_enable = .false,
|
||||||
|
.depth_write_enable = .false,
|
||||||
|
.depth_compare_op = .always,
|
||||||
|
.depth_bounds_test_enable = .false,
|
||||||
|
.stencil_test_enable = .false,
|
||||||
|
.front = .{
|
||||||
|
.fail_op = .keep,
|
||||||
|
.pass_op = .keep,
|
||||||
|
.depth_fail_op = .keep,
|
||||||
|
.compare_op = .never,
|
||||||
|
.compare_mask = 0,
|
||||||
|
.write_mask = 0,
|
||||||
|
.reference = 0,
|
||||||
|
},
|
||||||
|
.back = .{
|
||||||
|
.fail_op = .keep,
|
||||||
|
.pass_op = .keep,
|
||||||
|
.depth_fail_op = .keep,
|
||||||
|
.compare_op = .never,
|
||||||
|
.compare_mask = 0,
|
||||||
|
.write_mask = 0,
|
||||||
|
.reference = 0,
|
||||||
|
},
|
||||||
|
.min_depth_bounds = 0,
|
||||||
|
.max_depth_bounds = 1,
|
||||||
|
},
|
||||||
|
.color_blend_state = .{
|
||||||
|
.logic_op_enable = .false,
|
||||||
|
.logic_op = .copy,
|
||||||
|
.attachments = &.{
|
||||||
|
.{
|
||||||
|
.blend_enable = .true,
|
||||||
|
.src_color_blend_factor = .one,
|
||||||
|
.dst_color_blend_factor = .one_minus_src_alpha,
|
||||||
|
.color_blend_op = .add,
|
||||||
|
.src_alpha_blend_factor = .one,
|
||||||
|
.dst_alpha_blend_factor = .one_minus_src_alpha,
|
||||||
|
.alpha_blend_op = .add,
|
||||||
|
.color_write_mask = .{
|
||||||
|
.r_bit = true,
|
||||||
|
.g_bit = true,
|
||||||
|
.b_bit = true,
|
||||||
|
.a_bit = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.blend_constants = .{ 0, 0, 0, 0 },
|
||||||
|
},
|
||||||
|
.dynamic_state = .{
|
||||||
|
.dynamic_states = &.{ .viewport, .scissor },
|
||||||
|
},
|
||||||
|
.layout = text_pipeline_layout,
|
||||||
|
.view_mask = 0,
|
||||||
|
.color_attachment_formats = &.{swapchain.params.surface_format.format},
|
||||||
|
.depth_attachment_format = Texture.Usage.depth.vkFormat(),
|
||||||
|
.stencil_attachment_format = .undefined,
|
||||||
|
});
|
||||||
|
errdefer engine.destroyPipeline(text_pipeline);
|
||||||
|
engine.setObjectName(text_pipeline, "P GUI Box", .{});
|
||||||
|
|
||||||
const descriptor_pool = try engine.createDescriptorPool(.{
|
const descriptor_pool = try engine.createDescriptorPool(.{
|
||||||
.max_sets = 1,
|
.max_sets = 2,
|
||||||
.pool_sizes = &.{
|
.pool_sizes = &.{
|
||||||
.{
|
.{
|
||||||
.type = .uniform_buffer,
|
.type = .uniform_buffer,
|
||||||
.descriptor_count = 1,
|
.descriptor_count = 1 + // box
|
||||||
|
1, // text
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.type = .storage_buffer,
|
.type = .storage_buffer,
|
||||||
.descriptor_count = 1,
|
.descriptor_count = 1 + // box
|
||||||
|
(1 + 3 * Fonts.max_fonts), // text
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -295,6 +453,12 @@ pub fn init() !*Gui {
|
|||||||
});
|
});
|
||||||
engine.setObjectName(box_descriptor_set, "DS GUI Box", .{});
|
engine.setObjectName(box_descriptor_set, "DS GUI Box", .{});
|
||||||
|
|
||||||
|
const text_descriptor_set = try engine.allocateDescriptorSet(.{
|
||||||
|
.descriptor_pool = descriptor_pool,
|
||||||
|
.set_layout = text_descriptor_set_layout,
|
||||||
|
});
|
||||||
|
engine.setObjectName(text_descriptor_set, "DS GUI Text", .{});
|
||||||
|
|
||||||
try engine.updateDescriptorSets(.{
|
try engine.updateDescriptorSets(.{
|
||||||
.writes = &.{
|
.writes = &.{
|
||||||
.{
|
.{
|
||||||
@@ -312,6 +476,21 @@ pub fn init() !*Gui {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = text_descriptor_set,
|
||||||
|
.dst_binding = 1,
|
||||||
|
.dst_array_element = 0,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_infos = .{
|
||||||
|
.buffer = &.{
|
||||||
|
.{
|
||||||
|
.buffer = text_gpu_buffer.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = vk.WHOLE_SIZE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -333,8 +512,13 @@ pub fn init() !*Gui {
|
|||||||
.box_pipeline_layout = box_pipeline_layout,
|
.box_pipeline_layout = box_pipeline_layout,
|
||||||
.box_pipeline = box_pipeline,
|
.box_pipeline = box_pipeline,
|
||||||
|
|
||||||
|
.text_descriptor_set_layout = text_descriptor_set_layout,
|
||||||
|
.text_pipeline_layout = text_pipeline_layout,
|
||||||
|
.text_pipeline = text_pipeline,
|
||||||
|
|
||||||
.descriptor_pool = descriptor_pool,
|
.descriptor_pool = descriptor_pool,
|
||||||
.box_descriptor_set = box_descriptor_set,
|
.box_descriptor_set = box_descriptor_set,
|
||||||
|
.text_descriptor_set = text_descriptor_set,
|
||||||
};
|
};
|
||||||
|
|
||||||
return gui;
|
return gui;
|
||||||
@@ -346,9 +530,12 @@ pub fn deinit(self: *Gui) void {
|
|||||||
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||||||
|
|
||||||
engine.destroyDescriptorPool(self.descriptor_pool);
|
engine.destroyDescriptorPool(self.descriptor_pool);
|
||||||
|
engine.destroyPipeline(self.text_pipeline);
|
||||||
engine.destroyPipeline(self.box_pipeline);
|
engine.destroyPipeline(self.box_pipeline);
|
||||||
self.index_buffer.deinit();
|
self.index_buffer.deinit();
|
||||||
|
engine.destroyPipelineLayout(self.text_pipeline_layout);
|
||||||
engine.destroyPipelineLayout(self.box_pipeline_layout);
|
engine.destroyPipelineLayout(self.box_pipeline_layout);
|
||||||
|
engine.destroyDescriptorSetLayout(self.text_descriptor_set_layout);
|
||||||
engine.destroyDescriptorSetLayout(self.box_descriptor_set_layout);
|
engine.destroyDescriptorSetLayout(self.box_descriptor_set_layout);
|
||||||
engine.destroySampler(self.sampler);
|
engine.destroySampler(self.sampler);
|
||||||
|
|
||||||
@@ -379,6 +566,67 @@ pub fn pushText(self: *Gui, text: Draw.Text) void {
|
|||||||
self.extendOrAddBatch(.Text, instance);
|
self.extendOrAddBatch(.Text, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const PrintLineArgs = struct {
|
||||||
|
position_sspx: vm.Vector2,
|
||||||
|
font_id: Fonts.Id,
|
||||||
|
cap_height_px: f32,
|
||||||
|
color: vm.Vector4,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn printLine(self: *Gui, comptime fmt: []const u8, fmt_args: anytype, print_args: PrintLineArgs) void {
|
||||||
|
const allocator_frame = ctx.allocator_frame;
|
||||||
|
const fonts = ctx.fonts;
|
||||||
|
|
||||||
|
const text = std.fmt.allocPrint(allocator_frame, fmt, fmt_args) catch |err| {
|
||||||
|
std.log.err("Failed formatting of string \"{s}\": {}", .{ fmt, err });
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const font = fonts.array.items[@intFromEnum(print_args.font_id)];
|
||||||
|
const font_size_px = print_args.cap_height_px / font.cap_height_em;
|
||||||
|
|
||||||
|
const utf8_view = std.unicode.Utf8View.init(text) catch {
|
||||||
|
std.log.err("Invalid UTF-8 sequence in GUI print line", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
var cursor_sspx = print_args.position_sspx;
|
||||||
|
var maybe_prev_glyph: ?u16 = null;
|
||||||
|
var it = utf8_view.iterator();
|
||||||
|
while (it.nextCodepoint()) |codepoint| {
|
||||||
|
const glyph_id = font.codepoint_glyph_id.get(codepoint) orelse {
|
||||||
|
std.log.warn("Missing codepoint 0x{X} '{u}' in font #{d}", .{ codepoint, codepoint, print_args.font_id });
|
||||||
|
maybe_prev_glyph = null;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
defer maybe_prev_glyph = glyph_id;
|
||||||
|
|
||||||
|
const glyph_data = &font.cpu_glyph_data[glyph_id];
|
||||||
|
|
||||||
|
var cursor_offset_em = glyph_data.bottom_left_em;
|
||||||
|
cursor_offset_em.y = -cursor_offset_em.y;
|
||||||
|
cursor_offset_em.x += glyph_data.left_side_bearing_em;
|
||||||
|
|
||||||
|
if (maybe_prev_glyph) |prev_glyph| {
|
||||||
|
const kerning_em = font.kerning_em.get(.{ prev_glyph, glyph_id }) orelse 0;
|
||||||
|
cursor_offset_em.x += kerning_em;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glyph_data.gpu_glyph_id) |gpu_glyph_id| {
|
||||||
|
self.pushText(.{
|
||||||
|
.color = print_args.color,
|
||||||
|
.bottom_left_sspx = cursor_sspx.add(cursor_offset_em.mulScalar(font_size_px)),
|
||||||
|
.font_size_px = font_size_px,
|
||||||
|
.glyph_id = gpu_glyph_id,
|
||||||
|
.font_id = print_args.font_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const advance_px = font_size_px * glyph_data.advance_em;
|
||||||
|
cursor_sspx.x += advance_px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pushImage(self: *Gui, image: Draw.Image) void {
|
pub fn pushImage(self: *Gui, image: Draw.Image) void {
|
||||||
const instance: u32 = @intCast(self.image_cpu_buffer.items.len);
|
const instance: u32 = @intCast(self.image_cpu_buffer.items.len);
|
||||||
self.image_cpu_buffer.appendBounded(image) catch return;
|
self.image_cpu_buffer.appendBounded(image) catch return;
|
||||||
@@ -413,8 +661,8 @@ pub fn draw(self: *const Gui, command_buffer: CommandBuffer) !void {
|
|||||||
command_buffer.bindDescriptorSet(.graphics, self.box_pipeline_layout, 0, self.box_descriptor_set, null);
|
command_buffer.bindDescriptorSet(.graphics, self.box_pipeline_layout, 0, self.box_descriptor_set, null);
|
||||||
},
|
},
|
||||||
.Text => {
|
.Text => {
|
||||||
std.log.warn("GUI Text not implemented; skipping batch", .{});
|
command_buffer.bindPipeline(.graphics, self.text_pipeline);
|
||||||
continue;
|
command_buffer.bindDescriptorSet(.graphics, self.text_pipeline_layout, 0, self.text_descriptor_set, null);
|
||||||
},
|
},
|
||||||
.Image => {
|
.Image => {
|
||||||
std.log.warn("GUI Image not implemented; skipping batch", .{});
|
std.log.warn("GUI Image not implemented; skipping batch", .{});
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
ctx.stbi = try ctx.allocator_persistent.create(media.stbi);
|
ctx.stbi = try ctx.allocator_persistent.create(media.stbi);
|
||||||
ctx.stbi.* = .init(ctx.allocator_general, ctx.io);
|
ctx.stbi.* = .init(ctx.allocator_general, ctx.io);
|
||||||
defer ctx.stbi.deinit();
|
defer ctx.stbi.deinit();
|
||||||
|
ctx.stbtt = try ctx.allocator_persistent.create(media.stbtt);
|
||||||
|
ctx.stbtt.* = .init(ctx.allocator_general, ctx.io);
|
||||||
|
defer ctx.stbtt.deinit();
|
||||||
|
|
||||||
// --- INITIALIZE APP CONTEXT ---
|
// --- INITIALIZE APP CONTEXT ---
|
||||||
|
|
||||||
@@ -105,6 +108,12 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
ctx.materials = undefined;
|
ctx.materials = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.fonts = try .init();
|
||||||
|
defer {
|
||||||
|
ctx.fonts.deinit();
|
||||||
|
ctx.fonts = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.gui = try .init();
|
ctx.gui = try .init();
|
||||||
defer {
|
defer {
|
||||||
ctx.gui.deinit();
|
ctx.gui.deinit();
|
||||||
|
|||||||
@@ -99,7 +99,17 @@ pub const ObjectUniforms = extern struct {
|
|||||||
material: Materials.Id,
|
material: Materials.Id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const GlyphData = extern struct {
|
||||||
|
bottomLeftEM: vm.Vector2,
|
||||||
|
sizeEM: vm.Vector2,
|
||||||
|
verticalBandPtr: u32,
|
||||||
|
horizontalBandPtr: u32,
|
||||||
|
verticalBandCount: u32,
|
||||||
|
horizontalBandCount: u32,
|
||||||
|
};
|
||||||
|
|
||||||
pub const equirect_to_cube_spv align(4) = @embedFile("shaders/equirect_to_cube").*;
|
pub const equirect_to_cube_spv align(4) = @embedFile("shaders/equirect_to_cube").*;
|
||||||
pub const gui_box_spv align(4) = @embedFile("shaders/gui_box").*;
|
pub const gui_box_spv align(4) = @embedFile("shaders/gui_box").*;
|
||||||
|
pub const gui_text_spv align(4) = @embedFile("shaders/gui_text").*;
|
||||||
pub const main_spv align(4) = @embedFile("shaders/main").*;
|
pub const main_spv align(4) = @embedFile("shaders/main").*;
|
||||||
pub const skybox_spv align(4) = @embedFile("shaders/skybox").*;
|
pub const skybox_spv align(4) = @embedFile("shaders/skybox").*;
|
||||||
|
|||||||
Reference in New Issue
Block a user