Search code examples
javascriptperformancehtml5-canvaschromiumrequestanimationframe

HTML Canvas takes too long to repaint


I'm working on a game with JavaScript using Electron.js. I'm using the HTML <canvas> element with 2d context as renderer. At some point, I've noticed that the game runs quite slow (less than 30 FPS).

Every frame, a piece of code is run by requestAnimationFrame(). I ran it through a profiler and it showed that my code is fine (takes around 2ms per frame) and that page repaints take much longer than I would expect (sometimes it's more than 30ms per paint).

Screenshot of profiler results

Since the game renderer is too complex, I can't provide a full example, but here's a bit of my code:

// Set up the canvas
let r = document.getElementById("render");
let ctx = r.getContext("2d", {
    alpha:0,
    willReadFrequently:true
});

// Set up offscreen canvas
let offR = new OffscreenCanvas(1920, 1080);
let offCtx = offR.getContext("2d", {
    willReadFrequently: true
});

// Set canvas size
r.width = 1920;
r.height = 1080;

function drawVoxes() {
    for(let vx of voxes) {
        // If not empty, draw its texture
        if(vx.value != 0) {
            offCtx.drawImage( dataobj.textures.tiles[vx.value - 1], vx.rX, vx.rY, vx.width, vx.height );
        }
    }
}

function renderFrame() {
    // fpsTicker.tick();

    // Clear canvases
    ctx.clearRect(0, 0, r.width, r.height);
    offCtx.clearRect(0, 0, r.width, r.height);

    // Draw background
    ctx.drawImage(buf, 0, 0);

    // Draw grid
    drawVoxes();

    // Apply to the main canvas
    ctx.drawImage(offR, 0, 0);
    
    // Set up for the next frame
    requestAnimationFrame(renderFrame);
}

requestAnimationFrame(renderFrame);
  • buf is an OffscreenCanvas object,
  • voxes is an array of custom helper objects,
  • dataobj is an object that stores all the map data.

I hope this example is enough for you to understand.

Why is this happening? Is it normal? And what can I do to lower the paint times?


EDIT: The only element on-screen is that single <canvas> for rendering.


Solution

  • Disabling the willReadFrequently canvas context flag fixed the issue and paints do not take 30ms anymore.