unroll / lib /dragres.js
Chase Hendrick
Lower resolution while dragging (lib/dragres.js): renderer and composer pixel ratio drop on OrbitControls start and return 150 ms after the camera settles; Settings and panel control (off, light, strong), unrollView.dragRes API, ?dragRes= URL, glossary entry, docs/CT_VIEW.md; fix undefined prevClear in hover pick
1c31760
Raw History Blame Contribute Delete
4.22 kB
// 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', `<label class="ui-row" for="dragResSel"><span>Resolution while dragging</span><select id="dragResSel" aria-label="Resolution while dragging the camera"><option value="off">Full (off)</option><option value="light">Light (0.75x)</option><option value="strong">Strong (0.5x)</option></select></label>`);
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();