jassub 2.0.19 → 2.1.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 +26 -8
- package/dist/debug.js +2 -2
- package/dist/debug.js.map +1 -1
- package/dist/jassub.d.ts +1 -1
- package/dist/jassub.js +1 -1
- package/dist/jassub.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 +48 -0
- package/dist/worker/webgl-renderer.js +290 -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 +84 -99
- package/dist/worker/webgpu-renderer.js.map +1 -1
- package/dist/worker/worker.d.ts +3 -2
- package/dist/worker/worker.js +18 -8
- package/dist/worker/worker.js.map +1 -1
- package/package.json +3 -2
- package/src/debug.ts +2 -2
- package/src/jassub.ts +1 -1
- package/src/wasm/types.d.ts +1 -5
- package/src/worker/webgl-renderer.ts +383 -0
- package/src/worker/webgpu-renderer.ts +85 -103
- package/src/worker/worker.ts +21 -10
|
@@ -177,118 +177,100 @@ export class WebGPURenderer {
|
|
|
177
177
|
|
|
178
178
|
// eslint-disable-next-line no-undef
|
|
179
179
|
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
180
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
181
|
+
constructor (device: GPUDevice) {
|
|
182
|
+
this.device = device
|
|
183
|
+
this.format = navigator.gpu.getPreferredCanvasFormat()
|
|
193
184
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
185
|
+
// Create shader modules
|
|
186
|
+
const vertexModule = this.device.createShaderModule({
|
|
187
|
+
code: VERTEX_SHADER
|
|
188
|
+
})
|
|
197
189
|
|
|
198
|
-
|
|
199
|
-
|
|
190
|
+
const fragmentModule = this.device.createShaderModule({
|
|
191
|
+
code: FRAGMENT_SHADER
|
|
192
|
+
})
|
|
200
193
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
194
|
+
// Create uniform buffer
|
|
195
|
+
this.uniformBuffer = this.device.createBuffer({
|
|
196
|
+
size: 16, // vec2f resolution + padding
|
|
197
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
198
|
+
})
|
|
205
199
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
200
|
+
// Create color matrix buffer (mat3x3f requires 48 bytes: 3 vec3f padded to vec4f each)
|
|
201
|
+
this.colorMatrixBuffer = this.device.createBuffer({
|
|
202
|
+
size: 48, // 3 x vec4f (each column is vec3f padded to 16 bytes)
|
|
203
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
204
|
+
})
|
|
205
|
+
// Initialize with identity matrix
|
|
206
|
+
this.device.queue.writeBuffer(this.colorMatrixBuffer, 0, IDENTITY_MATRIX)
|
|
209
207
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
208
|
+
// Create bind group layout (no sampler needed - using textureLoad for pixel-perfect sampling)
|
|
209
|
+
this.bindGroupLayout = this.device.createBindGroupLayout({
|
|
210
|
+
entries: [
|
|
211
|
+
{
|
|
212
|
+
binding: 0,
|
|
213
|
+
visibility: GPUShaderStage.VERTEX,
|
|
214
|
+
buffer: { type: 'uniform' }
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
binding: 1,
|
|
218
|
+
visibility: GPUShaderStage.VERTEX,
|
|
219
|
+
buffer: { type: 'read-only-storage' }
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
binding: 3,
|
|
223
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
224
|
+
texture: { sampleType: 'unfilterable-float' } // textureLoad requires unfilterable
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
binding: 4,
|
|
228
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
229
|
+
buffer: { type: 'uniform' }
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
})
|
|
215
233
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
})
|
|
221
|
-
// Initialize with identity matrix
|
|
222
|
-
this.device.queue.writeBuffer(this.colorMatrixBuffer, 0, IDENTITY_MATRIX)
|
|
234
|
+
// Create pipeline layout
|
|
235
|
+
const pipelineLayout = this.device.createPipelineLayout({
|
|
236
|
+
bindGroupLayouts: [this.bindGroupLayout]
|
|
237
|
+
})
|
|
223
238
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
buffer: { type: 'read-only-storage' }
|
|
236
|
-
},
|
|
239
|
+
// Create render pipeline
|
|
240
|
+
this.pipeline = this.device.createRenderPipeline({
|
|
241
|
+
layout: pipelineLayout,
|
|
242
|
+
vertex: {
|
|
243
|
+
module: vertexModule,
|
|
244
|
+
entryPoint: 'vertexMain'
|
|
245
|
+
},
|
|
246
|
+
fragment: {
|
|
247
|
+
module: fragmentModule,
|
|
248
|
+
entryPoint: 'fragmentMain',
|
|
249
|
+
targets: [
|
|
237
250
|
{
|
|
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
|
-
}
|
|
251
|
+
format: this.format,
|
|
252
|
+
blend: {
|
|
253
|
+
color: {
|
|
254
|
+
srcFactor: 'one',
|
|
255
|
+
dstFactor: 'one-minus-src-alpha',
|
|
256
|
+
operation: 'add'
|
|
257
|
+
},
|
|
258
|
+
alpha: {
|
|
259
|
+
srcFactor: 'one',
|
|
260
|
+
dstFactor: 'one-minus-src-alpha',
|
|
261
|
+
operation: 'add'
|
|
279
262
|
}
|
|
280
263
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
})
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
primitive: {
|
|
268
|
+
topology: 'triangle-list'
|
|
269
|
+
}
|
|
270
|
+
})
|
|
288
271
|
}
|
|
289
272
|
|
|
290
|
-
|
|
291
|
-
await this._ready
|
|
273
|
+
setCanvas (canvas: OffscreenCanvas, width: number, height: number) {
|
|
292
274
|
if (!this.device) return
|
|
293
275
|
|
|
294
276
|
// WebGPU doesn't allow 0-sized textures/swapchains
|
|
@@ -324,13 +306,13 @@ export class WebGPURenderer {
|
|
|
324
306
|
* Pass null or undefined to use identity (no conversion).
|
|
325
307
|
* Matrix should be a pre-padded Float32Array with 12 values (3 columns × 4 floats each).
|
|
326
308
|
*/
|
|
327
|
-
|
|
328
|
-
await this._ready
|
|
309
|
+
setColorMatrix (subtitleColorSpace?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC', videoColorSpace?: 'BT601' | 'BT709') {
|
|
329
310
|
if (!this.device) return
|
|
330
|
-
|
|
311
|
+
const colorMatrix = (subtitleColorSpace && videoColorSpace && colorMatrixConversionMap[subtitleColorSpace]?.[videoColorSpace]) ?? IDENTITY_MATRIX
|
|
312
|
+
this.device.queue.writeBuffer(this.colorMatrixBuffer!, 0, colorMatrix)
|
|
331
313
|
}
|
|
332
314
|
|
|
333
|
-
|
|
315
|
+
createTextureInfo (width: number, height: number): TextureInfo {
|
|
334
316
|
const texture = this.device!.createTexture({
|
|
335
317
|
size: [width, height],
|
|
336
318
|
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
|
|
41
|
+
_gpurender?: WebGL2Renderer | WebGPURenderer
|
|
40
42
|
|
|
41
43
|
debug = false
|
|
42
44
|
useLocalFonts = false
|
|
@@ -58,16 +60,21 @@ 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
|
|
63
66
|
this._offCanvas = data.ctrl
|
|
64
|
-
this._gpurender
|
|
67
|
+
this._gpurender!.setCanvas(this._offCanvas!, this._offCanvas!.width, this._offCanvas!.height)
|
|
65
68
|
removeEventListener('message', handleMessage)
|
|
66
69
|
}
|
|
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
|
|
189
|
+
this._gpurender!.setColorMatrix(this._subtitleColorSpace, this._videoColorSpace)
|
|
180
190
|
}
|
|
181
191
|
|
|
182
192
|
_findAvailableFonts (font: string) {
|
|
@@ -228,14 +238,15 @@ 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
|
-
this._gpurender
|
|
249
|
+
this._gpurender!.destroy()
|
|
239
250
|
// @ts-expect-error force GC
|
|
240
251
|
this._wasm = null
|
|
241
252
|
// @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
|