0

I found this old question and couldn't reproduce the described problem Html canvas 1600x1200 screen tearing. It makes sense that updating canvas from setTimeout callback should produce screen tearing. But even though flickering is very apparent no tearing seems to happen. Here is a small demo: codepen
I can't see any tearing in Chrome and Safari. Surely browsers don't do VSync with setTimeout. So what's happening here?
I added a delay to the canvas update, so it should be more probable for buffer swap to occur in the middle of the update, but no luck here.

const STRIPE_WIDTH = 50;
const STEP = 4;

let canvas = document.createElement('canvas');
canvas.style.width = '100%';
canvas.style.height = '100%';
document.body.appendChild(canvas);
let ctx = canvas.getContext('2d');

let pos = 0;

function render(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    let width = Math.ceil(canvas.width/STRIPE_WIDTH) * STRIPE_WIDTH;
    ctx.fillStyle = 'black';
    for(i = pos; i < width; i+=STRIPE_WIDTH){
        for(j = 0; j < canvas.height; j++){
            ctx.fillRect(i, j, STRIPE_WIDTH/2, 1);
        }
    }
    for(i = pos; i > 0; i-=STRIPE_WIDTH){
        for(j = 0; j < canvas.height; j++){
            ctx.fillRect(i - STRIPE_WIDTH, j, STRIPE_WIDTH/2, 1);
        }
    }
    pos = pos + STEP;
    setTimeout(render, 1000/60);
}

render();
Yehor
  • 1
  • 2

1 Answers1

0

Yes, the canvas rendering is still tied to the document's rendering, itself in sync with the monitor's v-sync, so you should not see any tearing.

You can disable this by setting the desynchronized attribute of the context to true.

const low_latency_ctx = canvas.getContext("2d", { desynchronized: true });

However this is currently only supported by Chrome on ChromeOS and Windows. Since I don't have such a configuration at hand I unfortunately can't write a demo myself...

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • I see, but even though canvas rendering is in sync with the monitor, canvas update is not. Since `setTimeout` callback updates back buffer independently of VSync, buffer swap can happen with the back buffer not fully drawn. I think the real reason why there is no tearing is that I fill the whole canvas at once (in a single callback). `setTimeout` callback executes on the main thread and blocks all other operations. My guess is that buffer swap also happens on the main thread and therefore blocked until the callback is finished. So, by the time swap happens, the whole back buffer is updated. – Yehor May 23 '21 at 15:21
  • I wrote another demo that breaks one big canvas update into small chunks and frees the main thread in between these updates. https://codepen.io/yehor123/pen/GRWWbLo I didn't know about the `desynchronized` attribute, thank you. Unfortunately, I have no way of trying it out either. – Yehor May 23 '21 at 15:25
  • "buffer swap can happen with the back buffer not fully drawn", no it can't. That buffer swap is the rendering, and this happens in sync with the VSync. On a configuration where VSync is on, and on most common browsers, you can't have tearings from a canvas. You could have tearings from video, since they use an other rendering path, and you could by using the `desynchronized` attribute on supporting configurations, but that's about it. Of course you can [simulate](https://codepen.io/_-0-_/pen/KKWmdLy) such tearing, but you can't have true tearing from a canvas. – Kaiido May 24 '21 at 01:25
  • Sorry, the way I put it could be misleading. I meant that even though the browser's frames are in sync with the monitor's frames, `setTimeout` callback is not in sync with them. This would have created the possibility for the swap to occur in the middle of the callback's execution. And since the callback is updating the canvas, some updates would have happened before the swap and some after. It would have looked like tearing. – Yehor May 24 '21 at 21:25
  • But I believe that tearing isn't present because the swap can't happen when the main thread is busy. And it can be busy handling the callback. So, the swap can't happen during the callback execution and the browser/monitor frame is duplicated. When it finally happens the callback has already finished all the updates. [Here are diagrams](https://drive.google.com/file/d/1OzbTHfDhX4QiG1WCGu7GarQQN54aynS5/view?usp=sharing) explaining what I mean – Yehor May 24 '21 at 21:26
  • I was finally able to check it on Windows with the desynchronized attribute set to true. No screen tearing occurs even in this case https://codepen.io/yehor123/pen/WNdrRaR – Yehor Mar 23 '22 at 17:58