// SPDX-License-Identifier: Apache-2.0 // Lower render resolution while the camera moves: on OrbitControls 'start' the renderer and the // effect composer drop to a fraction of their pixel ratio; once the camera has stopped changing // (pointer up and damping settled) the full ratio comes back. Setting: dragRes (off, light, strong). const SCALE = { off: 1, light: 0.75, strong: 0.5 }; const MIN_PR = 0.5; let saved = null, timer = 0, g = null, held = false; const stats = { drags: 0, lowered: 0, restored: 0, lastScale: 1 }; function mode() { try { return window.unrollView.settings.get('dragRes'); } catch { return matchMedia('(pointer: coarse)').matches ? 'strong' : 'light'; } } function setPR(pr) { g.renderer.setPixelRatio(pr); g.composer?.setPixelRatio?.(pr); g.composer?.setSize?.(innerWidth, innerHeight); } function lower() { stats.drags++; held = true; clearTimeout(timer); const s = SCALE[mode()] ?? 1; if (s >= 1 || saved !== null) return; saved = g.renderer.getPixelRatio(); const pr = Math.max(MIN_PR, saved * s); if (pr >= saved) { saved = null; return; } stats.lowered++; stats.lastScale = s; setPR(pr); } function restore() { if (saved === null) return; const pr = saved; saved = null; stats.restored++; setPR(pr); } // keep the low resolution while damping still moves the camera, restore 150 ms after the last change function changed() { if (saved !== null && !held) { clearTimeout(timer); timer = setTimeout(restore, 150); } } // panel control: a labeled select in a Performance group of the control panel function buildGroup(P) { if (document.getElementById('uiPerf')) return; const grp = P.group('Performance', 'ui-perf'); grp.id = 'uiPerf'; grp.insertAdjacentHTML('beforeend', ``); P.body.append(grp); const sel = document.getElementById('dragResSel'); sel.value = mode(); sel.onchange = () => window.unrollView.settings?.set('dragRes', sel.value); addEventListener('unroll:settings', (e) => { sel.value = e.detail.dragRes; }); P.addAction?.('dragRes', { id: 'dragResSel', label: 'Resolution while dragging: off, light or strong', run: (m) => window.unrollView.settings?.set('dragRes', m) }); P.measure?.(); } function init() { const v = window.unrollView; if (!v?.gfx?.controls) return setTimeout(init, 100); g = v.gfx; g.controls.addEventListener('start', lower); g.controls.addEventListener('change', changed); g.controls.addEventListener('end', () => { held = false; clearTimeout(timer); timer = setTimeout(restore, 150); }); if (window.unrollPanel) buildGroup(window.unrollPanel); else addEventListener('unroll:panel', (e) => buildGroup(e.detail), { once: true }); v.dragRes = { state: () => ({ mode: mode(), active: saved !== null, pixelRatio: g.renderer.getPixelRatio(), fullPixelRatio: saved ?? g.renderer.getPixelRatio(), ...stats }), set: (m) => v.settings?.set('dragRes', m), // render cost at full and lowered pixel ratio: n synchronous composer renders each, gl.finish between bench: (n = 5, m) => { const s = SCALE[m || mode()] ?? 0.75, gl = g.renderer.getContext(), full = g.renderer.getPixelRatio(); const px = new Uint8Array(4), sync = () => gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, px); // readPixels waits for the GPU const run = (pr) => { setPR(pr); g.composer.render(); sync(); const t0 = performance.now(); for (let i = 0; i < n; i++) { g.composer.render(); sync(); } return +((performance.now() - t0) / n).toFixed(1); }; const out = { pixelRatio: full, scale: s, fullMs: run(full), lowMs: run(Math.max(MIN_PR, full * s)) }; setPR(full); out.speedup = +(out.fullMs / out.lowMs).toFixed(2); return out; }, // test hooks: simulate a drag without a pointer begin: () => { lower(); return g.renderer.getPixelRatio(); }, end: () => { held = false; clearTimeout(timer); restore(); return g.renderer.getPixelRatio(); }, }; } init();