@forgeax/engine-assets-runtime 0.1.3 → 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.
- package/README.md +442 -175
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/aabb.unit.test.d.ts +2 -0
- package/dist/__tests__/aabb.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-integrity.unit.test.d.ts +2 -0
- package/dist/__tests__/artifact-integrity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-io.unit.test.d.ts +2 -0
- package/dist/__tests__/artifact-io.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-kind.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-kind.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/catalog-replica.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-replica.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-source-v2.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-source-v2.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-source.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-source.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-subscription-order.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-subscription-order.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-subscription.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-subscription.unit.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.browser.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.browser.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.test-d.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.test-d.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.test.d.ts.map +1 -0
- package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
- package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors-asset.unit.test.d.ts +2 -0
- package/dist/__tests__/errors-asset.unit.test.d.ts.map +1 -0
- package/dist/__tests__/inline-pack-loaders.unit.test.d.ts +2 -0
- package/dist/__tests__/inline-pack-loaders.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate-context.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate-context.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-input-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-input-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-registry.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-registry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts +2 -0
- package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-generation-cache.unit.test.d.ts +2 -0
- package/dist/__tests__/material-generation-cache.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-load-consumer.test-d.d.ts +2 -0
- package/dist/__tests__/material-load-consumer.test-d.d.ts.map +1 -0
- package/dist/__tests__/material-ready.integration.test.d.ts +2 -0
- package/dist/__tests__/material-ready.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-stale-generation.unit.test.d.ts +2 -0
- package/dist/__tests__/material-stale-generation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-morph-v3.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-morph-v3.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mipmap-generator.unit.test.d.ts +2 -0
- package/dist/__tests__/mipmap-generator.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts +2 -0
- package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts.map +1 -0
- package/dist/__tests__/pack-basis-load.integration.test.d.ts +2 -0
- package/dist/__tests__/pack-basis-load.integration.test.d.ts.map +1 -0
- package/dist/__tests__/payload-validate.unit.test.d.ts +2 -0
- package/dist/__tests__/payload-validate.unit.test.d.ts.map +1 -0
- package/dist/__tests__/plugin.test.d.ts +2 -0
- package/dist/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/__tests__/registry-concurrency.bench.test.d.ts +2 -0
- package/dist/__tests__/registry-concurrency.bench.test.d.ts.map +1 -0
- package/dist/__tests__/registry-failure-retry.integration.test.d.ts +2 -0
- package/dist/__tests__/registry-failure-retry.integration.test.d.ts.map +1 -0
- package/dist/__tests__/registry-load-graph.integration.test.d.ts +2 -0
- package/dist/__tests__/registry-load-graph.integration.test.d.ts.map +1 -0
- package/dist/__tests__/registry-load-state.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-load-state.unit.test.d.ts.map +1 -0
- package/dist/__tests__/registry-package-cache.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-package-cache.unit.test.d.ts.map +1 -0
- package/dist/__tests__/registry-public-ready.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-public-ready.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-handle-fields.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-handle-fields.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-payload.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-payload.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-publication-fence.test.d.ts +2 -0
- package/dist/__tests__/scene-publication-fence.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/static-rig.integration.test.d.ts +2 -0
- package/dist/__tests__/static-rig.integration.test.d.ts.map +1 -0
- package/dist/__tests__/validate-material.unit.test.d.ts +2 -0
- package/dist/__tests__/validate-material.unit.test.d.ts.map +1 -0
- package/dist/__tests__/video-pack-loader.integration.test.d.ts +2 -0
- package/dist/__tests__/video-pack-loader.integration.test.d.ts.map +1 -0
- package/dist/aabb.d.ts +3 -0
- package/dist/aabb.d.ts.map +1 -0
- package/dist/asset-registry.d.ts +552 -0
- package/dist/asset-registry.d.ts.map +1 -0
- package/dist/builtin-asset-registry.d.ts +22 -0
- package/dist/builtin-asset-registry.d.ts.map +1 -0
- package/dist/catalog-source.d.ts +2 -1
- package/dist/catalog-source.d.ts.map +1 -1
- package/dist/decode-image-bytes.d.ts +57 -0
- package/dist/decode-image-bytes.d.ts.map +1 -0
- package/dist/dynamic-texture-store.d.ts +89 -0
- package/dist/dynamic-texture-store.d.ts.map +1 -0
- package/dist/errors/asset.d.ts +201 -0
- package/dist/errors/asset.d.ts.map +1 -0
- package/dist/handles.d.ts +141 -0
- package/dist/handles.d.ts.map +1 -0
- package/dist/image-error.d.ts +6 -0
- package/dist/image-error.d.ts.map +1 -0
- package/dist/index.d.ts +32 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +7218 -114
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +317 -3
- package/dist/internal.mjs.map +1 -1
- package/dist/loader-registry.d.ts +63 -0
- package/dist/loader-registry.d.ts.map +1 -0
- package/dist/loaders/inline-pack.d.ts +72 -0
- package/dist/loaders/inline-pack.d.ts.map +1 -0
- package/dist/loaders/mesh-bin.d.ts +21 -0
- package/dist/loaders/mesh-bin.d.ts.map +1 -0
- package/dist/loaders/pack-artifact.d.ts +20 -0
- package/dist/loaders/pack-artifact.d.ts.map +1 -0
- package/dist/material/dependency-graph.d.ts +9 -0
- package/dist/material/dependency-graph.d.ts.map +1 -0
- package/dist/material/generation-cache.d.ts +24 -0
- package/dist/material/generation-cache.d.ts.map +1 -0
- package/dist/material/inspection.d.ts +24 -0
- package/dist/material/inspection.d.ts.map +1 -0
- package/dist/material/loader.d.ts +59 -0
- package/dist/material/loader.d.ts.map +1 -0
- package/dist/material/runtime-shader.d.ts +14 -0
- package/dist/material/runtime-shader.d.ts.map +1 -0
- package/dist/mesh-bin.d.ts +3 -0
- package/dist/mesh-bin.d.ts.map +1 -0
- package/dist/mipmap-generator.d.ts +132 -0
- package/dist/mipmap-generator.d.ts.map +1 -0
- package/dist/payload-validate.d.ts +60 -0
- package/dist/payload-validate.d.ts.map +1 -0
- package/dist/plugin.d.ts +16 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/registry/artifact-io.d.ts +16 -0
- package/dist/registry/artifact-io.d.ts.map +1 -0
- package/dist/registry/asset-evidence.d.ts +13 -0
- package/dist/registry/asset-evidence.d.ts.map +1 -0
- package/dist/registry/catalog-state.d.ts +47 -0
- package/dist/registry/catalog-state.d.ts.map +1 -0
- package/dist/registry/catalog.d.ts +48 -0
- package/dist/registry/catalog.d.ts.map +1 -0
- package/dist/registry/instantiate.d.ts +153 -0
- package/dist/registry/instantiate.d.ts.map +1 -0
- package/dist/registry/load-by-guid.d.ts +247 -0
- package/dist/registry/load-by-guid.d.ts.map +1 -0
- package/dist/registry/load-state.d.ts +26 -0
- package/dist/registry/load-state.d.ts.map +1 -0
- package/dist/registry/runtime-projection.d.ts +16 -0
- package/dist/registry/runtime-projection.d.ts.map +1 -0
- package/dist/registry/scene-publication-fence.d.ts +38 -0
- package/dist/registry/scene-publication-fence.d.ts.map +1 -0
- package/dist/registry/validate-material.d.ts +77 -0
- package/dist/registry/validate-material.d.ts.map +1 -0
- package/dist/resolve-asset-handle.d.ts +43 -0
- package/dist/resolve-asset-handle.d.ts.map +1 -0
- package/dist/resolve-tileset-runtime.d.ts +23 -0
- package/dist/resolve-tileset-runtime.d.ts.map +1 -0
- package/dist/scene-handle-fields.d.ts +88 -0
- package/dist/scene-handle-fields.d.ts.map +1 -0
- package/dist/scene-payload.d.ts +15 -0
- package/dist/scene-payload.d.ts.map +1 -0
- package/dist/wire-default-loaders.d.ts +29 -0
- package/dist/wire-default-loaders.d.ts.map +1 -0
- package/package.json +22 -9
- package/src/__tests__/aabb.unit.test.ts +97 -0
- package/src/__tests__/artifact-integrity.unit.test.ts +99 -0
- package/src/__tests__/artifact-io.unit.test.ts +127 -0
- package/src/__tests__/asset-evidence-sdk.integration.test.ts +32 -0
- package/src/__tests__/asset-evidence.unit.test.ts +32 -0
- package/src/__tests__/{asset-kind.test.ts → asset-kind.unit.test.ts} +1 -1
- package/src/__tests__/asset-registry-catalog-reconcile.integration.test.ts +74 -0
- package/src/__tests__/asset-registry-catalog-reconcile.unit.test.ts +89 -0
- package/src/__tests__/asset-registry-catalog.unit.test.ts +170 -0
- package/src/__tests__/asset-registry.pack-cache.test.ts +96 -0
- package/src/__tests__/asset-registry.unit.test.ts +511 -0
- package/src/__tests__/asset-runtime-error-code-owner.test-d.ts +63 -0
- package/src/__tests__/catalog-replica.unit.test.ts +196 -0
- package/src/__tests__/catalog-source-v2.unit.test.ts +158 -0
- package/src/__tests__/catalog-source.unit.test.ts +126 -0
- package/src/__tests__/catalog-subscription-order.unit.test.ts +65 -0
- package/src/__tests__/catalog-subscription.unit.test.ts +28 -0
- package/src/__tests__/decode-image-bytes-pass-through.test.ts +34 -0
- package/src/__tests__/decode-image-bytes.browser.test.ts +102 -0
- package/src/__tests__/decode-image-bytes.test-d.ts +108 -0
- package/src/__tests__/decode-image-bytes.test.ts +90 -0
- package/src/__tests__/decode-image-mime-owner.test.ts +2 -5
- package/src/__tests__/dynamic-texture-store.unit.test.ts +115 -0
- package/src/__tests__/errors-asset.unit.test.ts +92 -0
- package/src/__tests__/inline-pack-loaders.unit.test.ts +548 -0
- package/src/__tests__/instantiate-context.unit.test.ts +65 -0
- package/src/__tests__/instantiate-publication-fence.unit.test.ts +158 -0
- package/src/__tests__/instantiate.unit.test.ts +684 -0
- package/src/__tests__/loader-input-contract.unit.test.ts +89 -0
- package/src/__tests__/loader-registry.unit.test.ts +101 -0
- package/src/__tests__/material-dependency-invalidation.unit.test.ts +14 -0
- package/src/__tests__/material-generation-cache.unit.test.ts +46 -0
- package/src/__tests__/material-load-consumer.test-d.ts +42 -0
- package/src/__tests__/material-ready.integration.test.ts +492 -0
- package/src/__tests__/material-stale-generation.unit.test.ts +152 -0
- package/src/__tests__/mesh-bin-morph-v3.test.ts +22 -0
- package/src/__tests__/mesh-bin.unit.test.ts +152 -0
- package/src/__tests__/mipmap-generator.unit.test.ts +202 -0
- package/src/__tests__/pack-artifact-transcode-model-owner.test.ts +32 -0
- package/src/__tests__/pack-basis-load.integration.test.ts +168 -0
- package/src/__tests__/payload-validate.unit.test.ts +285 -0
- package/src/__tests__/plugin.test.ts +55 -0
- package/src/__tests__/registry-concurrency.bench.test.ts +40 -0
- package/src/__tests__/registry-failure-retry.integration.test.ts +69 -0
- package/src/__tests__/registry-load-graph.integration.test.ts +246 -0
- package/src/__tests__/registry-load-state.unit.test.ts +35 -0
- package/src/__tests__/registry-package-cache.unit.test.ts +53 -0
- package/src/__tests__/registry-public-ready.unit.test.ts +38 -0
- package/src/__tests__/resolve-asset-handle.unit.test.ts +238 -0
- package/src/__tests__/scene-handle-fields.unit.test.ts +129 -0
- package/src/__tests__/scene-payload.unit.test.ts +151 -0
- package/src/__tests__/scene-publication-fence.test.ts +129 -0
- package/src/__tests__/scriptable-pack-public-surface.test-d.ts +16 -0
- package/src/__tests__/static-rig.integration.test.ts +8 -0
- package/src/__tests__/validate-material.unit.test.ts +359 -0
- package/src/__tests__/video-pack-loader.integration.test.ts +83 -0
- package/src/aabb.ts +107 -0
- package/src/asset-registry.ts +2040 -0
- package/src/builtin-asset-registry.ts +164 -0
- package/src/catalog-source.ts +14 -70
- package/src/decode-image-bytes.ts +126 -0
- package/src/dynamic-texture-store.ts +234 -0
- package/src/errors/asset.ts +347 -0
- package/src/handles.ts +180 -0
- package/src/image-error.ts +45 -0
- package/src/index.ts +177 -19
- package/src/internal.ts +2 -0
- package/src/loader-registry.ts +157 -0
- package/src/loaders/inline-pack.ts +837 -0
- package/src/loaders/mesh-bin.ts +304 -0
- package/src/loaders/pack-artifact.ts +665 -0
- package/src/material/dependency-graph.ts +46 -0
- package/src/material/generation-cache.ts +177 -0
- package/src/material/inspection.ts +46 -0
- package/src/material/loader.ts +326 -0
- package/src/material/runtime-shader.ts +133 -0
- package/src/mesh-bin.ts +5 -0
- package/src/mipmap-generator.ts +450 -0
- package/src/payload-validate.ts +573 -0
- package/src/plugin.ts +48 -0
- package/src/registry/artifact-io.ts +259 -0
- package/src/registry/asset-evidence.ts +51 -0
- package/src/registry/catalog-state.ts +409 -0
- package/src/registry/catalog.ts +369 -0
- package/src/registry/instantiate.ts +1056 -0
- package/src/registry/load-by-guid.ts +1657 -0
- package/src/registry/load-state.ts +115 -0
- package/src/registry/runtime-projection.ts +63 -0
- package/src/registry/scene-publication-fence.ts +256 -0
- package/src/registry/validate-material.ts +386 -0
- package/src/resolve-asset-handle.ts +147 -0
- package/src/resolve-tileset-runtime.ts +72 -0
- package/src/scene-handle-fields.ts +221 -0
- package/src/scene-payload.ts +207 -0
- package/src/wire-default-loaders.ts +79 -0
- package/dist/__tests__/asset-graph-red.integration.test.d.ts +0 -2
- package/dist/__tests__/asset-graph-red.integration.test.d.ts.map +0 -1
- package/dist/__tests__/asset-kind.test.d.ts +0 -2
- package/dist/__tests__/asset-kind.test.d.ts.map +0 -1
- package/dist/__tests__/asset-registry-core.integration.test.d.ts +0 -2
- package/dist/__tests__/asset-registry-core.integration.test.d.ts.map +0 -1
- package/dist/__tests__/asset-registry-public-api.test-d.d.ts +0 -2
- package/dist/__tests__/asset-registry-public-api.test-d.d.ts.map +0 -1
- package/dist/__tests__/asset-runtime-core-lifecycle.integration.test.d.ts +0 -2
- package/dist/__tests__/asset-runtime-core-lifecycle.integration.test.d.ts.map +0 -1
- package/dist/__tests__/asset-runtime-snapshot.unit.test.d.ts +0 -2
- package/dist/__tests__/asset-runtime-snapshot.unit.test.d.ts.map +0 -1
- package/dist/__tests__/catalog-session-red.unit.test.d.ts +0 -2
- package/dist/__tests__/catalog-session-red.unit.test.d.ts.map +0 -1
- package/dist/__tests__/registry-lifecycle-red.integration.test.d.ts +0 -2
- package/dist/__tests__/registry-lifecycle-red.integration.test.d.ts.map +0 -1
- package/src/__tests__/asset-graph-red.integration.test.ts +0 -113
- package/src/__tests__/asset-registry-core.integration.test.ts +0 -161
- package/src/__tests__/asset-registry-public-api.test-d.ts +0 -31
- package/src/__tests__/asset-runtime-core-lifecycle.integration.test.ts +0 -79
- package/src/__tests__/asset-runtime-snapshot.unit.test.ts +0 -23
- package/src/__tests__/catalog-session-red.unit.test.ts +0 -276
- package/src/__tests__/registry-lifecycle-red.integration.test.ts +0 -80
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import type { CodecError, TranscodeModel } from '@forgeax/engine-codec';
|
|
2
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
3
|
+
import type {
|
|
4
|
+
AssetCodec,
|
|
5
|
+
AssetCodecFailureDetail,
|
|
6
|
+
EquirectAsset,
|
|
7
|
+
FontAsset,
|
|
8
|
+
LoadContext,
|
|
9
|
+
Loader,
|
|
10
|
+
LoaderAsyncResult,
|
|
11
|
+
RenderPipelineAsset,
|
|
12
|
+
TextureAsset,
|
|
13
|
+
TilesetAsset,
|
|
14
|
+
} from '@forgeax/engine-types';
|
|
15
|
+
import { AssetError } from '@forgeax/engine-types';
|
|
16
|
+
import type { PackLoaderInput } from '../loader-registry';
|
|
17
|
+
import { numMipLevels } from '../mipmap-generator';
|
|
18
|
+
|
|
19
|
+
type PackArtifact = PackLoaderInput['artifacts'][string];
|
|
20
|
+
|
|
21
|
+
type CodecFailureProjection = {
|
|
22
|
+
readonly code: string;
|
|
23
|
+
readonly expected: string;
|
|
24
|
+
readonly hint: string;
|
|
25
|
+
readonly detail: Readonly<Record<string, unknown>>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function firstArtifact(input: PackLoaderInput): PackArtifact | undefined {
|
|
29
|
+
return input.artifacts.body ?? Object.values(input.artifacts)[0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function payloadNumber(payload: Record<string, unknown>, key: string, fallback: number): number {
|
|
33
|
+
const value = payload[key];
|
|
34
|
+
return typeof value === 'number' ? value : fallback;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function payloadColorSpace(payload: Record<string, unknown>): 'srgb' | 'linear' {
|
|
38
|
+
return payload.colorSpace === 'linear' ? 'linear' : 'srgb';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function invalidPackAsset<T>(input: PackLoaderInput, expected: string): LoaderAsyncResult<T> {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
error: new AssetError({
|
|
45
|
+
code: 'asset-parse-failed',
|
|
46
|
+
expected,
|
|
47
|
+
hint: `Pack v2 asset ${input.guid} must provide an asset-local artifact`,
|
|
48
|
+
detail: { sourcePath: input.guid },
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
54
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function finiteNumber(value: unknown): value is number {
|
|
58
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function positiveInteger(value: unknown): value is number {
|
|
62
|
+
return finiteNumber(value) && Number.isInteger(value) && value > 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validRenderPipelineConfig(value: unknown): value is Record<string, unknown> {
|
|
66
|
+
if (!record(value)) return false;
|
|
67
|
+
const passCount = value.passCount;
|
|
68
|
+
if (passCount !== undefined && !positiveInteger(passCount)) return false;
|
|
69
|
+
const clusterGrid = value.clusterGrid;
|
|
70
|
+
if (clusterGrid !== undefined) {
|
|
71
|
+
if (
|
|
72
|
+
!record(clusterGrid) ||
|
|
73
|
+
!['x', 'y', 'z'].every((axis) => {
|
|
74
|
+
const size = clusterGrid[axis];
|
|
75
|
+
return positiveInteger(size) && size <= 64;
|
|
76
|
+
})
|
|
77
|
+
) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const ssao = value.ssao;
|
|
82
|
+
if (ssao !== undefined && (!record(ssao) || typeof ssao.enabled !== 'boolean')) return false;
|
|
83
|
+
const postEffects = value.postEffects;
|
|
84
|
+
return (
|
|
85
|
+
postEffects === undefined ||
|
|
86
|
+
(Array.isArray(postEffects) &&
|
|
87
|
+
postEffects.every((effect) => typeof effect === 'string' && effect.length > 0))
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validTilesetRegion(value: unknown, atlasCount: number): boolean {
|
|
92
|
+
if (!record(value)) return false;
|
|
93
|
+
const { x, y, width, height, atlasIndex } = value;
|
|
94
|
+
return (
|
|
95
|
+
finiteNumber(x) &&
|
|
96
|
+
finiteNumber(y) &&
|
|
97
|
+
positiveInteger(width) &&
|
|
98
|
+
positiveInteger(height) &&
|
|
99
|
+
(atlasIndex === undefined ||
|
|
100
|
+
(typeof atlasIndex === 'number' &&
|
|
101
|
+
Number.isInteger(atlasIndex) &&
|
|
102
|
+
atlasIndex >= 0 &&
|
|
103
|
+
atlasIndex < atlasCount))
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function validTilesetTile(value: unknown, regionCount: number): boolean {
|
|
108
|
+
if (!record(value)) return false;
|
|
109
|
+
const regionIndex = value.regionIndex;
|
|
110
|
+
return (
|
|
111
|
+
typeof regionIndex === 'number' &&
|
|
112
|
+
Number.isInteger(regionIndex) &&
|
|
113
|
+
regionIndex >= 0 &&
|
|
114
|
+
regionIndex < regionCount
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validAtlasSize(value: unknown): boolean {
|
|
119
|
+
return record(value) && positiveInteger(value.pixelWidth) && positiveInteger(value.pixelHeight);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readJsonArtifact(
|
|
123
|
+
input: PackLoaderInput,
|
|
124
|
+
kind: string,
|
|
125
|
+
): Record<string, unknown> | undefined {
|
|
126
|
+
const artifact = firstArtifact(input);
|
|
127
|
+
if (artifact === undefined) return undefined;
|
|
128
|
+
try {
|
|
129
|
+
const value: unknown = JSON.parse(new TextDecoder().decode(artifact.bytes));
|
|
130
|
+
return record(value) && value.kind === kind ? value : undefined;
|
|
131
|
+
} catch {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderPipelineDescriptor(value: Record<string, unknown>): RenderPipelineAsset | undefined {
|
|
137
|
+
if (value.kind !== 'render-pipeline') return undefined;
|
|
138
|
+
const pipelineId = value.pipelineId;
|
|
139
|
+
if (typeof pipelineId !== 'string' || pipelineId.trim().length === 0) return undefined;
|
|
140
|
+
const config = value.config;
|
|
141
|
+
if (config !== undefined && !validRenderPipelineConfig(config)) return undefined;
|
|
142
|
+
return {
|
|
143
|
+
kind: 'render-pipeline',
|
|
144
|
+
pipelineId,
|
|
145
|
+
...(config === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: { config: config as NonNullable<RenderPipelineAsset['config']> }),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function tilesetDescriptor(
|
|
152
|
+
value: Record<string, unknown>,
|
|
153
|
+
refs: readonly string[],
|
|
154
|
+
): TilesetAsset | undefined {
|
|
155
|
+
if (value.kind !== 'tileset') return undefined;
|
|
156
|
+
const rawAtlases = value.atlases;
|
|
157
|
+
if (!Array.isArray(rawAtlases) || rawAtlases.length === 0) return undefined;
|
|
158
|
+
if (!rawAtlases.every((atlas) => typeof atlas === 'string' && atlas.length > 0)) return undefined;
|
|
159
|
+
const atlases = rawAtlases as string[];
|
|
160
|
+
if (atlases.length !== refs.length || atlases.some((guid, index) => guid !== refs[index])) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
const { tileWidth, tileHeight, columns, rows, regions, tiles, atlasSizes } = value;
|
|
164
|
+
if (
|
|
165
|
+
typeof tileWidth !== 'number' ||
|
|
166
|
+
tileWidth <= 0 ||
|
|
167
|
+
typeof tileHeight !== 'number' ||
|
|
168
|
+
tileHeight <= 0 ||
|
|
169
|
+
typeof columns !== 'number' ||
|
|
170
|
+
!Number.isInteger(columns) ||
|
|
171
|
+
columns <= 0 ||
|
|
172
|
+
typeof rows !== 'number' ||
|
|
173
|
+
!Number.isInteger(rows) ||
|
|
174
|
+
rows <= 0 ||
|
|
175
|
+
!Array.isArray(regions) ||
|
|
176
|
+
!regions.every((region) => record(region) && validTilesetRegion(region, atlases.length)) ||
|
|
177
|
+
!Array.isArray(tiles) ||
|
|
178
|
+
!tiles.every((tile) => validTilesetTile(tile, regions.length)) ||
|
|
179
|
+
(atlasSizes !== undefined &&
|
|
180
|
+
(!Array.isArray(atlasSizes) ||
|
|
181
|
+
atlasSizes.length !== atlases.length ||
|
|
182
|
+
!atlasSizes.every((size) => validAtlasSize(size))))
|
|
183
|
+
) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
kind: 'tileset',
|
|
188
|
+
atlases: [...atlases],
|
|
189
|
+
tileWidth,
|
|
190
|
+
tileHeight,
|
|
191
|
+
columns,
|
|
192
|
+
rows,
|
|
193
|
+
regions: regions as TilesetAsset['regions'],
|
|
194
|
+
tiles: tiles as TilesetAsset['tiles'],
|
|
195
|
+
...(Array.isArray(atlasSizes)
|
|
196
|
+
? { atlasSizes: atlasSizes as NonNullable<TilesetAsset['atlasSizes']> }
|
|
197
|
+
: {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function codecProfile(codec: AssetCodec | undefined): string | undefined {
|
|
202
|
+
return codec?.profile;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export type CodecContextFailure =
|
|
206
|
+
| {
|
|
207
|
+
readonly code: 'basis-profile-unsupported';
|
|
208
|
+
readonly detail: { readonly profile: string };
|
|
209
|
+
}
|
|
210
|
+
| {
|
|
211
|
+
readonly code: 'ktx2-color-space-mismatch';
|
|
212
|
+
readonly detail: {
|
|
213
|
+
readonly authoredColorSpace: 'srgb' | 'linear';
|
|
214
|
+
readonly dfdColorSpace: 'srgb' | 'linear' | 'unknown';
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
function projectCodecFailureDetail(
|
|
219
|
+
input: PackLoaderInput,
|
|
220
|
+
codec: AssetCodec,
|
|
221
|
+
caps: LoadContext['transcodeCaps'],
|
|
222
|
+
failure: CodecFailureProjection,
|
|
223
|
+
targetFormat?: GPUTextureFormat,
|
|
224
|
+
): AssetCodecFailureDetail {
|
|
225
|
+
return {
|
|
226
|
+
sourcePath: input.guid,
|
|
227
|
+
codecCode: failure.code,
|
|
228
|
+
codecExpected: failure.expected,
|
|
229
|
+
codecHint: failure.hint,
|
|
230
|
+
codecDetail: failure.detail,
|
|
231
|
+
container: codec.container ?? 'basis',
|
|
232
|
+
...(codec.profile === undefined ? {} : { profile: codec.profile }),
|
|
233
|
+
...(targetFormat === undefined ? {} : { targetFormat }),
|
|
234
|
+
capabilities: caps,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function codecFailure(
|
|
239
|
+
input: PackLoaderInput,
|
|
240
|
+
codec: AssetCodec,
|
|
241
|
+
caps: LoadContext['transcodeCaps'],
|
|
242
|
+
failure: CodecError['error'],
|
|
243
|
+
targetFormat?: GPUTextureFormat,
|
|
244
|
+
): AssetError {
|
|
245
|
+
const detail = projectCodecFailureDetail(input, codec, caps, failure, targetFormat);
|
|
246
|
+
return new AssetError({
|
|
247
|
+
code: 'asset-parse-failed',
|
|
248
|
+
expected: failure.expected,
|
|
249
|
+
hint: failure.hint,
|
|
250
|
+
detail,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function codecContextFailure(
|
|
255
|
+
input: PackLoaderInput,
|
|
256
|
+
codec: AssetCodec,
|
|
257
|
+
caps: LoadContext['transcodeCaps'],
|
|
258
|
+
failure: CodecContextFailure,
|
|
259
|
+
expected: string,
|
|
260
|
+
hint: string,
|
|
261
|
+
targetFormat?: GPUTextureFormat,
|
|
262
|
+
): AssetError {
|
|
263
|
+
const detail = projectCodecFailureDetail(
|
|
264
|
+
input,
|
|
265
|
+
codec,
|
|
266
|
+
caps,
|
|
267
|
+
{
|
|
268
|
+
code: failure.code,
|
|
269
|
+
expected,
|
|
270
|
+
hint,
|
|
271
|
+
detail: failure.detail,
|
|
272
|
+
},
|
|
273
|
+
targetFormat,
|
|
274
|
+
);
|
|
275
|
+
return new AssetError({
|
|
276
|
+
code: 'asset-parse-failed',
|
|
277
|
+
expected,
|
|
278
|
+
hint,
|
|
279
|
+
detail,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function transcodeModel(profile: string): TranscodeModel | undefined {
|
|
284
|
+
if (profile === 'etc1s') return 'etc1s';
|
|
285
|
+
if (profile === 'uastc' || profile === 'uastc-ldr') return 'uastc-ldr';
|
|
286
|
+
if (profile === 'uastc-hdr') return 'uastc-hdr';
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function loadTexturePack(
|
|
291
|
+
input: PackLoaderInput,
|
|
292
|
+
ctx: LoadContext,
|
|
293
|
+
): Promise<LoaderAsyncResult<TextureAsset>> {
|
|
294
|
+
const artifact = firstArtifact(input);
|
|
295
|
+
if (artifact === undefined)
|
|
296
|
+
return invalidPackAsset<TextureAsset>(input, 'texture asset-local image artifact');
|
|
297
|
+
const payload = input.payload;
|
|
298
|
+
const width = payloadNumber(payload, 'width', 0);
|
|
299
|
+
const height = payloadNumber(payload, 'height', 0);
|
|
300
|
+
const colorSpace = payloadColorSpace(payload);
|
|
301
|
+
const codec = artifact.descriptor.assetCodec;
|
|
302
|
+
const profile = codecProfile(codec);
|
|
303
|
+
const model =
|
|
304
|
+
codec?.name === 'basis' && profile !== undefined ? transcodeModel(profile) : undefined;
|
|
305
|
+
|
|
306
|
+
if (codec?.name === 'basis' && codec.container === 'basis') {
|
|
307
|
+
const basisCodec = codec;
|
|
308
|
+
if (model === undefined) {
|
|
309
|
+
return {
|
|
310
|
+
ok: false,
|
|
311
|
+
error: codecContextFailure(
|
|
312
|
+
input,
|
|
313
|
+
basisCodec,
|
|
314
|
+
ctx.transcodeCaps,
|
|
315
|
+
{
|
|
316
|
+
code: 'basis-profile-unsupported',
|
|
317
|
+
detail: { profile: codec.profile ?? 'missing' },
|
|
318
|
+
},
|
|
319
|
+
'raw Basis artifact with an explicit ETC1S or UASTC-LDR profile',
|
|
320
|
+
'set assetCodec.profile to etc1s or uastc-ldr and re-cook the source',
|
|
321
|
+
),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const { selectTranscodeTarget, transcodeBasis } = await import('@forgeax/engine-codec');
|
|
326
|
+
const target = selectTranscodeTarget(
|
|
327
|
+
{ model, srgb: colorSpace === 'srgb', channels: 'rgba' },
|
|
328
|
+
ctx.transcodeCaps,
|
|
329
|
+
);
|
|
330
|
+
const transcoded = await transcodeBasis(artifact.bytes, target);
|
|
331
|
+
if (!transcoded.ok) {
|
|
332
|
+
return {
|
|
333
|
+
ok: false,
|
|
334
|
+
error: codecFailure(input, basisCodec, ctx.transcodeCaps, transcoded.error, target),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const data = new Uint8Array(
|
|
338
|
+
transcoded.value.mips.reduce((size, mip) => size + mip.data.length, 0),
|
|
339
|
+
);
|
|
340
|
+
let offset = 0;
|
|
341
|
+
for (const mip of transcoded.value.mips) {
|
|
342
|
+
data.set(mip.data, offset);
|
|
343
|
+
offset += mip.data.length;
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
ok: true,
|
|
347
|
+
value: {
|
|
348
|
+
kind: 'texture',
|
|
349
|
+
width: transcoded.value.width,
|
|
350
|
+
height: transcoded.value.height,
|
|
351
|
+
format: target,
|
|
352
|
+
data,
|
|
353
|
+
colorSpace,
|
|
354
|
+
mipmap: transcoded.value.mips.length > 1,
|
|
355
|
+
mipLevelCount: Math.max(1, transcoded.value.mips.length),
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
} catch (error) {
|
|
359
|
+
return {
|
|
360
|
+
ok: false,
|
|
361
|
+
error: new AssetError({
|
|
362
|
+
code: 'asset-fetch-failed',
|
|
363
|
+
expected: 'loadable raw Basis texture artifact',
|
|
364
|
+
hint: error instanceof Error ? error.message : String(error),
|
|
365
|
+
detail: {
|
|
366
|
+
sourcePath: input.guid,
|
|
367
|
+
codecCode: 'runtime-loader-exception',
|
|
368
|
+
codecExpected: 'loadable raw Basis texture artifact',
|
|
369
|
+
codecHint: error instanceof Error ? error.message : String(error),
|
|
370
|
+
codecDetail: { reason: 'runtime-loader-exception' },
|
|
371
|
+
container: basisCodec.container ?? 'basis',
|
|
372
|
+
...(basisCodec.profile === undefined ? {} : { profile: basisCodec.profile }),
|
|
373
|
+
capabilities: ctx.transcodeCaps,
|
|
374
|
+
},
|
|
375
|
+
}),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (model !== undefined && codec !== undefined) {
|
|
381
|
+
const ktx2Codec = codec;
|
|
382
|
+
try {
|
|
383
|
+
const { ktx2ColorSpace, parseKtx2, selectTranscodeTarget, transcodeKtx2 } = await import(
|
|
384
|
+
'@forgeax/engine-codec'
|
|
385
|
+
);
|
|
386
|
+
const parsed = await parseKtx2(artifact.bytes);
|
|
387
|
+
if (!parsed.ok) {
|
|
388
|
+
return {
|
|
389
|
+
ok: false,
|
|
390
|
+
error: codecFailure(input, ktx2Codec, ctx.transcodeCaps, parsed.error),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const projectedColorSpace = ktx2ColorSpace(parsed.value);
|
|
394
|
+
if (projectedColorSpace === undefined || projectedColorSpace !== colorSpace) {
|
|
395
|
+
return {
|
|
396
|
+
ok: false,
|
|
397
|
+
error: codecContextFailure(
|
|
398
|
+
input,
|
|
399
|
+
ktx2Codec,
|
|
400
|
+
ctx.transcodeCaps,
|
|
401
|
+
{
|
|
402
|
+
code: 'ktx2-color-space-mismatch',
|
|
403
|
+
detail: {
|
|
404
|
+
authoredColorSpace: colorSpace,
|
|
405
|
+
dfdColorSpace: projectedColorSpace ?? 'unknown',
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
'Basis KTX2 DFD transfer function matching the texture colorSpace',
|
|
409
|
+
'align the texture payload colorSpace with the KTX2 DFD and re-cook the source',
|
|
410
|
+
),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
const target = selectTranscodeTarget(
|
|
414
|
+
{ model, srgb: colorSpace === 'srgb', channels: 'rgba' },
|
|
415
|
+
ctx.transcodeCaps,
|
|
416
|
+
);
|
|
417
|
+
const transcoded = await transcodeKtx2(parsed.value, target);
|
|
418
|
+
if (!transcoded.ok) {
|
|
419
|
+
return {
|
|
420
|
+
ok: false,
|
|
421
|
+
error: codecFailure(input, ktx2Codec, ctx.transcodeCaps, transcoded.error, target),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const data = new Uint8Array(
|
|
425
|
+
transcoded.value.mips.reduce((size, mip) => size + mip.data.length, 0),
|
|
426
|
+
);
|
|
427
|
+
let offset = 0;
|
|
428
|
+
for (const mip of transcoded.value.mips) {
|
|
429
|
+
data.set(mip.data, offset);
|
|
430
|
+
offset += mip.data.length;
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
ok: true,
|
|
434
|
+
value: {
|
|
435
|
+
kind: 'texture',
|
|
436
|
+
width: transcoded.value.width,
|
|
437
|
+
height: transcoded.value.height,
|
|
438
|
+
format: target,
|
|
439
|
+
data,
|
|
440
|
+
colorSpace,
|
|
441
|
+
mipmap: transcoded.value.mips.length > 1,
|
|
442
|
+
mipLevelCount: Math.max(1, transcoded.value.mips.length),
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (error instanceof AssetError) return { ok: false, error };
|
|
447
|
+
return {
|
|
448
|
+
ok: false,
|
|
449
|
+
error: new AssetError({
|
|
450
|
+
code: 'asset-fetch-failed',
|
|
451
|
+
expected: 'loadable Basis KTX2 texture artifact',
|
|
452
|
+
hint: error instanceof Error ? error.message : String(error),
|
|
453
|
+
detail: {
|
|
454
|
+
sourcePath: input.guid,
|
|
455
|
+
codecCode: 'runtime-loader-exception',
|
|
456
|
+
codecExpected: 'loadable Basis KTX2 texture artifact',
|
|
457
|
+
codecHint: error instanceof Error ? error.message : String(error),
|
|
458
|
+
codecDetail: { reason: 'runtime-loader-exception' },
|
|
459
|
+
container: ktx2Codec.container ?? 'ktx2',
|
|
460
|
+
...(ktx2Codec.profile === undefined ? {} : { profile: ktx2Codec.profile }),
|
|
461
|
+
capabilities: ctx.transcodeCaps,
|
|
462
|
+
},
|
|
463
|
+
}),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const mipmap = payload.mipmap === true;
|
|
469
|
+
return {
|
|
470
|
+
ok: true,
|
|
471
|
+
value: {
|
|
472
|
+
kind: 'texture',
|
|
473
|
+
width,
|
|
474
|
+
height,
|
|
475
|
+
format: (payload.format ??
|
|
476
|
+
(colorSpace === 'srgb' ? 'rgba8unorm-srgb' : 'rgba8unorm')) as GPUTextureFormat,
|
|
477
|
+
data: artifact.bytes,
|
|
478
|
+
colorSpace,
|
|
479
|
+
mipmap,
|
|
480
|
+
mipLevelCount: mipmap ? numMipLevels({ width, height }) : 1,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async function loadEquirectPack(input: PackLoaderInput): Promise<LoaderAsyncResult<EquirectAsset>> {
|
|
486
|
+
const artifact = firstArtifact(input);
|
|
487
|
+
if (artifact === undefined)
|
|
488
|
+
return invalidPackAsset<EquirectAsset>(input, 'equirect asset-local image artifact');
|
|
489
|
+
const payload = input.payload;
|
|
490
|
+
return {
|
|
491
|
+
ok: true,
|
|
492
|
+
value: {
|
|
493
|
+
kind: 'equirect',
|
|
494
|
+
width: payloadNumber(payload, 'width', 0),
|
|
495
|
+
height: payloadNumber(payload, 'height', 0),
|
|
496
|
+
format: (payload.format ?? 'rgba16float') as GPUTextureFormat,
|
|
497
|
+
data: artifact.bytes,
|
|
498
|
+
colorSpace: payloadColorSpace(payload),
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function parseGlyphs(value: unknown): FontAsset['glyphs'] {
|
|
504
|
+
if (typeof value !== 'object' || value === null) return {};
|
|
505
|
+
const glyphs: FontAsset['glyphs'] = {};
|
|
506
|
+
for (const [codepoint, raw] of Object.entries(value as Record<string, unknown>)) {
|
|
507
|
+
if (typeof raw !== 'object' || raw === null) continue;
|
|
508
|
+
const metric = raw as Record<string, unknown>;
|
|
509
|
+
const size = metric.size as Record<string, unknown> | undefined;
|
|
510
|
+
const region = metric.region as Record<string, unknown> | undefined;
|
|
511
|
+
if (
|
|
512
|
+
typeof metric.advance !== 'number' ||
|
|
513
|
+
typeof metric.bearingX !== 'number' ||
|
|
514
|
+
typeof metric.bearingY !== 'number' ||
|
|
515
|
+
typeof size?.w !== 'number' ||
|
|
516
|
+
typeof size.h !== 'number' ||
|
|
517
|
+
typeof region?.x !== 'number' ||
|
|
518
|
+
typeof region.y !== 'number' ||
|
|
519
|
+
typeof region.w !== 'number' ||
|
|
520
|
+
typeof region.h !== 'number'
|
|
521
|
+
) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
glyphs[Number(codepoint)] = {
|
|
525
|
+
advance: metric.advance,
|
|
526
|
+
bearingX: metric.bearingX,
|
|
527
|
+
bearingY: metric.bearingY,
|
|
528
|
+
size: { w: size.w, h: size.h },
|
|
529
|
+
region: { x: region.x, y: region.y, w: region.w, h: region.h },
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return glyphs;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async function loadFontPack(
|
|
536
|
+
input: PackLoaderInput,
|
|
537
|
+
ctx: LoadContext,
|
|
538
|
+
): Promise<LoaderAsyncResult<FontAsset>> {
|
|
539
|
+
const payload = input.payload;
|
|
540
|
+
const atlasGuid = payload.atlasGuid;
|
|
541
|
+
const samplerGuid = payload.samplerGuid;
|
|
542
|
+
const common = payload.common;
|
|
543
|
+
if (
|
|
544
|
+
typeof atlasGuid !== 'string' ||
|
|
545
|
+
typeof samplerGuid !== 'string' ||
|
|
546
|
+
typeof common !== 'object' ||
|
|
547
|
+
common === null
|
|
548
|
+
) {
|
|
549
|
+
return invalidPackAsset<FontAsset>(
|
|
550
|
+
input,
|
|
551
|
+
'font payload with atlasGuid, samplerGuid, and common',
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
const parsedAtlas = AssetGuid.parse(atlasGuid);
|
|
555
|
+
const parsedSampler = AssetGuid.parse(samplerGuid);
|
|
556
|
+
if (!parsedAtlas.ok) return { ok: false, error: parsedAtlas.error };
|
|
557
|
+
if (!parsedSampler.ok) return { ok: false, error: parsedSampler.error };
|
|
558
|
+
const atlasResolved = await ctx.resolveRef(atlasGuid);
|
|
559
|
+
if (!atlasResolved.ok) return atlasResolved;
|
|
560
|
+
const samplerResolved = await ctx.resolveRef(samplerGuid);
|
|
561
|
+
if (!samplerResolved.ok) return samplerResolved;
|
|
562
|
+
const commonRecord = common as Record<string, unknown>;
|
|
563
|
+
const commonFields = [
|
|
564
|
+
'lineHeight',
|
|
565
|
+
'base',
|
|
566
|
+
'distanceRange',
|
|
567
|
+
'pxRange',
|
|
568
|
+
'atlasWidth',
|
|
569
|
+
'atlasHeight',
|
|
570
|
+
];
|
|
571
|
+
if (commonFields.some((key) => typeof commonRecord[key] !== 'number')) {
|
|
572
|
+
return invalidPackAsset<FontAsset>(input, 'font common block with numeric layout fields');
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
ok: true,
|
|
576
|
+
value: {
|
|
577
|
+
kind: 'font',
|
|
578
|
+
atlas: parsedAtlas.value,
|
|
579
|
+
sampler: parsedSampler.value,
|
|
580
|
+
glyphs: parseGlyphs(payload.glyphs),
|
|
581
|
+
common: {
|
|
582
|
+
lineHeight: commonRecord.lineHeight as number,
|
|
583
|
+
base: commonRecord.base as number,
|
|
584
|
+
distanceRange: commonRecord.distanceRange as number,
|
|
585
|
+
pxRange: commonRecord.pxRange as number,
|
|
586
|
+
atlasWidth: commonRecord.atlasWidth as number,
|
|
587
|
+
atlasHeight: commonRecord.atlasHeight as number,
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export const textureLoader: Loader = {
|
|
594
|
+
kind: 'texture',
|
|
595
|
+
load: () => undefined,
|
|
596
|
+
loadPack: loadTexturePack,
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
export const equirectLoader: Loader = {
|
|
600
|
+
kind: 'equirect',
|
|
601
|
+
load: () => undefined,
|
|
602
|
+
loadPack: loadEquirectPack,
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
export const fontLoader: Loader = {
|
|
606
|
+
kind: 'font',
|
|
607
|
+
load: () => undefined,
|
|
608
|
+
loadPack: loadFontPack,
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
export const renderPipelineLoader: Loader = {
|
|
612
|
+
kind: 'render-pipeline',
|
|
613
|
+
load: (payload) => renderPipelineDescriptor({ ...payload, kind: 'render-pipeline' }),
|
|
614
|
+
loadPack: async (input) => {
|
|
615
|
+
if (firstArtifact(input) === undefined) {
|
|
616
|
+
const value = renderPipelineDescriptor({ ...input.payload, kind: 'render-pipeline' });
|
|
617
|
+
return value === undefined
|
|
618
|
+
? invalidPackAsset<RenderPipelineAsset>(input, 'a valid render-pipeline descriptor')
|
|
619
|
+
: { ok: true, value };
|
|
620
|
+
}
|
|
621
|
+
const payload = readJsonArtifact(input, 'render-pipeline');
|
|
622
|
+
const value = payload === undefined ? undefined : renderPipelineDescriptor(payload);
|
|
623
|
+
return payload === undefined
|
|
624
|
+
? invalidPackAsset<RenderPipelineAsset>(input, 'render-pipeline JSON descriptor artifact')
|
|
625
|
+
: value === undefined
|
|
626
|
+
? invalidPackAsset<RenderPipelineAsset>(input, 'a complete render-pipeline descriptor')
|
|
627
|
+
: { ok: true, value };
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
export const tilesetLoader: Loader = {
|
|
632
|
+
kind: 'tileset',
|
|
633
|
+
load: (payload, refs) => {
|
|
634
|
+
const rawAtlases = payload.atlases;
|
|
635
|
+
const resolved = Array.isArray(rawAtlases)
|
|
636
|
+
? rawAtlases.map((value) => (typeof value === 'number' ? refs?.[value] : value))
|
|
637
|
+
: undefined;
|
|
638
|
+
return resolved === undefined || resolved.some((value) => typeof value !== 'string')
|
|
639
|
+
? undefined
|
|
640
|
+
: tilesetDescriptor({ ...payload, kind: 'tileset', atlases: resolved }, resolved);
|
|
641
|
+
},
|
|
642
|
+
loadPack: async (input) => {
|
|
643
|
+
if (firstArtifact(input) === undefined) {
|
|
644
|
+
const value = tilesetDescriptor({ ...input.payload, kind: 'tileset' }, input.refs);
|
|
645
|
+
return value === undefined
|
|
646
|
+
? invalidPackAsset<TilesetAsset>(input, 'a valid tileset descriptor')
|
|
647
|
+
: { ok: true, value };
|
|
648
|
+
}
|
|
649
|
+
const payload = readJsonArtifact(input, 'tileset');
|
|
650
|
+
const value = payload === undefined ? undefined : tilesetDescriptor(payload, input.refs);
|
|
651
|
+
return payload === undefined
|
|
652
|
+
? invalidPackAsset<TilesetAsset>(input, 'tileset JSON descriptor artifact')
|
|
653
|
+
: value === undefined
|
|
654
|
+
? invalidPackAsset<TilesetAsset>(input, 'a tileset descriptor matching envelope refs')
|
|
655
|
+
: { ok: true, value };
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
export const PACK_ARTIFACT_LOADERS: readonly Loader[] = [
|
|
660
|
+
textureLoader,
|
|
661
|
+
fontLoader,
|
|
662
|
+
equirectLoader,
|
|
663
|
+
renderPipelineLoader,
|
|
664
|
+
tilesetLoader,
|
|
665
|
+
];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class MaterialDependencyGraph {
|
|
2
|
+
readonly #dependencies = new Map<string, Set<string>>();
|
|
3
|
+
readonly #dependents = new Map<string, Set<string>>();
|
|
4
|
+
readonly #invalidated = new Set<string>();
|
|
5
|
+
|
|
6
|
+
link(materialGuid: string, dependencies: readonly string[]): void {
|
|
7
|
+
const previous = this.#dependencies.get(materialGuid) ?? new Set<string>();
|
|
8
|
+
for (const dependency of previous) this.#dependents.get(dependency)?.delete(materialGuid);
|
|
9
|
+
const next = new Set(dependencies);
|
|
10
|
+
this.#dependencies.set(materialGuid, next);
|
|
11
|
+
for (const dependency of next) {
|
|
12
|
+
const materials = this.#dependents.get(dependency) ?? new Set<string>();
|
|
13
|
+
materials.add(materialGuid);
|
|
14
|
+
this.#dependents.set(dependency, materials);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
dependentsOf(dependency: string): readonly string[] {
|
|
19
|
+
return [...(this.#dependents.get(dependency) ?? [])].sort();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
invalidate(dependencies: readonly string[]): readonly string[] {
|
|
23
|
+
const queue = [...dependencies];
|
|
24
|
+
const visited = new Set<string>();
|
|
25
|
+
const result = new Set<string>();
|
|
26
|
+
while (queue.length > 0) {
|
|
27
|
+
const dependency = queue.shift();
|
|
28
|
+
if (dependency === undefined || visited.has(dependency)) continue;
|
|
29
|
+
visited.add(dependency);
|
|
30
|
+
for (const material of this.#dependents.get(dependency) ?? []) {
|
|
31
|
+
if (!result.has(material)) queue.push(material);
|
|
32
|
+
result.add(material);
|
|
33
|
+
this.#invalidated.add(material);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [...result].sort();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
isInvalidated(materialGuid: string): boolean {
|
|
40
|
+
return this.#invalidated.has(materialGuid);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
clear(materialGuid: string): void {
|
|
44
|
+
this.#invalidated.delete(materialGuid);
|
|
45
|
+
}
|
|
46
|
+
}
|