perf(ui): gate per-pixel clip compares behind a flags bit (#50)

uiResolveScreenPixel ran four float compares against hdr.clipRectPx for
every pixel of every UI dispatch, even though the clip rect is the
full-surface default {0,0,1e9,1e9} in the overwhelmingly common case.

Reserve the high bit of the header flags word (UI_FLAG_CLIP) and have
FillHeader set it only when the clip rect is narrower than the surface
(extracted into the unit-testable UIRenderer::ClipFlags). The shader
gates the compares on the bit; the branch is push-constant-uniform so it
never diverges. When the rect covers the surface the skipped compares
could never reject an in-surface pixel, so output is pixel-identical.

Mirrored on the WebGPU/WGSL path (dom-webgpu.js) for parity. Adds the
UIClipFlag CPU test covering the gate decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-06-16 16:57:07 +00:00
commit 65c19ea84a
6 changed files with 192 additions and 9 deletions

View file

@ -413,6 +413,34 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
memImpls.emplace_back("tests/MemoryTypeFallback/main");
mc.GetInterfacesAndImplementations(ifaces, memImpls);
cfg.tests.push_back(std::move(memTest));
// Issue #50: uiResolveScreenPixel now gates its per-pixel clip-rect
// compares on the reserved kUIFlagClip bit, which FillHeader sets only
// when the clip rect is narrower than the surface. The decision lives
// in UIRenderer::ClipFlags — pure CPU logic over the clip rect and the
// surface size — so this test drives it directly with synthetic
// dimensions, no Window or GPU device needed at runtime.
Test clipTest;
Configuration& clc = clipTest.config;
clc.path = cfg.path;
clc.name = "UIClipFlag";
clc.outputName = "UIClipFlag";
clc.type = ConfigurationType::Executable;
clc.target = cfg.target;
clc.march = cfg.march;
clc.mtune = cfg.mtune;
clc.debug = cfg.debug;
clc.sysroot = cfg.sysroot;
clc.dependencies = cfg.dependencies;
clc.externalDependencies = cfg.externalDependencies;
clc.compileFlags = cfg.compileFlags;
clc.linkFlags = cfg.linkFlags;
clc.defines = cfg.defines;
clc.cFiles = cfg.cFiles;
std::vector<fs::path> clipImpls(impls.begin(), impls.end());
clipImpls.emplace_back("tests/UIClipFlag/main");
clc.GetInterfacesAndImplementations(ifaces, clipImpls);
cfg.tests.push_back(std::move(clipTest));
}
return cfg;