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 <noreply@anthropic.com>
This commit is contained in:
parent
528e9e508c
commit
abe3f0a944
@ -865,6 +865,11 @@ const off=document.createElement('canvas');
|
|||||||
const offCtx=off.getContext('2d');
|
const offCtx=off.getContext('2d');
|
||||||
const cv=document.getElementById('cv');
|
const cv=document.getElementById('cv');
|
||||||
const ctx=cv.getContext('2d');
|
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;
|
let lastT=performance.now(),rafId=null;
|
||||||
|
|
||||||
// ── HELPERS ───────────────────────────────────────────────
|
// ── HELPERS ───────────────────────────────────────────────
|
||||||
@ -1353,8 +1358,9 @@ function rafLoop(now){
|
|||||||
zF=Math.max(0.38,Math.min(2.6,zF));
|
zF=Math.max(0.38,Math.min(2.6,zF));
|
||||||
|
|
||||||
// ── DRAW ──
|
// ── DRAW ──
|
||||||
ctx.imageSmoothingEnabled=false;
|
// ── DOUBLE BUFFER: Draw to backBuffer first ──
|
||||||
ctx.clearRect(0,0,W,H);
|
bbCtx.imageSmoothingEnabled=false;
|
||||||
|
bbCtx.clearRect(0,0,W,H);
|
||||||
|
|
||||||
const cx=W/2+panX+glitchX;
|
const cx=W/2+panX+glitchX;
|
||||||
const cy=H/2+panY+glitchY;
|
const cy=H/2+panY+glitchY;
|
||||||
@ -1367,10 +1373,10 @@ function rafLoop(now){
|
|||||||
|
|
||||||
if(tileTTL>0 && tileScales.length>0){
|
if(tileTTL>0 && tileScales.length>0){
|
||||||
// PER-TILE : zoom global + scale individuel par tuile
|
// PER-TILE : zoom global + scale individuel par tuile
|
||||||
ctx.save();
|
bbCtx.save();
|
||||||
ctx.translate(cx,cy);
|
bbCtx.translate(cx,cy);
|
||||||
ctx.scale(zF,zF);
|
bbCtx.scale(zF,zF);
|
||||||
ctx.translate(-cx,-cy);
|
bbCtx.translate(-cx,-cy);
|
||||||
|
|
||||||
let ti=0;
|
let ti=0;
|
||||||
for(let y=startY-margin;y<=H+margin*3;y+=motifPx){
|
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;
|
const ts=tileScales[ti%tileScales.length]??1;
|
||||||
ti++;
|
ti++;
|
||||||
if(ts===1.0){
|
if(ts===1.0){
|
||||||
ctx.drawImage(off,x,y);
|
bbCtx.drawImage(off,x,y);
|
||||||
} else {
|
} else {
|
||||||
const tcx=x+motifPx/2, tcy=y+motifPx/2;
|
const tcx=x+motifPx/2, tcy=y+motifPx/2;
|
||||||
ctx.save();
|
bbCtx.save();
|
||||||
ctx.translate(tcx,tcy);
|
bbCtx.translate(tcx,tcy);
|
||||||
ctx.scale(ts,ts);
|
bbCtx.scale(ts,ts);
|
||||||
ctx.translate(-tcx,-tcy);
|
bbCtx.translate(-tcx,-tcy);
|
||||||
ctx.drawImage(off,x,y);
|
bbCtx.drawImage(off,x,y);
|
||||||
ctx.restore();
|
bbCtx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.restore();
|
bbCtx.restore();
|
||||||
} else {
|
} else {
|
||||||
// ZOOM GLOBAL UNIFORME
|
// ZOOM GLOBAL UNIFORME
|
||||||
ctx.save();
|
bbCtx.save();
|
||||||
ctx.translate(cx,cy);
|
bbCtx.translate(cx,cy);
|
||||||
ctx.scale(zF,zF);
|
bbCtx.scale(zF,zF);
|
||||||
ctx.translate(-cx,-cy);
|
bbCtx.translate(-cx,-cy);
|
||||||
for(let y=startY-margin;y<=H+margin*3;y+=motifPx)
|
for(let y=startY-margin;y<=H+margin*3;y+=motifPx)
|
||||||
for(let x=startX-margin;x<=W+margin*3;x+=motifPx)
|
for(let x=startX-margin;x<=W+margin*3;x+=motifPx)
|
||||||
ctx.drawImage(off,x,y);
|
bbCtx.drawImage(off,x,y);
|
||||||
ctx.restore();
|
bbCtx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── DOUBLE BUFFER: Single blit to visible canvas ──
|
||||||
|
ctx.drawImage(backBuffer, 0, 0);
|
||||||
|
|
||||||
document.getElementById('bZoom').textContent=
|
document.getElementById('bZoom').textContent=
|
||||||
'Z'+zF.toFixed(2)
|
'Z'+zF.toFixed(2)
|
||||||
+' P'+panX.toFixed(0)+','+panY.toFixed(0)
|
+' P'+panX.toFixed(0)+','+panY.toFixed(0)
|
||||||
@ -1493,6 +1502,9 @@ function resize(){
|
|||||||
const scale = 2;
|
const scale = 2;
|
||||||
cv.width = Math.ceil(window.innerWidth * scale);
|
cv.width = Math.ceil(window.innerWidth * scale);
|
||||||
cv.height = Math.ceil(window.innerHeight * 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
|
// Center the oversized canvas in wrapper
|
||||||
cv.style.marginLeft = -cv.width/2 + 'px';
|
cv.style.marginLeft = -cv.width/2 + 'px';
|
||||||
cv.style.marginTop = -cv.height/2 + 'px';
|
cv.style.marginTop = -cv.height/2 + 'px';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user