From abe3f0a9441608fa628098856a09c9ae387446b5 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sun, 15 Mar 2026 11:44:28 +0100 Subject: [PATCH] fix(wall): Implement true double buffering to eliminate visual glitches - Created backBuffer canvas (same size as visible) - All tile drawing now uses bbCtx (backBuffer context) - Single ctx.drawImage(backBuffer) blit at frame end - Eliminates tearing and square artifacts from partial renders - BackBuffer resized in sync with visible canvas Technical: Classic double buffering pattern - compose entire frame offscreen, then atomic copy to display buffer. Co-Authored-By: Claude Opus 4.5 --- config/samples/wall-magic-chess-360.html | 52 +++++++++++++++--------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/config/samples/wall-magic-chess-360.html b/config/samples/wall-magic-chess-360.html index 77fea912..9203f152 100644 --- a/config/samples/wall-magic-chess-360.html +++ b/config/samples/wall-magic-chess-360.html @@ -865,6 +865,11 @@ const off=document.createElement('canvas'); const offCtx=off.getContext('2d'); const cv=document.getElementById('cv'); const ctx=cv.getContext('2d'); + +// ── TRUE DOUBLE BUFFERING ───────────────────────────────── +// BackBuffer: compose entire frame offscreen, then blit to visible +const backBuffer = document.createElement('canvas'); +const bbCtx = backBuffer.getContext('2d'); let lastT=performance.now(),rafId=null; // ── HELPERS ─────────────────────────────────────────────── @@ -1353,8 +1358,9 @@ function rafLoop(now){ zF=Math.max(0.38,Math.min(2.6,zF)); // ── DRAW ── - ctx.imageSmoothingEnabled=false; - ctx.clearRect(0,0,W,H); + // ── DOUBLE BUFFER: Draw to backBuffer first ── + bbCtx.imageSmoothingEnabled=false; + bbCtx.clearRect(0,0,W,H); const cx=W/2+panX+glitchX; const cy=H/2+panY+glitchY; @@ -1367,10 +1373,10 @@ function rafLoop(now){ if(tileTTL>0 && tileScales.length>0){ // PER-TILE : zoom global + scale individuel par tuile - ctx.save(); - ctx.translate(cx,cy); - ctx.scale(zF,zF); - ctx.translate(-cx,-cy); + bbCtx.save(); + bbCtx.translate(cx,cy); + bbCtx.scale(zF,zF); + bbCtx.translate(-cx,-cy); let ti=0; for(let y=startY-margin;y<=H+margin*3;y+=motifPx){ @@ -1378,31 +1384,34 @@ function rafLoop(now){ const ts=tileScales[ti%tileScales.length]??1; ti++; if(ts===1.0){ - ctx.drawImage(off,x,y); + bbCtx.drawImage(off,x,y); } else { const tcx=x+motifPx/2, tcy=y+motifPx/2; - ctx.save(); - ctx.translate(tcx,tcy); - ctx.scale(ts,ts); - ctx.translate(-tcx,-tcy); - ctx.drawImage(off,x,y); - ctx.restore(); + bbCtx.save(); + bbCtx.translate(tcx,tcy); + bbCtx.scale(ts,ts); + bbCtx.translate(-tcx,-tcy); + bbCtx.drawImage(off,x,y); + bbCtx.restore(); } } } - ctx.restore(); + bbCtx.restore(); } else { // ZOOM GLOBAL UNIFORME - ctx.save(); - ctx.translate(cx,cy); - ctx.scale(zF,zF); - ctx.translate(-cx,-cy); + bbCtx.save(); + bbCtx.translate(cx,cy); + bbCtx.scale(zF,zF); + bbCtx.translate(-cx,-cy); for(let y=startY-margin;y<=H+margin*3;y+=motifPx) for(let x=startX-margin;x<=W+margin*3;x+=motifPx) - ctx.drawImage(off,x,y); - ctx.restore(); + bbCtx.drawImage(off,x,y); + bbCtx.restore(); } + // ── DOUBLE BUFFER: Single blit to visible canvas ── + ctx.drawImage(backBuffer, 0, 0); + document.getElementById('bZoom').textContent= 'Z'+zF.toFixed(2) +' P'+panX.toFixed(0)+','+panY.toFixed(0) @@ -1493,6 +1502,9 @@ function resize(){ const scale = 2; cv.width = Math.ceil(window.innerWidth * scale); cv.height = Math.ceil(window.innerHeight * scale); + // Sync backBuffer size + backBuffer.width = cv.width; + backBuffer.height = cv.height; // Center the oversized canvas in wrapper cv.style.marginLeft = -cv.width/2 + 'px'; cv.style.marginTop = -cv.height/2 + 'px';