在绘制到屏幕之前修改内存中的画布会大大降低Javascript性能

在绘制到屏幕之前修改内存中的画布会大大降低Javascript性能,javascript,performance,canvas,Javascript,Performance,Canvas,我注意到,如果我的内存中有大量画布,在将它们绘制到屏幕之前修改每个画布会大大降低我的机器的性能。即使画布很小且修改很小,也会发生这种情况 下面是我能想到的最做作的例子: var{canvas,ctx}=generateCanvas(); ctx.strokeStyle=“#000”; var图像=[]; 对于(变量i=0;i1000){ console.clear(); 控制台日志(fps); fps=0; lastFps=newdate().getTime(); } images.forEa

我注意到,如果我的内存中有大量画布,在将它们绘制到屏幕之前修改每个画布会大大降低我的机器的性能。即使画布很小且修改很小,也会发生这种情况

下面是我能想到的最做作的例子:

var{canvas,ctx}=generateCanvas();
ctx.strokeStyle=“#000”;
var图像=[];
对于(变量i=0;i<500;i++){
图像推送(generateCanvas(50,“红色”);
}
var fps=0,
lastFps=newdate().getTime();
请求动画帧(绘制);
函数绘图(){
请求动画帧(绘制);
var modRects=document.getElementById(“mod rects”)。选中;
var drawRects=document.getElementById(“drawRects”)。选中;
ctx.clearRect(0,0500500);
ctx.strokeRect(0,0,500,500);
fps++;
如果(新日期().getTime()-lastFps>1000){
console.clear();
控制台日志(fps);
fps=0;
lastFps=newdate().getTime();
}
images.forEach(img=>{
img.ctx.fillStyle=“黄色”;
if(modRects)img.ctx.fillRect(20,20,10,10);
if(drawRects)ctx.drawImage(img.canvas,225225);
});
}
函数generateCanvas(大小=500,颜色=“黑色”){
var canvas=document.createElement(“canvas”);
canvas.width=canvas.height=size;
var ctx=canvas.getContext(“2d”);
ctx.fillStyle=颜色;
ctx.fillRect(0,0,size,size);
返回{
帆布,
ctx
};
}
函数generateCECKBox(名称){
var div=document.createElement(“div”);
var check=document.createElement(“输入”);
check.type=“checkbox”;
check.id=名称;
var label=document.createElement(“标签”);
label.for=名称;
label.innerHTML=名称;
子分区(检查);
子目录(标签);
返回div;
}
document.body.appendChild(画布);
document.body.appendChild(generateCheckbox(“mod rects”);
document.body.appendChild(generateCheckbox(“draw rects”)

canvas+div+div{margin bottom:20px;}
我希望浏览器优化为显示1个画布,或者一次最多显示几个画布。我打赌每个画布都会单独上传到GPU,这比单个画布的开销要大得多。GPU的资源数量有限,如果重复清除每个画布的纹理和缓冲区,那么使用大量画布可能会导致大量混乱。这个答案还声称,在256px以下,Chrome没有硬件加速画布


由于您正在尝试使用精灵来实现粒子效果,因此最好使用为此类内容构建的webgl库。我在这方面有很好的经验。如果你在做3d,它也很流行。您可以构建自己的webgl引擎,但这非常复杂,而且工作量很大。您必须担心向量数学、顶点缓冲区、支持移动GPU、批处理绘图调用等问题。除非您确实非常需要一些独特的东西,否则最好使用现有库。

如前所述,这是一个每帧向GPU发送数百张画布的开销问题。当画布在CPU中被修改时,它会被标记为“脏”,并在下次使用时重新发送到GPU

我找到的解决方法是创建一个包含粒子图像网格的大画布。每个粒子对象都会对其指定的网格部分进行修改。然后,一旦所有的修改都完成了,我们就开始进行绘制图像调用,根据需要切割更大的画布

我还需要切换到
globalCompositeOperation=“source-top”
,以防止每次尝试更改一个粒子时,所有其他粒子都被破坏

代码:

示例:

您可以在这里看到,当draw中的
this.newRender===true
时,它会排队等待稍后绘制。 然后,一旦每个粒子都有机会排队,就会调用静态drawQueuedParticles


最终的结果是,这个较大的画布每帧只发送一次到GPU。在我的Razorblade Pro上,运行2700 RTX GPU和1500个屏幕粒子,性能从15 FPS提高到60 FPS。

很可能是在GPU中合成较小的图像到较大的图像,但绘制画布是在CPU中完成的。将这样脏的图像传输到GPU内存会很慢……我想@AKX已经解决了。听起来很像,注意到只有Chrome似乎显示了这种行为。对于github repo中的代码,现在只需非常快速地读取它,但不要在每帧多次调用getImageData,在整个画布中只调用一次,并从单个ImageData中读取多个像素。此外,它可能会迫使您进行大规模重写,但最好在单个路径中批处理类似的图形调用(即具有相同颜色样式的调用),并且只调用一次
fill()
,而不是多次调用
fillStyle=color;fillRect()
。如果您真的需要性能,请转到WebGL…我会去查看其他问题。谢谢你的信息!注意:我只调用一次getImageData。请注意我是如何缓存
generateParticle.particle
的,如果该方法已经生成,我会尽早从该方法返回。因此,基本上,这是第一条评论中所说的,也是你的“第一个想法”:他们在GPU中缓存位图,每次你在GPU上重新绘制位图时,都会使缓存失效,并花费比应该发生的更多的时间。请注意,之前的性能总是良好的。所以IIUC,他们为了记忆牺牲了性能。我确实找到了那个来源,关于256px以下的画布。但是,在更改画布大小时,我没有注意到任何性能断点。我可能会签出pixijs。我真的很想从头开始做这件事,但我以前也经历过向量数学/gpu兔子洞,这是一个很深的洞。是的,当然是!如果您已经熟悉webgl或opengl,那么肯定会更容易,但您仍然需要进行大量的工作。然而,有时如果