1
\$\begingroup\$

I have the following function that draws a canvas pattern. It works so slow when canvas size bigger then 512x512.

const defaultConfig: Divot = { step: 8, triangleWidth: 4, radius: 0.6, strokeStyle: '#000' };

export function DivotTemplate(divot: Partial<Divot> = defaultConfig, canvas: HTMLCanvasElement): HTMLCanvasElement {
  const config = { ...defaultConfig, ...divot };
  const { width, height } = canvas;
  const { step, radius, strokeStyle, triangleWidth } = config;
  const TAU = 2 * Math.PI;

  let x = [1, 1, triangleWidth];
  const y = [1, triangleWidth, triangleWidth / 2];

  const context = canvas.getContext('2d');

  if (!context) throw Error('Canvas context error');

  context.strokeStyle = strokeStyle;

  /* Рисуем три точки для первого треугольника */

  for (let c = 0; c < height; c++) {
    if (c % 2 === 0) {
      x[0] = triangleWidth + 1;
      x[1] = triangleWidth + 1;
      x[2] = 1;
    } else {
      let s = step / 2;
      x = [1 + s, 1 + s, triangleWidth + 1 + s];
    }

    for (let i = 0; i < width; i = i + step) {
      context.beginPath();
      context.arc(x[0], y[0], radius, 0, TAU);
      context.stroke();

      context.beginPath();
      context.arc(x[1], y[1], radius, 0, TAU);
      context.stroke();

      context.beginPath();
      context.arc(x[2], y[2], radius, 0, TAU);
      context.stroke();

      /* Соединяем две точки из трех */

      context.beginPath();
      context.moveTo(x[2], y[2]);
      context.lineTo(x[0], y[0]);
      context.stroke();

      context.beginPath();
      context.moveTo(x[2], y[2]);
      context.lineTo(x[1], y[1]);
      context.stroke();

      x[0] = x[0] + step;
      x[1] = x[1] + step;
      x[2] = x[2] + step;
    }

    x = [1, 1, triangleWidth + 1];

    y[0] = y[0] + step;
    y[1] = y[1] + step;
    y[2] = y[2] + step;
  }

  context.stroke();

  return canvas;
}

As result I get a canvas pattern like:

enter image description here

I what can I optimize in this code according canvas and loops?

\$\endgroup\$
8
  • \$\begingroup\$ Would be great if you could turn this in to a working Js snippet \$\endgroup\$
    – konijn
    Feb 3, 2022 at 16:28
  • 1
    \$\begingroup\$ I published full code there to test it: jsfiddle.net/nbut7jhr/5 \$\endgroup\$
    – Nika
    Feb 3, 2022 at 16:40
  • \$\begingroup\$ Also I have added a time start, time end to measure the time execution. jsfiddle.net/nbut7jhr/6 It takes 150 ms \$\endgroup\$
    – Nika
    Feb 3, 2022 at 17:23
  • \$\begingroup\$ Do you have any suggestions? \$\endgroup\$
    – Nika
    Feb 3, 2022 at 17:40
  • \$\begingroup\$ It's in TS, so it won't work for a stack snippet. \$\endgroup\$
    – ggorlen
    Feb 3, 2022 at 20:53

1 Answer 1

1
\$\begingroup\$

Optimizing rendering performance

  • Avoid unnecessary state changes.

    beginPath then stroke or fill requires a GPU state change (move data from CPU controlled RAM to GPU controlled RAM). Reducing the number of times you call begin path will increase the performance of your rendering code.

  • Don't throw in performance code. Code that can throw can flag the function as Do Not Optimize meaning it will be ignored by the built in optimizer.

  • Avoid array indexing in performance code. Accessing an array element is slower than accessing a variable directly.

  • Avoid repeating calculations inside a loop that can be done once outside the loop.

Basic rewrite.

The rewrite replaces the many beginPath / draw calls with one beginPath and stroke outside the loops (because all the rendering is the same style). This will result in a significant performance increase.

To avoid the connecting strokes between arcs move to the beginning of the arc using moveTo(x + radius, y)

I have changed the signature of the function DivotTemplate so that the default parameters is at the end.

const defaultConfig  = { step: 8, triangleWidth: 4, radius: 0.6, strokeStyle: '#000' };
const now = performance.now();
bivotTemplate(canvas);
console.log("1024 by 1024 canvas in " + performance.now().toFixed(2) + "ms");
function bivotTemplate(canvas, divot  = defaultConfig) {
    const config = {...defaultConfig, ...divot };
    const { width, height } = canvas;
    const { step, radius, strokeStyle, triangleWidth } = config;
    const TAU = 2 * Math.PI;
    const halfStep = step / 2;
    var c, i, x0 = 1, x1 = 1, x2 = triangleWidth;
    var y0 = 1, y1 = triangleWidth, y2 = triangleWidth / 2;
    const ctx = canvas.getContext('2d');
    ctx.strokeStyle = strokeStyle;

    ctx.beginPath();
    for (c = 0; c < height; c++) {
        if (c % 2 === 0) {
            x0 = triangleWidth + 1;
            x1 = triangleWidth + 1;
            x2 = 1;
        } else {
            x0 = 1 + halfStep;
            x1 = 1 + halfStep;
            x2 = triangleWidth + 1 + halfStep;
        }

        for (i = 0; i < width; i = i + step) {
            ctx.moveTo(x0 + radius, y0);
            ctx.arc(x0, y0, radius, 0, TAU);

            ctx.moveTo(x1 + radius, y1);
            ctx.arc(x1, y1, radius, 0, TAU);

            ctx.moveTo(x1 + radius, y1);
            ctx.arc(x2, y2, radius, 0, TAU);

            ctx.moveTo(x2, y2);
            ctx.lineTo(x0, y0);

            ctx.moveTo(x2, y2);
            ctx.lineTo(x1, y1);

            x0 += step;
            x1 += step;
            x2 += step;
        }
        y0 += step;
        y1 += step;
        y2 += step;
    }
    ctx.stroke();
    return canvas;
}
<canvas id="canvas" width = "1024" height = "1024"></canvas>

Real-time

The above code will be much quicker then your original,. However it is still rather slow.

As the rendering is a repeated tile pattern you can increase the performance by 2 (maybe 3) orders of magnitude (real-time) using the 2D pattern.

Render the pattern to a canvas (not on the page) the size of one pattern. The use ctx.createPattern(canvas, repeat) to create a repeating pattern.

Draw the pattern over the whole canvas using fillRect. This will be almost as fast as drawing the pattern on a 8 by 8 canvas (in this case)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.