From 56fd5e19ff9b99c46d82713c1a565595275fd41e Mon Sep 17 00:00:00 2001 From: jacob Date: Thu, 23 Oct 2025 03:03:12 -0500 Subject: [PATCH] text layouting --- src/base/base_math.h | 2 +- src/base/base_math_gpu.h | 14 +++++ src/font/font.c | 84 +++++++++++++++++++++++++--- src/font/font.h | 45 +++++++++------ src/pp/pp.c | 87 +++++++++++++++++------------ src/pp/pp.h | 3 +- src/pp/pp_draw.gpu | 72 ++++++++++-------------- src/pp/pp_draw.h | 10 ++-- src/ttf/ttf.h | 13 +++-- src/ttf/ttf_dwrite/ttf_dwrite.c | 21 +++---- src/ui/ui.c | 99 +++++++++++++++++++++++++-------- src/ui/ui.h | 13 ++++- src/ui/ui_draw.gpu | 56 +++++++++++++------ src/ui/ui_draw.h | 2 + 14 files changed, 354 insertions(+), 167 deletions(-) diff --git a/src/base/base_math.h b/src/base/base_math.h index a8d837db..88f5edef 100644 --- a/src/base/base_math.h +++ b/src/base/base_math.h @@ -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); diff --git a/src/base/base_math_gpu.h b/src/base/base_math_gpu.h index 2c80bd5d..e060400f 100644 --- a/src/base/base_math_gpu.h +++ b/src/base/base_math_gpu.h @@ -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 diff --git a/src/font/font.c b/src/font/font.c index 263b06af..5a28f52c 100644 --- a/src/font/font.c +++ b/src/font/font.c @@ -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 } diff --git a/src/font/font.h b/src/font/font.h index 3d178ef4..091a68a1 100644 --- a/src/font/font.h +++ b/src/font/font.h @@ -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); diff --git a/src/pp/pp.c b/src/pp/pp.c index f28a914a..8817dc55 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -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); ////////////////////////////// diff --git a/src/pp/pp.h b/src/pp/pp.h index fc517e12..7d6c9b9c 100644 --- a/src/pp/pp.h +++ b/src/pp/pp.h @@ -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); diff --git a/src/pp/pp_draw.gpu b/src/pp/pp_draw.gpu index 32cb8769..6d7b77fe 100644 --- a/src/pp/pp_draw.gpu +++ b/src/pp/pp_draw.gpu @@ -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 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 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 tex = NonUniformResourceFromRid(input.tex); - albedo *= tex.Sample(sampler, input.uv); + albedo *= tex.Sample(sampler, input.tex_uv); } /* Grid */ @@ -155,15 +149,15 @@ void CSDef(FloodCS, Semantic(Vec3U32, sv_dispatchthreadid)) { /* Flood */ Vec2I32 read_coords[9] = { - (Vec2I32)id + Vec2I32(-step_len, -step_len), /* top left */ - (Vec2I32)id + Vec2I32(0, -step_len), /* top center */ - (Vec2I32)id + Vec2I32(+step_len, -step_len), /* top right */ - (Vec2I32)id + Vec2I32(-step_len, 0), /* center left */ - (Vec2I32)id + Vec2I32(0, 0), /* center center */ - (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), /* top left */ + (Vec2I32)id + Vec2I32(0, -step_len), /* top center */ + (Vec2I32)id + Vec2I32(+step_len, -step_len), /* top right */ + (Vec2I32)id + Vec2I32(-step_len, 0), /* center left */ + (Vec2I32)id + Vec2I32(0, 0), /* center center */ + (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 */ }; 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 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 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 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 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 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; diff --git a/src/pp/pp_draw.h b/src/pp/pp_draw.h index 22854b4b..1a922f94 100644 --- a/src/pp/pp_draw.h +++ b/src/pp/pp_draw.h @@ -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; }; diff --git a/src/ttf/ttf.h b/src/ttf/ttf.h index 69de1d6f..a6b51d3c 100644 --- a/src/ttf/ttf.h +++ b/src/ttf/ttf.h @@ -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); diff --git a/src/ttf/ttf_dwrite/ttf_dwrite.c b/src/ttf/ttf_dwrite/ttf_dwrite.c index cd09aa4b..67a93817 100644 --- a/src/ttf/ttf_dwrite/ttf_dwrite.c +++ b/src/ttf/ttf_dwrite/ttf_dwrite.c @@ -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; } diff --git a/src/ui/ui.c b/src/ui/ui.c index dc763503..1ab47649 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -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); + } + if (axis == Axis_X) + { + f32 baseline_length = box->glyph_run.baseline_length; + text_size = baseline_length; + } + else + { + text_size = box->font->ascent + box->font->descent; } - text_size += box->glyph_run.width * (axis == Axis_X); - text_size += box->glyph_run.height * (axis == Axis_Y); } - box->solved_dims[axis] = text_size; + 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; - 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); - rect->tex = tex_rid; - offset += glyph.advance; + 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; + + /* 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; + } } } } diff --git a/src/ui/ui.h b/src/ui/ui.h index 7f0dd981..1d3076b7 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -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 diff --git a/src/ui/ui_draw.gpu b/src/ui/ui_draw.gpu index 2cb29b28..abc38661 100644 --- a/src/ui/ui_draw.gpu +++ b/src/ui/ui_draw.gpu @@ -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 sig = UI_rect_sig; - static const Vec2 uvs[4] = { - Vec2(0, 0), - Vec2(1, 0), - Vec2(1, 1), - Vec2(0, 1), - }; - StructuredBuffer 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 sig = UI_rect_sig; StructuredBuffer 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 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; } diff --git a/src/ui/ui_draw.h b/src/ui/ui_draw.h index cb9c7c2a..3d5ea9f6 100644 --- a/src/ui/ui_draw.h +++ b/src/ui/ui_draw.h @@ -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;