From f8734dce88161564ea36acefedff5ef71c05f737 Mon Sep 17 00:00:00 2001 From: n30nex Date: Sun, 27 Sep 2026 00:18:24 -0400 Subject: [PATCH] Bound GPU presentation cost without dropping packet motion --- web/src/gpuEffects.ts | 12 +++++++++--- web/src/packetAnimator.ts | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/web/src/gpuEffects.ts b/web/src/gpuEffects.ts index a355431..4e1d430 100644 --- a/web/src/gpuEffects.ts +++ b/web/src/gpuEffects.ts @@ -146,21 +146,27 @@ export class GPUEffects { export function mapEffects(map: LibreMap) { const batch=new GPUEffects(); let activeGL: WebGL2RenderingContext|undefined; - let lastInk=false, requestedAt=0, slowFrames=0, budgetFallback=false; + let lastInk=false, requestedAt=0, slowFrames=0, lastFrameAt=0, slowPresentationFrames=0, budgetFallback=false; const measure=()=>{ if(!requestedAt)return; const elapsed=performance.now()-requestedAt;requestedAt=0; slowFrames=elapsed>80?slowFrames+1:Math.max(0,slowFrames-1); if(slowFrames>=3)budgetFallback=true; }; - const retry=()=>{budgetFallback=false;slowFrames=0;requestedAt=0;}; + const retry=()=>{budgetFallback=false;slowFrames=0;slowPresentationFrames=0;lastFrameAt=0;requestedAt=0;}; map.on('render',measure);map.on('moveend',retry); const layer: CustomLayerInterface={id:'live-packet-effects',type:'custom',renderingMode:'2d',onAdd(_map,gl){activeGL=gl as WebGL2RenderingContext;batch.initialize(activeGL);},render(){batch.draw();},onRemove(){batch.dispose();}}; const attach=()=>{if(map.isStyleLoaded?.() && !map.getLayer(layer.id)) map.addLayer(layer);}; const lost=()=>batch.dispose(); const restored=()=>{if(activeGL)batch.initialize(activeGL);attach();map.triggerRepaint();}; map.on('load',attach); map.on('idle',attach); map.on('styledata',attach); map.on('webglcontextlost',lost); map.on('webglcontextrestored',restored); attach(); - return {batch,prepare:()=>batch.setEnabled(displayPreferences().quality !== 'economy' && (!budgetFallback || displayPreferences().quality === 'high')),flush:()=>{ + return {batch,prepare:()=>{ + const now=performance.now(); + if(lastInk&&lastFrameAt)slowPresentationFrames=now-lastFrameAt>80?slowPresentationFrames+1:Math.max(0,slowPresentationFrames-1); + if(slowPresentationFrames>=3)budgetFallback=true; + lastFrameAt=now; + batch.setEnabled(displayPreferences().quality !== 'economy' && !budgetFallback); + },flush:()=>{ const ink=batch.ready&&batch.hasInk; // A stationary faded trail does not require rerasterizing the whole basemap. if(ink||lastInk){if(!requestedAt)requestedAt=performance.now();map.triggerRepaint();} diff --git a/web/src/packetAnimator.ts b/web/src/packetAnimator.ts index aa4781e..a194afd 100644 --- a/web/src/packetAnimator.ts +++ b/web/src/packetAnimator.ts @@ -396,6 +396,7 @@ export class PacketAnimator { this.frameId = 0; this.residueTimer = undefined; this.clearCanvas(); + this.gpu.batch.clear(); this.gpu.flush(); this.clearResidueCanvas(); } else { this.requestFrame(); @@ -1087,8 +1088,6 @@ export class PacketAnimator { } private clearCanvas(): void { - this.gpu.batch.clear(); - this.gpu.flush(); const width = this.canvas.width / this.dpr; const height = this.canvas.height / this.dpr; this.context.clearRect(0, 0, width, height);