perf(viewer): keep GLB light pool at a fixed visible count (no WebGPU recompiles)
Toggling pointLight.visible changes the active-light count, which makes the WebGPU renderer rebuild every material's lighting node + pipeline — a multi- hundred-ms stall. The pool reassigns on every camera move, so zooming churned visibility and tanked FPS. Keep all 12 pool lights permanently visible and animate only intensity (an idle light lerps to 0); the light-set never changes, so no recompiles. Also make reassignment O(n) (score lookup map, not find). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a5c08b5950
commit
df901b1d4e
@@ -296,6 +296,7 @@ function GlbItemLights({
|
||||
score: scoreReg(reg, selectedLevelId, levelMode, levelIndexById, interactiveState),
|
||||
}))
|
||||
scored.sort((a, b) => a.score - b.score)
|
||||
const scoreByKey = new Map(scored.map((s) => [s.key, s.score] as const))
|
||||
const desired = scored
|
||||
.filter((s) => Number.isFinite(s.score))
|
||||
.slice(0, POOL_SIZE)
|
||||
@@ -328,9 +329,8 @@ function GlbItemLights({
|
||||
const freeSlotData = slots.current[freeSlot]
|
||||
const currentKey = freeSlotData ? (freeSlotData.key ?? freeSlotData.pendingKey) : null
|
||||
if (currentKey && !desired.includes(currentKey)) {
|
||||
const currentScore =
|
||||
scored.find((s) => s.key === currentKey)?.score ?? Number.POSITIVE_INFINITY
|
||||
const newScore = scored.find((s) => s.key === key)?.score ?? 0
|
||||
const currentScore = scoreByKey.get(currentKey) ?? Number.POSITIVE_INFINITY
|
||||
const newScore = scoreByKey.get(key) ?? 0
|
||||
if (currentScore - newScore < HYSTERESIS) {
|
||||
freeSlot++
|
||||
continue
|
||||
@@ -370,17 +370,20 @@ function GlbItemLights({
|
||||
}
|
||||
|
||||
// Per-frame: fade, snap position, and track intensity from control state.
|
||||
// The pool lights stay permanently `visible` — only `intensity` is animated
|
||||
// (an idle light just lerps to 0). Toggling `visible` would change the
|
||||
// active-light count, which forces the WebGPU renderer to recompile every
|
||||
// material's lighting node — a hard frame-time spike on every reassignment
|
||||
// (i.e. on every camera move). Keeping the count fixed avoids that entirely.
|
||||
for (let i = 0; i < POOL_SIZE; i++) {
|
||||
const light = lightRefs.current[i]
|
||||
const slot = slots.current[i]
|
||||
if (!(light && slot)) continue
|
||||
|
||||
if (slot.isFadingOut) {
|
||||
light.visible = true
|
||||
light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12)
|
||||
if (light.intensity < 0.01) {
|
||||
light.intensity = 0
|
||||
light.visible = false
|
||||
slot.isFadingOut = false
|
||||
slot.key = slot.pendingKey
|
||||
slot.pendingKey = null
|
||||
@@ -396,15 +399,12 @@ function GlbItemLights({
|
||||
}
|
||||
|
||||
if (!slot.key) {
|
||||
light.intensity = 0
|
||||
light.visible = false
|
||||
light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12)
|
||||
continue
|
||||
}
|
||||
const reg = regByKey.get(slot.key)
|
||||
if (!reg) {
|
||||
slot.key = null
|
||||
light.intensity = 0
|
||||
light.visible = false
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -428,13 +428,7 @@ function GlbItemLights({
|
||||
const targetIntensity = isOn
|
||||
? MathUtils.lerp(reg.effect.intensityRange[0], reg.effect.intensityRange[1], t)
|
||||
: reg.effect.intensityRange[0]
|
||||
|
||||
if (targetIntensity > 0) light.visible = true
|
||||
light.intensity = MathUtils.lerp(light.intensity, targetIntensity, dt * 12)
|
||||
if (targetIntensity <= 0 && light.intensity < 0.01) {
|
||||
light.intensity = 0
|
||||
light.visible = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -448,7 +442,6 @@ function GlbItemLights({
|
||||
ref={(el) => {
|
||||
lightRefs.current[i] = el
|
||||
}}
|
||||
visible={false}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user