canvas2gl 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,871 @@
1
+ const VERT_SRC = `
2
+ attribute vec2 a_position;
3
+ uniform vec2 u_resolution;
4
+ uniform mat3 u_transform;
5
+ void main() {
6
+ vec3 transformed = u_transform * vec3(a_position, 1.0);
7
+ vec2 clipspace = (transformed.xy / u_resolution) * 2.0 - 1.0;
8
+ gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);
9
+ }
10
+ `;
11
+ const FRAG_SRC = `
12
+ precision mediump float;
13
+ uniform vec4 u_color;
14
+ void main() {
15
+ gl_FragColor = u_color;
16
+ }
17
+ `;
18
+ const TEX_VERT_SRC = `
19
+ attribute vec2 a_position;
20
+ attribute vec2 a_texCoord;
21
+ uniform vec2 u_resolution;
22
+ uniform mat3 u_transform;
23
+ varying vec2 v_texCoord;
24
+ void main() {
25
+ vec3 transformed = u_transform * vec3(a_position, 1.0);
26
+ vec2 clipspace = (transformed.xy / u_resolution) * 2.0 - 1.0;
27
+ gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);
28
+ v_texCoord = a_texCoord;
29
+ }
30
+ `;
31
+ const TEX_FRAG_SRC = `
32
+ precision mediump float;
33
+ varying vec2 v_texCoord;
34
+ uniform sampler2D u_texture;
35
+ uniform float u_alpha;
36
+ void main() {
37
+ vec4 color = texture2D(u_texture, v_texCoord);
38
+ color.a *= u_alpha;
39
+ gl_FragColor = color;
40
+ }
41
+ `;
42
+ const MUL_VERT_SRC = `
43
+ attribute vec2 a_position;
44
+ attribute vec2 a_texCoord;
45
+ uniform vec2 u_resolution;
46
+ uniform mat3 u_transform;
47
+ varying vec2 v_texCoord;
48
+ void main() {
49
+ vec3 transformed = u_transform * vec3(a_position, 1.0);
50
+ vec2 clipspace = (transformed.xy / u_resolution) * 2.0 - 1.0;
51
+ gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);
52
+ v_texCoord = a_texCoord;
53
+ }
54
+ `;
55
+ const MUL_FRAG_SRC = `
56
+ precision mediump float;
57
+ varying vec2 v_texCoord;
58
+ uniform sampler2D u_srcTexture;
59
+ uniform sampler2D u_dstTexture;
60
+ void main() {
61
+ vec4 src = texture2D(u_srcTexture, v_texCoord);
62
+ vec4 dst = texture2D(u_dstTexture, v_texCoord);
63
+
64
+ // Canvas2D multiply:
65
+ // - Where src is opaque: result = src.rgb * dst.rgb
66
+ // - Where src is transparent: result = dst.rgb (unchanged)
67
+ // - Where src is semi-transparent: linear interpolation
68
+ vec3 multiplyColor = src.rgb * dst.rgb;
69
+ vec3 resultColor = mix(dst.rgb, multiplyColor, src.a);
70
+
71
+ // Alpha compositing (source-over)
72
+ float resultAlpha = src.a + dst.a - src.a * dst.a;
73
+
74
+ gl_FragColor = vec4(resultColor, resultAlpha);
75
+ }
76
+ `;
77
+ const GRAD_VERT_SRC = `
78
+ attribute vec2 a_position;
79
+ uniform vec2 u_resolution;
80
+ uniform mat3 u_transform;
81
+ uniform vec2 u_gradP0;
82
+ uniform vec2 u_gradP1;
83
+ varying float v_t;
84
+ void main() {
85
+ vec3 transformed = u_transform * vec3(a_position, 1.0);
86
+ vec2 clipspace = (transformed.xy / u_resolution) * 2.0 - 1.0;
87
+ gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);
88
+ vec2 dir = u_gradP1 - u_gradP0;
89
+ float len2 = dot(dir, dir);
90
+ if (len2 < 0.0001) {
91
+ v_t = 0.0;
92
+ } else {
93
+ v_t = dot(transformed.xy - u_gradP0, dir) / len2;
94
+ }
95
+ }
96
+ `;
97
+ const GRAD_FRAG_SRC = `
98
+ precision mediump float;
99
+ varying float v_t;
100
+ uniform sampler2D u_gradTex;
101
+ uniform float u_alpha;
102
+ void main() {
103
+ vec4 color = texture2D(u_gradTex, vec2(clamp(v_t, 0.0, 1.0), 0.5));
104
+ color.a *= u_alpha;
105
+ gl_FragColor = color;
106
+ }
107
+ `;
108
+ const FULLSCREEN_QUAD = new Float32Array([
109
+ 0, 0, 0, 1,
110
+ 1, 0, 1, 1,
111
+ 0, 1, 0, 0,
112
+ 1, 0, 1, 1,
113
+ 1, 1, 1, 0,
114
+ 0, 1, 0, 0,
115
+ ]);
116
+ const SIMPLE_OPS = new Set(['source-over', 'lighter', 'copy']);
117
+ const FBO_OPS = {
118
+ 'source-in': ['ZERO', 'SRC_ALPHA'],
119
+ 'source-out': ['ZERO', 'ONE_MINUS_SRC_ALPHA'],
120
+ 'source-atop': ['ONE_MINUS_DST_ALPHA', 'SRC_ALPHA'],
121
+ 'destination-in': ['DST_ALPHA', 'ZERO'],
122
+ 'destination-out': ['ONE_MINUS_DST_ALPHA', 'ZERO'],
123
+ 'destination-atop': ['DST_ALPHA', 'ONE_MINUS_SRC_ALPHA'],
124
+ 'xor': ['ONE_MINUS_DST_ALPHA', 'ONE_MINUS_SRC_ALPHA'],
125
+ };
126
+ const SHADER_OPS = new Set(['multiply']);
127
+ const IDENTITY_MATRIX = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
128
+ export class WebGLRenderer {
129
+ gl;
130
+ canvas;
131
+ program;
132
+ buffer;
133
+ aPosition;
134
+ uResolution;
135
+ uColor;
136
+ uTransform;
137
+ texProgram;
138
+ texBuffer;
139
+ aTexCoords;
140
+ uTexResolution;
141
+ uTexture;
142
+ uTexAlpha;
143
+ uTexTransform;
144
+ screenTex = null;
145
+ srcTex = null;
146
+ screenW = 0;
147
+ screenH = 0;
148
+ mulProgram;
149
+ aMulPosition;
150
+ aMulTexCoords;
151
+ uMulResolution;
152
+ uSrcTexture;
153
+ uDstTexture;
154
+ uMulTransform;
155
+ gradProgram;
156
+ aGradPosition;
157
+ uGradResolution;
158
+ uGradTransform;
159
+ uGradP0;
160
+ uGradP1;
161
+ uGradTex;
162
+ uGradAlpha;
163
+ constructor(canvas) {
164
+ this.canvas = canvas;
165
+ const gl = canvas.getContext('webgl', { alpha: true, antialias: true, premultipliedAlpha: false });
166
+ if (!gl)
167
+ throw new Error('WebGL not supported');
168
+ this.gl = gl;
169
+ this.program = this.createProgram(VERT_SRC, FRAG_SRC);
170
+ this.texProgram = this.createProgram(TEX_VERT_SRC, TEX_FRAG_SRC);
171
+ this.mulProgram = this.createProgram(MUL_VERT_SRC, MUL_FRAG_SRC);
172
+ this.gradProgram = this.createProgram(GRAD_VERT_SRC, GRAD_FRAG_SRC);
173
+ gl.useProgram(this.program);
174
+ this.aPosition = gl.getAttribLocation(this.program, 'a_position');
175
+ this.uResolution = gl.getUniformLocation(this.program, 'u_resolution');
176
+ this.uColor = gl.getUniformLocation(this.program, 'u_color');
177
+ this.uTransform = gl.getUniformLocation(this.program, 'u_transform');
178
+ gl.useProgram(this.texProgram);
179
+ this.aTexCoords = gl.getAttribLocation(this.texProgram, 'a_texCoord');
180
+ this.uTexResolution = gl.getUniformLocation(this.texProgram, 'u_resolution');
181
+ this.uTexture = gl.getUniformLocation(this.texProgram, 'u_texture');
182
+ this.uTexAlpha = gl.getUniformLocation(this.texProgram, 'u_alpha');
183
+ this.uTexTransform = gl.getUniformLocation(this.texProgram, 'u_transform');
184
+ gl.useProgram(this.mulProgram);
185
+ this.aMulPosition = gl.getAttribLocation(this.mulProgram, 'a_position');
186
+ this.aMulTexCoords = gl.getAttribLocation(this.mulProgram, 'a_texCoord');
187
+ this.uMulResolution = gl.getUniformLocation(this.mulProgram, 'u_resolution');
188
+ this.uSrcTexture = gl.getUniformLocation(this.mulProgram, 'u_srcTexture');
189
+ this.uDstTexture = gl.getUniformLocation(this.mulProgram, 'u_dstTexture');
190
+ this.uMulTransform = gl.getUniformLocation(this.mulProgram, 'u_transform');
191
+ gl.useProgram(this.gradProgram);
192
+ this.aGradPosition = gl.getAttribLocation(this.gradProgram, 'a_position');
193
+ this.uGradResolution = gl.getUniformLocation(this.gradProgram, 'u_resolution');
194
+ this.uGradTransform = gl.getUniformLocation(this.gradProgram, 'u_transform');
195
+ this.uGradP0 = gl.getUniformLocation(this.gradProgram, 'u_gradP0');
196
+ this.uGradP1 = gl.getUniformLocation(this.gradProgram, 'u_gradP1');
197
+ this.uGradTex = gl.getUniformLocation(this.gradProgram, 'u_gradTex');
198
+ this.uGradAlpha = gl.getUniformLocation(this.gradProgram, 'u_alpha');
199
+ gl.useProgram(this.program);
200
+ this.buffer = gl.createBuffer();
201
+ this.texBuffer = gl.createBuffer();
202
+ this.resize();
203
+ }
204
+ resize() {
205
+ const { gl, canvas } = this;
206
+ const displayW = canvas.clientWidth;
207
+ const displayH = canvas.clientHeight;
208
+ if (canvas.width !== displayW || canvas.height !== displayH) {
209
+ canvas.width = displayW;
210
+ canvas.height = displayH;
211
+ }
212
+ gl.viewport(0, 0, canvas.width, canvas.height);
213
+ this.screenW = canvas.width;
214
+ this.screenH = canvas.height;
215
+ if (!this.screenTex) {
216
+ this.screenTex = gl.createTexture();
217
+ }
218
+ gl.bindTexture(gl.TEXTURE_2D, this.screenTex);
219
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.screenW, this.screenH, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
220
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
221
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
222
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
223
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
224
+ gl.useProgram(this.program);
225
+ gl.uniform2f(this.uResolution, canvas.width, canvas.height);
226
+ gl.useProgram(this.texProgram);
227
+ gl.uniform2f(this.uTexResolution, canvas.width, canvas.height);
228
+ gl.useProgram(this.mulProgram);
229
+ gl.uniform2f(this.uMulResolution, canvas.width, canvas.height);
230
+ gl.useProgram(this.program);
231
+ if (!this.srcTex) {
232
+ this.srcTex = gl.createTexture();
233
+ }
234
+ gl.bindTexture(gl.TEXTURE_2D, this.srcTex);
235
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.screenW, this.screenH, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
236
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
237
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
238
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
239
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
240
+ }
241
+ clear() {
242
+ const { gl } = this;
243
+ gl.clearColor(0, 0, 0, 0);
244
+ gl.clear(gl.COLOR_BUFFER_BIT);
245
+ }
246
+ readPixels(x, y, w, h) {
247
+ const { gl, canvas } = this;
248
+ const glY = canvas.height - y - h;
249
+ const pixels = new Uint8Array(w * h * 4);
250
+ gl.readPixels(x, glY, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
251
+ const rowSize = w * 4;
252
+ const flipped = new Uint8Array(pixels.length);
253
+ for (let row = 0; row < h; row++) {
254
+ const srcRow = (h - 1 - row) * rowSize;
255
+ const dstRow = row * rowSize;
256
+ flipped.set(pixels.subarray(srcRow, srcRow + rowSize), dstRow);
257
+ }
258
+ return flipped;
259
+ }
260
+ captureScreen() {
261
+ const { gl, screenTex, screenW, screenH } = this;
262
+ gl.bindTexture(gl.TEXTURE_2D, screenTex);
263
+ gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, screenW, screenH, 0);
264
+ }
265
+ drawScreenTexture(srcFactor, dstFactor) {
266
+ const { gl } = this;
267
+ gl.useProgram(this.texProgram);
268
+ gl.activeTexture(gl.TEXTURE0);
269
+ gl.bindTexture(gl.TEXTURE_2D, this.screenTex);
270
+ gl.uniform1i(this.uTexture, 0);
271
+ gl.uniform1f(this.uTexAlpha, 1);
272
+ gl.uniform2f(this.uTexResolution, this.screenW, this.screenH);
273
+ gl.enable(gl.BLEND);
274
+ gl.blendFunc(srcFactor, dstFactor);
275
+ const verts = new Float32Array([
276
+ 0, 0, 0, 1,
277
+ this.screenW, 0, 1, 1,
278
+ 0, this.screenH, 0, 0,
279
+ this.screenW, 0, 1, 1,
280
+ this.screenW, this.screenH, 1, 0,
281
+ 0, this.screenH, 0, 0,
282
+ ]);
283
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
284
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
285
+ gl.enableVertexAttribArray(0);
286
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
287
+ gl.enableVertexAttribArray(this.aTexCoords);
288
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
289
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
290
+ }
291
+ drawCompositeShader() {
292
+ const { gl } = this;
293
+ gl.useProgram(this.mulProgram);
294
+ gl.disable(gl.BLEND);
295
+ gl.activeTexture(gl.TEXTURE0);
296
+ gl.bindTexture(gl.TEXTURE_2D, this.srcTex);
297
+ gl.uniform1i(this.uSrcTexture, 0);
298
+ gl.activeTexture(gl.TEXTURE1);
299
+ gl.bindTexture(gl.TEXTURE_2D, this.screenTex);
300
+ gl.uniform1i(this.uDstTexture, 1);
301
+ gl.uniform2f(this.uMulResolution, this.screenW, this.screenH);
302
+ gl.uniformMatrix3fv(this.uMulTransform, false, IDENTITY_MATRIX);
303
+ const verts = new Float32Array([
304
+ 0, 0, 0, 1,
305
+ this.screenW, 0, 1, 1,
306
+ 0, this.screenH, 0, 0,
307
+ this.screenW, 0, 1, 1,
308
+ this.screenW, this.screenH, 1, 0,
309
+ 0, this.screenH, 0, 0,
310
+ ]);
311
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
312
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
313
+ gl.enableVertexAttribArray(this.aMulPosition);
314
+ gl.vertexAttribPointer(this.aMulPosition, 2, gl.FLOAT, false, 16, 0);
315
+ gl.enableVertexAttribArray(this.aMulTexCoords);
316
+ gl.vertexAttribPointer(this.aMulTexCoords, 2, gl.FLOAT, false, 16, 8);
317
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
318
+ }
319
+ _drawShaderComposite(vertices, color, transform) {
320
+ const { gl } = this;
321
+ this.captureScreen();
322
+ gl.clearColor(0, 0, 0, 0);
323
+ gl.clear(gl.COLOR_BUFFER_BIT);
324
+ gl.useProgram(this.program);
325
+ gl.uniformMatrix3fv(this.uTransform, false, transform);
326
+ gl.disable(gl.BLEND);
327
+ gl.disableVertexAttribArray(this.aTexCoords);
328
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
329
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
330
+ gl.enableVertexAttribArray(this.aPosition);
331
+ gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 0, 0);
332
+ gl.uniform4fv(this.uColor, color);
333
+ gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
334
+ gl.bindTexture(gl.TEXTURE_2D, this.srcTex);
335
+ gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.screenW, this.screenH, 0);
336
+ gl.clearColor(0, 0, 0, 0);
337
+ gl.clear(gl.COLOR_BUFFER_BIT);
338
+ this.drawCompositeShader();
339
+ gl.useProgram(this.program);
340
+ }
341
+ _drawTextureShaderComposite(texture, sx, sy, sw, sh, dx, dy, dw, dh, texW, texH, alpha, transform) {
342
+ const { gl } = this;
343
+ this.captureScreen();
344
+ gl.clearColor(0, 0, 0, 0);
345
+ gl.clear(gl.COLOR_BUFFER_BIT);
346
+ gl.useProgram(this.texProgram);
347
+ gl.uniformMatrix3fv(this.uTexTransform, false, transform);
348
+ gl.disable(gl.BLEND);
349
+ const u0 = sx / texW;
350
+ const v0 = sy / texH;
351
+ const u1 = (sx + sw) / texW;
352
+ const v1 = (sy + sh) / texH;
353
+ const verts = new Float32Array([
354
+ dx, dy, u0, v0,
355
+ dx + dw, dy, u1, v0,
356
+ dx, dy + dh, u0, v1,
357
+ dx + dw, dy, u1, v0,
358
+ dx + dw, dy + dh, u1, v1,
359
+ dx, dy + dh, u0, v1,
360
+ ]);
361
+ gl.activeTexture(gl.TEXTURE0);
362
+ gl.bindTexture(gl.TEXTURE_2D, texture);
363
+ gl.uniform1i(this.uTexture, 0);
364
+ gl.uniform1f(this.uTexAlpha, alpha);
365
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
366
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
367
+ gl.enableVertexAttribArray(0);
368
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
369
+ gl.enableVertexAttribArray(this.aTexCoords);
370
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
371
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
372
+ gl.bindTexture(gl.TEXTURE_2D, this.srcTex);
373
+ gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.screenW, this.screenH, 0);
374
+ gl.clearColor(0, 0, 0, 0);
375
+ gl.clear(gl.COLOR_BUFFER_BIT);
376
+ this.drawCompositeShader();
377
+ gl.useProgram(this.program);
378
+ }
379
+ draw(vertices, color, compositeOp, transform) {
380
+ const { gl } = this;
381
+ if (SHADER_OPS.has(compositeOp)) {
382
+ this._drawShaderComposite(vertices, color, transform);
383
+ return;
384
+ }
385
+ if (SIMPLE_OPS.has(compositeOp)) {
386
+ gl.useProgram(this.program);
387
+ gl.uniformMatrix3fv(this.uTransform, false, transform);
388
+ gl.disableVertexAttribArray(this.aTexCoords);
389
+ gl.enable(gl.BLEND);
390
+ const m = gl;
391
+ const blendMap = {
392
+ 'source-over': [m.SRC_ALPHA, m.ONE_MINUS_SRC_ALPHA],
393
+ 'lighter': [m.ONE, m.ONE],
394
+ 'copy': [m.ONE, m.ZERO],
395
+ };
396
+ const [src, dst] = blendMap[compositeOp] ?? blendMap['source-over'];
397
+ gl.blendFunc(src, dst);
398
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
399
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
400
+ gl.enableVertexAttribArray(this.aPosition);
401
+ gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 0, 0);
402
+ gl.uniform4fv(this.uColor, color);
403
+ gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
404
+ }
405
+ else if (compositeOp === 'destination-over') {
406
+ this.captureScreen();
407
+ gl.clearColor(0, 0, 0, 0);
408
+ gl.clear(gl.COLOR_BUFFER_BIT);
409
+ gl.useProgram(this.texProgram);
410
+ gl.uniformMatrix3fv(this.uTexTransform, false, IDENTITY_MATRIX);
411
+ gl.disable(gl.BLEND);
412
+ gl.disableVertexAttribArray(this.aTexCoords);
413
+ const verts = new Float32Array([
414
+ 0, 0, 0, 1,
415
+ this.screenW, 0, 1, 1,
416
+ 0, this.screenH, 0, 0,
417
+ this.screenW, 0, 1, 1,
418
+ this.screenW, this.screenH, 1, 0,
419
+ 0, this.screenH, 0, 0,
420
+ ]);
421
+ gl.activeTexture(gl.TEXTURE0);
422
+ gl.bindTexture(gl.TEXTURE_2D, this.screenTex);
423
+ gl.uniform1i(this.uTexture, 0);
424
+ gl.uniform1f(this.uTexAlpha, 1);
425
+ gl.uniform2f(this.uTexResolution, this.screenW, this.screenH);
426
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
427
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
428
+ gl.enableVertexAttribArray(0);
429
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
430
+ gl.enableVertexAttribArray(this.aTexCoords);
431
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
432
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
433
+ gl.useProgram(this.program);
434
+ gl.uniformMatrix3fv(this.uTransform, false, transform);
435
+ gl.enable(gl.BLEND);
436
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
437
+ gl.disableVertexAttribArray(this.aTexCoords);
438
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
439
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
440
+ gl.enableVertexAttribArray(this.aPosition);
441
+ gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 0, 0);
442
+ gl.uniform4fv(this.uColor, color);
443
+ gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
444
+ }
445
+ else {
446
+ this.captureScreen();
447
+ gl.clearColor(0, 0, 0, 0);
448
+ gl.clear(gl.COLOR_BUFFER_BIT);
449
+ gl.useProgram(this.program);
450
+ gl.uniformMatrix3fv(this.uTransform, false, transform);
451
+ gl.disableVertexAttribArray(this.aTexCoords);
452
+ gl.disable(gl.BLEND);
453
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
454
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
455
+ gl.enableVertexAttribArray(this.aPosition);
456
+ gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 0, 0);
457
+ gl.uniform4fv(this.uColor, color);
458
+ gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
459
+ gl.bindTexture(gl.TEXTURE_2D, this.srcTex);
460
+ gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.screenW, this.screenH, 0);
461
+ gl.clearColor(0, 0, 0, 0);
462
+ gl.clear(gl.COLOR_BUFFER_BIT);
463
+ gl.useProgram(this.texProgram);
464
+ gl.uniformMatrix3fv(this.uTexTransform, false, IDENTITY_MATRIX);
465
+ gl.disable(gl.BLEND);
466
+ const srcVerts = new Float32Array([
467
+ 0, 0, 0, 1,
468
+ this.screenW, 0, 1, 1,
469
+ 0, this.screenH, 0, 0,
470
+ this.screenW, 0, 1, 1,
471
+ this.screenW, this.screenH, 1, 0,
472
+ 0, this.screenH, 0, 0,
473
+ ]);
474
+ gl.activeTexture(gl.TEXTURE0);
475
+ gl.bindTexture(gl.TEXTURE_2D, this.srcTex);
476
+ gl.uniform1i(this.uTexture, 0);
477
+ gl.uniform1f(this.uTexAlpha, 1);
478
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
479
+ gl.bufferData(gl.ARRAY_BUFFER, srcVerts, gl.DYNAMIC_DRAW);
480
+ gl.enableVertexAttribArray(0);
481
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
482
+ gl.enableVertexAttribArray(this.aTexCoords);
483
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
484
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
485
+ const [srcName, dstName] = FBO_OPS[compositeOp] ?? ['ONE', 'ZERO'];
486
+ const m = gl;
487
+ const factorMap = {
488
+ 'ZERO': m.ZERO,
489
+ 'ONE': m.ONE,
490
+ 'SRC_ALPHA': m.SRC_ALPHA,
491
+ 'ONE_MINUS_SRC_ALPHA': m.ONE_MINUS_SRC_ALPHA,
492
+ 'DST_ALPHA': m.DST_ALPHA,
493
+ 'ONE_MINUS_DST_ALPHA': m.ONE_MINUS_DST_ALPHA,
494
+ };
495
+ const srcFactor = factorMap[srcName];
496
+ const dstFactor = factorMap[dstName];
497
+ gl.enable(gl.BLEND);
498
+ gl.blendFunc(srcFactor, dstFactor);
499
+ const dstVerts = new Float32Array([
500
+ 0, 0, 0, 1,
501
+ this.screenW, 0, 1, 1,
502
+ 0, this.screenH, 0, 0,
503
+ this.screenW, 0, 1, 1,
504
+ this.screenW, this.screenH, 1, 0,
505
+ 0, this.screenH, 0, 0,
506
+ ]);
507
+ gl.bindTexture(gl.TEXTURE_2D, this.screenTex);
508
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
509
+ gl.bufferData(gl.ARRAY_BUFFER, dstVerts, gl.DYNAMIC_DRAW);
510
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
511
+ gl.useProgram(this.program);
512
+ }
513
+ }
514
+ drawGradient(vertices, gradient, compositeOp, transform) {
515
+ const { gl } = this;
516
+ if (SHADER_OPS.has(compositeOp)) {
517
+ this.drawGradientFBO(vertices, gradient, compositeOp, transform);
518
+ return;
519
+ }
520
+ if (SIMPLE_OPS.has(compositeOp)) {
521
+ gl.useProgram(this.gradProgram);
522
+ gl.uniform2f(this.uGradResolution, this.screenW, this.screenH);
523
+ gl.uniformMatrix3fv(this.uGradTransform, false, transform);
524
+ gl.enable(gl.BLEND);
525
+ const m = gl;
526
+ const blendMap = {
527
+ 'source-over': [m.SRC_ALPHA, m.ONE_MINUS_SRC_ALPHA],
528
+ 'lighter': [m.ONE, m.ONE],
529
+ 'copy': [m.ONE, m.ZERO],
530
+ };
531
+ const [src, dst] = blendMap[compositeOp] ?? blendMap['source-over'];
532
+ gl.blendFunc(src, dst);
533
+ const params = gradient.getParams();
534
+ gl.uniform2f(this.uGradP0, params.x0, params.y0);
535
+ gl.uniform2f(this.uGradP1, params.x1, params.y1);
536
+ gl.uniform1f(this.uGradAlpha, 1);
537
+ gl.activeTexture(gl.TEXTURE0);
538
+ gl.bindTexture(gl.TEXTURE_2D, gradient.getTexture(gl));
539
+ gl.uniform1i(this.uGradTex, 0);
540
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
541
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
542
+ gl.enableVertexAttribArray(this.aGradPosition);
543
+ gl.vertexAttribPointer(this.aGradPosition, 2, gl.FLOAT, false, 0, 0);
544
+ gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
545
+ }
546
+ else {
547
+ this.drawGradientFBO(vertices, gradient, compositeOp, transform);
548
+ }
549
+ }
550
+ drawGradientFBO(vertices, gradient, compositeOp, transform) {
551
+ const { gl } = this;
552
+ if (compositeOp === 'destination-over') {
553
+ this.captureScreen();
554
+ gl.clearColor(0, 0, 0, 0);
555
+ gl.clear(gl.COLOR_BUFFER_BIT);
556
+ gl.useProgram(this.texProgram);
557
+ gl.uniformMatrix3fv(this.uTexTransform, false, IDENTITY_MATRIX);
558
+ gl.disable(gl.BLEND);
559
+ gl.disableVertexAttribArray(this.aTexCoords);
560
+ const verts = new Float32Array([
561
+ 0, 0, 0, 1,
562
+ this.screenW, 0, 1, 1,
563
+ 0, this.screenH, 0, 0,
564
+ this.screenW, 0, 1, 1,
565
+ this.screenW, this.screenH, 1, 0,
566
+ 0, this.screenH, 0, 0,
567
+ ]);
568
+ gl.activeTexture(gl.TEXTURE0);
569
+ gl.bindTexture(gl.TEXTURE_2D, this.screenTex);
570
+ gl.uniform1i(this.uTexture, 0);
571
+ gl.uniform1f(this.uTexAlpha, 1);
572
+ gl.uniform2f(this.uTexResolution, this.screenW, this.screenH);
573
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
574
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
575
+ gl.enableVertexAttribArray(0);
576
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
577
+ gl.enableVertexAttribArray(this.aTexCoords);
578
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
579
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
580
+ gl.disableVertexAttribArray(0);
581
+ gl.disableVertexAttribArray(this.aTexCoords);
582
+ gl.useProgram(this.gradProgram);
583
+ gl.uniform2f(this.uGradResolution, this.screenW, this.screenH);
584
+ gl.uniformMatrix3fv(this.uGradTransform, false, transform);
585
+ gl.enable(gl.BLEND);
586
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
587
+ const params = gradient.getParams();
588
+ gl.uniform2f(this.uGradP0, params.x0, params.y0);
589
+ gl.uniform2f(this.uGradP1, params.x1, params.y1);
590
+ gl.uniform1f(this.uGradAlpha, 1);
591
+ gl.activeTexture(gl.TEXTURE0);
592
+ gl.bindTexture(gl.TEXTURE_2D, gradient.getTexture(gl));
593
+ gl.uniform1i(this.uGradTex, 0);
594
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
595
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
596
+ gl.enableVertexAttribArray(this.aGradPosition);
597
+ gl.vertexAttribPointer(this.aGradPosition, 2, gl.FLOAT, false, 0, 0);
598
+ gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
599
+ }
600
+ else {
601
+ this.captureScreen();
602
+ gl.clearColor(0, 0, 0, 0);
603
+ gl.clear(gl.COLOR_BUFFER_BIT);
604
+ gl.useProgram(this.gradProgram);
605
+ gl.uniform2f(this.uGradResolution, this.screenW, this.screenH);
606
+ gl.uniformMatrix3fv(this.uGradTransform, false, transform);
607
+ gl.disable(gl.BLEND);
608
+ const params = gradient.getParams();
609
+ gl.uniform2f(this.uGradP0, params.x0, params.y0);
610
+ gl.uniform2f(this.uGradP1, params.x1, params.y1);
611
+ gl.uniform1f(this.uGradAlpha, 1);
612
+ gl.activeTexture(gl.TEXTURE0);
613
+ gl.bindTexture(gl.TEXTURE_2D, gradient.getTexture(gl));
614
+ gl.uniform1i(this.uGradTex, 0);
615
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
616
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
617
+ gl.enableVertexAttribArray(this.aGradPosition);
618
+ gl.vertexAttribPointer(this.aGradPosition, 2, gl.FLOAT, false, 0, 0);
619
+ gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
620
+ gl.disableVertexAttribArray(this.aGradPosition);
621
+ gl.bindTexture(gl.TEXTURE_2D, this.srcTex);
622
+ gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.screenW, this.screenH, 0);
623
+ gl.clearColor(0, 0, 0, 0);
624
+ gl.clear(gl.COLOR_BUFFER_BIT);
625
+ gl.useProgram(this.texProgram);
626
+ gl.uniformMatrix3fv(this.uTexTransform, false, IDENTITY_MATRIX);
627
+ gl.disable(gl.BLEND);
628
+ const srcVerts = new Float32Array([
629
+ 0, 0, 0, 1,
630
+ this.screenW, 0, 1, 1,
631
+ 0, this.screenH, 0, 0,
632
+ this.screenW, 0, 1, 1,
633
+ this.screenW, this.screenH, 1, 0,
634
+ 0, this.screenH, 0, 0,
635
+ ]);
636
+ gl.activeTexture(gl.TEXTURE0);
637
+ gl.bindTexture(gl.TEXTURE_2D, this.srcTex);
638
+ gl.uniform1i(this.uTexture, 0);
639
+ gl.uniform1f(this.uTexAlpha, 1);
640
+ gl.uniform2f(this.uTexResolution, this.screenW, this.screenH);
641
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
642
+ gl.bufferData(gl.ARRAY_BUFFER, srcVerts, gl.DYNAMIC_DRAW);
643
+ gl.enableVertexAttribArray(0);
644
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
645
+ gl.enableVertexAttribArray(this.aTexCoords);
646
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
647
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
648
+ const [srcName, dstName] = FBO_OPS[compositeOp] ?? ['ONE', 'ZERO'];
649
+ const m = gl;
650
+ const factorMap = {
651
+ 'ZERO': m.ZERO,
652
+ 'ONE': m.ONE,
653
+ 'SRC_ALPHA': m.SRC_ALPHA,
654
+ 'ONE_MINUS_SRC_ALPHA': m.ONE_MINUS_SRC_ALPHA,
655
+ 'DST_ALPHA': m.DST_ALPHA,
656
+ 'ONE_MINUS_DST_ALPHA': m.ONE_MINUS_DST_ALPHA,
657
+ };
658
+ const srcFactor = factorMap[srcName];
659
+ const dstFactor = factorMap[dstName];
660
+ gl.enable(gl.BLEND);
661
+ gl.blendFunc(srcFactor, dstFactor);
662
+ const dstVerts = new Float32Array([
663
+ 0, 0, 0, 1,
664
+ this.screenW, 0, 1, 1,
665
+ 0, this.screenH, 0, 0,
666
+ this.screenW, 0, 1, 1,
667
+ this.screenW, this.screenH, 1, 0,
668
+ 0, this.screenH, 0, 0,
669
+ ]);
670
+ gl.bindTexture(gl.TEXTURE_2D, this.screenTex);
671
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
672
+ gl.bufferData(gl.ARRAY_BUFFER, dstVerts, gl.DYNAMIC_DRAW);
673
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
674
+ gl.useProgram(this.gradProgram);
675
+ }
676
+ }
677
+ createTexture(source) {
678
+ const { gl } = this;
679
+ const tex = gl.createTexture();
680
+ gl.bindTexture(gl.TEXTURE_2D, tex);
681
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
682
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
683
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
684
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
685
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
686
+ return tex;
687
+ }
688
+ updateTexture(tex, source) {
689
+ const { gl } = this;
690
+ gl.bindTexture(gl.TEXTURE_2D, tex);
691
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
692
+ }
693
+ drawTexture(texture, sx, sy, sw, sh, dx, dy, dw, dh, texW, texH, alpha, compositeOp, transform) {
694
+ const { gl } = this;
695
+ if (SHADER_OPS.has(compositeOp)) {
696
+ this._drawTextureShaderComposite(texture, sx, sy, sw, sh, dx, dy, dw, dh, texW, texH, alpha, transform);
697
+ return;
698
+ }
699
+ if (SIMPLE_OPS.has(compositeOp)) {
700
+ gl.useProgram(this.texProgram);
701
+ gl.uniformMatrix3fv(this.uTexTransform, false, transform);
702
+ gl.enable(gl.BLEND);
703
+ const m = gl;
704
+ const blendMap = {
705
+ 'source-over': [m.SRC_ALPHA, m.ONE_MINUS_SRC_ALPHA],
706
+ 'lighter': [m.ONE, m.ONE],
707
+ 'copy': [m.ONE, m.ZERO],
708
+ };
709
+ const [src, dst] = blendMap[compositeOp] ?? blendMap['source-over'];
710
+ gl.blendFunc(src, dst);
711
+ const u0 = sx / texW;
712
+ const v0 = sy / texH;
713
+ const u1 = (sx + sw) / texW;
714
+ const v1 = (sy + sh) / texH;
715
+ const verts = new Float32Array([
716
+ dx, dy, u0, v0,
717
+ dx + dw, dy, u1, v0,
718
+ dx, dy + dh, u0, v1,
719
+ dx + dw, dy, u1, v0,
720
+ dx + dw, dy + dh, u1, v1,
721
+ dx, dy + dh, u0, v1,
722
+ ]);
723
+ gl.activeTexture(gl.TEXTURE0);
724
+ gl.bindTexture(gl.TEXTURE_2D, texture);
725
+ gl.uniform1i(this.uTexture, 0);
726
+ gl.uniform1f(this.uTexAlpha, alpha);
727
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
728
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
729
+ gl.enableVertexAttribArray(0);
730
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
731
+ gl.enableVertexAttribArray(this.aTexCoords);
732
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
733
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
734
+ gl.useProgram(this.program);
735
+ }
736
+ else if (compositeOp === 'destination-over') {
737
+ this.captureScreen();
738
+ gl.clearColor(0, 0, 0, 0);
739
+ gl.clear(gl.COLOR_BUFFER_BIT);
740
+ gl.useProgram(this.texProgram);
741
+ gl.uniformMatrix3fv(this.uTexTransform, false, IDENTITY_MATRIX);
742
+ gl.disable(gl.BLEND);
743
+ const screenVerts = new Float32Array([
744
+ 0, 0, 0, 1,
745
+ this.screenW, 0, 1, 1,
746
+ 0, this.screenH, 0, 0,
747
+ this.screenW, 0, 1, 1,
748
+ this.screenW, this.screenH, 1, 0,
749
+ 0, this.screenH, 0, 0,
750
+ ]);
751
+ gl.activeTexture(gl.TEXTURE0);
752
+ gl.bindTexture(gl.TEXTURE_2D, this.screenTex);
753
+ gl.uniform1i(this.uTexture, 0);
754
+ gl.uniform1f(this.uTexAlpha, 1);
755
+ gl.uniform2f(this.uTexResolution, this.screenW, this.screenH);
756
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
757
+ gl.bufferData(gl.ARRAY_BUFFER, screenVerts, gl.DYNAMIC_DRAW);
758
+ gl.enableVertexAttribArray(0);
759
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
760
+ gl.enableVertexAttribArray(this.aTexCoords);
761
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
762
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
763
+ gl.useProgram(this.texProgram);
764
+ gl.uniformMatrix3fv(this.uTexTransform, false, transform);
765
+ gl.enable(gl.BLEND);
766
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
767
+ const u0 = sx / texW;
768
+ const v0 = sy / texH;
769
+ const u1 = (sx + sw) / texW;
770
+ const v1 = (sy + sh) / texH;
771
+ const verts = new Float32Array([
772
+ dx, dy, u0, v0,
773
+ dx + dw, dy, u1, v0,
774
+ dx, dy + dh, u0, v1,
775
+ dx + dw, dy, u1, v0,
776
+ dx + dw, dy + dh, u1, v1,
777
+ dx, dy + dh, u0, v1,
778
+ ]);
779
+ gl.activeTexture(gl.TEXTURE0);
780
+ gl.bindTexture(gl.TEXTURE_2D, texture);
781
+ gl.uniform1i(this.uTexture, 0);
782
+ gl.uniform1f(this.uTexAlpha, alpha);
783
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
784
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
785
+ gl.enableVertexAttribArray(0);
786
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
787
+ gl.enableVertexAttribArray(this.aTexCoords);
788
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
789
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
790
+ gl.useProgram(this.program);
791
+ }
792
+ else {
793
+ this.captureScreen();
794
+ gl.clearColor(0, 0, 0, 0);
795
+ gl.clear(gl.COLOR_BUFFER_BIT);
796
+ gl.useProgram(this.texProgram);
797
+ gl.uniformMatrix3fv(this.uTexTransform, false, transform);
798
+ gl.disable(gl.BLEND);
799
+ const u0 = sx / texW;
800
+ const v0 = sy / texH;
801
+ const u1 = (sx + sw) / texW;
802
+ const v1 = (sy + sh) / texH;
803
+ const verts = new Float32Array([
804
+ dx, dy, u0, v0,
805
+ dx + dw, dy, u1, v0,
806
+ dx, dy + dh, u0, v1,
807
+ dx + dw, dy, u1, v0,
808
+ dx + dw, dy + dh, u1, v1,
809
+ dx, dy + dh, u0, v1,
810
+ ]);
811
+ gl.activeTexture(gl.TEXTURE0);
812
+ gl.bindTexture(gl.TEXTURE_2D, texture);
813
+ gl.uniform1i(this.uTexture, 0);
814
+ gl.uniform1f(this.uTexAlpha, alpha);
815
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
816
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
817
+ gl.enableVertexAttribArray(0);
818
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
819
+ gl.enableVertexAttribArray(this.aTexCoords);
820
+ gl.vertexAttribPointer(this.aTexCoords, 2, gl.FLOAT, false, 16, 8);
821
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
822
+ const [srcName, dstName] = FBO_OPS[compositeOp] ?? ['ONE', 'ZERO'];
823
+ const m = gl;
824
+ const factorMap = {
825
+ 'ZERO': m.ZERO,
826
+ 'ONE': m.ONE,
827
+ 'SRC_ALPHA': m.SRC_ALPHA,
828
+ 'ONE_MINUS_SRC_ALPHA': m.ONE_MINUS_SRC_ALPHA,
829
+ 'DST_ALPHA': m.DST_ALPHA,
830
+ 'ONE_MINUS_DST_ALPHA': m.ONE_MINUS_DST_ALPHA,
831
+ };
832
+ const srcFactor = factorMap[srcName];
833
+ const dstFactor = factorMap[dstName];
834
+ this.drawScreenTexture(srcFactor, dstFactor);
835
+ gl.useProgram(this.program);
836
+ }
837
+ }
838
+ clearRect(x, y, w, h) {
839
+ const { gl, canvas } = this;
840
+ gl.enable(gl.SCISSOR_TEST);
841
+ gl.scissor(x, canvas.height - y - h, w, h);
842
+ gl.clearColor(0, 0, 0, 0);
843
+ gl.clear(gl.COLOR_BUFFER_BIT);
844
+ gl.disable(gl.SCISSOR_TEST);
845
+ }
846
+ createProgram(vsSource, fsSource) {
847
+ const { gl } = this;
848
+ const vs = this.compileShader(gl.VERTEX_SHADER, vsSource);
849
+ const fs = this.compileShader(gl.FRAGMENT_SHADER, fsSource);
850
+ const program = gl.createProgram();
851
+ gl.attachShader(program, vs);
852
+ gl.attachShader(program, fs);
853
+ gl.linkProgram(program);
854
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
855
+ throw new Error(gl.getProgramInfoLog(program) ?? 'Program link failed');
856
+ }
857
+ return program;
858
+ }
859
+ compileShader(type, source) {
860
+ const { gl } = this;
861
+ const shader = gl.createShader(type);
862
+ gl.shaderSource(shader, source);
863
+ gl.compileShader(shader);
864
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
865
+ const msg = gl.getShaderInfoLog(shader) ?? 'Shader compile failed';
866
+ gl.deleteShader(shader);
867
+ throw new Error(msg);
868
+ }
869
+ return shader;
870
+ }
871
+ }