Repository navigation
[M2-TEXT-02] Text items in the UI pass (string table + glyph quads) - #87
Merged
Merged
Conversation
- New laige::render text module (public header-only laige/render/text_items.h; no new .cpp, no GL calls): the FR-2.8 text-item half of the bitmap P0 text. - StringTable (PRD 10.4, no per-frame std::string in the path): u32 code points in a fixed pool (two budgeted allocations at create; defaults 4096 strings / 16 384 code points, both capped at 2^24 via kStringTableMax*); intern idempotent (same bytes -> the first interned handle); pool-bound BudgetExhausted + one rate-limited Warn string_table/table_full; a stopped table rejects intern with InvalidArgument (no log); move-only, the moved-from table is stopped. - TextItem (screen space, y down): the first line's baseline anchor, integer scale in [1, 16] (every metric x scale, exact), left/center/right per-line alignment, maxWidth wrap in 0 or [1, 2^20] px, the hand-typed UI z depthKey, the white-on-alpha SpriteTint color. - declareText: O(glyphs), zero allocation, zero logging, zero GL. One SpriteItem per ink glyph (the centered quad at the ink center, scale = the ink size, the atlas cell uv rect, depthOverride = true -- the G-R11 escape hatch, counted + warned at build). The shared detail::nextLine walk (word wrap, one collapsed space per inter-word gap, the character split for over-wide words, an over-wide single character takes its own line, leading/trailing spaces excluded) is used by the pre-check (any line wider than kTextMaxLineWidthPx fails InvalidArgument with NOTHING declared) and by the emit; a first failed batcher.add fails the call (the quads already declared remain in the frame -- the declareParticles precedent). An empty or all-space text declares nothing. - measureText: the no-wrap O(glyphs) ink-block width probe (exact int sums). - expandGlyphAtlasRgba8: fills the caller's W*H*4 span with the white-on-alpha RGBA8 upload bytes (the M2-SPRITE-02 bindAtlas input; the setup path). - Tests: CTest entry text_items (19 tests / 6 suites; LAIGE_TEST_FONT_PATH on the entry + the unfiltered entry + both sanitizer blocks): TextStringTable (create validation no-log, intern identity + dedup, both pool bounds + the cumulative warn, stopped/moved-from), TextMeasure (width goldens against the committed Vera test font), TextLayout (the exact wrap model + alignment anchors), TextDeclare (the per-glyph quad goldens against hand-computed values, the group + order contract, the failure paths incl. the nothing-declared screen-domain check, the batcher overflow drop-oldest), TextZeroAlloc (1000 frames of a wrapped 2x-scale declare = 0 heap blocks), TextSmoke (the offscreen GL smoke: the white-on-alpha upload through the real M2-SPRITE-02 submit stage, exact pixel checks -- opaque texels (255,255,255,255), transparent texels the clear, semi-transparent texels rgb exactly the atlas alpha + the alpha channel within +/-1 of the documented SRC_ALPHA/ONE_MINUS_SRC_ALPHA blend; GTEST_SKIP on no GL). - Docs (DOC-006/007): NEW docs/api/text_items.md (full contract incl. the DOC-004 Performance section); docs/api/font.md + font.h references updated to shipped; docs/concepts/coordinates.md (4.12 the text coordinate section, the 5 conversion row, the Related link); docs/README.md API index; src/laige-render/README.md status. - Roadmap: M2-TEXT-02 box checked + Shipped box; Progress Board M2 23/33 (total 69/194); changelog row. - No budgets.json entry: the per-frame text cost is part of the composite 50k render-CPU budget (M2-PERF-01); the entry count stays 17. - laige-api.json regenerated LAST (1462 -> 1512 symbols, 43 -> 44 headers); api-real-tree green; include-lint + determinism-lint OK.
…acos labels in the payload
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M2-TEXT-02 · Text items in the UI pass
FR-2.8 text-item half: the scene's string table (interned u32 code points, PRD §10.4 — no per-frame
std::stringin the path) +TextItemlayout (screen space, y down) declared into the sprite batcher as batched glyph quads in the UI pass (M2-TEXT-01 consumes the glyph atlas; M2-SPRITE-02 consumes the quads).What landed
laige::rendertext module — new header-only public headerinclude/laige/render/text_items.h(no new.cpp, no GL calls anywhere).StringTable(PRD §10.4): u32 code points in a fixed pool (two budgeted allocations atcreate; defaults 4096 strings / 16 384 code points, both capped at 2^24 viakStringTableMax*).internis idempotent (same bytes return the first interned handle — shared strings share one pool range); a pool-bound overrun isBudgetExhausted+ one rate-limited Warnstring_table/table_full; a stopped table rejectsinternwithInvalidArgument(no log); move-only, the moved-from table is stopped.TextItem+declareText: screen-space layout (the first line's baseline anchor, integerscalein [1, 16] — every metric × scale, exact, no fractional-pixel rasterization; left/center/right per-line alignment;maxWidthwrap in 0 or [1, 2^20] px). OneSpriteItemper ink glyph: the centered quad at the ink center, scale = the ink size, the atlas cell's uv rect,depthOverride = true(the G-R11 escape hatch — counted + warned at build). One (atlas, material, blend) group = one instanced draw call (RENDER-001). The sharednextLinewalk (word wrap, one collapsed space per inter-word gap, the character split for over-wide words, an over-wide single character takes its own line, leading/trailing spaces excluded) is used twice: the pre-check (any line wider thankTextMaxLineWidthPxfailsInvalidArgumentwith NOTHING declared) and the emit; a first failedbatcher.addfails the call (the quads already declared remain — thedeclareParticlesprecedent). The declare path is O(glyphs), zero allocation, zero logging, zero GL.measureText— the no-wrap O(glyphs) ink-block width probe (exact int sums).expandGlyphAtlasRgba8— fills the caller's W·H·4 span with the white-on-alpha RGBA8 upload bytes (the M2-SPRITE-02bindAtlasinput; the setup path).ctest -R text_items(19 tests / 6 suites;LAIGE_TEST_FONT_PATHon the entry + the unfiltered entry + both sanitizer blocks):TextStringTable(create validation no-log, intern identity + dedup, both pool bounds + the cumulative warn, stopped/moved-from),TextMeasure(width goldens against the committed Vera test font),TextLayout(the exact wrap model + alignment anchors),TextDeclare(the per-glyph quad goldens against hand-computed values, the group + order contract, the failure paths incl. the nothing-declared screen-domain check, the batcher overflow drop-oldest),TextZeroAlloc(1000 frames of a wrapped 2x-scale declare = 0 heap blocks),TextSmoke(the offscreen GL smoke: the white-on-alpha upload through the real M2-SPRITE-02 submit stage — exact pixel checks;GTEST_SKIPon no GL). The smoke's semi-transparent check documents the engine's blend on the alpha channel:SRC_ALPHA/ONE_MINUS_SRC_ALPHAapplies to all four channels, so over an opaque clear the output alpha isa² + (1−a)(one driver-defined 8-bit rounding step — asserted within ±1); the rgb channels are exactly the atlas alpha.docs/api/text_items.md(the full contract incl. the DOC-004 Performance section);docs/api/font.md+font.hreferences updated to shipped;docs/concepts/coordinates.md(§4.12 the text coordinate section, the §5 conversion row, the Related link);docs/README.mdAPI index;src/laige-render/README.mdstatus.laige-api.jsonregenerated LAST (1462 → 1512 symbols, 43 → 44 headers). Nobudgets.jsonentry: the per-frame text cost is part of the composite 50k render-CPU budget (M2-PERF-01); the entry count stays 17.Local verification (all six canonical trees, warning-clean)
(prior counts + 1 for the new
text_itemsentry each;ctest -R text_itemsgreen in all six;tools/laige-include-lintOK — 75 files, 16 allowed vendored edges;tools/laige-determinism-lintOK — 0 violations.)CI scope
Both
ci:windows+ci:macoslabels on this PR (the standing instruction). Windows: first MSVC compile of the text C++ (text_items.h+text_items_tests.cpp) — the C4267/C4244 audit is done against the final code (explicit casts at every size boundary; the test font read uses the houselaige::testing::ReadEnvVar+fopen_spattern). macOS: arm64 (macos-15) has no usable GL, soTextSmokeskips there; the Intel lane runs it. The full matrix runs on the merge lane.