jassub 2.3.3 → 2.4.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jassub",
3
- "version": "2.3.3",
3
+ "version": "2.4.1",
4
4
  "description": "The Fastest JavaScript SSA/ASS Subtitle Renderer For Browsers",
5
5
  "main": "dist/jassub.js",
6
6
  "type": "module",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsc --noCheck",
56
- "dev": "vite",
56
+ "dev": "vite --host",
57
57
  "docker:build": "docker build -t thaunknown/jassub-build .",
58
58
  "docker:run": "docker run -it --rm -v ${PWD}:/code --name thaunknown_jassub-build thaunknown/jassub-build:latest"
59
59
  }
@@ -0,0 +1,81 @@
1
+ // fallback for browsers that don't support GPU acceleration
2
+ import type { ASSImage } from '../util.ts'
3
+
4
+ export class Canvas2DRenderer {
5
+ canvas: OffscreenCanvas | null = null
6
+ ctx: OffscreenCanvasRenderingContext2D | null = null
7
+ bufferCanvas = new OffscreenCanvas(1, 1)
8
+ bufferCtx = this.bufferCanvas.getContext('2d', {
9
+ alpha: true,
10
+ desynchronized: true,
11
+ willReadFrequently: false
12
+ })
13
+
14
+ _scheduledResize?: { width: number, height: number }
15
+
16
+ resizeCanvas (width: number, height: number) {
17
+ if (width <= 0 || height <= 0) return
18
+
19
+ this._scheduledResize = { width, height }
20
+ }
21
+
22
+ setCanvas (canvas: OffscreenCanvas) {
23
+ this.canvas = canvas
24
+ this.ctx = canvas.getContext('2d', {
25
+ alpha: true,
26
+ desynchronized: true,
27
+ willReadFrequently: false
28
+ })
29
+
30
+ if (!this.ctx) throw new Error('Could not get 2D context')
31
+ }
32
+
33
+ setColorMatrix (subtitleColorSpace?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC', videoColorSpace?: 'BT601' | 'BT709') {}
34
+
35
+ // this is horribly inefficient, but it's a fallback for systems without a GPU, this is the least of their problems
36
+ render (images: ASSImage[], heap: Uint8Array): void {
37
+ if (!this.ctx || !this.canvas) return
38
+
39
+ if (this._scheduledResize) {
40
+ const { width, height } = this._scheduledResize
41
+ this._scheduledResize = undefined
42
+ this.canvas.width = width
43
+ this.canvas.height = height
44
+ } else {
45
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
46
+ }
47
+
48
+ for (const img of images) {
49
+ if (img.w <= 0 || img.h <= 0) continue
50
+ const imageData = new ImageData(img.w, img.h)
51
+ const pixels = new Uint32Array(imageData.data.buffer)
52
+
53
+ const color = ((img.color << 8) & 0xff0000) | ((img.color >> 8) & 0xff00) | ((img.color >> 24) & 0xff)
54
+ const alpha = (255 - (img.color & 255)) / 255
55
+
56
+ const stride = img.stride
57
+ const h = img.h
58
+ const w = img.w
59
+
60
+ for (let y = h + 1, pos = img.bitmap, res = 0; --y; pos += stride) {
61
+ for (let z = 0; z < w; ++z, ++res) {
62
+ const k = heap[pos + z]!
63
+ if (k !== 0) pixels[res] = ((alpha * k) << 24) | color
64
+ }
65
+ }
66
+
67
+ // Draw the ImageData to canvas at the destination position
68
+ this.bufferCanvas.width = w
69
+ this.bufferCanvas.height = h
70
+ this.bufferCtx!.putImageData(imageData, 0, 0)
71
+ this.ctx.drawImage(this.bufferCanvas, img.dst_x, img.dst_y)
72
+ }
73
+ }
74
+
75
+ destroy () {
76
+ this.ctx = null
77
+ this.canvas = null
78
+ this.bufferCtx = null!
79
+ this.bufferCanvas = null!
80
+ }
81
+ }
@@ -0,0 +1,445 @@
1
+ import { colorMatrixConversionMap, IDENTITY_MATRIX, type ASSImage } from '../util.ts'
2
+
3
+ // GLSL ES 1.0 Vertex Shader with Instancing (using extension)
4
+ const VERTEX_SHADER = /* glsl */`
5
+ precision mediump float;
6
+
7
+ // Quad position attribute (0,0), (1,0), (0,1), (1,0), (1,1), (0,1)
8
+ attribute vec2 a_quadPos;
9
+
10
+ uniform vec2 u_resolution;
11
+
12
+ // Instance attributes
13
+ attribute vec4 a_destRect; // x, y, w, h
14
+ attribute vec4 a_color; // r, g, b, a
15
+ attribute float a_texLayer;
16
+
17
+ varying vec2 v_destXY;
18
+ varying vec4 v_color;
19
+ varying vec2 v_texSize;
20
+ varying float v_texLayer;
21
+ varying vec2 v_texCoord;
22
+
23
+ void main() {
24
+ vec2 pixelPos = a_destRect.xy + a_quadPos * a_destRect.zw;
25
+ vec2 clipPos = (pixelPos / u_resolution) * 2.0 - 1.0;
26
+ clipPos.y = -clipPos.y;
27
+
28
+ gl_Position = vec4(clipPos, 0.0, 1.0);
29
+ v_destXY = a_destRect.xy;
30
+ v_color = a_color;
31
+ v_texSize = a_destRect.zw;
32
+ v_texLayer = a_texLayer;
33
+ v_texCoord = a_quadPos;
34
+ }
35
+ `
36
+
37
+ // GLSL ES 1.0 Fragment Shader
38
+ // WebGL1 doesn't support texture arrays or texelFetch, so we use individual textures
39
+ const FRAGMENT_SHADER = /* glsl */`
40
+ precision mediump float;
41
+
42
+ uniform sampler2D u_tex;
43
+ uniform mat3 u_colorMatrix;
44
+ uniform vec2 u_resolution;
45
+ uniform vec2 u_texDimensions; // Actual texture dimensions
46
+
47
+ varying vec2 v_destXY;
48
+ varying vec4 v_color;
49
+ varying vec2 v_texSize;
50
+ varying float v_texLayer;
51
+ varying vec2 v_texCoord;
52
+
53
+ void main() {
54
+ // v_texCoord is in 0-1 range for the quad
55
+ // We need to map it to the actual image size within the texture
56
+ // The image occupies only (v_texSize.x / u_texDimensions.x, v_texSize.y / u_texDimensions.y) of the texture
57
+ vec2 normalizedImageSize = v_texSize / u_texDimensions;
58
+ vec2 texCoord = v_texCoord * normalizedImageSize;
59
+
60
+ // Sample texture (r channel contains mask)
61
+ float mask = texture2D(u_tex, texCoord).r;
62
+
63
+ // Apply color matrix conversion (identity if no conversion needed)
64
+ vec3 correctedColor = u_colorMatrix * v_color.rgb;
65
+
66
+ // libass color alpha: 0 = opaque, 255 = transparent (inverted)
67
+ float colorAlpha = 1.0 - v_color.a;
68
+
69
+ // Final alpha = colorAlpha * mask
70
+ float a = colorAlpha * mask;
71
+
72
+ // Premultiplied alpha output
73
+ gl_FragColor = vec4(correctedColor * a, a);
74
+ }
75
+ `
76
+
77
+ // Configuration
78
+ const MAX_INSTANCES = 256 // Maximum instances per draw call
79
+
80
+ export class WebGL1Renderer {
81
+ canvas: OffscreenCanvas | null = null
82
+ gl: WebGLRenderingContext | null = null
83
+ program: WebGLProgram | null = null
84
+
85
+ // Extensions
86
+ instancedArraysExt: ANGLE_instanced_arrays | null = null
87
+
88
+ // Uniform locations
89
+ u_resolution: WebGLUniformLocation | null = null
90
+ u_tex: WebGLUniformLocation | null = null
91
+ u_colorMatrix: WebGLUniformLocation | null = null
92
+ u_texDimensions: WebGLUniformLocation | null = null
93
+
94
+ // Attribute locations
95
+ a_quadPos = -1
96
+ a_destRect = -1
97
+ a_color = -1
98
+ a_texLayer = -1
99
+
100
+ // Quad vertex buffer (shared for all instances)
101
+ quadPosBuffer: WebGLBuffer | null = null
102
+
103
+ // Instance attribute buffers
104
+ instanceDestRectBuffer: WebGLBuffer | null = null
105
+ instanceColorBuffer: WebGLBuffer | null = null
106
+ instanceTexLayerBuffer: WebGLBuffer | null = null
107
+
108
+ // Instance data arrays
109
+ instanceDestRectData: Float32Array
110
+ instanceColorData: Float32Array
111
+ instanceTexLayerData: Float32Array
112
+
113
+ // Texture cache (since WebGL1 doesn't support texture arrays)
114
+ textureCache = new Map<number, WebGLTexture>()
115
+ textureWidth = 0
116
+ textureHeight = 0
117
+
118
+ colorMatrix: Float32Array = IDENTITY_MATRIX
119
+
120
+ constructor () {
121
+ this.instanceDestRectData = new Float32Array(MAX_INSTANCES * 4)
122
+ this.instanceColorData = new Float32Array(MAX_INSTANCES * 4)
123
+ this.instanceTexLayerData = new Float32Array(MAX_INSTANCES)
124
+ }
125
+
126
+ _scheduledResize?: { width: number, height: number }
127
+
128
+ resizeCanvas (width: number, height: number) {
129
+ // WebGL doesn't allow 0-sized canvases
130
+ if (width <= 0 || height <= 0) return
131
+
132
+ this._scheduledResize = { width, height }
133
+ }
134
+
135
+ setCanvas (canvas: OffscreenCanvas) {
136
+ this.canvas = canvas
137
+ this.gl = canvas.getContext('webgl', {
138
+ alpha: true,
139
+ premultipliedAlpha: true,
140
+ antialias: false,
141
+ depth: false,
142
+ preserveDrawingBuffer: false,
143
+ stencil: false,
144
+ desynchronized: true,
145
+ powerPreference: 'high-performance'
146
+ })
147
+
148
+ if (!this.gl) {
149
+ throw new Error('Could not get WebGL context')
150
+ }
151
+
152
+ // Get instanced arrays extension (required for instancing in WebGL1)
153
+ this.instancedArraysExt = this.gl.getExtension('ANGLE_instanced_arrays')
154
+ if (!this.instancedArraysExt) {
155
+ throw new Error('ANGLE_instanced_arrays extension not supported')
156
+ }
157
+
158
+ // Create shaders
159
+ const vertexShader = this.createShader(this.gl.VERTEX_SHADER, VERTEX_SHADER)
160
+ const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, FRAGMENT_SHADER)
161
+
162
+ if (!vertexShader || !fragmentShader) {
163
+ throw new Error('Failed to create shaders')
164
+ }
165
+
166
+ // Create program
167
+ this.program = this.gl.createProgram()
168
+ this.gl.attachShader(this.program, vertexShader)
169
+ this.gl.attachShader(this.program, fragmentShader)
170
+ this.gl.linkProgram(this.program)
171
+
172
+ if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
173
+ const info = this.gl.getProgramInfoLog(this.program)
174
+ throw new Error('Failed to link program: ' + info)
175
+ }
176
+
177
+ // Get uniform locations
178
+ this.u_resolution = this.gl.getUniformLocation(this.program, 'u_resolution')
179
+ this.u_tex = this.gl.getUniformLocation(this.program, 'u_tex')
180
+ this.u_colorMatrix = this.gl.getUniformLocation(this.program, 'u_colorMatrix')
181
+ this.u_texDimensions = this.gl.getUniformLocation(this.program, 'u_texDimensions')
182
+
183
+ // Get attribute locations
184
+ this.a_quadPos = this.gl.getAttribLocation(this.program, 'a_quadPos')
185
+ this.a_destRect = this.gl.getAttribLocation(this.program, 'a_destRect')
186
+ this.a_color = this.gl.getAttribLocation(this.program, 'a_color')
187
+ this.a_texLayer = this.gl.getAttribLocation(this.program, 'a_texLayer')
188
+
189
+ // Create quad position buffer (6 vertices for 2 triangles)
190
+ this.quadPosBuffer = this.gl.createBuffer()
191
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPosBuffer)
192
+ const quadPositions = new Float32Array([
193
+ 0.0, 0.0,
194
+ 1.0, 0.0,
195
+ 0.0, 1.0,
196
+ 1.0, 0.0,
197
+ 1.0, 1.0,
198
+ 0.0, 1.0
199
+ ])
200
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, quadPositions, this.gl.STATIC_DRAW)
201
+
202
+ // Create instance attribute buffers
203
+ this.instanceDestRectBuffer = this.gl.createBuffer()
204
+ this.instanceColorBuffer = this.gl.createBuffer()
205
+ this.instanceTexLayerBuffer = this.gl.createBuffer()
206
+
207
+ // Set up vertex attributes
208
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPosBuffer)
209
+ this.gl.enableVertexAttribArray(this.a_quadPos)
210
+ this.gl.vertexAttribPointer(this.a_quadPos, 2, this.gl.FLOAT, false, 0, 0)
211
+
212
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceDestRectBuffer)
213
+ this.gl.enableVertexAttribArray(this.a_destRect)
214
+ this.gl.vertexAttribPointer(this.a_destRect, 4, this.gl.FLOAT, false, 0, 0)
215
+ this.instancedArraysExt.vertexAttribDivisorANGLE(this.a_destRect, 1)
216
+
217
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceColorBuffer)
218
+ this.gl.enableVertexAttribArray(this.a_color)
219
+ this.gl.vertexAttribPointer(this.a_color, 4, this.gl.FLOAT, false, 0, 0)
220
+ this.instancedArraysExt.vertexAttribDivisorANGLE(this.a_color, 1)
221
+
222
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceTexLayerBuffer)
223
+ this.gl.enableVertexAttribArray(this.a_texLayer)
224
+ this.gl.vertexAttribPointer(this.a_texLayer, 1, this.gl.FLOAT, false, 0, 0)
225
+ this.instancedArraysExt.vertexAttribDivisorANGLE(this.a_texLayer, 1)
226
+
227
+ // Set up blending for premultiplied alpha
228
+ this.gl.enable(this.gl.BLEND)
229
+ this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA)
230
+
231
+ // Use the program
232
+ this.gl.useProgram(this.program)
233
+
234
+ // Set texture unit
235
+ this.gl.uniform1i(this.u_tex, 0)
236
+
237
+ // Set initial color matrix
238
+ this.gl.uniformMatrix3fv(this.u_colorMatrix, false, this.colorMatrix)
239
+
240
+ // Set one-time GL state
241
+ this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, 1)
242
+ this.gl.clearColor(0, 0, 0, 0)
243
+ this.gl.activeTexture(this.gl.TEXTURE0)
244
+ }
245
+
246
+ createShader (type: number, source: string): WebGLShader | null {
247
+ const shader = this.gl!.createShader(type)!
248
+ this.gl!.shaderSource(shader, source)
249
+ this.gl!.compileShader(shader)
250
+
251
+ if (!this.gl!.getShaderParameter(shader, this.gl!.COMPILE_STATUS)) {
252
+ const info = this.gl!.getShaderInfoLog(shader)
253
+ console.log(info)
254
+ this.gl!.deleteShader(shader)
255
+ return null
256
+ }
257
+
258
+ return shader
259
+ }
260
+
261
+ // Set the color matrix for color space conversion.
262
+ // Pass null or undefined to use identity (no conversion).
263
+ setColorMatrix (subtitleColorSpace?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC', videoColorSpace?: 'BT601' | 'BT709') {
264
+ this.colorMatrix = (subtitleColorSpace && videoColorSpace && colorMatrixConversionMap[subtitleColorSpace]?.[videoColorSpace]) ?? IDENTITY_MATRIX
265
+ if (this.gl && this.u_colorMatrix && this.program) {
266
+ this.gl.useProgram(this.program)
267
+ this.gl.uniformMatrix3fv(this.u_colorMatrix, false, this.colorMatrix)
268
+ }
269
+ }
270
+
271
+ createTexture (width: number, height: number): WebGLTexture {
272
+ const texture = this.gl!.createTexture()
273
+ this.gl!.bindTexture(this.gl!.TEXTURE_2D, texture)
274
+
275
+ // Allocate storage for texture (WebGL1 uses LUMINANCE instead of R8)
276
+ this.gl!.texImage2D(
277
+ this.gl!.TEXTURE_2D,
278
+ 0,
279
+ this.gl!.LUMINANCE,
280
+ width,
281
+ height,
282
+ 0,
283
+ this.gl!.LUMINANCE,
284
+ this.gl!.UNSIGNED_BYTE,
285
+ null
286
+ )
287
+
288
+ // Set texture parameters
289
+ this.gl!.texParameteri(this.gl!.TEXTURE_2D, this.gl!.TEXTURE_MIN_FILTER, this.gl!.NEAREST)
290
+ this.gl!.texParameteri(this.gl!.TEXTURE_2D, this.gl!.TEXTURE_MAG_FILTER, this.gl!.NEAREST)
291
+ this.gl!.texParameteri(this.gl!.TEXTURE_2D, this.gl!.TEXTURE_WRAP_S, this.gl!.CLAMP_TO_EDGE)
292
+ this.gl!.texParameteri(this.gl!.TEXTURE_2D, this.gl!.TEXTURE_WRAP_T, this.gl!.CLAMP_TO_EDGE)
293
+
294
+ return texture
295
+ }
296
+
297
+ render (images: ASSImage[], heap: Uint8Array): void {
298
+ if (!this.gl || !this.program || !this.instancedArraysExt) return
299
+
300
+ // we scheduled a resize because changing the canvas size clears it, and we don't want it to flicker
301
+ // so we do it here, right before rendering
302
+ if (this._scheduledResize) {
303
+ const { width, height } = this._scheduledResize
304
+ this._scheduledResize = undefined
305
+ this.canvas!.width = width
306
+ this.canvas!.height = height
307
+
308
+ // Update viewport and resolution uniform
309
+ this.gl.viewport(0, 0, width, height)
310
+ this.gl.uniform2f(this.u_resolution, width, height)
311
+ } else {
312
+ // Clear canvas
313
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT)
314
+ }
315
+
316
+ // Find max dimensions needed and filter valid images
317
+ let maxW = this.textureWidth
318
+ let maxH = this.textureHeight
319
+ const validImages: ASSImage[] = []
320
+
321
+ for (const img of images) {
322
+ if (img.w <= 0 || img.h <= 0) continue
323
+ validImages.push(img)
324
+ if (img.w > maxW) maxW = img.w
325
+ if (img.h > maxH) maxH = img.h
326
+ }
327
+
328
+ if (validImages.length === 0) return
329
+
330
+ // Update texture dimensions if needed
331
+ if (maxW > this.textureWidth || maxH > this.textureHeight) {
332
+ this.textureWidth = maxW
333
+ this.textureHeight = maxH
334
+ // Clear texture cache as we need to recreate textures
335
+ for (const texture of this.textureCache.values()) {
336
+ this.gl.deleteTexture(texture)
337
+ }
338
+ this.textureCache.clear()
339
+ }
340
+
341
+ // Process images individually (WebGL1 limitation: no texture arrays)
342
+ // We'll render them one by one instead of in batches
343
+ for (let i = 0; i < validImages.length; i++) {
344
+ const img = validImages[i]!
345
+
346
+ // Get or create texture for this image
347
+ let texture = this.textureCache.get(i)
348
+ if (!texture) {
349
+ texture = this.createTexture(this.textureWidth, this.textureHeight)
350
+ this.textureCache.set(i, texture)
351
+ }
352
+
353
+ this.gl.bindTexture(this.gl.TEXTURE_2D, texture)
354
+
355
+ // Upload bitmap data to texture
356
+ // WebGL1 doesn't support UNPACK_ROW_LENGTH, so we need to handle strided data manually
357
+ // Strided data - need to copy row by row to remove padding
358
+ const sourceView = new Uint8Array(heap.buffer, img.bitmap, img.stride * img.h)
359
+ const tightData = new Uint8Array(img.w * img.h)
360
+
361
+ for (let y = 0; y < img.h; y++) {
362
+ const srcOffset = y * img.stride
363
+ const dstOffset = y * img.w
364
+ tightData.set(sourceView.subarray(srcOffset, srcOffset + img.w), dstOffset)
365
+ }
366
+
367
+ this.gl.texSubImage2D(
368
+ this.gl.TEXTURE_2D,
369
+ 0,
370
+ 0, 0, // x, y offset
371
+ img.w,
372
+ img.h,
373
+ this.gl.LUMINANCE,
374
+ this.gl.UNSIGNED_BYTE,
375
+ tightData
376
+ )
377
+
378
+ // Fill instance data (single instance)
379
+ this.instanceDestRectData[0] = img.dst_x
380
+ this.instanceDestRectData[1] = img.dst_y
381
+ this.instanceDestRectData[2] = img.w
382
+ this.instanceDestRectData[3] = img.h
383
+
384
+ this.instanceColorData[0] = ((img.color >>> 24) & 0xFF) / 255
385
+ this.instanceColorData[1] = ((img.color >>> 16) & 0xFF) / 255
386
+ this.instanceColorData[2] = ((img.color >>> 8) & 0xFF) / 255
387
+ this.instanceColorData[3] = (img.color & 0xFF) / 255
388
+
389
+ this.instanceTexLayerData[0] = 0 // Not used in WebGL1 version
390
+
391
+ // Upload instance data to buffers
392
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceDestRectBuffer)
393
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.instanceDestRectData.subarray(0, 4), this.gl.DYNAMIC_DRAW)
394
+
395
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceColorBuffer)
396
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.instanceColorData.subarray(0, 4), this.gl.DYNAMIC_DRAW)
397
+
398
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceTexLayerBuffer)
399
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.instanceTexLayerData.subarray(0, 1), this.gl.DYNAMIC_DRAW)
400
+
401
+ // Set texture dimensions uniform
402
+ this.gl.uniform2f(this.u_texDimensions, this.textureWidth, this.textureHeight)
403
+
404
+ // Single instanced draw call
405
+ this.instancedArraysExt.drawArraysInstancedANGLE(this.gl.TRIANGLES, 0, 6, 1)
406
+ }
407
+ }
408
+
409
+ destroy () {
410
+ if (this.gl) {
411
+ // Delete all cached textures
412
+ for (const texture of this.textureCache.values()) {
413
+ this.gl.deleteTexture(texture)
414
+ }
415
+ this.textureCache.clear()
416
+
417
+ if (this.quadPosBuffer) {
418
+ this.gl.deleteBuffer(this.quadPosBuffer)
419
+ this.quadPosBuffer = null
420
+ }
421
+
422
+ if (this.instanceDestRectBuffer) {
423
+ this.gl.deleteBuffer(this.instanceDestRectBuffer)
424
+ this.instanceDestRectBuffer = null
425
+ }
426
+
427
+ if (this.instanceColorBuffer) {
428
+ this.gl.deleteBuffer(this.instanceColorBuffer)
429
+ this.instanceColorBuffer = null
430
+ }
431
+
432
+ if (this.instanceTexLayerBuffer) {
433
+ this.gl.deleteBuffer(this.instanceTexLayerBuffer)
434
+ this.instanceTexLayerBuffer = null
435
+ }
436
+
437
+ if (this.program) {
438
+ this.gl.deleteProgram(this.program)
439
+ this.program = null
440
+ }
441
+
442
+ this.gl = null
443
+ }
444
+ }
445
+ }
@@ -1,4 +1,5 @@
1
- import { IS_FIREFOX, type ASSImage } from './util.ts'
1
+ // fallback for browsers that don't support WebGL2
2
+ import { colorMatrixConversionMap, IDENTITY_MATRIX, IS_FIREFOX, SHOULD_REFERENCE_MEMORY, type ASSImage } from '../util.ts'
2
3
 
3
4
  declare const self: DedicatedWorkerGlobalScope &
4
5
  typeof globalThis & {
@@ -6,70 +7,6 @@ declare const self: DedicatedWorkerGlobalScope &
6
7
  WASMMEMORY: WebAssembly.Memory
7
8
  }
8
9
 
9
- const THREAD_COUNT = !IS_FIREFOX && self.crossOriginIsolated ? Math.min(Math.max(1, navigator.hardwareConcurrency - 2), 8) : 1
10
-
11
- // @ts-expect-error new experimental API
12
- const SUPPORTS_GROWTH = !!WebAssembly.Memory.prototype.toResizableBuffer
13
-
14
- // HACK: 3 memory hacks to support here:
15
- // 1. Chrome WASM Growable memory which can use a reference to the buffer to fix visual artifacts, which happen both with multithreading or without [fastest]
16
- // 2. Chrome WASM non-growable, but mult-threaded only memory which needs to re-create the HEAPU8 on growth because of race conditions [medium]
17
- // 3. Firefox non-growable memory which needs a copy of the data into a non-resizable buffer and can't use a reference [fastest single threaded, but only on Firefox, on Chrome this is slowest]
18
- const SHOULD_REFERENCE_MEMORY = !IS_FIREFOX && (SUPPORTS_GROWTH || THREAD_COUNT > 1)
19
-
20
- const IDENTITY_MATRIX = new Float32Array([
21
- 1, 0, 0,
22
- 0, 1, 0,
23
- 0, 0, 1
24
- ])
25
-
26
- // Color matrix conversion map - mat3x3 for WebGL2
27
- // Each matrix converts FROM the key color space TO the nested key color space
28
- export const colorMatrixConversionMap = {
29
- BT601: {
30
- BT709: new Float32Array([
31
- 1.0863, 0.0965, -0.01411,
32
- -0.0723, 0.8451, -0.0277,
33
- -0.0141, 0.0584, 1.0418
34
- ]),
35
- BT601: IDENTITY_MATRIX
36
- },
37
- BT709: {
38
- BT601: new Float32Array([
39
- 0.9137, 0.0784, 0.0079,
40
- -0.1049, 1.1722, -0.0671,
41
- 0.0096, 0.0322, 0.9582
42
- ]),
43
- BT709: IDENTITY_MATRIX
44
- },
45
- FCC: {
46
- BT709: new Float32Array([
47
- 1.0873, -0.0736, -0.0137,
48
- 0.0974, 0.8494, 0.0531,
49
- -0.0127, -0.0251, 1.0378
50
- ]),
51
- BT601: new Float32Array([
52
- 1.001, -0.0008, -0.0002,
53
- 0.0009, 1.005, -0.006,
54
- 0.0013, 0.0027, 0.996
55
- ])
56
- },
57
- SMPTE240M: {
58
- BT709: new Float32Array([
59
- 0.9993, 0.0006, 0.0001,
60
- -0.0004, 0.9812, 0.0192,
61
- -0.0034, -0.0114, 1.0148
62
- ]),
63
- BT601: new Float32Array([
64
- 0.913, 0.0774, 0.0096,
65
- -0.1051, 1.1508, -0.0456,
66
- 0.0063, 0.0207, 0.973
67
- ])
68
- }
69
- } as const
70
-
71
- export type ColorSpace = keyof typeof colorMatrixConversionMap
72
-
73
10
  // GLSL ES 3.0 Vertex Shader with Instancing
74
11
  const VERTEX_SHADER = /* glsl */`#version 300 es
75
12
  precision mediump float;
@@ -231,7 +168,7 @@ export class WebGL2Renderer {
231
168
  }
232
169
 
233
170
  // Create program
234
- this.program = this.gl.createProgram()!
171
+ this.program = this.gl.createProgram()
235
172
  this.gl.attachShader(this.program, vertexShader)
236
173
  this.gl.attachShader(this.program, fragmentShader)
237
174
  this.gl.linkProgram(this.program)
@@ -2,7 +2,7 @@
2
2
  // This has been deprecated as WebGL is simply faster
3
3
  // Know how to optimise this to beat WebGL? submit a PR!
4
4
 
5
- import type { ASSImage } from '../jassub'
5
+ import type { ASSImage } from '../util.ts'
6
6
 
7
7
  const IDENTITY_MATRIX = new Float32Array([
8
8
  1, 0, 0, 0,