perf(ui): memoize the input-field caret prefix width (#128)
DrawInputField re-measured the cursor prefix via Font::GetLineWidth on every frame of a focused field, even though only the blink (caretVisible) changes frame-to-frame — value, cursorPos and fontSize are stable across the vast majority of frames. Cache the measured prefix WIDTH on the InputField, keyed on (prefix bytes, fontSize). A cheap byte compare of the cached prefix guards the expensive per-glyph UTF-8 decode + advance accumulation. The cache is mutable so the const-ref draw fn can refresh it. The absolute caretX is deliberately NOT cached: it adds the layout- dependent textX (rect.x + paddingX) each frame, so caching it would give a relocated field a stale caret. Caching the width keeps that correct. Adds tests/InputFieldCaretCache pinning: the cached caret matches the uncached GetLineWidth oracle across states, a redraw (cache hit) is identical to the miss, the cache invalidates on value/cursorPos/fontSize changes, the blink never moves the caret, and a relocated field shifts the caret by exactly the rect delta. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2c07abee00
commit
a45e793da4
5 changed files with 309 additions and 1 deletions
32
project.cpp
32
project.cpp
|
|
@ -863,6 +863,38 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
hc.GetInterfacesAndImplementations(ifaces, hitImpls);
|
||||
cfg.tests.push_back(std::move(hitTest));
|
||||
|
||||
// Issue #128: DrawInputField re-measured the cursor prefix via
|
||||
// Font::GetLineWidth every frame of a focused field even though only the
|
||||
// blink changes frame-to-frame. The prefix WIDTH is now memoised on the
|
||||
// InputField keyed on (prefix bytes, fontSize); the absolute caretX is
|
||||
// deliberately not cached so a relocated field can't get a stale caret.
|
||||
// The memo is transparent presentation logic over a TrueType file —
|
||||
// DrawText no-ops with a null atlas/renderer — so this test drives
|
||||
// DrawInputField directly with no Vulkan device or window. The font is
|
||||
// copied next to the binary; the test also probes the project root.
|
||||
Test caretTest;
|
||||
Configuration& crc = caretTest.config;
|
||||
crc.path = cfg.path;
|
||||
crc.name = "InputFieldCaretCache";
|
||||
crc.outputName = "InputFieldCaretCache";
|
||||
crc.type = ConfigurationType::Executable;
|
||||
crc.target = cfg.target;
|
||||
crc.march = cfg.march;
|
||||
crc.mtune = cfg.mtune;
|
||||
crc.debug = cfg.debug;
|
||||
crc.sysroot = cfg.sysroot;
|
||||
crc.dependencies = cfg.dependencies;
|
||||
crc.externalDependencies = cfg.externalDependencies;
|
||||
crc.compileFlags = cfg.compileFlags;
|
||||
crc.linkFlags = cfg.linkFlags;
|
||||
crc.defines = cfg.defines;
|
||||
crc.cFiles = cfg.cFiles;
|
||||
crc.files = { fs::path("tests/InputFieldCaretCache/font.ttf") };
|
||||
std::vector<fs::path> caretImpls(impls.begin(), impls.end());
|
||||
caretImpls.emplace_back("tests/InputFieldCaretCache/main");
|
||||
crc.GetInterfacesAndImplementations(ifaces, caretImpls);
|
||||
cfg.tests.push_back(std::move(caretTest));
|
||||
|
||||
// Issue #69: the engine feeds one shared Device::pipelineCache to every
|
||||
// vkCreate*Pipelines call and persists it across runs, discarding an
|
||||
// on-disk blob whose header doesn't match the current GPU. That gate —
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue