@bloopjs/toodle 0.0.102 → 0.0.104
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/dist/Toodle.d.ts +9 -1
- package/dist/Toodle.d.ts.map +1 -1
- package/dist/mod.d.ts +1 -0
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +340 -159
- package/dist/mod.js.map +9 -8
- package/dist/scene/QuadNode.d.ts.map +1 -1
- package/dist/scene/SceneNode.d.ts +2 -2
- package/dist/scene/SceneNode.d.ts.map +1 -1
- package/dist/textures/AssetManager.d.ts +10 -2
- package/dist/textures/AssetManager.d.ts.map +1 -1
- package/dist/textures/Bundles.d.ts +183 -0
- package/dist/textures/Bundles.d.ts.map +1 -0
- package/dist/textures/mod.d.ts +2 -0
- package/dist/textures/mod.d.ts.map +1 -1
- package/dist/textures/types.d.ts +4 -2
- package/dist/textures/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Toodle.ts +31 -15
- package/src/mod.ts +1 -0
- package/src/scene/QuadNode.ts +1 -4
- package/src/scene/SceneNode.ts +11 -12
- package/src/text/TextNode.ts +2 -2
- package/src/textures/AssetManager.ts +44 -195
- package/src/textures/Bundles.ts +541 -0
- package/src/textures/mod.ts +2 -0
- package/src/textures/types.ts +4 -2
|
@@ -8,14 +8,13 @@ import { FontPipeline } from "../text/FontPipeline";
|
|
|
8
8
|
import { MsdfFont } from "../text/MsdfFont";
|
|
9
9
|
import { TextShader } from "../text/TextShader";
|
|
10
10
|
import { assert } from "../utils/mod";
|
|
11
|
+
import { Bundles } from "./Bundles";
|
|
11
12
|
import { TextureComputeShader } from "./TextureComputeShader";
|
|
12
13
|
import type {
|
|
13
14
|
AtlasBundleOpts,
|
|
14
15
|
AtlasCoords,
|
|
15
16
|
CpuTextureAtlas,
|
|
16
|
-
PixiRegion,
|
|
17
17
|
TextureBundleOpts,
|
|
18
|
-
TextureRegion,
|
|
19
18
|
TextureWithMetadata,
|
|
20
19
|
} from "./types";
|
|
21
20
|
import { getBitmapFromUrl, packBitmapsToAtlas } from "./util";
|
|
@@ -24,18 +23,18 @@ export type TextureId = string;
|
|
|
24
23
|
export type BundleId = string;
|
|
25
24
|
export type FontId = string;
|
|
26
25
|
|
|
27
|
-
type
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
export type AssetManagerOptions = {
|
|
27
|
+
/** Existing Bundles instance to use for CPU-side storage. If not provided, a new one is created. */
|
|
28
|
+
bundles?: Bundles;
|
|
29
|
+
/** Texture format (default: "rgba8unorm") */
|
|
30
|
+
format?: "rgba8unorm" | "rg8unorm";
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export class AssetManager {
|
|
34
34
|
readonly textureAtlas: GPUTexture;
|
|
35
|
+
readonly bundles: Bundles;
|
|
35
36
|
#device: GPUDevice;
|
|
36
37
|
#presentationFormat: GPUTextureFormat;
|
|
37
|
-
#bundles: Map<BundleId, Bundle> = new Map();
|
|
38
|
-
#textures: Map<string, AtlasCoords[]> = new Map();
|
|
39
38
|
#fonts: Map<string, TextShader> = new Map();
|
|
40
39
|
#cropComputeShader: TextureComputeShader;
|
|
41
40
|
#limits: Limits;
|
|
@@ -45,11 +44,14 @@ export class AssetManager {
|
|
|
45
44
|
device: GPUDevice,
|
|
46
45
|
presentationFormat: GPUTextureFormat,
|
|
47
46
|
limits: Limits,
|
|
48
|
-
|
|
47
|
+
options: AssetManagerOptions = {},
|
|
49
48
|
) {
|
|
50
49
|
this.#device = device;
|
|
51
50
|
this.#presentationFormat = presentationFormat;
|
|
52
51
|
this.#limits = limits;
|
|
52
|
+
this.bundles =
|
|
53
|
+
options.bundles ?? new Bundles({ atlasSize: limits.textureSize });
|
|
54
|
+
const format = options.format ?? "rgba8unorm";
|
|
53
55
|
this.textureAtlas = device.createTexture({
|
|
54
56
|
label: "Asset Manager Atlas Texture",
|
|
55
57
|
size: [
|
|
@@ -108,27 +110,27 @@ export class AssetManager {
|
|
|
108
110
|
* @returns Whether the image has been cropped (i.e. if it has uvScaledCropped)
|
|
109
111
|
*/
|
|
110
112
|
isCropped(id: TextureId): boolean {
|
|
111
|
-
if (!this
|
|
113
|
+
if (!this.bundles.hasTexture(id)) {
|
|
112
114
|
throw new Error(
|
|
113
115
|
`Texture ${id} not found in atlas. Have you called toodle.loadTextures with this id or toodle.loadBundle with a bundle that contains it?`,
|
|
114
116
|
);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
return this
|
|
119
|
+
return this.bundles.getAtlasCoords(id)[0].uvScaleCropped === undefined;
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
/**
|
|
121
123
|
* A read-only map of all currently loaded textures.
|
|
122
124
|
*/
|
|
123
125
|
get textures() {
|
|
124
|
-
return this
|
|
126
|
+
return this.bundles.textures;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
/**
|
|
128
130
|
* A read-only array of all currently loaded texture ids.
|
|
129
131
|
*/
|
|
130
132
|
get textureIds() {
|
|
131
|
-
return
|
|
133
|
+
return this.bundles.textureIds;
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
/**
|
|
@@ -200,7 +202,7 @@ export class AssetManager {
|
|
|
200
202
|
atlasIndex,
|
|
201
203
|
};
|
|
202
204
|
|
|
203
|
-
this
|
|
205
|
+
this.bundles.addTextureEntry(id, coords);
|
|
204
206
|
this.#availableIndices.delete(atlasIndex);
|
|
205
207
|
|
|
206
208
|
textureWrapper.texture.destroy();
|
|
@@ -226,7 +228,8 @@ export class AssetManager {
|
|
|
226
228
|
await this.#registerBundleFromAtlases(bundleId, opts);
|
|
227
229
|
}
|
|
228
230
|
|
|
229
|
-
|
|
231
|
+
const autoLoad = opts.autoLoad ?? true;
|
|
232
|
+
if (autoLoad) {
|
|
230
233
|
await this.loadBundle(bundleId);
|
|
231
234
|
}
|
|
232
235
|
return bundleId;
|
|
@@ -238,22 +241,25 @@ export class AssetManager {
|
|
|
238
241
|
* See: https://toodle.gg/f849595b3ed13fc956fc1459a5cb5f0228f9d259/examples/texture-bundles.html
|
|
239
242
|
*/
|
|
240
243
|
async loadBundle(bundleId: BundleId) {
|
|
241
|
-
|
|
242
|
-
if (!bundle) {
|
|
244
|
+
if (!this.bundles.hasBundle(bundleId)) {
|
|
243
245
|
throw new Error(`Bundle ${bundleId} not found`);
|
|
244
246
|
}
|
|
245
247
|
|
|
246
|
-
if (
|
|
248
|
+
if (this.bundles.isBundleLoaded(bundleId)) {
|
|
247
249
|
console.warn(`Bundle ${bundleId} is already loaded.`);
|
|
248
250
|
return;
|
|
249
251
|
}
|
|
250
252
|
|
|
251
|
-
|
|
253
|
+
const atlases = this.bundles.getBundleAtlases(bundleId);
|
|
254
|
+
const atlasIndices: number[] = [];
|
|
255
|
+
|
|
256
|
+
for (const atlas of atlases) {
|
|
252
257
|
const atlasIndex = await this.extra.loadAtlas(atlas);
|
|
253
|
-
|
|
258
|
+
atlasIndices.push(atlasIndex);
|
|
254
259
|
}
|
|
255
260
|
|
|
256
|
-
|
|
261
|
+
// Use setBundleLoaded (not markBundleLoaded) since loadAtlas already populated textures
|
|
262
|
+
this.bundles.setBundleLoaded(bundleId, atlasIndices);
|
|
257
263
|
}
|
|
258
264
|
|
|
259
265
|
/**
|
|
@@ -263,24 +269,21 @@ export class AssetManager {
|
|
|
263
269
|
* @param bundleId - The id of the bundle to unload
|
|
264
270
|
*/
|
|
265
271
|
async unloadBundle(bundleId: BundleId) {
|
|
266
|
-
|
|
267
|
-
if (!bundle) {
|
|
272
|
+
if (!this.bundles.hasBundle(bundleId)) {
|
|
268
273
|
throw new Error(`Bundle ${bundleId} not found`);
|
|
269
274
|
}
|
|
270
275
|
|
|
271
|
-
if (!
|
|
276
|
+
if (!this.bundles.isBundleLoaded(bundleId)) {
|
|
272
277
|
console.warn(`Bundle ${bundleId} is not loaded.`);
|
|
273
278
|
return;
|
|
274
279
|
}
|
|
275
280
|
|
|
281
|
+
const atlasIndices = this.bundles.getBundleAtlasIndices(bundleId);
|
|
276
282
|
await Promise.all(
|
|
277
|
-
|
|
278
|
-
this.extra.unloadAtlas(atlasIndex),
|
|
279
|
-
),
|
|
283
|
+
atlasIndices.map((atlasIndex) => this.extra.unloadAtlas(atlasIndex)),
|
|
280
284
|
);
|
|
281
285
|
|
|
282
|
-
|
|
283
|
-
bundle.atlasIndices = [];
|
|
286
|
+
this.bundles.unloadBundle(bundleId);
|
|
284
287
|
}
|
|
285
288
|
|
|
286
289
|
/**
|
|
@@ -328,15 +331,13 @@ export class AssetManager {
|
|
|
328
331
|
)
|
|
329
332
|
return;
|
|
330
333
|
|
|
331
|
-
|
|
332
|
-
node.textureId,
|
|
333
|
-
);
|
|
334
|
-
if (!coords || !coords.length) {
|
|
334
|
+
if (!this.bundles.hasTexture(node.textureId)) {
|
|
335
335
|
throw new Error(
|
|
336
336
|
`Node ${node.id} references an invalid texture ${node.textureId}.`,
|
|
337
337
|
);
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
const coords = this.bundles.getAtlasCoords(node.textureId);
|
|
340
341
|
if (
|
|
341
342
|
coords.find((coord) => coord.atlasIndex === node.atlasCoords.atlasIndex)
|
|
342
343
|
)
|
|
@@ -345,22 +346,6 @@ export class AssetManager {
|
|
|
345
346
|
node.extra.setAtlasCoords(coords[0]);
|
|
346
347
|
}
|
|
347
348
|
|
|
348
|
-
/**
|
|
349
|
-
* Sets a designated texture ID to the corresponding `AtlasRegion` built from a `TextureRegion` and `numerical atlas index.
|
|
350
|
-
* @param id - `String` representing the texture name. I.e. "PlayerSprite"
|
|
351
|
-
* @param textureRegion - `TextureRegion` corresponding the uv and texture offsets
|
|
352
|
-
* @param atlasIndex - `number` of the atlas that the texture will live in.
|
|
353
|
-
* @private
|
|
354
|
-
*/
|
|
355
|
-
#addTexture(id: string, textureRegion: TextureRegion, atlasIndex: number) {
|
|
356
|
-
this.#textures.set(id, [
|
|
357
|
-
{
|
|
358
|
-
...textureRegion,
|
|
359
|
-
atlasIndex,
|
|
360
|
-
},
|
|
361
|
-
]);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
349
|
/**
|
|
365
350
|
*
|
|
366
351
|
* @param bitmap - `ImageBitmap` to be processed into a `GPUTexture` for storage and manipulation
|
|
@@ -422,115 +407,12 @@ export class AssetManager {
|
|
|
422
407
|
this.#device,
|
|
423
408
|
);
|
|
424
409
|
|
|
425
|
-
this
|
|
426
|
-
atlases,
|
|
427
|
-
atlasIndices: [],
|
|
428
|
-
isLoaded: false,
|
|
429
|
-
});
|
|
410
|
+
this.bundles.registerDynamicBundle(bundleId, atlases);
|
|
430
411
|
}
|
|
431
412
|
|
|
432
413
|
async #registerBundleFromAtlases(bundleId: BundleId, opts: AtlasBundleOpts) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
for (const atlas of opts.atlases) {
|
|
436
|
-
const jsonUrl =
|
|
437
|
-
atlas.json ??
|
|
438
|
-
new URL(
|
|
439
|
-
atlas.png!.toString().replace(".png", ".json"),
|
|
440
|
-
atlas.png!.origin,
|
|
441
|
-
);
|
|
442
|
-
const pngUrl =
|
|
443
|
-
atlas.png ??
|
|
444
|
-
new URL(
|
|
445
|
-
atlas.json!.toString().replace(".json", ".png"),
|
|
446
|
-
atlas.json!.origin,
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
const atlasDef = await (await fetch(jsonUrl)).json();
|
|
450
|
-
const bitmap = !opts.rg8
|
|
451
|
-
? await getBitmapFromUrl(pngUrl)
|
|
452
|
-
: await createImageBitmap(new ImageData(1, 1)); // placeholder bitmap if using rg8
|
|
453
|
-
|
|
454
|
-
let rg8Bytes: Uint8Array<ArrayBuffer> | undefined;
|
|
455
|
-
if (opts.rg8) {
|
|
456
|
-
const rg8url = new URL(
|
|
457
|
-
pngUrl.toString().replace(".png", ".rg8.gz"),
|
|
458
|
-
pngUrl.origin,
|
|
459
|
-
);
|
|
460
|
-
const rgBytes = await fetch(rg8url).then(async (r) => {
|
|
461
|
-
const enc = (r.headers.get("content-encoding") || "").toLowerCase();
|
|
462
|
-
// If server/CDN already set Content-Encoding, Fetch returns decompressed bytes.
|
|
463
|
-
if (
|
|
464
|
-
enc.includes("gzip") ||
|
|
465
|
-
enc.includes("br") ||
|
|
466
|
-
enc.includes("deflate")
|
|
467
|
-
) {
|
|
468
|
-
return new Uint8Array(await r.arrayBuffer());
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
assert(r.body, "Response body of rg8 file is null");
|
|
472
|
-
const ds = new DecompressionStream("gzip");
|
|
473
|
-
const ab = await new Response(r.body.pipeThrough(ds)).arrayBuffer();
|
|
474
|
-
return new Uint8Array(ab);
|
|
475
|
-
});
|
|
476
|
-
rg8Bytes = rgBytes;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const cpuTextureAtlas: CpuTextureAtlas = {
|
|
480
|
-
texture: bitmap,
|
|
481
|
-
rg8Bytes,
|
|
482
|
-
textureRegions: new Map(),
|
|
483
|
-
width: opts.rg8 ? this.#limits.textureSize : bitmap.width,
|
|
484
|
-
height: opts.rg8 ? this.#limits.textureSize : bitmap.height,
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
for (const [assetId, frame] of Object.entries(atlasDef.frames) as [
|
|
488
|
-
string,
|
|
489
|
-
PixiRegion,
|
|
490
|
-
][]) {
|
|
491
|
-
const leftCrop = frame.spriteSourceSize.x;
|
|
492
|
-
const rightCrop =
|
|
493
|
-
frame.sourceSize.w -
|
|
494
|
-
frame.spriteSourceSize.x -
|
|
495
|
-
frame.spriteSourceSize.w;
|
|
496
|
-
const topCrop = frame.spriteSourceSize.y;
|
|
497
|
-
const bottomCrop =
|
|
498
|
-
frame.sourceSize.h -
|
|
499
|
-
frame.spriteSourceSize.y -
|
|
500
|
-
frame.spriteSourceSize.h;
|
|
501
|
-
|
|
502
|
-
cpuTextureAtlas.textureRegions.set(assetId, {
|
|
503
|
-
cropOffset: {
|
|
504
|
-
x: leftCrop - rightCrop,
|
|
505
|
-
y: bottomCrop - topCrop,
|
|
506
|
-
},
|
|
507
|
-
originalSize: {
|
|
508
|
-
width: frame.sourceSize.w,
|
|
509
|
-
height: frame.sourceSize.h,
|
|
510
|
-
},
|
|
511
|
-
uvOffset: {
|
|
512
|
-
x: frame.frame.x / cpuTextureAtlas.width,
|
|
513
|
-
y: frame.frame.y / cpuTextureAtlas.height,
|
|
514
|
-
},
|
|
515
|
-
uvScale: {
|
|
516
|
-
width: frame.sourceSize.w / cpuTextureAtlas.width,
|
|
517
|
-
height: frame.sourceSize.h / cpuTextureAtlas.height,
|
|
518
|
-
},
|
|
519
|
-
uvScaleCropped: {
|
|
520
|
-
width: frame.frame.w / cpuTextureAtlas.width,
|
|
521
|
-
height: frame.frame.h / cpuTextureAtlas.height,
|
|
522
|
-
},
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
atlases.push(cpuTextureAtlas);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
this.#bundles.set(bundleId, {
|
|
530
|
-
atlases,
|
|
531
|
-
atlasIndices: [],
|
|
532
|
-
isLoaded: false,
|
|
533
|
-
});
|
|
414
|
+
// Delegate to the Bundles instance for atlas parsing
|
|
415
|
+
await this.bundles.registerAtlasBundle(bundleId, opts);
|
|
534
416
|
}
|
|
535
417
|
|
|
536
418
|
/**
|
|
@@ -539,14 +421,12 @@ export class AssetManager {
|
|
|
539
421
|
extra = {
|
|
540
422
|
// Get an array of all currently registered bundle ids.
|
|
541
423
|
getRegisteredBundleIds: (): string[] => {
|
|
542
|
-
return this
|
|
424
|
+
return this.bundles.getRegisteredBundleIds();
|
|
543
425
|
},
|
|
544
426
|
|
|
545
427
|
// Get an array of all currently loaded bundle ids.
|
|
546
428
|
getLoadedBundleIds: (): string[] => {
|
|
547
|
-
return
|
|
548
|
-
.filter(([, value]) => value.isLoaded)
|
|
549
|
-
.map(([key]) => key);
|
|
429
|
+
return this.bundles.getLoadedBundleIds();
|
|
550
430
|
},
|
|
551
431
|
|
|
552
432
|
/**
|
|
@@ -558,14 +438,7 @@ export class AssetManager {
|
|
|
558
438
|
* @param coords - The atlas coordinates to set
|
|
559
439
|
*/
|
|
560
440
|
setAtlasCoords: (id: TextureId, coords: AtlasCoords) => {
|
|
561
|
-
|
|
562
|
-
if (!oldCoords) return;
|
|
563
|
-
const indexToModify = oldCoords.findIndex(
|
|
564
|
-
(coord) => coord.atlasIndex === coords.atlasIndex,
|
|
565
|
-
);
|
|
566
|
-
if (indexToModify === -1) return;
|
|
567
|
-
oldCoords[indexToModify] = coords;
|
|
568
|
-
this.#textures.set(id, oldCoords);
|
|
441
|
+
this.bundles.setAtlasCoords(id, coords);
|
|
569
442
|
},
|
|
570
443
|
|
|
571
444
|
/**
|
|
@@ -575,12 +448,7 @@ export class AssetManager {
|
|
|
575
448
|
* @returns An array of the atlas coordinates for the texture
|
|
576
449
|
*/
|
|
577
450
|
getAtlasCoords: (id: TextureId): AtlasCoords[] => {
|
|
578
|
-
|
|
579
|
-
throw new Error(
|
|
580
|
-
`Texture ${id} not found in atlas. Have you called toodle.loadBundle with a bundle that contains this id (or toodle.loadTextures with this id as a key)?`,
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
return this.#textures.get(id) ?? [];
|
|
451
|
+
return this.bundles.getAtlasCoords(id);
|
|
584
452
|
},
|
|
585
453
|
|
|
586
454
|
/**
|
|
@@ -590,13 +458,7 @@ export class AssetManager {
|
|
|
590
458
|
* @returns Point of the texture's associated X,Y offset
|
|
591
459
|
*/
|
|
592
460
|
getTextureOffset: (id: TextureId): Vec2 => {
|
|
593
|
-
|
|
594
|
-
if (!texture) {
|
|
595
|
-
throw new Error(
|
|
596
|
-
`Texture ${id} not found in atlas. Have you called toodle.loadTextures with this id or toodle.loadBundle with a bundle that contains it?`,
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
return texture[0].cropOffset;
|
|
461
|
+
return this.bundles.getTextureOffset(id);
|
|
600
462
|
},
|
|
601
463
|
|
|
602
464
|
/**
|
|
@@ -675,10 +537,7 @@ export class AssetManager {
|
|
|
675
537
|
}
|
|
676
538
|
|
|
677
539
|
for (const [id, region] of atlas.textureRegions) {
|
|
678
|
-
|
|
679
|
-
if (existing) {
|
|
680
|
-
existing.push({ ...region, atlasIndex });
|
|
681
|
-
} else this.#addTexture(id, region, atlasIndex);
|
|
540
|
+
this.bundles.addTextureEntry(id, { ...region, atlasIndex });
|
|
682
541
|
}
|
|
683
542
|
return atlasIndex;
|
|
684
543
|
},
|
|
@@ -690,17 +549,7 @@ export class AssetManager {
|
|
|
690
549
|
*/
|
|
691
550
|
unloadAtlas: async (atlasIndex: number) => {
|
|
692
551
|
this.#availableIndices.add(atlasIndex);
|
|
693
|
-
|
|
694
|
-
const indexToModify = coords.findIndex(
|
|
695
|
-
(coord) => coord.atlasIndex === atlasIndex,
|
|
696
|
-
);
|
|
697
|
-
if (indexToModify !== -1) {
|
|
698
|
-
coords.splice(indexToModify, 1);
|
|
699
|
-
}
|
|
700
|
-
if (!coords.length) {
|
|
701
|
-
this.#textures.delete(id);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
552
|
+
this.bundles.removeTextureEntriesForAtlas(atlasIndex);
|
|
704
553
|
},
|
|
705
554
|
};
|
|
706
555
|
|