text layouting

This commit is contained in:
jacob 2025-10-23 03:03:12 -05:00
parent ee1f720fa1
commit 56fd5e19ff
14 changed files with 354 additions and 167 deletions

View File

@ -257,7 +257,7 @@ i64 LerpI64(i64 val0, i64 val1, f64 t);
////////////////////////////////////////////////////////////
//~ Vec2 operations
#define Vec2FromVec2I32(v) VEC2((v).x, (v).y)
#define Vec2FromVec(v) VEC2((v).x, (v).y)
b32 IsVec2Zero(Vec2 a);
b32 EqVec2(Vec2 a, Vec2 b);

View File

@ -52,6 +52,20 @@ Vec4 LinearFromSrgbU32(u32 srgb32)
return LinearFromSrgbVec4(Vec4NormFromU32(srgb32));
}
////////////////////////////////////////////////////////////
//~ Vertex ID helpers
Vec2 RectUvFromVertexId(u32 id)
{
static const Vec2 uvs[4] = {
Vec2(0, 0),
Vec2(1, 0),
Vec2(1, 1),
Vec2(0, 1)
};
return uvs[id];
}
////////////////////////////////////////////////////////////
//~ Ndc helpers

View File

@ -130,6 +130,8 @@ JobDef(F_Load, sig, _)
font->image_height = decoded.image_height;
font->glyphs_count = decoded.glyphs_count;
font->point_size = point_size;
font->ascent = decoded.ascent;
font->descent = decoded.descent;
/* FIXME: Load baked font instead of panicking */
if (font->glyphs_count <= 0)
@ -140,7 +142,8 @@ JobDef(F_Load, sig, _)
}
/* Copy glyphs from decode decoded */
StaticAssert(sizeof(*font->glyphs) == sizeof(*decoded.glyphs)); /* Font glyph size must match TTF glyph size for memcpy */
/* NOTE: Font glyph size must match TTF glyph size for memcpy */
StaticAssert(sizeof(*font->glyphs) == sizeof(*decoded.glyphs));
CopyBytes(font->glyphs, decoded.glyphs, sizeof(*font->glyphs) * decoded.glyphs_count);
/* Build lookup table */
@ -237,19 +240,86 @@ F_Glyph F_GlyphFromCodepoint(F_Font *font, u32 codepoint)
}
}
F_GlyphRun F_GlyphRunFromString(Arena *arena, F_Font *font, String str)
F_Run F_RunFromString(Arena *arena, F_Font *font, String str)
{
F_GlyphRun result = ZI;
result.glyphs = PushDry(arena, F_Glyph);
F_Run result = ZI;
result.rects = PushDry(arena, F_RunRect);
for (CodepointIter it = InitCodepointIter(str); NextCodepoint(&it);)
{
u32 codepoint = it.codepoint;
u16 index = font->lookup[codepoint];
F_Glyph glyph = font->glyphs[index];
*PushStructNoZero(arena, F_Glyph) = glyph;
F_RunRect *rect = PushStruct(arena, F_RunRect);
++result.count;
result.width += glyph.advance;
result.height = MaxF32(result.height, glyph.height);
rect->atlas_p0 = glyph.atlas_p0;
rect->atlas_p1 = glyph.atlas_p1;
Vec2I32 size = SubVec2I32(glyph.atlas_p1, glyph.atlas_p0);
rect->baseline_start_offset = glyph.baseline_offset;
rect->baseline_start_offset.x += result.baseline_length;
result.p0.x = MinF32(result.p0.x, rect->baseline_start_offset.x); /* Left run bounds */
result.p1.x = MaxF32(result.p1.x, rect->baseline_start_offset.x + size.x); /* Right run bounds */
result.p0.y = MinF32(result.p0.y, rect->baseline_start_offset.y); /* Top run bounds */
result.p1.y = MaxF32(result.p1.y, rect->baseline_start_offset.y + size.y); /* Bottom run bounds */
result.baseline_length += glyph.advance;
}
return result;
#if 0
F_Run result = ZI;
result.rects = PushDry(arena, F_RunRect);
f32 left = 0;
f32 right = 0;
f32 width = 0;
f32 top = 0;
f32 bottom = 0;
f32 baseline_cursor = 0;
f32 baseline_depth = 0;
for (CodepointIter it = InitCodepointIter(str); NextCodepoint(&it);)
{
u32 codepoint = it.codepoint;
u16 index = font->lookup[codepoint];
F_Glyph glyph = font->glyphs[index];
F_RunRect *rect = PushStruct(arena, F_RunRect);
++result.count;
rect->atlas_p0 = glyph.atlas_p0;
rect->atlas_p1 = glyph.atlas_p1;
Vec2I32 size = SubVec2I32(glyph.atlas_p1, glyph.atlas_p0);
f32 offset_x = glyph.baseline_offset.x;
f32 offset_y = glyph.baseline_offset.y;
rect->pos.x = baseline_cursor + offset_x;
/* NOTE: Actual Y position gets calculated in second pass after baseline depth is determined */
rect->pos.y = offset_y;
baseline_depth = MaxF32(baseline_depth, -offset_y);
left = MinF32(left, rect->pos.x);
top = MinF32(top, offset_y);
right = MaxF32(right, rect->pos.x + size.x);
bottom = MaxF32(bottom, rect->pos.y + size.y);
baseline_cursor += glyph.advance;
}
/* Adjust rect Y positions so they're relative to top of run */
for (u32 i = 0; i < result.count; ++i)
{
F_RunRect *rect = &result.rects[i];
rect->pos.y += baseline_depth;
}
result.size = VEC2(right - left, bottom - top);
result.baseline_depth = baseline_depth;
return result;
#endif
}

View File

@ -5,20 +5,10 @@
Struct(F_Glyph)
{
f32 off_x;
f32 off_y;
f32 width;
f32 height;
i32 advance;
Rect atlas_rect;
};
Struct(F_GlyphRun)
{
f32 width;
f32 height;
u64 count;
F_Glyph *glyphs;
Vec2 baseline_offset;
Vec2I32 atlas_p0;
Vec2I32 atlas_p1;
};
Struct(F_Font)
@ -26,10 +16,33 @@ Struct(F_Font)
GPU_Resource *texture;
u32 image_width;
u32 image_height;
f32 point_size;
u16 glyphs_count;
F_Glyph *glyphs;
u16 *lookup;
/* Metrics */
f32 point_size;
f32 ascent;
f32 descent;
};
////////////////////////////////////////////////////////////
//~ Run types
Struct(F_RunRect)
{
Vec2 baseline_start_offset; /* Vector from start of baseline to top left of rect */
Vec2I32 atlas_p0;
Vec2I32 atlas_p1;
};
Struct(F_Run)
{
Vec2 p0; /* Start of baseline to top-left-most rect */
Vec2 p1; /* Start of baseline to bottom-right-most rect corner */
f32 baseline_length;
u32 count;
F_RunRect *rects;
};
////////////////////////////////////////////////////////////
@ -45,7 +58,7 @@ F_Font *F_LoadFontAsync(Resource resource, f32 point_size);
F_Font *F_LoadFontWait(Resource resource, f32 point_size);
////////////////////////////////////////////////////////////
//~ Font data operations
//~ Run operations
F_Glyph F_GlyphFromCodepoint(F_Font *font, u32 codepoint);
F_GlyphRun F_GlyphRunFromString(Arena *arena, F_Font *font, String str);
F_Run F_RunFromString(Arena *arena, F_Font *font, String str);

View File

@ -59,7 +59,19 @@ ExitFuncDef(ShutdownUser)
}
////////////////////////////////////////////////////////////
//~ Debug draw
//~ Ui helpers
//- Ui style
void PushGameUiStyle(void)
{
// UI_Push(FontSize, 48);
UI_Push(FontSize, 24);
// UI_Push(FontSize, 12);
UI_Push(TextPadding, 12);
// UI_Push(Border, 1);
// UI_Push(BorderColor, Rgba32F(1, 0, 1, 0.4));
}
//- Draw xform
void DrawDebugXform(Xform xf, u32 color_x, u32 color_y)
@ -451,6 +463,7 @@ void UpdateUser(P_Window *window)
//- Begin UI
UI_BeginBuild();
PushGameUiStyle();
UI_Box *pp_root_box = UI_BuildBox(UI_BoxFlag_DrawImage, UI_NilKey);
UI_Push(Parent, pp_root_box);
@ -1224,8 +1237,8 @@ void UpdateUser(P_Window *window)
mat->xf = sprite_xform;
mat->tex = GPU_Texture2DRidFromResource(texture->gpu_texture);
mat->tint_srgb = tint;
mat->uv0 = frame.clip.p0;
mat->uv1 = frame.clip.p1;
mat->tex_uv0 = frame.clip.p0;
mat->tex_uv1 = frame.clip.p1;
mat->is_light = is_light;
mat->light_emittance_srgb = emittance;
}
@ -1999,8 +2012,6 @@ void UpdateUser(P_Window *window)
//////////////////////////////
//- Debug draw
//~ Draw global debug info
/* FIXME: Enable this */
#if 1
#if 1
@ -2015,90 +2026,93 @@ void UpdateUser(P_Window *window)
UI_Push(Parent, dbg_box);
UI_Push(Width, UI_TextSize(0));
UI_Push(Height, UI_TextSize(0));
UI_Push(BackgroundColor, Rgba32F(0.3, 0.3, 0.3, 1));
UI_Push(BackgroundColor, Rgba32F(0.3, 0.6, 0.3, 0.5));
UI_BuildLabelF("ytest string");
// UI_BuildSeparator();
UI_BuildLabelF("blended world entities: %F/%F", FmtUint(g->ss_blended->num_ents_allocated), FmtUint(g->ss_blended->num_ents_reserved));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("blended world tick: %F", FmtUint(g->ss_blended->tick));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("blended world time: %F", FmtFloat(SecondsFromNs(g->ss_blended->sim_time_ns)));
UI_BuildSeparator();
UI_BuildSeparator();
// UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("average local sim publish dt: %F", FmtFloat(SecondsFromNs(g->average_local_to_user_snapshot_publish_dt_ns)));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("local sim last known tick: %F", FmtUint(g->local_sim_last_known_tick));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("local sim last known time: %F", FmtFloat(SecondsFromNs(g->local_sim_last_known_time_ns)));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("local sim predicted time: %F", FmtFloat(SecondsFromNs(g->local_sim_predicted_time_ns)));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("render time target: %F", FmtFloat(SecondsFromNs(g->render_time_target_ns)));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("render time: %F", FmtFloat(SecondsFromNs(g->render_time_ns)));
UI_BuildSeparator();
UI_BuildSeparator();
// UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("local player: [%F]", FmtUid(local_player->id.uid));
UI_BuildSeparator();
UI_BuildSeparator();
// UI_BuildSeparator();
// UI_BuildSeparator();
Vec2 world_cursor = g->world_cursor;
UI_BuildLabelF("cursor world: %F, %F", FmtFloat(world_cursor.x), FmtFloat(world_cursor.y));
UI_BuildSeparator();
// UI_BuildSeparator();
Vec2I32 world_tile_cursor = WorldTileIndexFromPos(world_cursor);
UI_BuildLabelF("cursor world tile: %F, %F", FmtSint(world_tile_cursor.x), FmtSint(world_tile_cursor.y));
UI_BuildSeparator();
// UI_BuildSeparator();
Vec2I32 local_tile_cursor = LocalTileIndexFromWorldTileIndex(world_tile_cursor);
UI_BuildLabelF("cursor local tile: %F, %F", FmtSint(local_tile_cursor.x), FmtSint(local_tile_cursor.y));
UI_BuildSeparator();
// UI_BuildSeparator();
Vec2I32 tile_chunk_cursor = TileChunkIndexFromWorldTileIndex(world_tile_cursor);
UI_BuildLabelF("cursor tile chunk: %F, %F", FmtSint(tile_chunk_cursor.x), FmtSint(tile_chunk_cursor.y));
UI_BuildSeparator();
UI_BuildSeparator();
// UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Network read: %F mbit/s", FmtFloat((f64)g->net_bytes_read.last_second * 8 / 1000 / 1000));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Network write: %F mbit/s", FmtFloat((f64)g->net_bytes_sent.last_second * 8 / 1000 / 1000));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Ping (real): %F ms", FmtFloat(SecondsFromNs(local_player->player_last_rtt_ns) * 1000));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Ping (average): %F ms", FmtFloat(local_player->player_average_rtt_seconds * 1000));
UI_BuildSeparator();
UI_BuildSeparator();
// UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Memory committed: %F MiB", FmtFloat((f64)GetGstat(GSTAT_MEMORY_COMMITTED) / 1024 / 1024));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Virtual memory reserved: %F TiB", FmtFloat((f64)GetGstat(GSTAT_MEMORY_RESERVED) / 1024 / 1024 / 1024 / 1024));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Arenas allocated: %F", FmtUint(GetGstat(GSTAT_NUM_ARENAS)));
UI_BuildSeparator();
UI_BuildSeparator();
// UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Video memory (GPU): %F MiB", FmtFloat((f64)vram.local_used / 1024 / 1024));
UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Video memory (shared): %F MiB", FmtFloat((f64)vram.non_local_used / 1024 / 1024));
//UI_BuildLabelF(\n"));
//UI_BuildLabelF(\n"));
#if RtcIsEnabled
UI_BuildSeparator();
UI_BuildSeparator();
// UI_BuildSeparator();
// UI_BuildSeparator();
UI_BuildLabelF("Debug steps: %F", FmtUint(GetGstat(GSTAT_DEBUG_STEPS)));
//UI_BuildLabelF(\n"));
@ -2552,6 +2566,7 @@ void UpdateUser(P_Window *window)
/* Render UI */
UI_SetDisplayImage(pp_root_box, g->ui_target);
// UI_SetDisplayImage(pp_root_box, F_LoadFontWait(UI_GetDefaultFontResource(), 12.0)->texture);
GPU_Resource *ui_render = UI_EndBuild(ui_viewport);
//////////////////////////////

View File

@ -282,8 +282,9 @@ void StartupUser(void);
ExitFuncDef(ShutdownUser);
////////////////////////////////////////////////////////////
//~ Debug draw operations
//~ Ui helpers
void PushGameUiStyle(void);
void DrawDebugXform(Xform xf, u32 color_x, u32 color_y);
void DrawDebugMovement(Entity *ent);
String DebugStringFromEntity(Arena *arena, Entity *ent);

View File

@ -13,7 +13,7 @@ Struct(MaterialPS_Input)
Semantic(Vec4, sv_position);
Semantic(nointerpolation Texture2DRid, tex);
Semantic(nointerpolation u32, grid_id);
Semantic(Vec2, uv);
Semantic(Vec2, tex_uv);
Semantic(Vec4, tint_lin);
Semantic(Vec4, emittance_lin);
};
@ -29,24 +29,18 @@ Struct(MaterialPS_Output)
MaterialPS_Input VSDef(MaterialVS, Semantic(u32, sv_instanceid), Semantic(u32, sv_vertexid))
{
ConstantBuffer<MaterialSig> sig = mat_sig;
static const Vec2 unit_quad_verts[4] = {
Vec2(-0.5f, -0.5f),
Vec2(0.5f, -0.5f),
Vec2(0.5f, 0.5f),
Vec2(-0.5f, 0.5f)
};
StructuredBuffer<MaterialInstance> instances = UniformResourceFromRid(sig.instances);
Vec2 vert = unit_quad_verts[sv_vertexid];
MaterialInstance instance = instances[sv_instanceid];
Vec2 world_pos = mul(instance.xf, Vec3(vert, 1)).xy;
Vec2 mat_uv = RectUvFromVertexId(sv_vertexid);
Vec2 mat_uv_centered = mat_uv - 0.5f;
Vec2 world_pos = mul(instance.xf, Vec3(mat_uv_centered, 1)).xy;
MaterialPS_Input result;
result.sv_position = mul(sig.projection, Vec4(world_pos, 0, 1));
result.tex = instance.tex;
result.grid_id = instance.grid_id;
result.uv = instance.uv0 + ((vert + 0.5) * (instance.uv1 - instance.uv0));
result.tex_uv = lerp(instance.tex_uv0, instance.tex_uv1, mat_uv);
result.tint_lin = LinearFromSrgbU32(instance.tint_srgb);
result.emittance_lin = LinearFromSrgbVec4(Vec4(instance.light_emittance_srgb, instance.is_light));
return result;
@ -66,7 +60,7 @@ MaterialPS_Output PSDef(MaterialPS, MaterialPS_Input input)
{
SamplerState sampler = UniformSamplerFromRid(sig.sampler);
Texture2D<Vec4> tex = NonUniformResourceFromRid(input.tex);
albedo *= tex.Sample(sampler, input.uv);
albedo *= tex.Sample(sampler, input.tex_uv);
}
/* Grid */
@ -163,7 +157,7 @@ void CSDef(FloodCS, Semantic(Vec3U32, sv_dispatchthreadid))
(Vec2I32)id + Vec2I32(+step_len, 0), /* center right */
(Vec2I32)id + Vec2I32(-step_len, +step_len), /* bottom left */
(Vec2I32)id + Vec2I32(0, +step_len), /* bottom center */
(Vec2I32)id + Vec2I32(+step_len, +step_len) /* bottom right */
(Vec2I32)id + Vec2I32(+step_len, +step_len), /* bottom right */
};
Vec2U32 closest_seed = Vec2U32(0xFFFF, 0xFFFF);
u32 closest_seed_len_sq = 0xFFFFFFFF;
@ -300,7 +294,7 @@ void CSDef(ShadeCS, Semantic(Vec3U32, sv_dispatchthreadid))
Struct(UiBlitPS_Input)
{
Semantic(Vec4, sv_position);
Semantic(Vec2, uv);
Semantic(Vec2, tex_uv);
};
Struct(UiBlitPS_Output)
@ -322,17 +316,12 @@ Vec3 ToneMap(Vec3 v)
UiBlitPS_Input VSDef(UiBlitVS, Semantic(u32, sv_vertexid))
{
ConstantBuffer<UiBlitSig> sig = ui_blit_sig;
static const Vec2 unit_quad_verts[4] = {
Vec2(-0.5f, -0.5f),
Vec2(0.5f, -0.5f),
Vec2(0.5f, 0.5f),
Vec2(-0.5f, 0.5f)
};
Vec2 vert = unit_quad_verts[sv_vertexid];
Vec2 tex_uv = RectUvFromVertexId(sv_vertexid);
Vec2 tex_uv_centered = tex_uv - 0.5;
UiBlitPS_Input result;
result.sv_position = mul(sig.projection, Vec4(vert, 0, 1));
result.uv = vert + 0.5;
result.sv_position = mul(sig.projection, Vec4(tex_uv_centered, 0, 1));
result.tex_uv = tex_uv;
return result;
}
@ -345,7 +334,7 @@ UiBlitPS_Output PSDef(UiBlitPS, UiBlitPS_Input input)
UiBlitPS_Output result;
Texture2D<Vec4> tex = UniformResourceFromRid(sig.src);
Vec4 color = tex.Sample(sampler, input.uv);
Vec4 color = tex.Sample(sampler, input.tex_uv);
/* Apply tone map */
if (sig.flags & UiBlitFlag_ToneMap)
@ -372,7 +361,7 @@ Struct(UiRectPS_Input)
{
Semantic(Vec4, sv_position);
Semantic(nointerpolation Texture2DRid, tex);
Semantic(Vec2, uv);
Semantic(Vec2, tex_uv);
Semantic(Vec4, tint_srgb);
};
@ -386,22 +375,17 @@ Struct(UiRectPS_Output)
UiRectPS_Input VSDef(UiRectVS, Semantic(u32, sv_instanceid), Semantic(u32, sv_vertexid))
{
ConstantBuffer<UiRectSig> sig = ui_rect_sig;
static const Vec2 unit_quad_verts[4] = {
Vec2(-0.5f, -0.5f),
Vec2(0.5f, -0.5f),
Vec2(0.5f, 0.5f),
Vec2(-0.5f, 0.5f)
};
StructuredBuffer<UiRectInstance> instances = UniformResourceFromRid(sig.instances);
UiRectInstance instance = instances[sv_instanceid];
Vec2 vert = unit_quad_verts[sv_vertexid];
Vec2 world_pos = mul(instance.xf, Vec3(vert, 1)).xy;
Vec2 rect_uv = RectUvFromVertexId(sv_vertexid);
Vec2 rect_uv_centered = rect_uv - 0.5;
Vec2 world_pos = mul(instance.xf, Vec3(rect_uv_centered, 1)).xy;
UiRectPS_Input result;
result.sv_position = mul(sig.projection, Vec4(world_pos, 0, 1));
result.tex = instance.tex;
result.uv = instance.uv0 + ((vert + 0.5) * (instance.uv1 - instance.uv0));
result.tex_uv = lerp(instance.tex_uv0, instance.tex_uv1, rect_uv);
result.tint_srgb = Vec4NormFromU32(instance.tint_srgb);
return result;
}
@ -419,7 +403,7 @@ UiRectPS_Output PSDef(UiRectPS, UiRectPS_Input input)
{
Texture2D<Vec4> tex = NonUniformResourceFromRid(input.tex);
SamplerState sampler = UniformSamplerFromRid(sig.sampler);
color *= tex.Sample(sampler, input.uv);
color *= tex.Sample(sampler, input.tex_uv);
}
result.sv_target0 = color;

View File

@ -19,8 +19,8 @@ Struct(MaterialInstance)
Texture2DRid tex;
u32 grid_id;
Xform xf;
Vec2 uv0;
Vec2 uv1;
Vec2 tex_uv0;
Vec2 tex_uv1;
u32 tint_srgb;
u32 is_light;
Vec3 light_emittance_srgb;
@ -29,7 +29,7 @@ Struct(MaterialInstance)
.tex = { U32Max }, \
.grid_id = U32Max, \
.xf = XformIdentity, \
.uv1 = VEC2(1, 1), \
.tex_uv1 = VEC2(1, 1), \
.tint_srgb = Color_White, \
}
@ -149,8 +149,8 @@ Struct(UiRectInstance)
{
Texture2DRid tex;
Xform xf;
Vec2 uv0;
Vec2 uv1;
Vec2 tex_uv0;
Vec2 tex_uv1;
u32 tint_srgb;
};

View File

@ -1,11 +1,9 @@
Struct(TTF_Glyph)
{
f32 off_x;
f32 off_y;
f32 width;
f32 height;
i32 advance;
Rect atlas_rect;
Vec2 baseline_offset;
Vec2I32 atlas_p0;
Vec2I32 atlas_p1;
};
Struct(TTF_Decoded)
@ -16,6 +14,11 @@ Struct(TTF_Decoded)
u32 image_width;
u32 image_height;
u32 *image_pixels; /* Array of [width * height] pixels */
/* Metrics */
f32 ascent;
f32 descent;
};
void TTF_Startup(void);

View File

@ -128,13 +128,16 @@ TTF_Decoded TTF_Decode(Arena *arena, String encoded, f32 point_size, u32 *cache_
TTF_Glyph *glyphs = (TTF_Glyph *)PushStructs(arena, TTF_Glyph, glyph_count);
/* Acquire (starting) atlas memory
* NOTE: This is unecessary since atlas memory will grow anyway. Could
* NOTE: This is unnecessary since atlas memory will grow anyway. Could
* just start w/ atlas height 0.
*/
u64 atlas_w = 1024;
u64 atlas_h = 1;
u32 *atlas_memory = PushStructs(arena, u32, atlas_w * atlas_h);
i32 ascent = 0;
i32 descent = 0;
//- Fill atlas & metric data
u32 out_offset_x = 0;
u32 out_offset_y = 0;
@ -173,6 +176,8 @@ TTF_Decoded TTF_Decode(Arena *arena, String encoded, f32 point_size, u32 *cache_
//- Compute glyph metrics
DWRITE_GLYPH_METRICS glyph_metrics = ZI;
error = IDWriteFontFace_GetDesignGlyphMetrics(font_face, &i, 1, &glyph_metrics, 0);
ascent = metrics.ascent * pixel_per_design_unit;
descent = metrics.descent * pixel_per_design_unit;
f32 off_x = (f32)bounding_box.left - raster_target_x;
f32 off_y = (f32)bounding_box.top - raster_target_y;
@ -181,10 +186,7 @@ TTF_Decoded TTF_Decode(Arena *arena, String encoded, f32 point_size, u32 *cache_
f32 advance = CeilF32ToI32((f32)glyph_metrics.advanceWidth * pixel_per_design_unit);
TTF_Glyph *glyph = &glyphs[i];
glyph->off_x = off_x;
glyph->off_y = off_y;
glyph->width = (f32)tex_w;
glyph->height = (f32)tex_h;
glyph->baseline_offset = VEC2(off_x, off_y);
glyph->advance = advance;
/* Get the bitmap */
@ -211,11 +213,8 @@ TTF_Decoded TTF_Decode(Arena *arena, String encoded, f32 point_size, u32 *cache_
}
/* Set bounding box metrics (now that we know atlas x & y) */
ZeroStruct(&glyph->atlas_rect);
glyph->atlas_rect.x = (f32)out_offset_x;
glyph->atlas_rect.y = (f32)out_offset_y;
glyph->atlas_rect.width = (f32)tex_w;
glyph->atlas_rect.height = (f32)tex_h;
glyph->atlas_p0 = VEC2I32(out_offset_x, out_offset_y);
glyph->atlas_p1 = VEC2I32(out_offset_x + tex_w, out_offset_y + tex_h);
//- Fill atlas
u64 in_pitch = (u64)dib.dsBm.bmWidthBytes / 4;
@ -281,5 +280,7 @@ TTF_Decoded TTF_Decode(Arena *arena, String encoded, f32 point_size, u32 *cache_
result.image_width = (u32)atlas_w;
result.image_height = (u32)atlas_h;
result.image_pixels = (u32 *)atlas_memory;
result.ascent = ascent;
result.descent = descent;
return result;
}

View File

@ -6,10 +6,18 @@ UI_SharedState UI_shared_state = ZI;
void UI_Startup(void)
{
/* Prefetch default font */
Resource default_font = ResourceFromStore(&UI_Resources, Lit("font/default.ttf"));
Resource default_font = UI_GetDefaultFontResource();
F_LoadFontAsync(default_font, 12);
}
////////////////////////////////////////////////////////////
//~ Font helpers
Resource UI_GetDefaultFontResource(void)
{
return ResourceFromStore(&UI_Resources, Lit("font/default.ttf"));
}
////////////////////////////////////////////////////////////
//~ Key helpers
@ -188,8 +196,11 @@ UI_Box *UI_BuildBox(UI_BoxFlag flags, UI_Key key)
box->pref_size[Axis_Y] = UI_UseTop(Height);
box->layout_axis = UI_UseTop(LayoutAxis);
box->background_color = UI_UseTop(BackgroundColor);
box->border_color = UI_UseTop(BorderColor);
box->border = UI_UseTop(Border);
box->font_resource = UI_UseTop(Font);
box->font_size = UI_UseTop(FontSize);
box->text_padding = UI_UseTop(TextPadding);
/* Prefetch font */
if (box->flags & UI_BoxFlag_DrawText)
@ -268,7 +279,7 @@ void UI_BeginBuild(void)
g->style_tops[UI_StyleKind_Parent]->style.Parent = g->root_box;
g->style_tops[UI_StyleKind_Width]->style.Width = UI_RatioSize(1, 0);
g->style_tops[UI_StyleKind_Height]->style.Height = UI_RatioSize(1, 0);
g->style_tops[UI_StyleKind_Font]->style.Font = ResourceFromStore(&UI_Resources, Lit("font/default.ttf"));
g->style_tops[UI_StyleKind_Font]->style.Font = UI_GetDefaultFontResource();
g->style_tops[UI_StyleKind_FontSize]->style.FontSize = 12.0f;
}
@ -351,17 +362,25 @@ GPU_Resource *UI_EndBuild(Rect render_viewport)
}
else if (pref_size.kind == UI_SizeKind_Text)
{
/* TODO: Distinguish between baseline alignment & visual alignment */
f32 text_size = 0;
if (box->display_text.len > 0 && box->font)
{
if (box->glyph_run.count == 0)
{
box->glyph_run = F_GlyphRunFromString(g->build_arena, box->font, box->display_text);
box->glyph_run = F_RunFromString(g->build_arena, box->font, box->display_text);
}
text_size += box->glyph_run.width * (axis == Axis_X);
text_size += box->glyph_run.height * (axis == Axis_Y);
if (axis == Axis_X)
{
f32 baseline_length = box->glyph_run.baseline_length;
text_size = baseline_length;
}
box->solved_dims[axis] = text_size;
else
{
text_size = box->font->ascent + box->font->descent;
}
}
box->solved_dims[axis] = text_size + box->text_padding;
}
}
}
@ -477,13 +496,17 @@ GPU_Resource *UI_EndBuild(Rect render_viewport)
rect->tex_uv0 = VEC2(0, 0);
rect->tex_uv1 = VEC2(1, 1);
rect->background_srgb = box->background_color;
rect->border_srgb = box->border_color;
rect->border = box->border;
/* FIXME: Remove this */
u64 test_idx = rect - ((UI_RectInstance *)ArenaBase(g->draw_rects_arena));
if (test_idx == 931)
#if 1
if (pre_index > 2)
{
DEBUGBREAKABLE;
rect->border_srgb = Rgba32F(0, 1, 1, 0.4);
rect->border = 1;
}
#endif
if ((box->flags & UI_BoxFlag_DrawImage) && box->display_image)
@ -495,24 +518,54 @@ GPU_Resource *UI_EndBuild(Rect render_viewport)
if ((box->flags & UI_BoxFlag_DrawText) && box->glyph_run.count > 0 && box->font)
{
Texture2DRid tex_rid = GPU_Texture2DRidFromResource(box->font->texture);
f32 inv_font_image_width = 1.0f / (f32)box->font->image_width;
f32 inv_font_image_height = 1.0f / (f32)box->font->image_height;
Vec2 inv_font_image_size = VEC2(1.0f / (f32)box->font->image_width, 1.0f / (f32)box->font->image_height);
String text = box->display_text;
F_GlyphRun run = box->glyph_run;
f32 offset = 0;
F_Run run = box->glyph_run;
f32 ascent = box->font->ascent;
f32 descent = box->font->descent;
f32 padding = box->text_padding;
f32 half_padding = padding * 0.5f;
Vec2 baseline = box->p0;
baseline = AddVec2(baseline, VEC2(half_padding, half_padding + ascent));
// offset.x += run.width / 2;
// offset.y += run.baseline_depth;
// offset.x += half_padding;
// offset.y += half_padding;
for (u64 i = 0; i < run.count; ++i)
{
F_Glyph glyph = run.glyphs[i];
Rect atlas_rect = glyph.atlas_rect;
F_RunRect rr = run.rects[i];
Vec2 atlas_p0 = Vec2FromVec(rr.atlas_p0);
Vec2 atlas_p1 = Vec2FromVec(rr.atlas_p1);
Vec2 glyph_size = SubVec2(atlas_p1, atlas_p0);
if (glyph_size.x != 0 || glyph_size.y != 0)
{
UI_RectInstance *rect = PushStruct(g->draw_rects_arena, UI_RectInstance);
rect->flags |= UI_RectFlag_DrawTexture;
rect->p0 = AddVec2(box->p0, VEC2(offset, 0));
rect->p1 = AddVec2(rect->p0, VEC2(glyph.width, glyph.height));
rect->tex_uv0 = VEC2(atlas_rect.x * inv_font_image_width, atlas_rect.y * inv_font_image_height);
rect->tex_uv1 = VEC2((atlas_rect.x + atlas_rect.width) * inv_font_image_width, (atlas_rect.y + atlas_rect.height) * inv_font_image_height);
/* FIXME: Shouldn't have to do this */
atlas_p0.x += 1;
atlas_p0.y += 1;
atlas_p1.x += 1;
atlas_p1.y += 1;
rect->p0 = AddVec2(baseline, rr.baseline_start_offset);
rect->p1 = AddVec2(rect->p0, glyph_size);
rect->tex_uv0 = MulVec2Vec2(atlas_p0, inv_font_image_size);
rect->tex_uv1 = MulVec2Vec2(atlas_p1, inv_font_image_size);
/* FIXME: Remove this (debugging) */
#if 0
rect->border_srgb = Rgba32F(1, 0, 1, 0.4);
rect->border = 1;
#endif
rect->tex = tex_rid;
offset += glyph.advance;
}
}
}
}

View File

@ -66,12 +66,15 @@ Struct(UI_Box)
UI_Size pref_size[Axis_CountXY];
u32 background_color;
u32 border_color;
f32 border;
f32 text_padding;
Resource font_resource;
f32 font_size;
//- Layout data
F_GlyphRun glyph_run;
F_Run glyph_run;
F_Font *font;
f32 solved_dims[Axis_CountXY];
f32 layout_cursor;
@ -96,8 +99,11 @@ Struct(UI_BoxBin)
x(Width, UI_Size) \
x(Height, UI_Size) \
x(BackgroundColor, u32) \
x(BorderColor, u32) \
x(Border, f32) \
x(Font, Resource) \
x(FontSize, u32) \
x(TextPadding, f32) \
/* ----------------------------------- */
Enum(UI_StyleKind)
@ -178,6 +184,11 @@ Struct(UI_SharedState)
void UI_Startup(void);
////////////////////////////////////////////////////////////
//~ Font helpers
Resource UI_GetDefaultFontResource(void);
////////////////////////////////////////////////////////////
//~ Key helpers

View File

@ -8,6 +8,8 @@ Struct(UI_RectPS_Input)
Semantic(Vec4, sv_position);
Semantic(Vec4, background_lin);
Semantic(Vec4, border_lin);
Semantic(Vec2, rect_uv);
Semantic(Vec2, tex_uv);
Semantic(nointerpolation u32, instance_idx);
};
@ -22,25 +24,25 @@ Struct(UI_RectPS_Output)
UI_RectPS_Input VSDef(UI_RectVS, Semantic(u32, sv_instanceid), Semantic(u32, sv_vertexid))
{
ConstantBuffer<UI_RectSig> sig = UI_rect_sig;
static const Vec2 uvs[4] = {
Vec2(0, 0),
Vec2(1, 0),
Vec2(1, 1),
Vec2(0, 1),
};
StructuredBuffer<UI_RectInstance> instances = UniformResourceFromRid(sig.instances);
UI_RectInstance instance = instances[sv_instanceid];
Vec2 uv = uvs[sv_vertexid];
Vec2 tex_uv = lerp(instance.tex_uv0, instance.tex_uv1, uv);
Vec2 screen_vert = lerp(instance.p0, instance.p1, uv);
Vec2 ndc = NdcFromViewport(sig.viewport_size, screen_vert);
#if 0
Vec2 rect_uv = RectUvFromVertexId(sv_vertexid);
Vec2 tex_uv = lerp(instance.tex_uv0, instance.tex_uv1, rect_uv);
Vec2 screen_vert = lerp(instance.p0, instance.p1, rect_uv);
#else
Vec2 rect_uv = RectUvFromVertexId(sv_vertexid);
Vec2 tex_uv = lerp(instance.tex_uv0, instance.tex_uv1, rect_uv);
Vec2 screen_vert = lerp(instance.p0, instance.p1, rect_uv);
#endif
UI_RectPS_Input result;
result.sv_position = Vec4(ndc.xy, 0, 1);
result.sv_position = Vec4(NdcFromViewport(sig.viewport_size, screen_vert).xy, 0, 1);
result.background_lin = LinearFromSrgbU32(instance.background_srgb);
result.border_lin = LinearFromSrgbU32(instance.border_srgb);
result.instance_idx = sv_instanceid;
result.rect_uv = rect_uv;
result.tex_uv = tex_uv;
return result;
@ -53,21 +55,39 @@ UI_RectPS_Output PSDef(UI_RectPS, UI_RectPS_Input input)
ConstantBuffer<UI_RectSig> sig = UI_rect_sig;
StructuredBuffer<UI_RectInstance> instances = UniformResourceFromRid(sig.instances);
UI_RectInstance instance = instances[input.instance_idx];
Vec4 color;
Vec4 result = 0;
Vec2 p = input.sv_position.xy;
Vec2 p0 = instance.p0;
Vec2 p1 = instance.p1;
Vec2 rect_uv = input.rect_uv;
/* Background */
if (instance.flags & UI_RectFlag_DrawTexture)
{
SamplerState sampler = UniformSamplerFromRid(sig.sampler);
Texture2D<Vec4> tex = NonUniformResourceFromRid(instance.tex);
color = tex.Sample(sampler, input.tex_uv);
result = tex.Sample(sampler, input.tex_uv);
}
else
{
color = input.background_lin;
result = input.background_lin;
}
UI_RectPS_Output result;
result.sv_target0 = color;
return result;
/* Border */
Vec2 to_border = Vec2(0, 0);
to_border.x = min(p.x - p0.x, p1.x - p.x);
to_border.y = min(p.y - p0.y, p1.y - p.y);
f32 dist_to_border = min(to_border.x, to_border.y);
if (dist_to_border < instance.border)
{
result = input.border_lin;
}
f32 border_thickness = instance.border;
UI_RectPS_Output output;
output.sv_target0 = result;
return output;
}

View File

@ -23,6 +23,8 @@ Struct(UI_RectInstance)
Vec2 p0;
Vec2 p1;
u32 background_srgb;
u32 border_srgb;
f32 border;
Vec2 tex_uv0;
Vec2 tex_uv1;
Texture2DRid tex;