在canvas / javascript中缓慢填充多边形图像

时间:2015-11-09 13:19:20

标签: javascript canvas polygon fill

我正在制作一个带有HTML,javascript和画布动画的商业应用。

我填充一个屏幕,几乎就像一张地图,带有多边形 - 每个多边形都充满了图像 - 多边形正在移动并不断变换形状。我使用createpattern(图像,"重复")和图像,翻译画布使图像居中于多边形的中心,指定边缘然后进行填充。

当我为每个多边形使用相同的256x256图像时,绘制需要大约2ms。使用共享的1024x1024图像,速度降至约13-40ms。然而 - 当给每个多边形一个1920x1080的不同的图片(有29个多边形)时 - 它会减慢到1500-2500ms来渲染 - 即使它填充了完全相同的多边形,完全相同的区域,理论上完成相同的工作量 - (并且所有图像都已预先加载,并且位于图像对象中)

似乎只是拥有/使用了许多不同/更大的图像或者模式正在减慢速度 - 基本上是接近3个数量级的因素。难道我做错了什么?有没有其他/更好的方法来做到这一点?

3 个答案:

答案 0 :(得分:1)

GPU需要在其内存中提供资源(图像)才能呈现它们。浏览器确保无论您有多少图像,它们始终可用于渲染。问题是当图像的内存需求超过GPU的RAM容量时,浏览器将根据需要交换图像。计算机的RAM和GPU之间的接口与GPU的内部速度相比非常慢,同时它正在等待新数据无法渲染这些图像。

结果是,当您达到GPU内存限制时,性能会大幅下降。除了使用较小的图像之外,你可以做很多事情来解决这个问题。

您还必须考虑到不同的设备具有不同的GPU可用RAM,因此图像的大小应该适合最低的常见GPU RAM容量。

无法直接查询GPU并从浏览器中发现其设置。可以通过在增加图像数量的同时测试帧速率来近似发现它。当帧速率突然下降时,您知道已找到该设备的最大内存。然后,您可以调整图像大小以适应该限制。不幸的是,这不是非常用户友好。

但我个人认为总有一个解决方案。用户永远看不到比屏幕上可用的像素更多的像素。如果使用超过GPU RAM容量的图像进行渲染,则在渲染过程中必须丢失许多像素,因为缩放,剪切,z缓冲等等。

仔细查看您正在渲染的内容。如果在渲染过程中缩小比例,则在加载图像时对图像执行缩放。

如果要剪掉大量像素,则在加载时剪切图像。或者,如果您只显示大图像的一小部分,请考虑仅使用所需内容制作第二张图像,然后再渲染。较大的图像可以保留在计算机RAM中,而GPU只处理图像的较小部分。

如果许多像素被顶部渲染的其他图像遮挡,请考虑降低遮挡像素的分辨率。

在考虑这些事情的情况下仔细检查您的场景会使您的帧速率恢复正常。它可能会增加代码的复杂性,但是为平滑的用户体验付出的代价很小。

答案 1 :(得分:0)

感谢所有 - 我通过调整浏览器上的图像大小来解决问题,通过绘制到画布然后从画布中复制新图像,生成许多不同的图像大小,然后选择最小的图像我需要任何多边形。这很有效,现在又降到3毫秒了。

另外,有趣的是,我尝试切换到drawimage而不是fill - 并且发现它的速度稍慢。

答案 2 :(得分:0)

这是一项测试,可以在1920x1080生成30张图像并对其进行动画处理。我每秒大约需要60帧。如果您获得了良好的帧速率,那么您可以排除缓存问题。尝试在动画循环之前创建模式并销毁图像。这应该删除任何重复的图像数据。



function iGen(w, h, n) {
  var imgs = [];
  var step = 100 / n;
  var cx = Math.floor(w / 2);
  var cy = Math.floor(h / 2);

  for (var s = 0; s < n; s++) {

    var can = document.createElement('canvas');
    var ctx = can.getContext('2d');

    can.width = w;
    can.height = h;
    ctx.strokeStyle = "#FFFFFF";
    ctx.lineWidth = 3;

    genNoise(ctx);

    for (var i = 10 + (step * s); i < cx + 200; i += 100) {
      ctx.beginPath();
      ctx.moveTo(cx + i, cy);
      ctx.arc(cx, cy, i, 0, Math.PI * 2);
      ctx.stroke();
    }

    imgs.push(can);
  }
  return imgs;
}


function rCol() {
  var r = Math.floor(Math.random() * 255 * 255 * 255);
  var h = r.toString(16);
  return "#" + "000000".substr(h.length) + h;
}

function genNoise(ctx) {

  var imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  var d = imageData.data;
  for (var i = 0; i < d.length; i += 4) {
    d[i] = d[i + 1] = d[i + 2] = Math.floor(Math.random() * 64);
    d[i+3] = 255;
  }
  ctx.putImageData(imageData, 0, 0);
}



function testImages() {
  var w = 1920;
  var h = 1080;
  var n = 30; // Number of images to generate
  var imgs = iGen(w, h, n);
  var can = document.getElementById('can');
  can.width = w;
  can.height = h;
  var ctx = can.getContext('2d');
  var i = 0;

  var frames = 0;
  var start = (new Date()).getTime();
  var fps = "fps: ?";

  ctx.font = "32px Sans-serif";
  ctx.fillStyle = "red";

  function loop() {
    ctx.drawImage(imgs[i++], 0, 0);
    i %= imgs.length;

    ctx.fillText(fps + ", img: " + i, 32, 32);
    frames++;
    var now = (new Date()).getTime();
    if ((now - start) >= 1000) {
      fps = "fps: " + frames;
      frames = 0;
      start = now;
    }
    requestAnimationFrame(loop);
  }
  loop();
}

testImages();
&#13;
<canvas id="can"></canvas>
&#13;
&#13;
&#13;