jassub 2.0.20 → 2.2.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.
@@ -1,3 +1,7 @@
1
+ // WARN:
2
+ // This has been deprecated as WebGL is simply faster
3
+ // Know how to optimise this to beat WebGL? submit a PR!
4
+
1
5
  import type { ASSImage } from '../jassub'
2
6
 
3
7
  const IDENTITY_MATRIX = new Float32Array([
@@ -177,118 +181,100 @@ export class WebGPURenderer {
177
181
 
178
182
  // eslint-disable-next-line no-undef
179
183
  format: GPUTextureFormat = 'bgra8unorm'
180
- _ready
181
-
182
- constructor () {
183
- // Start async initialization immediately
184
- this._ready = (async () => {
185
- // Check WebGPU support
186
- if (!navigator.gpu) {
187
- throw new Error('WebGPU not supported')
188
- }
189
184
 
190
- const adapter = await navigator.gpu.requestAdapter({
191
- powerPreference: 'high-performance'
192
- })
185
+ constructor (device: GPUDevice) {
186
+ this.device = device
187
+ this.format = navigator.gpu.getPreferredCanvasFormat()
193
188
 
194
- if (!adapter) {
195
- throw new Error('No WebGPU adapter found')
196
- }
189
+ // Create shader modules
190
+ const vertexModule = this.device.createShaderModule({
191
+ code: VERTEX_SHADER
192
+ })
197
193
 
198
- this.device = await adapter.requestDevice()
199
- this.format = navigator.gpu.getPreferredCanvasFormat()
194
+ const fragmentModule = this.device.createShaderModule({
195
+ code: FRAGMENT_SHADER
196
+ })
200
197
 
201
- // Create shader modules
202
- const vertexModule = this.device.createShaderModule({
203
- code: VERTEX_SHADER
204
- })
198
+ // Create uniform buffer
199
+ this.uniformBuffer = this.device.createBuffer({
200
+ size: 16, // vec2f resolution + padding
201
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
202
+ })
205
203
 
206
- const fragmentModule = this.device.createShaderModule({
207
- code: FRAGMENT_SHADER
208
- })
204
+ // Create color matrix buffer (mat3x3f requires 48 bytes: 3 vec3f padded to vec4f each)
205
+ this.colorMatrixBuffer = this.device.createBuffer({
206
+ size: 48, // 3 x vec4f (each column is vec3f padded to 16 bytes)
207
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
208
+ })
209
+ // Initialize with identity matrix
210
+ this.device.queue.writeBuffer(this.colorMatrixBuffer, 0, IDENTITY_MATRIX)
209
211
 
210
- // Create uniform buffer
211
- this.uniformBuffer = this.device.createBuffer({
212
- size: 16, // vec2f resolution + padding
213
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
214
- })
212
+ // Create bind group layout (no sampler needed - using textureLoad for pixel-perfect sampling)
213
+ this.bindGroupLayout = this.device.createBindGroupLayout({
214
+ entries: [
215
+ {
216
+ binding: 0,
217
+ visibility: GPUShaderStage.VERTEX,
218
+ buffer: { type: 'uniform' }
219
+ },
220
+ {
221
+ binding: 1,
222
+ visibility: GPUShaderStage.VERTEX,
223
+ buffer: { type: 'read-only-storage' }
224
+ },
225
+ {
226
+ binding: 3,
227
+ visibility: GPUShaderStage.FRAGMENT,
228
+ texture: { sampleType: 'unfilterable-float' } // textureLoad requires unfilterable
229
+ },
230
+ {
231
+ binding: 4,
232
+ visibility: GPUShaderStage.FRAGMENT,
233
+ buffer: { type: 'uniform' }
234
+ }
235
+ ]
236
+ })
215
237
 
216
- // Create color matrix buffer (mat3x3f requires 48 bytes: 3 vec3f padded to vec4f each)
217
- this.colorMatrixBuffer = this.device.createBuffer({
218
- size: 48, // 3 x vec4f (each column is vec3f padded to 16 bytes)
219
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
220
- })
221
- // Initialize with identity matrix
222
- this.device.queue.writeBuffer(this.colorMatrixBuffer, 0, IDENTITY_MATRIX)
238
+ // Create pipeline layout
239
+ const pipelineLayout = this.device.createPipelineLayout({
240
+ bindGroupLayouts: [this.bindGroupLayout]
241
+ })
223
242
 
224
- // Create bind group layout (no sampler needed - using textureLoad for pixel-perfect sampling)
225
- this.bindGroupLayout = this.device.createBindGroupLayout({
226
- entries: [
227
- {
228
- binding: 0,
229
- visibility: GPUShaderStage.VERTEX,
230
- buffer: { type: 'uniform' }
231
- },
232
- {
233
- binding: 1,
234
- visibility: GPUShaderStage.VERTEX,
235
- buffer: { type: 'read-only-storage' }
236
- },
243
+ // Create render pipeline
244
+ this.pipeline = this.device.createRenderPipeline({
245
+ layout: pipelineLayout,
246
+ vertex: {
247
+ module: vertexModule,
248
+ entryPoint: 'vertexMain'
249
+ },
250
+ fragment: {
251
+ module: fragmentModule,
252
+ entryPoint: 'fragmentMain',
253
+ targets: [
237
254
  {
238
- binding: 3,
239
- visibility: GPUShaderStage.FRAGMENT,
240
- texture: { sampleType: 'unfilterable-float' } // textureLoad requires unfilterable
241
- },
242
- {
243
- binding: 4,
244
- visibility: GPUShaderStage.FRAGMENT,
245
- buffer: { type: 'uniform' }
246
- }
247
- ]
248
- })
249
-
250
- // Create pipeline layout
251
- const pipelineLayout = this.device.createPipelineLayout({
252
- bindGroupLayouts: [this.bindGroupLayout]
253
- })
254
-
255
- // Create render pipeline
256
- this.pipeline = this.device.createRenderPipeline({
257
- layout: pipelineLayout,
258
- vertex: {
259
- module: vertexModule,
260
- entryPoint: 'vertexMain'
261
- },
262
- fragment: {
263
- module: fragmentModule,
264
- entryPoint: 'fragmentMain',
265
- targets: [
266
- {
267
- format: this.format,
268
- blend: {
269
- color: {
270
- srcFactor: 'one',
271
- dstFactor: 'one-minus-src-alpha',
272
- operation: 'add'
273
- },
274
- alpha: {
275
- srcFactor: 'one',
276
- dstFactor: 'one-minus-src-alpha',
277
- operation: 'add'
278
- }
255
+ format: this.format,
256
+ blend: {
257
+ color: {
258
+ srcFactor: 'one',
259
+ dstFactor: 'one-minus-src-alpha',
260
+ operation: 'add'
261
+ },
262
+ alpha: {
263
+ srcFactor: 'one',
264
+ dstFactor: 'one-minus-src-alpha',
265
+ operation: 'add'
279
266
  }
280
267
  }
281
- ]
282
- },
283
- primitive: {
284
- topology: 'triangle-list'
285
- }
286
- })
287
- })()
268
+ }
269
+ ]
270
+ },
271
+ primitive: {
272
+ topology: 'triangle-list'
273
+ }
274
+ })
288
275
  }
289
276
 
290
- async setCanvas (canvas: OffscreenCanvas, width: number, height: number) {
291
- await this._ready
277
+ setCanvas (canvas: OffscreenCanvas, width: number, height: number) {
292
278
  if (!this.device) return
293
279
 
294
280
  // WebGPU doesn't allow 0-sized textures/swapchains
@@ -324,13 +310,13 @@ export class WebGPURenderer {
324
310
  * Pass null or undefined to use identity (no conversion).
325
311
  * Matrix should be a pre-padded Float32Array with 12 values (3 columns × 4 floats each).
326
312
  */
327
- async setColorMatrix (matrix?: Float32Array<ArrayBuffer>) {
328
- await this._ready
313
+ setColorMatrix (subtitleColorSpace?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC', videoColorSpace?: 'BT601' | 'BT709') {
329
314
  if (!this.device) return
330
- this.device.queue.writeBuffer(this.colorMatrixBuffer!, 0, matrix ?? IDENTITY_MATRIX)
315
+ const colorMatrix = (subtitleColorSpace && videoColorSpace && colorMatrixConversionMap[subtitleColorSpace]?.[videoColorSpace]) ?? IDENTITY_MATRIX
316
+ this.device.queue.writeBuffer(this.colorMatrixBuffer!, 0, colorMatrix)
331
317
  }
332
318
 
333
- private createTextureInfo (width: number, height: number): TextureInfo {
319
+ createTextureInfo (width: number, height: number): TextureInfo {
334
320
  const texture = this.device!.createTexture({
335
321
  size: [width, height],
336
322
  format: 'r8unorm',
@@ -5,7 +5,8 @@ import { expose } from 'abslink/w3c'
5
5
  import WASM from '../wasm/jassub-worker.js'
6
6
 
7
7
  import { libassYCbCrMap, read_, readAsync, _applyKeys } from './util'
8
- import { colorMatrixConversionMap, WebGPURenderer } from './webgpu-renderer'
8
+ import { WebGL2Renderer } from './webgl-renderer'
9
+ // import { WebGPURenderer } from './webgpu-renderer'
9
10
 
10
11
  import type { ASSEvent, ASSImage, ASSStyle } from '../jassub'
11
12
  import type { JASSUB, MainModule } from '../wasm/types.js'
@@ -13,6 +14,7 @@ import type { JASSUB, MainModule } from '../wasm/types.js'
13
14
  declare const self: DedicatedWorkerGlobalScope &
14
15
  typeof globalThis & {
15
16
  HEAPU8RAW: Uint8Array<ArrayBuffer>
17
+ WASMMEMORY: WebAssembly.Memory
16
18
  }
17
19
 
18
20
  interface opts {
@@ -36,7 +38,7 @@ export class ASSRenderer {
36
38
  _subtitleColorSpace?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC' | null
37
39
  _videoColorSpace?: 'BT709' | 'BT601'
38
40
  _malloc!: (size: number) => number
39
- _gpurender = new WebGPURenderer()
41
+ _gpurender = new WebGL2Renderer()
40
42
 
41
43
  debug = false
42
44
  useLocalFonts = false
@@ -58,8 +60,9 @@ export class ASSRenderer {
58
60
  globalThis.fetch = _ => _fetch(data.wasmUrl)
59
61
 
60
62
  // TODO: abslink doesnt support transferables yet
61
- const handleMessage = ({ data }: MessageEvent) => {
63
+ const handleMessage = async ({ data }: MessageEvent) => {
62
64
  if (data.name === 'offscreenCanvas') {
65
+ // await this._ready // needed for webGPU
63
66
  this._offCanvas = data.ctrl
64
67
  this._gpurender.setCanvas(this._offCanvas!, this._offCanvas!.width, this._offCanvas!.height)
65
68
  removeEventListener('message', handleMessage)
@@ -67,7 +70,11 @@ export class ASSRenderer {
67
70
  }
68
71
  addEventListener('message', handleMessage)
69
72
 
70
- this._ready = (WASM({ __url: data.wasmUrl }) as Promise<MainModule>).then(Module => {
73
+ // const devicePromise = navigator.gpu?.requestAdapter({
74
+ // powerPreference: 'high-performance'
75
+ // }).then(adapter => adapter?.requestDevice())
76
+
77
+ this._ready = (WASM({ __url: data.wasmUrl }) as Promise<MainModule>).then(async Module => {
71
78
  // eslint-disable-next-line @typescript-eslint/unbound-method
72
79
  this._malloc = Module._malloc
73
80
 
@@ -89,6 +96,9 @@ export class ASSRenderer {
89
96
  if (data.libassMemoryLimit > 0 || data.libassGlyphLimit > 0) {
90
97
  this._wasm.setMemoryLimits(data.libassGlyphLimit || 0, data.libassMemoryLimit || 0)
91
98
  }
99
+ // const device = await devicePromise
100
+ // this._gpurender = device ? new WebGPURenderer(device) : new WebGL2Renderer()
101
+ // if (this._offCanvas) this._gpurender.setCanvas(this._offCanvas, this._offCanvas.width, this._offCanvas.height)
92
102
  this._checkColorSpace()
93
103
  })
94
104
  }
@@ -176,7 +186,7 @@ export class ASSRenderer {
176
186
 
177
187
  _checkColorSpace () {
178
188
  if (!this._subtitleColorSpace || !this._videoColorSpace) return
179
- this._gpurender.setColorMatrix(colorMatrixConversionMap[this._subtitleColorSpace][this._videoColorSpace])
189
+ this._gpurender.setColorMatrix(this._subtitleColorSpace, this._videoColorSpace)
180
190
  }
181
191
 
182
192
  _findAvailableFonts (font: string) {
@@ -228,12 +238,13 @@ export class ASSRenderer {
228
238
  }
229
239
 
230
240
  _canvas (width: number, height: number, videoWidth: number, videoHeight: number) {
231
- if (this._offCanvas) this._gpurender.setCanvas(this._offCanvas, width, height)
241
+ if (this._offCanvas && this._gpurender) this._gpurender.setCanvas(this._offCanvas, width, height)
232
242
 
233
243
  this._wasm.resizeCanvas(width, height, videoWidth, videoHeight)
234
244
  }
235
245
 
236
- [finalizer] () {
246
+ async [finalizer] () {
247
+ await this._ready
237
248
  this._wasm.quitLibrary()
238
249
  this._gpurender.destroy()
239
250
  // @ts-expect-error force GC
@@ -244,7 +255,7 @@ export class ASSRenderer {
244
255
  }
245
256
 
246
257
  _draw (time: number, force = false) {
247
- if (!this._offCanvas) return
258
+ if (!this._offCanvas || !this._gpurender) return
248
259
 
249
260
  const result: ASSImage = this._wasm.rawRender(time, Number(force))!
250
261
  if (this._wasm.changed === 0 && !force) return