perf(text): cache shaped runs in ShapeText (#52)
ShapeText re-decoded UTF-8, re-looked-up every glyph, and recomputed layout every frame for static strings (DrawText / EmitCenteredLabel run inside per-frame onBuild). DrawText and EmitCenteredLabel also did a second full pass over the glyphs to apply alignment. Memoize the shaped run, keyed by (Font*, pxSize, color, utf8), as an origin-relative vector<GlyphItem>. A cache hit skips decode/lookup/layout and just translates the cached run into the output buffer by (x + alignShift, baselineY). The alignment shift (Center = -advance/2, Right = -advance) is folded into ShapeText's single pass, dropping the second loop in both callers. TextAlign moves to the :UI partition so ShapeText can take it. Pure CPU memoization — the glyph buffer is fully rewritten and re-flushed every frame, so a hit is byte-equivalent. A miss still calls Ensure so new glyphs rasterise and dirty the atlas; hits don't (atlas entries are never evicted). InvalidateFont drops a font's runs before it is destroyed (the cache, like FontAtlas's glyph cache, keys on the Font pointer). A soft cap bounds memory against pathological per-frame-unique text. Adds the ShapeTextCache test (15 checks): hit == miss byte-for-byte, hits don't touch the atlas, translate/alignment/truncation/InvalidateFont. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
58e9e0a747
commit
ca48f79465
7 changed files with 391 additions and 40 deletions
31
project.cpp
31
project.cpp
|
|
@ -295,6 +295,37 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
blasImpls.emplace_back("tests/BLASBuildOptions/main");
|
||||
bc.GetInterfacesAndImplementations(ifaces, blasImpls);
|
||||
cfg.tests.push_back(std::move(blasTest));
|
||||
|
||||
// Issue #52: shaped-run cache for UIRenderer::ShapeText. Shapes a
|
||||
// string against a real CPU-side FontAtlas and asserts that a cache
|
||||
// hit is byte-equivalent to the uncached path, that hits don't touch
|
||||
// the atlas, and that translate / alignment / truncation /
|
||||
// InvalidateFont all behave. Needs a headless Vulkan device (the
|
||||
// atlas image is a real GPU image) but no swapchain — same native
|
||||
// build settings as the others. The font file is copied next to the
|
||||
// binary; the test also probes the project-root path.
|
||||
Test textTest;
|
||||
Configuration& xc = textTest.config;
|
||||
xc.path = cfg.path;
|
||||
xc.name = "ShapeTextCache";
|
||||
xc.outputName = "ShapeTextCache";
|
||||
xc.type = ConfigurationType::Executable;
|
||||
xc.target = cfg.target;
|
||||
xc.march = cfg.march;
|
||||
xc.mtune = cfg.mtune;
|
||||
xc.debug = cfg.debug;
|
||||
xc.sysroot = cfg.sysroot;
|
||||
xc.dependencies = cfg.dependencies;
|
||||
xc.externalDependencies = cfg.externalDependencies;
|
||||
xc.compileFlags = cfg.compileFlags;
|
||||
xc.linkFlags = cfg.linkFlags;
|
||||
xc.defines = cfg.defines;
|
||||
xc.cFiles = cfg.cFiles;
|
||||
xc.files = { fs::path("tests/ShapeTextCache/font.ttf") };
|
||||
std::vector<fs::path> textImpls(impls.begin(), impls.end());
|
||||
textImpls.emplace_back("tests/ShapeTextCache/main");
|
||||
xc.GetInterfacesAndImplementations(ifaces, textImpls);
|
||||
cfg.tests.push_back(std::move(textTest));
|
||||
}
|
||||
|
||||
return cfg;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue