jassub 2.0.12 → 2.0.15

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.
@@ -177,123 +177,122 @@ export class WebGPURenderer {
177
177
 
178
178
  // eslint-disable-next-line no-undef
179
179
  format: GPUTextureFormat = 'bgra8unorm'
180
- readonly initPromise: Promise<void> | null = null
180
+ _ready
181
181
 
182
182
  constructor () {
183
183
  // Start async initialization immediately
184
- this.initPromise = this._initDevice()
185
- }
186
-
187
- async _initDevice (): Promise<void> {
184
+ this._ready = (async () => {
188
185
  // Check WebGPU support
189
- if (!navigator.gpu) {
190
- throw new Error('WebGPU not supported')
191
- }
186
+ if (!navigator.gpu) {
187
+ throw new Error('WebGPU not supported')
188
+ }
192
189
 
193
- const adapter = await navigator.gpu.requestAdapter({
194
- powerPreference: 'high-performance'
195
- })
190
+ const adapter = await navigator.gpu.requestAdapter({
191
+ powerPreference: 'high-performance'
192
+ })
196
193
 
197
- if (!adapter) {
198
- throw new Error('No WebGPU adapter found')
199
- }
194
+ if (!adapter) {
195
+ throw new Error('No WebGPU adapter found')
196
+ }
200
197
 
201
- this.device = await adapter.requestDevice()
202
- this.format = navigator.gpu.getPreferredCanvasFormat()
198
+ this.device = await adapter.requestDevice()
199
+ this.format = navigator.gpu.getPreferredCanvasFormat()
203
200
 
204
- // Create shader modules
205
- const vertexModule = this.device.createShaderModule({
206
- code: VERTEX_SHADER
207
- })
201
+ // Create shader modules
202
+ const vertexModule = this.device.createShaderModule({
203
+ code: VERTEX_SHADER
204
+ })
208
205
 
209
- const fragmentModule = this.device.createShaderModule({
210
- code: FRAGMENT_SHADER
211
- })
206
+ const fragmentModule = this.device.createShaderModule({
207
+ code: FRAGMENT_SHADER
208
+ })
212
209
 
213
- // Create uniform buffer
214
- this.uniformBuffer = this.device.createBuffer({
215
- size: 16, // vec2f resolution + padding
216
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
217
- })
210
+ // Create uniform buffer
211
+ this.uniformBuffer = this.device.createBuffer({
212
+ size: 16, // vec2f resolution + padding
213
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
214
+ })
218
215
 
219
- // Create color matrix buffer (mat3x3f requires 48 bytes: 3 vec3f padded to vec4f each)
220
- this.colorMatrixBuffer = this.device.createBuffer({
221
- size: 48, // 3 x vec4f (each column is vec3f padded to 16 bytes)
222
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
223
- })
224
- // Initialize with identity matrix
225
- this.setColorMatrix()
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)
226
223
 
227
- // Create bind group layout (no sampler needed - using textureLoad for pixel-perfect sampling)
228
- this.bindGroupLayout = this.device.createBindGroupLayout({
229
- entries: [
230
- {
231
- binding: 0,
232
- visibility: GPUShaderStage.VERTEX,
233
- buffer: { type: 'uniform' }
234
- },
235
- {
236
- binding: 1,
237
- visibility: GPUShaderStage.VERTEX,
238
- buffer: { type: 'read-only-storage' }
239
- },
240
- {
241
- binding: 3,
242
- visibility: GPUShaderStage.FRAGMENT,
243
- texture: { sampleType: 'unfilterable-float' } // textureLoad requires unfilterable
244
- },
245
- {
246
- binding: 4,
247
- visibility: GPUShaderStage.FRAGMENT,
248
- buffer: { type: 'uniform' }
249
- }
250
- ]
251
- })
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
+ },
237
+ {
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
+ })
252
249
 
253
- // Create pipeline layout
254
- const pipelineLayout = this.device.createPipelineLayout({
255
- bindGroupLayouts: [this.bindGroupLayout]
256
- })
250
+ // Create pipeline layout
251
+ const pipelineLayout = this.device.createPipelineLayout({
252
+ bindGroupLayouts: [this.bindGroupLayout]
253
+ })
257
254
 
258
- // Create render pipeline
259
- this.pipeline = this.device.createRenderPipeline({
260
- layout: pipelineLayout,
261
- vertex: {
262
- module: vertexModule,
263
- entryPoint: 'vertexMain'
264
- },
265
- fragment: {
266
- module: fragmentModule,
267
- entryPoint: 'fragmentMain',
268
- targets: [
269
- {
270
- format: this.format,
271
- blend: {
272
- color: {
273
- srcFactor: 'one',
274
- dstFactor: 'one-minus-src-alpha',
275
- operation: 'add'
276
- },
277
- alpha: {
278
- srcFactor: 'one',
279
- dstFactor: 'one-minus-src-alpha',
280
- operation: 'add'
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
+ }
281
279
  }
282
280
  }
283
- }
284
- ]
285
- },
286
- primitive: {
287
- topology: 'triangle-list'
288
- }
289
- })
281
+ ]
282
+ },
283
+ primitive: {
284
+ topology: 'triangle-list'
285
+ }
286
+ })
287
+ })()
290
288
  }
291
289
 
292
290
  async setCanvas (canvas: OffscreenCanvas, width: number, height: number) {
293
- await this.initPromise
294
- if (!this.device) {
295
- throw new Error('WebGPU device not initialized. Did you await the constructor promise?')
296
- }
291
+ await this._ready
292
+ if (!this.device) return
293
+
294
+ // WebGPU doesn't allow 0-sized textures/swapchains
295
+ if (width <= 0 || height <= 0) return
297
296
 
298
297
  canvas.width = width
299
298
  canvas.height = height
@@ -325,9 +324,10 @@ export class WebGPURenderer {
325
324
  * Pass null or undefined to use identity (no conversion).
326
325
  * Matrix should be a pre-padded Float32Array with 12 values (3 columns × 4 floats each).
327
326
  */
328
- setColorMatrix (matrix?: Float32Array<ArrayBuffer>): void {
329
- if (!this.device || !this.colorMatrixBuffer) return
330
- this.device.queue.writeBuffer(this.colorMatrixBuffer, 0, matrix ?? IDENTITY_MATRIX)
327
+ async setColorMatrix (matrix?: Float32Array<ArrayBuffer>) {
328
+ await this._ready
329
+ if (!this.device) return
330
+ this.device.queue.writeBuffer(this.colorMatrixBuffer!, 0, matrix ?? IDENTITY_MATRIX)
331
331
  }
332
332
 
333
333
  private createTextureInfo (width: number, height: number): TextureInfo {
@@ -348,9 +348,13 @@ export class WebGPURenderer {
348
348
  render (images: ASSImage[], heap: Uint8Array): void {
349
349
  if (!this.device || !this.context || !this.pipeline) return
350
350
 
351
+ // getCurrentTexture fails if canvas has 0 dimensions
352
+ const currentTexture = this.context.getCurrentTexture()
353
+ if (currentTexture.width === 0 || currentTexture.height === 0) return
354
+
351
355
  const commandEncoder = this.device.createCommandEncoder()
352
356
 
353
- const textureView = this.context.getCurrentTexture().createView()
357
+ const textureView = currentTexture.createView()
354
358
 
355
359
  // Begin render pass
356
360
  const renderPass = commandEncoder.beginRenderPass({
@@ -378,13 +382,13 @@ export class WebGPURenderer {
378
382
  }
379
383
 
380
384
  // Render each image
381
- for (let i = 0, texIndex = 0; i < images.length; i++, texIndex++) {
385
+ for (let i = 0, texIndex = -1; i < images.length; i++) {
382
386
  const img = images[i]!
383
387
 
384
388
  // Skip images with invalid dimensions (WebGPU doesn't allow 0-sized textures)
385
389
  if (img.w <= 0 || img.h <= 0) continue
386
390
 
387
- let texInfo = this.textures[texIndex]!
391
+ let texInfo = this.textures[++texIndex]!
388
392
 
389
393
  // Recreate texture if size changed (use actual w, not stride)
390
394
  if (texInfo.width !== img.w || texInfo.height !== img.h) {
@@ -456,28 +460,7 @@ export class WebGPURenderer {
456
460
  this.pendingDestroyTextures = []
457
461
  }
458
462
 
459
- clear (): void {
460
- if (!this.device || !this.context) return
461
-
462
- const commandEncoder = this.device.createCommandEncoder()
463
- const textureView = this.context.getCurrentTexture().createView()
464
-
465
- const renderPass = commandEncoder.beginRenderPass({
466
- colorAttachments: [
467
- {
468
- view: textureView,
469
- clearValue: { r: 0, g: 0, b: 0, a: 0 },
470
- loadOp: 'clear',
471
- storeOp: 'store'
472
- }
473
- ]
474
- })
475
-
476
- renderPass.end()
477
- this.device.queue.submit([commandEncoder.finish()])
478
- }
479
-
480
- destroy (): void {
463
+ destroy () {
481
464
  for (const tex of this.textures) {
482
465
  tex.texture.destroy()
483
466
  }
@@ -28,7 +28,6 @@ interface opts {
28
28
  libassMemoryLimit: number
29
29
  libassGlyphLimit: number
30
30
  useLocalFonts: boolean
31
- supportsSIMD: boolean
32
31
  }
33
32
 
34
33
  export class ASSRenderer {
@@ -68,7 +67,7 @@ export class ASSRenderer {
68
67
  }
69
68
  addEventListener('message', handleMessage)
70
69
 
71
- this._ready = (WASM({ __supportsSIMD: data.supportsSIMD, __url: data.wasmUrl }) as Promise<MainModule>).then(Module => {
70
+ this._ready = (WASM({ __url: data.wasmUrl }) as Promise<MainModule>).then(Module => {
72
71
  // eslint-disable-next-line @typescript-eslint/unbound-method
73
72
  this._malloc = Module._malloc
74
73