我注意到如果内存中有大量 Canvas ,在将它们绘制到屏幕之前修改每个 Canvas 会大大降低我机器的性能。即使 Canvas 很小并且修改很小,也会发生这种情况。
这是我能想到的最人为的例子:
var { canvas, ctx } = generateCanvas();
ctx.strokeStyle = "#000";
var images = [];
for (var i = 0; i < 500; i++) {
images.push(generateCanvas(50, "red"));
}
var fps = 0,
lastFps = new Date().getTime();
requestAnimationFrame(draw);
function draw() {
requestAnimationFrame(draw);
var modRects = document.getElementById("mod-rects").checked;
var drawRects = document.getElementById("draw-rects").checked;
ctx.clearRect(0, 0, 500, 500);
ctx.strokeRect(0, 0, 500, 500);
fps++;
if (new Date().getTime() - lastFps > 1000) {
console.clear();
console.log(fps);
fps = 0;
lastFps = new Date().getTime();
}
images.forEach(img => {
img.ctx.fillStyle = "yellow";
if (modRects) img.ctx.fillRect(20, 20, 10, 10);
if (drawRects) ctx.drawImage(img.canvas, 225, 225);
});
}
function generateCanvas(size = 500, color = "black") {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = size;
var ctx = canvas.getContext("2d");
ctx.fillStyle = color;
ctx.fillRect(0, 0, size, size);
return {
canvas,
ctx
};
}
function generateCheckbox(name) {
var div = document.createElement("div");
var check = document.createElement("input");
check.type = "checkbox";
check.id = name;
var label = document.createElement("label");
label.for = name;
label.innerHTML = name;
div.appendChild(check);
div.appendChild(label);
return div;
}
document.body.appendChild(canvas);
document.body.appendChild(generateCheckbox("mod-rects"));
document.body.appendChild(generateCheckbox("draw-rects"));
canvas+div+div { margin-bottom: 20px; }
在这个例子中,我们创建了 500 个大小为 50x50 的 Canvas 。在较大的屏幕 Canvas 下方有两个复选框。第一个导致在这 500 个 Canvas 中的每一个上绘制一个黄色的小方块。第二个导致 Canvas 被绘制到更大的 Canvas 上。 FPS 每秒发布到控制台一次。 I see no performance issues when one or the other checkbox is checked, but when both are checked, performance drops drastically.
我的第一个想法是它与在修改时每帧将内存 Canvas 发送到 gfx 卡有关。
这是我试图创造的实际效果。
视频:https://youtu.be/Vr6v2oF3G-8
代码:https://github.com/awhipple/base-command-dev/blob/e2c38946cdaf573abff5ded5399c90687ffa76a5/engine/gfx/shapes/Particle.js
我的最终目标是能够平滑过渡 Canvas 的颜色。我正在使用
globalCompositeOperation = "source-in"
和 fillRect()
在上面的代码链接中执行此操作。
最佳答案
如前所述,这是每一帧向 GPU 发送数百个 Canvas 的开销问题。当在 CPU 中修改 Canvas 时,它会被标记为“脏”,并在下次使用时重新发送到 GPU。
我找到的解决方法是创建一个包含粒子图像网格的大 Canvas 。每个粒子对象都会对其指定的网格部分进行修改。然后一旦完成所有修改,我们就开始进行绘制图像调用,根据需要切割较大的 Canvas
我还需要切换到 globalCompositeOperation = "source-atop"
以防止每次我尝试更改粒子时所有其他粒子都被破坏。
代码:https://github.com/awhipple/base-command-dev/blob/2514327c6c30cb9914962d2c8d604f04bfbdbed5/engine/gfx/shapes/Particle.js
示例:http://avocado.whipple.life/
你可以在这里看到,当 this.newRender === true
在 draw 中,它排队等待稍后绘制。
然后static drawQueuedParticles
一旦每个粒子都有机会自己排队,就会调用它。
最终结果是这个更大的 Canvas 每帧只发送一次到 GPU。我在运行 2700 RTX GPU 且屏幕粒子数为 1500 的 Razorblade Pro 上看到性能从 15 FPS 提高到 60 FPS。
关于javascript - 在绘制到屏幕之前在内存 Canvas 中进行修改会大大降低 Javascript 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63327734/