简体   繁体   中英

Improving Canvas draw call Performance

I want to draw quite a few dots. That's what I do:

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

function render () {
    window.requestAnimationFrame(render);
    // clear screen
    ctx.clearRect(0, 0, cwidth, cheight);
    for (let p of particles) {
        p.rmove();
        p.render(ctx);
    }
}

render();

The drawing function for my point looks like this:

class Point {

    x: number;
    y: number;

    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    rmove() {
        this.x += Math.round(Math.random() < 0.5 ? -1 : 1);
        this.y += Math.round(Math.random() < 0.5 ? -1 : 1);
    }

    render (ctx) {
        ctx.fillStyle = "gray";
        ctx.beginPath();
        ctx.rect(this.x, this.y, 1.5,1.5);
        ctx.fill();
        ctx.stroke();
    }
}

Note that I round the values in the rmove() function, as canvas draws points with integer coordinates more quickly.

I'd like to somehow put all these drawing calls together.

Make your points trace on the given context (could even be a Path2D), and keep the actual drawing for the renderer.

All your points have to do is to make the context moveTo their own coords before tracing the rect .

 class Point { constructor(x, y) { this.x = x; this.y = y; } rmove() { this.x += Math.round(Math.random() < 0.5 ? -1 : 1); this.y += Math.round(Math.random() < 0.5 ? -1 : 1); } trace (ctx) { ctx.moveTo( this.x, this.y ); ctx.rect(this.x, this.y, 1.5, 1.5); } } const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const cwidth = canvas.width = 300; const cheight = canvas.height = 300; const particles = Array.from( { length: 5000 }, ()=> new Point( cwidth/2, cheight/2 ) ); function animate () { update(); draw(); window.requestAnimationFrame(animate); } function update() { for (let p of particles) { p.rmove(); } } function draw() { // clear screen ctx.clearRect(0, 0, cwidth, cheight); // define our single path ctx.beginPath(); for (let p of particles) { p.trace(ctx); } ctx.fillStyle = "gray"; ctx.stroke(); // OP has it reversed, but then the fill-color is almost not visible // (1.5 width - 2*0.5 stroke leaves only 0.5 for the fill => antialiased... ctx.fill(); } window.requestAnimationFrame( animate );
 <canvas id="canvas"></canvas>

But this works only because all your particles share the same color. If they didn't, then you'd need a bit more logic:

 const colors = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow']; class Point { constructor(x, y, color=0) { this.x = x; this.y = y; this.color = color; } rmove() { this.x += Math.round(Math.random() < 0.5 ? -1 : 1); this.y += Math.round(Math.random() < 0.5 ? -1 : 1); } trace (ctx) { ctx.moveTo( this.x, this.y ); ctx.rect(this.x, this.y, 1.5, 1.5); } } const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const cwidth = canvas.width = 300; const cheight = canvas.height = 300; const particles = Array.from( { length: 5000 }, ()=> new Point( cwidth/2, cheight/2, (Math.random()*colors.length-1)|0 ) ); function animate () { update(); draw(); window.requestAnimationFrame(animate); } function update() { for (let p of particles) { p.rmove(); } } function draw() { // clear screen ctx.clearRect(0, 0, cwidth, cheight); // define our single path let last_color = -1; for (let p of particles) { let p_color = p.color; if( p_color !== last_color ) { paint(); last_color = p_color; } p.trace(ctx); } paint(); // the last function paint() { ctx.fillStyle = colors[ last_color ]; ctx.strokeStyle = colors[ (last_color + 1) % colors .length ]; ctx.stroke(); ctx.fill(); ctx.beginPath(); } } window.requestAnimationFrame( animate );
 <canvas id="canvas"></canvas>

Though doing this, you may very well end up with a lot of drawings, so a final trick which might not work everywhere is to sort your particles by their color. This results in a different graphic since this one color will always be at the top, but it might work in some cases and the performance gain can outfit the downside.

 const colors = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow']; class Point { constructor(x, y, color=0) { this.x = x; this.y = y; this.color = color; } rmove() { this.x += Math.round(Math.random() < 0.5 ? -1 : 1); this.y += Math.round(Math.random() < 0.5 ? -1 : 1); } trace (ctx) { ctx.moveTo( this.x, this.y ); ctx.rect(this.x, this.y, 1.5, 1.5); } } const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const cwidth = canvas.width = 300; const cheight = canvas.height = 300; const particles = Array.from( { length: 5000 }, ()=> new Point( cwidth/2, cheight/2, (Math.random()*colors.length-1)|0 ) ); particles.sort( (a, b) => a.color - b.color ); function animate () { update(); draw(); window.requestAnimationFrame(animate); } function update() { for (let p of particles) { p.rmove(); } } function draw() { // clear screen ctx.clearRect(0, 0, cwidth, cheight); // define our single path let last_color = -1; for (let p of particles) { let p_color = p.color; if( p_color !== last_color ) { paint(); last_color = p_color; } p.trace(ctx); } paint(); // the last function paint() { ctx.fillStyle = colors[ last_color ]; ctx.strokeStyle = colors[ (last_color + 1) % colors .length ]; ctx.stroke(); ctx.fill(); ctx.beginPath(); } } window.requestAnimationFrame( animate );
 <canvas id="canvas"></canvas>

And nothing prevents you to generate chunks from these sorted particles so it looks more random.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM