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.
- package/README.md +31 -11
- package/dist/debug.js +1 -1
- package/dist/debug.js.map +1 -1
- package/dist/wasm/jassub-worker-modern.wasm +0 -0
- package/dist/wasm/jassub-worker.js +1 -1
- package/dist/wasm/jassub-worker.js.map +1 -1
- package/dist/wasm/jassub-worker.wasm +0 -0
- package/dist/wasm/types.d.ts +1 -5
- package/dist/worker/webgl-renderer.d.ts +45 -0
- package/dist/worker/webgl-renderer.js +328 -0
- package/dist/worker/webgl-renderer.js.map +1 -0
- package/dist/worker/webgpu-renderer.d.ts +4 -5
- package/dist/worker/webgpu-renderer.js +87 -99
- package/dist/worker/webgpu-renderer.js.map +1 -1
- package/dist/worker/worker.d.ts +3 -3
- package/dist/worker/worker.js +16 -8
- package/dist/worker/worker.js.map +1 -1
- package/package.json +3 -2
- package/src/debug.ts +1 -1
- package/src/wasm/types.d.ts +1 -5
- package/src/worker/webgl-renderer.ts +412 -0
- package/src/worker/webgpu-renderer.ts +89 -103
- package/src/worker/worker.ts +19 -8
|
@@ -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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
185
|
+
constructor (device: GPUDevice) {
|
|
186
|
+
this.device = device
|
|
187
|
+
this.format = navigator.gpu.getPreferredCanvasFormat()
|
|
193
188
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
189
|
+
// Create shader modules
|
|
190
|
+
const vertexModule = this.device.createShaderModule({
|
|
191
|
+
code: VERTEX_SHADER
|
|
192
|
+
})
|
|
197
193
|
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
const fragmentModule = this.device.createShaderModule({
|
|
195
|
+
code: FRAGMENT_SHADER
|
|
196
|
+
})
|
|
200
197
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
})
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
primitive: {
|
|
272
|
+
topology: 'triangle-list'
|
|
273
|
+
}
|
|
274
|
+
})
|
|
288
275
|
}
|
|
289
276
|
|
|
290
|
-
|
|
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
|
-
|
|
328
|
-
await this._ready
|
|
313
|
+
setColorMatrix (subtitleColorSpace?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC', videoColorSpace?: 'BT601' | 'BT709') {
|
|
329
314
|
if (!this.device) return
|
|
330
|
-
|
|
315
|
+
const colorMatrix = (subtitleColorSpace && videoColorSpace && colorMatrixConversionMap[subtitleColorSpace]?.[videoColorSpace]) ?? IDENTITY_MATRIX
|
|
316
|
+
this.device.queue.writeBuffer(this.colorMatrixBuffer!, 0, colorMatrix)
|
|
331
317
|
}
|
|
332
318
|
|
|
333
|
-
|
|
319
|
+
createTextureInfo (width: number, height: number): TextureInfo {
|
|
334
320
|
const texture = this.device!.createTexture({
|
|
335
321
|
size: [width, height],
|
|
336
322
|
format: 'r8unorm',
|
package/src/worker/worker.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|