WebGL
Learn how (and why) to enable WebGL for an extension.
In the Developer Portal, some extensions have a Requires WebGL option. If you enable this option, Canva only shows the extension to users who are using a WebGL-enabled browser. If your extension depends on WebGL, you must enable this option.

Notes

  • Canva only supports WebGL 1.x, not WebGL 2.x and beyond. This is because some modern web browsers, such as Safari, don't enable WebGL 2.x by default.
  • You can't get the "webgl" context of a HTMLCanvasElement that's created with the toCanvas method. This is because the toCanvas method creates a "2d" context and a single HTMLCanvasElement can't have more than one type of context.

Example

This is an example of an editing extension that uses WebGL to transform the user's image.
1
const { imageHelpers } = window.canva;
2
const canva = window.canva.init();
3
4
// Vertex shader source code
5
const vertexShaderSource = `
6
attribute vec2 vertex;
7
varying highp vec2 uv;
8
9
void main(void)
10
{
11
gl_Position = vec4(vertex, 0.0, 1.0);
12
uv = vertex * 0.5 + 0.5;
13
uv.y = 1.0 - uv.y;
14
}
15
`;
16
17
// Fragment shader source code
18
const fragmentShaderSource = `
19
precision mediump float;
20
21
uniform sampler2D uImage;
22
uniform vec2 uImageSize;
23
uniform float uMix;
24
25
varying highp vec2 uv;
26
27
void main(void)
28
{
29
vec4 source = texture2D(uImage, uv);
30
vec4 target = source * vec4(0.0, 0.76, 0.8, 1.0);
31
gl_FragColor = mix(source, target, uMix);
32
}
33
`;
34
35
canva.onReady(async (opts) => {
36
// Set up the initial state
37
const state = {
38
mix: 1.0,
39
image: await imageHelpers.fromElement(opts.element),
40
};
41
42
// Create a canvas
43
// Set the dimensions of the canvas to match the image
44
// use styles to scale to the element size
45
const canvas = document.createElement("canvas");
46
canvas.width = state.image.width;
47
canvas.height = state.image.height;
48
canvas.style.width = "100%";
49
canvas.style.height = "100%";
50
document.body.append(canvas);
51
52
// Create WebGL context
53
const gl = canvas.getContext("webgl");
54
if (gl == null) {
55
throw new Error("Failed to create WebGL context.");
56
}
57
58
// Ensure we can render images this size
59
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
60
const maxRenderBufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
61
const maxSize = Math.max(state.image.width, state.image.height);
62
if (maxSize > maxTextureSize || maxSize > maxRenderBufferSize) {
63
throw new Error("Failed to render image. The image is too large.");
64
}
65
66
// Compile vertex shader
67
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
68
if (vertexShader == null) {
69
throw new Error("Failed to create vertex shader.");
70
}
71
72
gl.shaderSource(vertexShader, vertexShaderSource);
73
gl.compileShader(vertexShader);
74
75
let message = gl.getShaderInfoLog(vertexShader);
76
if (message != null && message.length > 0) {
77
throw new Error(message);
78
}
79
80
// Compile fragment shader
81
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
82
if (fragmentShader == null) {
83
throw new Error("Failed to create fragment shader.");
84
}
85
86
gl.shaderSource(fragmentShader, fragmentShaderSource);
87
gl.compileShader(fragmentShader);
88
89
message = gl.getShaderInfoLog(fragmentShader);
90
if (message != null && message.length > 0) {
91
throw new Error(message);
92
}
93
94
// Compile program
95
const program = gl.createProgram();
96
if (program == null) {
97
throw new Error("Failed to create WebGL program.");
98
}
99
gl.attachShader(program, vertexShader);
100
gl.attachShader(program, fragmentShader);
101
gl.linkProgram(program);
102
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
103
throw new Error(
104
"Failed to link WebGL program: " + gl.getProgramInfoLog(program)
105
);
106
}
107
gl.useProgram(program);
108
109
// Create quad geometry to render the image onto
110
const vertexAttributeLocation = gl.getAttribLocation(program, "vertex");
111
const vertexBuffer = gl.createBuffer();
112
const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
113
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
114
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
115
gl.vertexAttribPointer(vertexAttributeLocation, 2, gl.FLOAT, false, 0, 0);
116
gl.enableVertexAttribArray(vertexAttributeLocation);
117
118
// Upload the image to the GPU
119
const image = await imageHelpers.toImageElement(state.image);
120
const uImageLocation = gl.getUniformLocation(program, "uImage");
121
const uImage = gl.createTexture();
122
gl.activeTexture(gl.TEXTURE0);
123
gl.bindTexture(gl.TEXTURE_2D, uImage);
124
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
125
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
126
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
127
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
128
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
129
gl.uniform1i(uImageLocation, 0);
130
131
// Set the image size uniform
132
const uImageSizeLocation = gl.getUniformLocation(program, "uImageSize");
133
gl.uniform2fv(uImageSizeLocation, [state.image.width, state.image.height]);
134
135
// Set the mix uniform amount
136
const uMixLocation = gl.getUniformLocation(program, "uMix");
137
gl.uniform1f(uMixLocation, state.mix);
138
139
// Draw the quad
140
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
141
142
renderControls();
143
144
canva.onControlsEvent(({ message: event }) => {
145
if (event.controlType === "slider") {
146
state[event.controlId] = event.message.value;
147
}
148
149
if (event.commit) {
150
renderControls();
151
}
152
153
// Update the uniform
154
gl.uniform1f(uMixLocation, state.mix);
155
156
// Draw the quad
157
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
158
});
159
160
canva.onImageUpdate(async ({ image: newImage }) => {
161
// Check that the image can be rendered
162
const newImageSize = Math.max(newImage.width, newImage.height);
163
if (newImageSize > maxTextureSize || newImageSize > maxRenderBufferSize) {
164
throw new Error("Failed to render image. The image is too large.");
165
}
166
167
// Update the state
168
state.image = newImage;
169
170
// Resize the viewport
171
canvas.width = state.image.width;
172
canvas.height = state.image.height;
173
gl.viewport(0, 0, canvas.width, canvas.height);
174
175
// Upload the new image to the GPU
176
const img = await imageHelpers.toImageElement(newImage);
177
gl.activeTexture(gl.TEXTURE0);
178
gl.bindTexture(gl.TEXTURE_2D, uImage);
179
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
180
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
181
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
182
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
183
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
184
185
// Set the image size uniform
186
gl.uniform2fv(uImageSizeLocation, [state.image.width, state.image.height]);
187
});
188
189
canva.onSaveRequest(async () => {
190
// Update the uniform
191
gl.uniform1f(uMixLocation, state.mix);
192
193
// Draw the quad
194
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
195
196
return await imageHelpers.fromCanvas(state.image.imageType, canvas);
197
});
198
199
function renderControls() {
200
const controls = [
201
canva.create("slider", {
202
id: "mix",
203
label: "mix",
204
value: state.mix,
205
min: 0,
206
max: 1,
207
step: 0.01,
208
}),
209
];
210
211
canva.updateControlPanel(controls);
212
}
213
});
Copied!
Last modified 1mo ago
Copy link
Contents
Notes
Example