Merge pull request 'perf(ui): hoist loop-invariant per-item/per-glyph math out of the per-pixel loop (#125)' (#142) from claude/issue-125 into master

This commit is contained in:
catbot 2026-06-18 15:22:45 +02:00
commit 2c07abee00
3 changed files with 45 additions and 20 deletions

View file

@ -70,6 +70,13 @@ shared vec4 s_v1[UI_CHUNK];
shared vec4 s_v2[UI_CHUNK];
shared vec4 s_v3[UI_CHUNK];
shared uvec4 s_v4[UI_CHUNK];
// Per-item constants precomputed once at cooperative-load time, REUSED across
// phases like the s_v* scratch (the per-pixel inner loops read them instead of
// recomputing per pixel × item):
// images: s_inv = 1.0/rect.zw
// text: s_inv = 1.0/rect.zw, s_band = SDF AA scale (a divide + two maxes)
shared vec2 s_inv[UI_CHUNK];
shared float s_band[UI_CHUNK];
shared uint s_keep[UI_CHUNK];
shared uint s_order[UI_CHUNK];
shared uint s_count;
@ -232,6 +239,7 @@ void main() {
s_v1[lid] = uiImageHeap[heap].items[idx].uv;
s_v2[lid] = uiImageHeap[heap].items[idx].tint;
s_v4[lid] = uiImageHeap[heap].items[idx].slots;
s_inv[lid] = 1.0 / s_v0[lid].zw;
keep = uiAabbOverlapsTile(s_v0[lid].xy, s_v0[lid].xy + s_v0[lid].zw,
tileMin, tileMax);
}
@ -249,7 +257,7 @@ void main() {
if (sp.x < lo.x || sp.y < lo.y) continue;
if (sp.x >= hi.x || sp.y >= hi.y) continue;
vec2 t = (sp - s_v0[c].xy) / s_v0[c].zw;
vec2 t = (sp - s_v0[c].xy) * s_inv[c];
vec2 uv = mix(s_v1[c].xy, s_v1[c].zw, t);
uint texSlot = s_v4[c].x;
@ -282,6 +290,13 @@ void main() {
s_v0[lid] = uiGlyphHeap[heap].items[idx].rect;
s_v1[lid] = uiGlyphHeap[heap].items[idx].uv;
s_v2[lid] = uiGlyphHeap[heap].items[idx].color;
s_inv[lid] = 1.0 / s_v0[lid].zw;
// SDF AA band — atlas-px per screen-px, constant per glyph
// (uvSpan * kAtlasSize(1024) / screenSpan), max'd to 1px floor.
vec2 uvSpan = s_v1[lid].zw - s_v1[lid].xy;
vec2 atlasPerScreen = (uvSpan * 1024.0) * s_inv[lid];
float scalePx = max(atlasPerScreen.x, atlasPerScreen.y);
s_band[lid] = max(scalePx, 0.0001);
keep = uiAabbOverlapsTile(s_v0[lid].xy, s_v0[lid].xy + s_v0[lid].zw,
tileMin, tileMax);
}
@ -299,7 +314,7 @@ void main() {
if (sp.x < lo.x || sp.y < lo.y) continue;
if (sp.x >= hi.x || sp.y >= hi.y) continue;
vec2 t = (sp - s_v0[c].xy) / s_v0[c].zw;
vec2 t = (sp - s_v0[c].xy) * s_inv[c];
vec2 uv = mix(s_v1[c].xy, s_v1[c].zw, t);
// Font slots are push constants — provably dynamically uniform,
@ -312,12 +327,8 @@ void main() {
float dAtlas = (ON_EDGE - sdf) * DIST_SCALE;
vec2 uvSpan = s_v1[c].zw - s_v1[c].xy;
vec2 atlasPerScreen = (uvSpan * 1024.0) / s_v0[c].zw;
float scalePx = max(atlasPerScreen.x, atlasPerScreen.y);
float band = max(scalePx, 0.0001);
float a = clamp(0.5 - dAtlas / band, 0.0, 1.0);
// band (the AA scale) was precomputed once per glyph at load.
float a = clamp(0.5 - dAtlas / s_band[c], 0.0, 1.0);
if (a <= 0.0) continue;
vec4 col = s_v2[c];

View file

@ -16,6 +16,11 @@ shared vec4 s_rect[UI_CHUNK];
shared vec4 s_uv[UI_CHUNK];
shared vec4 s_tint[UI_CHUNK];
shared uvec4 s_slots[UI_CHUNK];
// 1.0/rect.zw, precomputed once per item at cooperative-load time so the
// per-pixel inner loop multiplies instead of recomputing a vec2 reciprocal of
// the (loop-invariant, but shared-mem + varying-index, so non-hoistable by the
// compiler) rect size for every pixel × item.
shared vec2 s_invRectSize[UI_CHUNK];
shared uint s_keep[UI_CHUNK];
shared uint s_order[UI_CHUNK];
shared uint s_count;
@ -43,6 +48,7 @@ void main() {
s_uv[lid] = uiImageHeap[pc.hdr.itemBuffer].items[idx].uv;
s_tint[lid] = uiImageHeap[pc.hdr.itemBuffer].items[idx].tint;
s_slots[lid] = uiImageHeap[pc.hdr.itemBuffer].items[idx].slots;
s_invRectSize[lid] = 1.0 / s_rect[lid].zw;
keep = uiAabbOverlapsTile(s_rect[lid].xy, s_rect[lid].xy + s_rect[lid].zw,
tileMin, tileMax);
}
@ -67,7 +73,7 @@ void main() {
if (sp.x < lo.x || sp.y < lo.y) continue;
if (sp.x >= hi.x || sp.y >= hi.y) continue;
vec2 t = (sp - s_rect[c].xy) / s_rect[c].zw;
vec2 t = (sp - s_rect[c].xy) * s_invRectSize[c];
vec2 uv = mix(s_uv[c].xy, s_uv[c].zw, t);
uint texSlot = s_slots[c].x;

View file

@ -23,6 +23,13 @@ const float DIST_SCALE = 32.0;
shared vec4 s_rect[UI_CHUNK];
shared vec4 s_uv[UI_CHUNK];
shared vec4 s_color[UI_CHUNK];
// Per-glyph constants precomputed once at cooperative-load time and read by the
// per-pixel inner loop, instead of being recomputed for every pixel × glyph:
// s_invRectSize = 1.0/rect.zw (turns the per-pixel divide into a multiply)
// s_band = the SDF AA scale (a divide + two maxes that depend only on
// the glyph's uv span and rect size — fully loop-invariant)
shared vec2 s_invRectSize[UI_CHUNK];
shared float s_band[UI_CHUNK];
shared uint s_keep[UI_CHUNK];
shared uint s_order[UI_CHUNK];
shared uint s_count;
@ -49,6 +56,15 @@ void main() {
s_rect[lid] = uiGlyphHeap[pc.hdr.itemBuffer].items[idx].rect;
s_uv[lid] = uiGlyphHeap[pc.hdr.itemBuffer].items[idx].uv;
s_color[lid] = uiGlyphHeap[pc.hdr.itemBuffer].items[idx].color;
s_invRectSize[lid] = 1.0 / s_rect[lid].zw;
// Atlas-px per screen-px along this glyph's transform — keeps AA
// crisp at any rendering size. uvSpan * atlasSize / screenSpan,
// with FontAtlas::kAtlasSize = 1024. Constant for the whole glyph.
vec2 uvSpan = s_uv[lid].zw - s_uv[lid].xy;
vec2 atlasPerScreen = (uvSpan * 1024.0) * s_invRectSize[lid];
float scalePx = max(atlasPerScreen.x, atlasPerScreen.y);
// 1-screen-px AA band, expressed in atlas-pixel units of dAtlas.
s_band[lid] = max(scalePx, 0.0001);
keep = uiAabbOverlapsTile(s_rect[lid].xy, s_rect[lid].xy + s_rect[lid].zw,
tileMin, tileMax);
}
@ -73,7 +89,7 @@ void main() {
if (sp.x < lo.x || sp.y < lo.y) continue;
if (sp.x >= hi.x || sp.y >= hi.y) continue;
vec2 t = (sp - s_rect[c].xy) / s_rect[c].zw;
vec2 t = (sp - s_rect[c].xy) * s_invRectSize[c];
vec2 uv = mix(s_uv[c].xy, s_uv[c].zw, t);
// Font slots are push constants — provably dynamically uniform, so no
@ -88,16 +104,8 @@ void main() {
// Distance in atlas-pixels (negative inside the glyph).
float dAtlas = (ON_EDGE - sdf) * DIST_SCALE;
// Atlas-px per screen-px along this glyph's transform — keeps AA crisp
// at any rendering size. uvSpan * atlasSize / screenSpan.
vec2 uvSpan = s_uv[c].zw - s_uv[c].xy;
// FontAtlas::kAtlasSize = 1024.
vec2 atlasPerScreen = (uvSpan * 1024.0) / s_rect[c].zw;
float scalePx = max(atlasPerScreen.x, atlasPerScreen.y);
// 1-screen-px AA band, expressed in atlas-pixel units of dAtlas.
float band = max(scalePx, 0.0001);
float a = clamp(0.5 - dAtlas / band, 0.0, 1.0);
// band (the AA scale) was precomputed once per glyph at load.
float a = clamp(0.5 - dAtlas / s_band[c], 0.0, 1.0);
if (a <= 0.0) continue;
vec4 col = s_color[c];