在实时Canvas中进行预渲染与渲染时结果奇怪 [英] Strange results when pre-rendering vs rendering in real-time Canvas

查看:57
本文介绍了在实时Canvas中进行预渲染与渲染时结果奇怪的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一种方法,可以在 Canvas 内使用 requestAnimationFrame 在下一个重新绘制之前渲染矩形矩阵。我正在努力实现最佳性能。我第一个解决此问题的方法是在 Canvas 内实时创建矩形。

I have a method which renders a matrix of rectangles within a Canvas before the next repaint using requestAnimationFrame. I'm trying to achieve maximum performances. My first approach to this problem was to create rectangles in real-time within the Canvas.

  render(display: PanelDisplay): void {
    const ctx = this.parameters.canva.getContext("2d");
    const widthEachBit = Math.floor(this.parameters.canva.width / display[0].length);
    const heightEachBit = Math.floor(this.parameters.canva.height / display.length);

    ctx.lineWidth = 1;
    ctx.strokeStyle = this.parameters.colorStroke;

    for(var i = 0; i < display.length; i++) {
      for(var j = 0; j < display[i].length; j++) {
        const x = j*widthEachBit;
        const y = i*heightEachBit;
        ctx.beginPath();
        ctx.fillStyle = display[i][j] == 1 ? this.parameters.colorBitOn : this.parameters.colorBitOff;
        ctx.rect(x, y, widthEachBit, heightEachBit);
        ctx.fill();
        ctx.stroke();
      }
    }
  }

这样做会导致性能下降对于3k元素的矩阵:

Doing so results in mediocre performance for a matrix of 3k elements:


  • Chrome:20-30 fps

  • Firefox:40 fps

第二种方法是,我决定预先渲染两个矩形并使用 drawImage 以在 Canvas 上渲染它们:

As a second approach, I decided to pre-render the two rectangles and use drawImage to render them on the Canvas:

  render(display: PanelDisplay): void {
    const ctx = this.parameters.canva.getContext("2d");
    const widthEachBit = Math.floor(this.parameters.canva.width / display[0].length);
    const heightEachBit = Math.floor(this.parameters.canva.height / display.length);

    // Render the different canvas once before instead of recalculating every loop
    const prerenderedBitOn = this._prerenderBit(this._prerenderedOn, widthEachBit, heightEachBit, this.parameters.colorBitOn);
    const prerenderedBitOff = this._prerenderBit(this._prerenderedOff, widthEachBit, heightEachBit, this.parameters.colorBitOff);

    for(var i = 0; i < display.length; i++) {
      for(var j = 0; j < display[i].length; j++) {
        const x = j*widthEachBit;
        const y = i*heightEachBit;
        ctx.drawImage(display[i][j] == 1 ? prerenderedBitOn : prerenderedBitOff, x, y);
      }
    }
  }

  private _prerenderBit(canvas: HTMLCanvasElement, widthEachBit: number, heightEachBit: number, color: string) {
    canvas.width = widthEachBit;
    canvas.height = heightEachBit;
    const ctx = canvas.getContext('2d');

    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.rect(0, 0, widthEachBit, heightEachBit);
    ctx.fill();
    ctx.lineWidth = 1;
    ctx.strokeStyle = this.parameters.colorStroke;
    ctx.stroke();

    return canvas;
  }

这样做的结果是我在Firefox中得到更好的结果,而在Chrome中得到最差的结果:

Doing so results I get better results in Firefox, and worst ones in Chrome:


  • Chrome:10 fps

  • Firefox:50 fps

我不太确定该如何解释这些结果。作为第三种方法,我正在考虑预先创建n个 Canvas ,其中n是矩阵的大小,仅在下一次重新绘制之前更新需要的大小。在此之前,我想向您介绍为什么我在一个浏览器上预渲染效果更好,而在另一个浏览器上预渲染效果差。我也希望有任何反馈来获得更好的性能。如有必要,我可以提供性能堆栈跟踪。

I'm not quite sure how I'm suppose to interpret these results. As a third approach, I'm thinking of pre-creating n Canvas where n is the size of the matrix and only updating the ones that need to before the next repaint. Before that, I would like to get your input on why I'm getting better results pre-rendering on one browser and worst results pre-rendering on the other. I would also like any feedback to get better performance. I can provide Performance stack traces if necessary.

推荐答案

不同的结果可能是由API的不同实现引起的,甚至可能是您在浏览器上设置的不同设置,使一种更喜欢GPU加速而不是C​​PU计算,或者一种比另一种或其他方式处理更好的grapich内存。

The different results may be caused by different implementations of the API, or maybe even different settings that you have set-up on your browser, which make one prefer GPU acceleration over CPU computation, or one handles better grapich memory than the other or what else.

但是无论如何,如果我正确地理解了您的代码,那么您会比这两个选项更好。

But anyway, if I understand you code correctly, you can get better than these two options.

我可以想到两种主要的方法,您必须进行测试。

I can think of two main ways, that you'll have to test.

第一个方法是用一种颜色渲染整个矩阵大小的大矩形,然后将另一种颜色遍历所有单元格,然后将它们组合成一个单一的颜色子路径,以便您仅在此子路径组成的结尾调用一次 fill()

The first one is to render one big rect the size of the whole matrix in one color, then loop over all the cells the other color, and compose them in a single sub path, so that you call fill() only at the end of this sub-path composition, once.

最后,您将在所有这一切上绘制网格(网格可以是简单的十字图案,也可以预渲染在屏幕外的画布上,或者再次是si ngle子路径)。

Finally you'd draw the grid over all this (grid which could be either a simple cross pattern, or pre-rendered on an offscreen canvas, or once again a single sub-path).

const W = 50;
const H = 50;
const cellSize = 10;
const grid_color = 'black';
var grid_mode = 'inline';


const ctx = canvas.getContext('2d');
const matrix = [];

canvas.width = W * cellSize;
canvas.height = H * cellSize;

for (let i = 0; i < H; i++) {
  let row = [];
  matrix.push(row);
  for (let j = 0; j < W; j++) {
    row.push(Math.random() > 0.5 ? 0 : 1);
  }
}

const grid_pattern = generateGridPattern();
const grid_img = generateGridImage();

draw();

function draw() {
  shuffle();

  // first draw all our green rects ;)
  ctx.fillStyle = 'green';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // now draw all the red ones
  ctx.fillStyle = 'red';
  ctx.beginPath(); // single sub-path declaration
  for (let i = 0; i < H; i++) {
    for (let j = 0; j < W; j++) {
      // only if a red cell
      if (matrix[i][j])
        ctx.rect(i * cellSize, j * cellSize, cellSize, cellSize);
    }
  }
  ctx.fill(); // single fill operation
  drawGrid();

  requestAnimationFrame(draw);

}

function shuffle() {
  let r = Math.floor(Math.random() * H);
  for (let i = r; i < r + Math.floor(Math.random() * (H - r)); i++) {
    let r = Math.floor(Math.random() * W);
    for (let j = r; j < r + Math.floor(Math.random() * (W - r)); j++) {
      matrix[i][j] = +!matrix[i][j];
    }
  }
}

function drawGrid() {
  if (grid_mode === 'pattern') {
    ctx.fillStyle = grid_pattern;
    ctx.beginPath();
    ctx.rect(0, 0, canvas.width, canvas.height);
    ctx.translate(-cellSize / 2, -cellSize / 2);
    ctx.fill();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
  } else if (grid_mode === 'image') {
    ctx.drawImage(grid_img, 0, 0);
  } else {
    ctx.strokeStyle = grid_color;
    ctx.beginPath();
    for (let i = 0; i <= cellSize * H; i += cellSize) {
      ctx.moveTo(0, i);
      ctx.lineTo(cellSize * W, i);
      for (let j = 0; j <= cellSize * W; j += cellSize) {
        ctx.moveTo(j, 0);
        ctx.lineTo(j, cellSize * H);
      }
    }
    ctx.stroke();
  }
}

function generateGridPattern() {
  const ctx = Object.assign(
    document.createElement('canvas'), {
      width: cellSize,
      height: cellSize
    }
  ).getContext('2d');
  // make a cross
  ctx.beginPath();
  ctx.moveTo(cellSize / 2, 0);
  ctx.lineTo(cellSize / 2, cellSize);
  ctx.moveTo(0, cellSize / 2);
  ctx.lineTo(cellSize, cellSize / 2);

  ctx.strokeStyle = grid_color;
  ctx.lineWidth = 2;
  ctx.stroke();

  return ctx.createPattern(ctx.canvas, 'repeat');
}

function generateGridImage() {
  grid_mode = 'inline';
  drawGrid();
  const buf = canvas.cloneNode(true);
  buf.getContext('2d').drawImage(canvas, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  return buf;
}
field.onchange = e => {
  grid_mode = document.querySelector('input:checked').value;
}

<fieldset id="field">
  <legend>Draw grid using:</legend>
  <label><input name="grid" type="radio" value="inline" checked>inline</label>
  <label><input name="grid" type="radio" value="pattern">pattern</label>
  <label><input name="grid" type="radio" value="image">image</label>
</fieldset>

<canvas id="canvas"></canvas>

您可以采用的另一种完全不同的方法是操作直接使用ImageData。
将其设置为矩阵的大小(cellSize为1),将其放在画布上,然后最后将其按比例重新绘制,然后将网格绘制到上方。

An other entirely different approach you could take, would be to manipulate an ImageData directly. Set it the size of your matrix (cellSize would be 1), put it on your canvas, and then finally just redraw it scaled up, and draw the grid over.

ctx.putImageData(smallImageData, 0,0);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(ctx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height);
drawgrid();

const W = 50;
const H = 50;
const cellSize = 10;
const grid_color = 'black';

canvas.width = W * cellSize;
canvas.height = H * cellSize;

const ctx = canvas.getContext('2d');
// we'll do the matrix operations directly on an imageData
const imgData = ctx.createImageData(W, H);
const matrix = new Uint32Array(imgData.data.buffer);

const red = 0xFF0000FF;
const green = 0xFF008000;

for (let i = 0; i < H*W; i++) {
    matrix[i] = (Math.random() > 0.5 ? green : red);
}

prepareGrid();
ctx.imageSmoothingEnabled = false;
draw();

function draw() {
  shuffle();
  // put our update ImageData
  ctx.putImageData(imgData, 0, 0);
  // scale its result
  ctx.drawImage(ctx.canvas,
    0,0,W,H,
    0,0,canvas.width,canvas.height
  );
  // draw the grid which is already drawn in memory
  ctx.stroke();
  requestAnimationFrame(draw);
}
function shuffle() {
  // here 'matrix' is actually the data of our ImageData
  // beware it is a 1D array, so we need to normalize the coords
  let r = Math.floor(Math.random() * H);
  for (let i = r; i < r + Math.floor(Math.random() * (H - r)); i++) {
    let r = Math.floor(Math.random() * W);
    for (let j = r; j < r + Math.floor(Math.random() * (W - r)); j++) {
      matrix[i*W + j] = matrix[i*W + j] === red ? green : red;
    }
  }
}
function prepareGrid() {
  // we draw it only once in memory
  // 'draw()' will then just have to call ctx.stroke()
    ctx.strokeStyle = grid_color;
    ctx.beginPath();
    for (let i = 0; i <= cellSize * H; i += cellSize) {
      ctx.moveTo(0, i);
      ctx.lineTo(cellSize * W, i);
      for (let j = 0; j <= cellSize * W; j += cellSize) {
        ctx.moveTo(j, 0);
        ctx.lineTo(j, cellSize * H);
      }
    }
}

<canvas id="canvas"></canvas>

这篇关于在实时Canvas中进行预渲染与渲染时结果奇怪的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆