@bloopjs/toodle 0.1.4 → 0.1.6

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 +1 @@
1
- {"version":3,"file":"AssetManager.d.ts","sourceRoot":"","sources":["../../src/textures/AssetManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AssetManager.d.ts","sourceRoot":"","sources":["../../src/textures/AssetManager.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,13 @@
1
1
  import type { CpuTextureAtlas, TextureWithMetadata } from "./types";
2
2
  export declare function getBitmapFromUrl(url: URL): Promise<ImageBitmap>;
3
3
  export declare function packBitmapsToAtlas(images: Map<string, TextureWithMetadata>, textureSize: number, device: GPUDevice): Promise<CpuTextureAtlas[]>;
4
+ /**
5
+ * CPU-only version of packBitmapsToAtlas for WebGL2 backend.
6
+ * Uses OffscreenCanvas to composite packed bitmaps instead of GPU textures.
7
+ * Does not support transparent pixel cropping.
8
+ */
9
+ export declare function packBitmapsToAtlasCPU(images: Map<string, {
10
+ bitmap: ImageBitmap;
11
+ id: string;
12
+ }>, textureSize: number): Promise<CpuTextureAtlas[]>;
4
13
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/textures/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAEjB,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CASrE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACxC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,EAAE,CAAC,CAqH5B"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/textures/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAEjB,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CASrE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACxC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,EAAE,CAAC,CAqH5B;AA2HD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,EACxD,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,EAAE,CAAC,CA8G5B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloopjs/toodle",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -28,6 +28,9 @@ export interface IBackendShader {
28
28
  /** Debug label for the shader */
29
29
  readonly label: string;
30
30
 
31
+ /** The final compiled shader code (for debugging) */
32
+ readonly code: string;
33
+
31
34
  /**
32
35
  * Prepare for a new frame.
33
36
  * Called once per frame before any processBatch calls.
@@ -16,6 +16,7 @@ const INSTANCE_BYTES = INSTANCE_FLOATS * Float32Array.BYTES_PER_ELEMENT;
16
16
  */
17
17
  export class WebGLQuadShader implements IBackendShader {
18
18
  readonly label: string;
19
+ readonly code: string;
19
20
 
20
21
  #backend: WebGLBackend;
21
22
  #atlas: ITextureAtlas;
@@ -45,6 +46,7 @@ export class WebGLQuadShader implements IBackendShader {
45
46
 
46
47
  this.#atlas = atlas;
47
48
  this.label = label;
49
+ this.code = userFragmentShader ?? fragmentShader;
48
50
  this.#backend = backend;
49
51
  this.#instanceCount = instanceCount;
50
52
 
@@ -21,6 +21,7 @@ import type { WebGLFontPipeline } from "./WebGLFontPipeline";
21
21
  */
22
22
  export class WebGLTextShader implements ITextShader {
23
23
  readonly label = "text";
24
+ readonly code: string = fragmentShader;
24
25
  readonly font: MsdfFont;
25
26
  readonly maxCharCount: number;
26
27
 
@@ -22,6 +22,7 @@ const textDescriptorInstanceSize = struct.size;
22
22
 
23
23
  export class WebGPUTextShader implements ITextShader {
24
24
  readonly label = "text";
25
+ readonly code: string = msdfShader;
25
26
 
26
27
  #backend: WebGPUBackend;
27
28
  #pipeline: GPURenderPipeline;
@@ -21,7 +21,11 @@ import type {
21
21
  TextureBundleOpts,
22
22
  TextureWithMetadata,
23
23
  } from "./types";
24
- import { getBitmapFromUrl, packBitmapsToAtlas } from "./util";
24
+ import {
25
+ getBitmapFromUrl,
26
+ packBitmapsToAtlas,
27
+ packBitmapsToAtlasCPU,
28
+ } from "./util";
25
29
 
26
30
  export type TextureId = string;
27
31
  export type BundleId = string;
@@ -432,41 +436,58 @@ export class AssetManager {
432
436
  bundleId: BundleId,
433
437
  opts: TextureBundleOpts,
434
438
  ) {
435
- if (this.#backend.type !== "webgpu") {
436
- throw new Error(
437
- "Dynamic texture bundle registration is only supported with WebGPU backend. Use prebaked atlases instead.",
439
+ if (this.#backend.type === "webgpu") {
440
+ // WebGPU path: supports optional crop compute shader
441
+ const device = (this.#backend as WebGPUBackend).device;
442
+ const images = new Map<string, TextureWithMetadata>();
443
+
444
+ await Promise.all(
445
+ Object.entries(opts.textures).map(async ([id, url]) => {
446
+ const bitmap = await getBitmapFromUrl(url);
447
+ let textureWrapper: TextureWithMetadata = this.#wrapBitmapToTexture(
448
+ bitmap,
449
+ id,
450
+ );
451
+
452
+ if (opts.cropTransparentPixels && this.#cropComputeShader) {
453
+ textureWrapper =
454
+ await this.#cropComputeShader.processTexture(textureWrapper);
455
+ }
456
+ images.set(id, textureWrapper);
457
+ }),
438
458
  );
439
- }
440
459
 
441
- const device = (this.#backend as WebGPUBackend).device;
442
- const images = new Map<string, TextureWithMetadata>();
460
+ const atlases = await packBitmapsToAtlas(
461
+ images,
462
+ this.#backend.limits.textureSize,
463
+ device,
464
+ );
443
465
 
444
- let _networkLoadTime = 0;
445
- await Promise.all(
446
- Object.entries(opts.textures).map(async ([id, url]) => {
447
- const now = performance.now();
448
- const bitmap = await getBitmapFromUrl(url);
449
- _networkLoadTime += performance.now() - now;
450
- let textureWrapper: TextureWithMetadata = this.#wrapBitmapToTexture(
451
- bitmap,
452
- id,
466
+ this.bundles.registerDynamicBundle(bundleId, atlases);
467
+ } else {
468
+ // WebGL2 path: CPU-only packing without cropping support
469
+ if (opts.cropTransparentPixels) {
470
+ console.warn(
471
+ "cropTransparentPixels is not supported on WebGL2 backend and will be ignored.",
453
472
  );
473
+ }
454
474
 
455
- if (opts.cropTransparentPixels && this.#cropComputeShader) {
456
- textureWrapper =
457
- await this.#cropComputeShader.processTexture(textureWrapper);
458
- }
459
- images.set(id, textureWrapper);
460
- }),
461
- );
475
+ const images = new Map<string, { bitmap: ImageBitmap; id: string }>();
462
476
 
463
- const atlases = await packBitmapsToAtlas(
464
- images,
465
- this.#backend.limits.textureSize,
466
- device,
467
- );
477
+ await Promise.all(
478
+ Object.entries(opts.textures).map(async ([id, url]) => {
479
+ const bitmap = await getBitmapFromUrl(url);
480
+ images.set(id, { bitmap, id });
481
+ }),
482
+ );
483
+
484
+ const atlases = await packBitmapsToAtlasCPU(
485
+ images,
486
+ this.#backend.limits.textureSize,
487
+ );
468
488
 
469
- this.bundles.registerDynamicBundle(bundleId, atlases);
489
+ this.bundles.registerDynamicBundle(bundleId, atlases);
490
+ }
470
491
  }
471
492
 
472
493
  async #registerBundleFromAtlases(bundleId: BundleId, opts: AtlasBundleOpts) {
@@ -258,3 +258,143 @@ type Rectangle = {
258
258
  width: number;
259
259
  height: number;
260
260
  };
261
+
262
+ /**
263
+ * CPU-only version of packBitmapsToAtlas for WebGL2 backend.
264
+ * Uses OffscreenCanvas to composite packed bitmaps instead of GPU textures.
265
+ * Does not support transparent pixel cropping.
266
+ */
267
+ export async function packBitmapsToAtlasCPU(
268
+ images: Map<string, { bitmap: ImageBitmap; id: string }>,
269
+ textureSize: number,
270
+ ): Promise<CpuTextureAtlas[]> {
271
+ const cpuTextureAtlases: CpuTextureAtlas[] = [];
272
+ const packed: PackedTexture[] = [];
273
+ const spaces: Rectangle[] = [
274
+ { x: 0, y: 0, width: textureSize, height: textureSize },
275
+ ];
276
+
277
+ let atlasRegionMap = new Map<string, TextureRegion>();
278
+
279
+ for (const [id, { bitmap }] of images) {
280
+ // Find best fitting space using guillotine method
281
+ let bestSpace = -1;
282
+ let bestScore = Number.POSITIVE_INFINITY;
283
+
284
+ for (let i = 0; i < spaces.length; i++) {
285
+ const space = spaces[i];
286
+ if (bitmap.width <= space.width && bitmap.height <= space.height) {
287
+ // Score based on how well it fits (smaller score is better)
288
+ const score = Math.abs(
289
+ space.width * space.height - bitmap.width * bitmap.height,
290
+ );
291
+ if (score < bestScore) {
292
+ bestScore = score;
293
+ bestSpace = i;
294
+ }
295
+ }
296
+ }
297
+
298
+ if (bestSpace === -1) {
299
+ // Current atlas is full, finalize it and start a new one
300
+ const tex = createAtlasBitmapFromPacked(packed, textureSize);
301
+ cpuTextureAtlases.push({
302
+ texture: tex,
303
+ textureRegions: atlasRegionMap,
304
+ width: tex.width,
305
+ height: tex.height,
306
+ });
307
+
308
+ atlasRegionMap = new Map<string, TextureRegion>();
309
+ packed.length = 0;
310
+
311
+ spaces.length = 0;
312
+ spaces.push({
313
+ x: 0,
314
+ y: 0,
315
+ width: textureSize,
316
+ height: textureSize,
317
+ });
318
+ bestSpace = 0;
319
+ }
320
+
321
+ const space = spaces[bestSpace];
322
+
323
+ // Pack the image
324
+ packed.push({
325
+ texture: bitmap,
326
+ x: space.x,
327
+ y: space.y,
328
+ width: bitmap.width,
329
+ height: bitmap.height,
330
+ });
331
+
332
+ // Split remaining space into two new spaces
333
+ spaces.splice(bestSpace, 1);
334
+
335
+ if (space.width - bitmap.width > 0) {
336
+ spaces.push({
337
+ x: space.x + bitmap.width,
338
+ y: space.y,
339
+ width: space.width - bitmap.width,
340
+ height: bitmap.height,
341
+ });
342
+ }
343
+
344
+ if (space.height - bitmap.height > 0) {
345
+ spaces.push({
346
+ x: space.x,
347
+ y: space.y + bitmap.height,
348
+ width: space.width,
349
+ height: space.height - bitmap.height,
350
+ });
351
+ }
352
+
353
+ // Create atlas coords (no cropping for CPU path)
354
+ const uvScale = {
355
+ width: bitmap.width / textureSize,
356
+ height: bitmap.height / textureSize,
357
+ };
358
+ atlasRegionMap.set(id, {
359
+ uvOffset: {
360
+ x: space.x / textureSize,
361
+ y: space.y / textureSize,
362
+ },
363
+ uvScale,
364
+ uvScaleCropped: uvScale,
365
+ cropOffset: { x: 0, y: 0 },
366
+ originalSize: { width: bitmap.width, height: bitmap.height },
367
+ });
368
+ }
369
+
370
+ // Finalize the last atlas
371
+ const tex = createAtlasBitmapFromPacked(packed, textureSize);
372
+ cpuTextureAtlases.push({
373
+ texture: tex,
374
+ textureRegions: atlasRegionMap,
375
+ width: tex.width,
376
+ height: tex.height,
377
+ });
378
+
379
+ return cpuTextureAtlases;
380
+ }
381
+
382
+ /**
383
+ * Composites packed textures onto an OffscreenCanvas and returns an ImageBitmap.
384
+ */
385
+ function createAtlasBitmapFromPacked(
386
+ packed: PackedTexture[],
387
+ atlasSize: number,
388
+ ): ImageBitmap {
389
+ const canvas = new OffscreenCanvas(atlasSize, atlasSize);
390
+ const ctx = canvas.getContext("2d");
391
+ if (!ctx) {
392
+ throw new Error("Failed to get 2d context from OffscreenCanvas");
393
+ }
394
+
395
+ for (const texture of packed) {
396
+ ctx.drawImage(texture.texture, texture.x, texture.y);
397
+ }
398
+
399
+ return canvas.transferToImageBitmap();
400
+ }