# How to optimize loops drawing canvas pattern?

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.stroke();

context.beginPath();
context.stroke();

context.beginPath();
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:

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

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

## 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(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)