@forgeax/engine-runtime 0.1.19 → 0.1.21
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 +102 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/create-renderer-gpu-pass-timing.unit.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-gpu-pass-timing.unit.test.d.ts.map +1 -0
- package/dist/__tests__/helpers/tilemap-assets.d.ts.map +1 -1
- package/dist/__tests__/lights-preservation.fixup.test.d.ts +2 -0
- package/dist/__tests__/lights-preservation.fixup.test.d.ts.map +1 -0
- package/dist/__tests__/material-texture-dimension-mismatch.unit.test.d.ts +2 -0
- package/dist/__tests__/material-texture-dimension-mismatch.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
- package/dist/__tests__/texture-dimensions.browser.test.d.ts +2 -0
- package/dist/__tests__/texture-dimensions.browser.test.d.ts.map +1 -0
- package/dist/__tests__/volumetric-fog-record-recovery.integration.test.d.ts +2 -0
- package/dist/__tests__/volumetric-fog-record-recovery.integration.test.d.ts.map +1 -0
- package/dist/createRenderer.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +3 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +30 -30
- package/src/__tests__/active-camera.unit.test.ts +6 -8
- package/src/__tests__/asset-registry.recursive.spec.ts +13 -19
- package/src/__tests__/asset.unit.test.ts +28 -25
- package/src/__tests__/basis-catalog-dispatch.integration.test.ts +10 -9
- package/src/__tests__/clamp-to-last.e2e.browser.test.ts +2 -2
- package/src/__tests__/components.test-d.ts +10 -3
- package/src/__tests__/components.unit.test.ts +13 -7
- package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +12 -10
- package/src/__tests__/create-renderer-assembly.integration.test.ts +24 -0
- package/src/__tests__/create-renderer-gpu-pass-timing.unit.test.ts +32 -0
- package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +66 -0
- package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +11 -4
- package/src/__tests__/dawn/instances-shadow.dawn.test.ts +15 -2
- package/src/__tests__/dawn/material-alpha.dawn.test.ts +2 -3
- package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +2 -3
- package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +2 -3
- package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +2 -3
- package/src/__tests__/dawn/upload-texture.dawn.test.ts +4 -6
- package/src/__tests__/derive-bgl-integration.test.ts +11 -0
- package/src/__tests__/equirect-bc6h.integration.test.ts +6 -4
- package/src/__tests__/errors.unit.test.ts +71 -20
- package/src/__tests__/extract-frames-merge.test.ts +48 -40
- package/src/__tests__/extract-skin-mismatch.unit.test.ts +6 -2
- package/src/__tests__/four-path-compression.integration.test.ts +2 -2
- package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +13 -0
- package/src/__tests__/geometry.unit.test.ts +7 -10
- package/src/__tests__/gpu-resource-cow-survivor.test.ts +2 -3
- package/src/__tests__/helpers/tilemap-assets.ts +3 -4
- package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +6 -3
- package/src/__tests__/light-snapshot.test-d.ts +8 -0
- package/src/__tests__/lights-preservation.fixup.test.ts +127 -0
- package/src/__tests__/lights.unit.test.ts +75 -2214
- package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +3 -4
- package/src/__tests__/loader-name-survival.test.ts +2 -3
- package/src/__tests__/material-texture-dimension-mismatch.unit.test.ts +18 -0
- package/src/__tests__/material-texture-uv-scale.unit.test.ts +10 -2
- package/src/__tests__/mip-gate.unit.test.ts +11 -8
- package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +2 -3
- package/src/__tests__/new-kind-refs-contract.test.ts +2 -3
- package/src/__tests__/pbr-pipeline.unit.test.ts +29 -5
- package/src/__tests__/pipeline.unit.test.ts +66 -221
- package/src/__tests__/point-light-shadow.browser.test.ts +208 -83
- package/src/__tests__/point-light-shadow.unit.test.ts +67 -9
- package/src/__tests__/render-error-exhaustive.test-d.ts +92 -9
- package/src/__tests__/render-system-extract.test.ts +4 -6
- package/src/__tests__/render-system-mega.test.ts +30 -41
- package/src/__tests__/render-system-record-multi-material-textureview.test.ts +8 -7
- package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +6 -4
- package/src/__tests__/render-system-skin-bg.unit.test.ts +14 -9
- package/src/__tests__/renderer-recover.unit.test.ts +8 -0
- package/src/__tests__/renderer-surface.unit.test.ts +229 -3
- package/src/__tests__/shadow-csm-atlas.test.ts +41 -0
- package/src/__tests__/shadow-csm-component.test.ts +31 -24
- package/src/__tests__/shadow-csm-extract.test.ts +156 -47
- package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +72 -0
- package/src/__tests__/shadow-csm-shader.dawn.test.ts +19 -0
- package/src/__tests__/shadow-csm-ubo.test.ts +41 -45
- package/src/__tests__/shadow-csm-viewport.browser.test.ts +29 -0
- package/src/__tests__/shadow-fields-observable.dawn.test.ts +140 -58
- package/src/__tests__/skinned-shadow-mixed.dawn.test.ts +6 -2
- package/src/__tests__/skylight-lazy-projection.test.ts +8 -5
- package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +4 -6
- package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +4 -2
- package/src/__tests__/ssao-bgl.test.ts +10 -16
- package/src/__tests__/ssao-passes.test.ts +19 -2
- package/src/__tests__/systems.unit.test.ts +59 -33
- package/src/__tests__/texture-dimensions.browser.test.ts +127 -0
- package/src/__tests__/transcode-fallback.unit.test.ts +5 -1
- package/src/__tests__/view-ubo-layout.browser.test.ts +20 -10
- package/src/__tests__/volumetric-fog-record-recovery.integration.test.ts +180 -0
- package/src/createRenderer.ts +4 -0
- package/src/index.ts +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -26,37 +26,37 @@
|
|
|
26
26
|
"LICENSE"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@forgeax/engine-animation": "0.1.
|
|
30
|
-
"@forgeax/engine-assets-runtime": "0.1.
|
|
31
|
-
"@forgeax/engine-audio-webaudio": "0.1.
|
|
32
|
-
"@forgeax/engine-codec": "0.1.
|
|
33
|
-
"@forgeax/engine-debug-draw": "0.1.
|
|
34
|
-
"@forgeax/engine-ecs": "0.1.
|
|
35
|
-
"@forgeax/engine-font": "0.1.
|
|
36
|
-
"@forgeax/engine-geometry": "0.1.
|
|
37
|
-
"@forgeax/engine-graphics-extras": "0.1.
|
|
38
|
-
"@forgeax/engine-image": "0.1.
|
|
39
|
-
"@forgeax/engine-input": "0.1.
|
|
40
|
-
"@forgeax/engine-math": "0.1.
|
|
41
|
-
"@forgeax/engine-pack": "0.1.
|
|
42
|
-
"@forgeax/engine-profiler": "0.1.
|
|
43
|
-
"@forgeax/engine-render": "0.1.
|
|
44
|
-
"@forgeax/engine-render-graph": "0.1.
|
|
45
|
-
"@forgeax/engine-rhi": "0.1.
|
|
46
|
-
"@forgeax/engine-rhi-null": "0.1.
|
|
47
|
-
"@forgeax/engine-rhi-webgpu": "0.1.
|
|
48
|
-
"@forgeax/engine-rhi-wgpu": "0.1.
|
|
49
|
-
"@forgeax/engine-scene": "0.1.
|
|
50
|
-
"@forgeax/engine-shader": "0.1.
|
|
51
|
-
"@forgeax/engine-skinning": "0.1.
|
|
52
|
-
"@forgeax/engine-types": "0.1.
|
|
53
|
-
"@forgeax/engine-vfx": "0.1.
|
|
29
|
+
"@forgeax/engine-animation": "0.1.21",
|
|
30
|
+
"@forgeax/engine-assets-runtime": "0.1.21",
|
|
31
|
+
"@forgeax/engine-audio-webaudio": "0.1.21",
|
|
32
|
+
"@forgeax/engine-codec": "0.1.21",
|
|
33
|
+
"@forgeax/engine-debug-draw": "0.1.21",
|
|
34
|
+
"@forgeax/engine-ecs": "0.1.21",
|
|
35
|
+
"@forgeax/engine-font": "0.1.21",
|
|
36
|
+
"@forgeax/engine-geometry": "0.1.21",
|
|
37
|
+
"@forgeax/engine-graphics-extras": "0.1.21",
|
|
38
|
+
"@forgeax/engine-image": "0.1.21",
|
|
39
|
+
"@forgeax/engine-input": "0.1.21",
|
|
40
|
+
"@forgeax/engine-math": "0.1.21",
|
|
41
|
+
"@forgeax/engine-pack": "0.1.21",
|
|
42
|
+
"@forgeax/engine-profiler": "0.1.21",
|
|
43
|
+
"@forgeax/engine-render": "0.1.21",
|
|
44
|
+
"@forgeax/engine-render-graph": "0.1.21",
|
|
45
|
+
"@forgeax/engine-rhi": "0.1.21",
|
|
46
|
+
"@forgeax/engine-rhi-null": "0.1.21",
|
|
47
|
+
"@forgeax/engine-rhi-webgpu": "0.1.21",
|
|
48
|
+
"@forgeax/engine-rhi-wgpu": "0.1.21",
|
|
49
|
+
"@forgeax/engine-scene": "0.1.21",
|
|
50
|
+
"@forgeax/engine-shader": "0.1.21",
|
|
51
|
+
"@forgeax/engine-skinning": "0.1.21",
|
|
52
|
+
"@forgeax/engine-types": "0.1.21",
|
|
53
|
+
"@forgeax/engine-vfx": "0.1.21"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@forgeax/engine-import": "0.1.
|
|
57
|
-
"@forgeax/engine-naga": "0.1.
|
|
58
|
-
"@forgeax/engine-state": "0.1.
|
|
59
|
-
"@forgeax/engine-vite-plugin-shader": "0.1.
|
|
56
|
+
"@forgeax/engine-import": "0.1.21",
|
|
57
|
+
"@forgeax/engine-naga": "0.1.21",
|
|
58
|
+
"@forgeax/engine-state": "0.1.21",
|
|
59
|
+
"@forgeax/engine-vite-plugin-shader": "0.1.21",
|
|
60
60
|
"@types/node": "^20.14.0"
|
|
61
61
|
},
|
|
62
62
|
"forgeax": {
|
|
@@ -147,16 +147,14 @@ describe('w7 — getActiveCamera / setActiveCamera', () => {
|
|
|
147
147
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
148
148
|
|
|
149
149
|
describe('w7 — extractFrame ActiveCamera selection', () => {
|
|
150
|
-
it('(2) no ActiveCamera + multiple cameras ->
|
|
150
|
+
it('(2) no ActiveCamera + multiple display cameras -> first display camera surfaces', () => {
|
|
151
151
|
const world = new World();
|
|
152
152
|
const assets = makeAssets();
|
|
153
153
|
spawnCamera(world, 1.0);
|
|
154
154
|
spawnCamera(world, 2.0);
|
|
155
155
|
const frame = extractFrame(world, prepareExtractContext(world, { assets }));
|
|
156
|
-
// No selection ->
|
|
157
|
-
|
|
158
|
-
// does NOT prune when there is no ActiveCamera.
|
|
159
|
-
expect(frame.cameras.length).toBe(2);
|
|
156
|
+
// No selection -> the first display camera remains the display authority.
|
|
157
|
+
expect(frame.cameras.length).toBe(1);
|
|
160
158
|
});
|
|
161
159
|
|
|
162
160
|
it('(1) ActiveCamera pointing at 2nd camera -> only that camera surfaces', () => {
|
|
@@ -171,15 +169,15 @@ describe('w7 — extractFrame ActiveCamera selection', () => {
|
|
|
171
169
|
expect(frame.cameras[0]?.fov).toBeCloseTo(2.0, 5);
|
|
172
170
|
});
|
|
173
171
|
|
|
174
|
-
it('(3) ActiveCamera pointing at non-existent entity -> fallback to first
|
|
172
|
+
it('(3) ActiveCamera pointing at non-existent entity -> fallback to first display camera', () => {
|
|
175
173
|
const world = new World();
|
|
176
174
|
const assets = makeAssets();
|
|
177
175
|
spawnCamera(world, 1.0);
|
|
178
176
|
spawnCamera(world, 2.0);
|
|
179
177
|
setActiveCamera(world, 999999);
|
|
180
178
|
const frame = extractFrame(world, prepareExtractContext(world, { assets }));
|
|
181
|
-
// Unresolvable -> behave as if no ActiveCamera
|
|
182
|
-
expect(frame.cameras.length).toBe(
|
|
179
|
+
// Unresolvable -> behave as if no ActiveCamera.
|
|
180
|
+
expect(frame.cameras.length).toBe(1);
|
|
183
181
|
});
|
|
184
182
|
|
|
185
183
|
it('(4) single camera + ActiveCamera pointing at it -> behavior unchanged (one camera)', () => {
|
|
@@ -374,12 +374,11 @@ describe('mesh default material ready boundary', () => {
|
|
|
374
374
|
if (refs.length > 0) {
|
|
375
375
|
reg.catalog(parseGuid(WRONG_DEFAULT_KIND_GUID), {
|
|
376
376
|
kind: 'texture' as const,
|
|
377
|
-
width: 1,
|
|
378
|
-
height: 1,
|
|
377
|
+
shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
|
|
379
378
|
format: 'rgba8unorm' as const,
|
|
380
379
|
data: new Uint8Array([255, 255, 255, 255]),
|
|
381
380
|
colorSpace: 'srgb' as const,
|
|
382
|
-
|
|
381
|
+
mips: { kind: 'none' as const },
|
|
383
382
|
});
|
|
384
383
|
}
|
|
385
384
|
const omittedDefaultGuid = 'b0000000-0000-4000-b000-000000000099';
|
|
@@ -445,12 +444,11 @@ describe('mesh default material ready boundary', () => {
|
|
|
445
444
|
const reg = makeRegistry();
|
|
446
445
|
reg.catalog(parseGuid(WRONG_DEFAULT_KIND_GUID), {
|
|
447
446
|
kind: 'texture' as const,
|
|
448
|
-
width: 1,
|
|
449
|
-
height: 1,
|
|
447
|
+
shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
|
|
450
448
|
format: 'rgba8unorm' as const,
|
|
451
449
|
data: new Uint8Array([255, 255, 255, 255]),
|
|
452
450
|
colorSpace: 'srgb' as const,
|
|
453
|
-
|
|
451
|
+
mips: { kind: 'none' as const },
|
|
454
452
|
});
|
|
455
453
|
|
|
456
454
|
const packIndex = [
|
|
@@ -740,21 +738,19 @@ describe('AC-02 — material recursive loadByGuid', () => {
|
|
|
740
738
|
// Pre-register textures in dev mode (fast-path compatible)
|
|
741
739
|
reg.catalog(parseGuid(TEXTURE_A_GUID), {
|
|
742
740
|
kind: 'texture' as const,
|
|
743
|
-
width: 4,
|
|
744
|
-
height: 4,
|
|
741
|
+
shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
|
|
745
742
|
format: 'rgba8unorm' as const,
|
|
746
743
|
data: new Uint8Array(64),
|
|
747
744
|
colorSpace: 'srgb' as const,
|
|
748
|
-
|
|
745
|
+
mips: { kind: 'none' as const },
|
|
749
746
|
});
|
|
750
747
|
reg.catalog(parseGuid(TEXTURE_B_GUID), {
|
|
751
748
|
kind: 'texture' as const,
|
|
752
|
-
width: 4,
|
|
753
|
-
height: 4,
|
|
749
|
+
shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
|
|
754
750
|
format: 'rgba8unorm' as const,
|
|
755
751
|
data: new Uint8Array(64),
|
|
756
752
|
colorSpace: 'srgb' as const,
|
|
757
|
-
|
|
753
|
+
mips: { kind: 'none' as const },
|
|
758
754
|
});
|
|
759
755
|
|
|
760
756
|
const packIndex = [
|
|
@@ -900,12 +896,11 @@ describe('AC-03 — gltf-shaped scene composite (via SceneAsset, D-9)', () => {
|
|
|
900
896
|
// Pre-register texture in dev mode.
|
|
901
897
|
reg.catalog(parseGuid(TEXTURE_C_GUID), {
|
|
902
898
|
kind: 'texture' as const,
|
|
903
|
-
width: 4,
|
|
904
|
-
height: 4,
|
|
899
|
+
shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
|
|
905
900
|
format: 'rgba8unorm' as const,
|
|
906
901
|
data: new Uint8Array(64),
|
|
907
902
|
colorSpace: 'srgb' as const,
|
|
908
|
-
|
|
903
|
+
mips: { kind: 'none' as const },
|
|
909
904
|
});
|
|
910
905
|
|
|
911
906
|
const packIndex = [
|
|
@@ -1103,12 +1098,11 @@ const LEAF_FIXTURES: readonly LeafFixture[] = [
|
|
|
1103
1098
|
kind: 'texture',
|
|
1104
1099
|
makeAsset: () => ({
|
|
1105
1100
|
kind: 'texture' as const,
|
|
1106
|
-
width: 4,
|
|
1107
|
-
height: 4,
|
|
1101
|
+
shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
|
|
1108
1102
|
format: 'rgba8unorm' as const,
|
|
1109
1103
|
data: new Uint8Array(64),
|
|
1110
1104
|
colorSpace: 'srgb' as const,
|
|
1111
|
-
|
|
1105
|
+
mips: { kind: 'none' as const },
|
|
1112
1106
|
}),
|
|
1113
1107
|
},
|
|
1114
1108
|
{
|
|
@@ -1172,7 +1166,7 @@ const LEAF_FIXTURES: readonly LeafFixture[] = [
|
|
|
1172
1166
|
kind: 'render-pipeline',
|
|
1173
1167
|
makeAsset: () => ({
|
|
1174
1168
|
kind: 'render-pipeline' as const,
|
|
1175
|
-
pipelineId: 'forgeax::
|
|
1169
|
+
pipelineId: 'forgeax::standard' as const,
|
|
1176
1170
|
}),
|
|
1177
1171
|
},
|
|
1178
1172
|
];
|
|
@@ -689,11 +689,10 @@ function _makeStubGPU(): unknown {
|
|
|
689
689
|
kind: 'texture',
|
|
690
690
|
payload: {
|
|
691
691
|
kind: 'texture',
|
|
692
|
-
width: 2,
|
|
693
|
-
height: 2,
|
|
692
|
+
shape: { viewDimension: '2d', extent: { width: 2, height: 2 } },
|
|
694
693
|
format: 'rgba8unorm',
|
|
695
694
|
colorSpace: 'srgb',
|
|
696
|
-
|
|
695
|
+
mips: { kind: 'none' },
|
|
697
696
|
},
|
|
698
697
|
refs: [],
|
|
699
698
|
artifacts: {
|
|
@@ -706,7 +705,11 @@ function _makeStubGPU(): unknown {
|
|
|
706
705
|
const out = await textureLoader.loadPack?.(input, mockCtx());
|
|
707
706
|
expect(out.ok).toBe(true);
|
|
708
707
|
if (out.ok) {
|
|
709
|
-
expect(out.value).toMatchObject({
|
|
708
|
+
expect(out.value).toMatchObject({
|
|
709
|
+
kind: 'texture',
|
|
710
|
+
shape: { viewDimension: '2d', extent: { width: 2, height: 2 } },
|
|
711
|
+
mips: { kind: 'none' },
|
|
712
|
+
});
|
|
710
713
|
}
|
|
711
714
|
});
|
|
712
715
|
|
|
@@ -3414,11 +3417,10 @@ function _makeStubGPU(): unknown {
|
|
|
3414
3417
|
|
|
3415
3418
|
const TEXTURE_METADATA = {
|
|
3416
3419
|
kind: 'texture',
|
|
3417
|
-
width: 2,
|
|
3418
|
-
height: 2,
|
|
3420
|
+
shape: { viewDimension: '2d', extent: { width: 2, height: 2 } },
|
|
3419
3421
|
format: 'rgba8unorm',
|
|
3420
3422
|
colorSpace: 'srgb',
|
|
3421
|
-
|
|
3423
|
+
mips: { kind: 'none' },
|
|
3422
3424
|
} as const;
|
|
3423
3425
|
|
|
3424
3426
|
function unimportedTextureRow() {
|
|
@@ -3731,10 +3733,13 @@ function _makeStubGPU(): unknown {
|
|
|
3731
3733
|
guid: GUID_IMPORTED,
|
|
3732
3734
|
kind: 'texture',
|
|
3733
3735
|
payload: {
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
+
shape: {
|
|
3737
|
+
viewDimension: '2d',
|
|
3738
|
+
extent: { width: binWidth, height: binHeight },
|
|
3739
|
+
},
|
|
3736
3740
|
format: 'rgba16float',
|
|
3737
3741
|
colorSpace: 'linear',
|
|
3742
|
+
mips: { kind: 'none' },
|
|
3738
3743
|
},
|
|
3739
3744
|
refs: [],
|
|
3740
3745
|
artifacts: {
|
|
@@ -3771,8 +3776,10 @@ function _makeStubGPU(): unknown {
|
|
|
3771
3776
|
const tex: TextureAsset = result.value;
|
|
3772
3777
|
expect(tex.format).toBe('rgba16float');
|
|
3773
3778
|
expect(tex.colorSpace).toBe('linear');
|
|
3774
|
-
expect(tex.
|
|
3775
|
-
|
|
3779
|
+
expect(tex.shape).toEqual({
|
|
3780
|
+
viewDimension: '2d',
|
|
3781
|
+
extent: { width: binWidth, height: binHeight },
|
|
3782
|
+
});
|
|
3776
3783
|
expect(tex.data.length).toBe(binWidth * binHeight * 4 * 2);
|
|
3777
3784
|
});
|
|
3778
3785
|
});
|
|
@@ -5544,11 +5551,10 @@ function _makeStubGPU(): unknown {
|
|
|
5544
5551
|
kind: 'texture',
|
|
5545
5552
|
payload: {
|
|
5546
5553
|
kind: 'texture',
|
|
5547
|
-
width: 4,
|
|
5548
|
-
height: 4,
|
|
5554
|
+
shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
|
|
5549
5555
|
format: 'rgba8unorm',
|
|
5550
5556
|
colorSpace: 'linear',
|
|
5551
|
-
|
|
5557
|
+
mips: { kind: 'none' },
|
|
5552
5558
|
},
|
|
5553
5559
|
refs: [],
|
|
5554
5560
|
artifacts: {
|
|
@@ -5577,11 +5583,10 @@ function _makeStubGPU(): unknown {
|
|
|
5577
5583
|
// loadByGuid returns the payload directly.
|
|
5578
5584
|
const asset = result.value;
|
|
5579
5585
|
expect(asset.kind).toBe('texture');
|
|
5580
|
-
expect(asset.
|
|
5581
|
-
expect(asset.height).toBe(4);
|
|
5586
|
+
expect(asset.shape).toEqual({ viewDimension: '2d', extent: { width: 4, height: 4 } });
|
|
5582
5587
|
expect(asset.format).toBe('rgba8unorm');
|
|
5583
5588
|
expect(asset.colorSpace).toBe('linear');
|
|
5584
|
-
expect(asset.
|
|
5589
|
+
expect(asset.mips).toEqual({ kind: 'none' });
|
|
5585
5590
|
expect(asset.data.byteLength).toBe(4 * 4 * 4);
|
|
5586
5591
|
expect(asset.data[0]).toBe(0);
|
|
5587
5592
|
expect(asset.data[63]).toBe(63);
|
|
@@ -6040,12 +6045,11 @@ function _makeStubGPU(): unknown {
|
|
|
6040
6045
|
|
|
6041
6046
|
return {
|
|
6042
6047
|
kind: 'texture' as const,
|
|
6043
|
-
width: 1,
|
|
6044
|
-
height: 1,
|
|
6048
|
+
shape: { viewDimension: '2d' as const, extent: { width: 1, height: 1 } },
|
|
6045
6049
|
format: 'rgba16float',
|
|
6046
6050
|
data,
|
|
6047
6051
|
colorSpace: 'linear' as const,
|
|
6048
|
-
|
|
6052
|
+
mips: { kind: 'none' as const },
|
|
6049
6053
|
};
|
|
6050
6054
|
}
|
|
6051
6055
|
|
|
@@ -6065,8 +6069,8 @@ function _makeStubGPU(): unknown {
|
|
|
6065
6069
|
if (got === undefined) return;
|
|
6066
6070
|
expect(got.format).toBe('rgba16float');
|
|
6067
6071
|
expect(got.colorSpace).toBe('linear');
|
|
6068
|
-
expect(got.
|
|
6069
|
-
expect(got.
|
|
6072
|
+
expect(got.shape).toEqual({ viewDimension: '2d', extent: { width: 1, height: 1 } });
|
|
6073
|
+
expect(got.mips).toEqual({ kind: 'none' });
|
|
6070
6074
|
expect(got.data.length).toBe(8);
|
|
6071
6075
|
});
|
|
6072
6076
|
|
|
@@ -6815,12 +6819,11 @@ function _makeStubGPU(): unknown {
|
|
|
6815
6819
|
function makeTexture(format: GPUTextureFormat, colorSpace: 'srgb' | 'linear'): TextureAsset {
|
|
6816
6820
|
return {
|
|
6817
6821
|
kind: 'texture',
|
|
6818
|
-
width: 1,
|
|
6819
|
-
height: 1,
|
|
6822
|
+
shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
|
|
6820
6823
|
format,
|
|
6821
6824
|
data: new Uint8Array([255, 255, 255, 255]),
|
|
6822
6825
|
colorSpace,
|
|
6823
|
-
|
|
6826
|
+
mips: { kind: 'none' },
|
|
6824
6827
|
};
|
|
6825
6828
|
}
|
|
6826
6829
|
|
|
@@ -166,10 +166,10 @@ function wireFetch(
|
|
|
166
166
|
guid: GUID_TEX,
|
|
167
167
|
kind: 'texture',
|
|
168
168
|
payload: {
|
|
169
|
-
width: W,
|
|
170
|
-
height: H,
|
|
169
|
+
shape: { viewDimension: '2d', extent: { width: W, height: H } },
|
|
171
170
|
format: fixture.colorSpace === 'srgb' ? 'rgba8unorm-srgb' : 'rgba16float',
|
|
172
171
|
colorSpace: fixture.colorSpace,
|
|
172
|
+
mips: { kind: 'none' },
|
|
173
173
|
},
|
|
174
174
|
refs: [],
|
|
175
175
|
artifacts: {
|
|
@@ -238,10 +238,13 @@ describe.skipIf(!pkgBuilt)('Basis catalog dispatch round-trip (M6 fix)', () => {
|
|
|
238
238
|
const result = await loadWith(true);
|
|
239
239
|
expect(result.ok).toBe(true);
|
|
240
240
|
if (!result.ok) throw new Error(`load failed: ${result.error.code}`);
|
|
241
|
-
const tex = result.value as {
|
|
241
|
+
const tex = result.value as {
|
|
242
|
+
kind: string;
|
|
243
|
+
shape: { viewDimension: string; extent: { width: number; height: number } };
|
|
244
|
+
format: string;
|
|
245
|
+
};
|
|
242
246
|
expect(tex.kind).toBe('texture');
|
|
243
|
-
expect(tex.
|
|
244
|
-
expect(tex.height).toBe(H);
|
|
247
|
+
expect(tex.shape).toEqual({ viewDimension: '2d', extent: { width: W, height: H } });
|
|
245
248
|
// NO_CAPS -> the transcode arm degrades to the uncompressed sRGB fallback
|
|
246
249
|
// (section 8 P3), never a scheme=1 reject.
|
|
247
250
|
expect(tex.format).toBe('rgba8unorm-srgb');
|
|
@@ -290,15 +293,13 @@ describe.skipIf(!pkgBuilt)('Basis catalog dispatch round-trip (M6 fix)', () => {
|
|
|
290
293
|
if (!result.ok) continue;
|
|
291
294
|
const texture = result.value as {
|
|
292
295
|
kind: string;
|
|
293
|
-
width: number;
|
|
294
|
-
height: number;
|
|
296
|
+
shape: { viewDimension: string; extent: { width: number; height: number } };
|
|
295
297
|
format: string;
|
|
296
298
|
data: Uint8Array;
|
|
297
299
|
colorSpace: string;
|
|
298
300
|
};
|
|
299
301
|
expect(texture.kind).toBe('texture');
|
|
300
|
-
expect(texture.
|
|
301
|
-
expect(texture.height).toBe(H);
|
|
302
|
+
expect(texture.shape).toEqual({ viewDimension: '2d', extent: { width: W, height: H } });
|
|
302
303
|
expect(texture.data.byteLength).toBeGreaterThan(0);
|
|
303
304
|
expect(texture.colorSpace).toBe(fixture.colorSpace);
|
|
304
305
|
const expected =
|
|
@@ -61,8 +61,8 @@ const SUT_ATTRIBUTABLE_RENDER_CODES: ReadonlySet<string> = new Set([
|
|
|
61
61
|
'vertex-storage-buffer-unavailable',
|
|
62
62
|
'mesh-ssbo-capacity-exceeded',
|
|
63
63
|
'mesh-ssbo-ceiling-reached',
|
|
64
|
-
'
|
|
65
|
-
'
|
|
64
|
+
'standard-light-budget-exceeded',
|
|
65
|
+
'standard-cluster-index-overflow',
|
|
66
66
|
]);
|
|
67
67
|
|
|
68
68
|
// Suppress WebGPU teardown race: chromium fires unhandled OperationError
|
|
@@ -67,7 +67,7 @@ describe('w7 type-level - 5 component schemas yield exact data shapes via ShapeO
|
|
|
67
67
|
expectTypeOf<{ materials: readonly [0] }>().not.toMatchTypeOf<SpawnData>();
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
it('Camera data shape has
|
|
70
|
+
it('Camera data shape has 21 fields (18 number + clearColor array + autoAspect boolean + target)', () => {
|
|
71
71
|
type Data = ShapeOf<SchemaOf<typeof Camera>>;
|
|
72
72
|
expectTypeOf<keyof Data>().toEqualTypeOf<
|
|
73
73
|
| 'fov'
|
|
@@ -90,6 +90,7 @@ describe('w7 type-level - 5 component schemas yield exact data shapes via ShapeO
|
|
|
90
90
|
| 'bloomBlurRadius'
|
|
91
91
|
| 'clearColor'
|
|
92
92
|
| 'autoAspect'
|
|
93
|
+
| 'target'
|
|
93
94
|
>();
|
|
94
95
|
expectTypeOf<Data['fov']>().toEqualTypeOf<number>();
|
|
95
96
|
expectTypeOf<Data['far']>().toEqualTypeOf<number>();
|
|
@@ -107,9 +108,10 @@ describe('w7 type-level - 5 component schemas yield exact data shapes via ShapeO
|
|
|
107
108
|
expectTypeOf<Data['clearColor']>().toEqualTypeOf<Float32Array>();
|
|
108
109
|
// feat-20260617 / M3: AC-09 -- bool column narrows to boolean, not number.
|
|
109
110
|
expectTypeOf<Data['autoAspect']>().toEqualTypeOf<boolean>();
|
|
111
|
+
expectTypeOf<Data['target']>().toEqualTypeOf<Handle<'RenderTarget', 'shared'>>();
|
|
110
112
|
});
|
|
111
113
|
|
|
112
|
-
it('DirectionalLight data shape
|
|
114
|
+
it('DirectionalLight data shape includes closed shadow quality fields', () => {
|
|
113
115
|
// feat-20260621: DirectionalLightShadow merged into DirectionalLight via castShadow toggle.
|
|
114
116
|
// shadowDistance replaced the nearPlane/farPlane pair (near derives from camera).
|
|
115
117
|
// feat-20260709 M2: direction/color collapsed to array<f32,3> columns.
|
|
@@ -126,13 +128,18 @@ describe('w7 type-level - 5 component schemas yield exact data shapes via ShapeO
|
|
|
126
128
|
| 'depthBias'
|
|
127
129
|
| 'normalBias'
|
|
128
130
|
| 'shadowDistance'
|
|
129
|
-
| '
|
|
131
|
+
| 'shadowFilter'
|
|
132
|
+
| 'shadowAngularRadius'
|
|
133
|
+
| 'maxPenumbraTexels'
|
|
130
134
|
>();
|
|
131
135
|
expectTypeOf<Data['direction']>().toEqualTypeOf<Float32Array>();
|
|
132
136
|
expectTypeOf<Data['color']>().toEqualTypeOf<Float32Array>();
|
|
133
137
|
expectTypeOf<Data['intensity']>().toEqualTypeOf<number>();
|
|
134
138
|
// bool column narrows to boolean, not number.
|
|
135
139
|
expectTypeOf<Data['castShadow']>().toEqualTypeOf<boolean>();
|
|
140
|
+
expectTypeOf<Data['shadowFilter']>().toEqualTypeOf<number>();
|
|
141
|
+
expectTypeOf<Data['shadowAngularRadius']>().toEqualTypeOf<number>();
|
|
142
|
+
expectTypeOf<Data['maxPenumbraTexels']>().toEqualTypeOf<number>();
|
|
136
143
|
});
|
|
137
144
|
});
|
|
138
145
|
|
|
@@ -176,9 +176,9 @@ import { extractFrame, prepareExtractContext } from '../../../render/src/render-
|
|
|
176
176
|
expect(schemaRecord.materials).toBe('array<shared<MaterialAsset>>');
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
-
it('Camera has
|
|
179
|
+
it('Camera has 21 fields (17 f32 + historyVersion u32 + clearColor array<f32,4> + autoAspect bool + target)', () => {
|
|
180
180
|
expect(Camera.name).toBe('Camera');
|
|
181
|
-
expect(Object.keys(componentSchema(Camera)).length).toBe(
|
|
181
|
+
expect(Object.keys(componentSchema(Camera)).length).toBe(21);
|
|
182
182
|
expect(componentSchema(Camera).fov).toBe('f32');
|
|
183
183
|
expect(componentSchema(Camera).aspect).toBe('f32');
|
|
184
184
|
expect(componentSchema(Camera).near).toBe('f32');
|
|
@@ -211,13 +211,13 @@ import { extractFrame, prepareExtractContext } from '../../../render/src/render-
|
|
|
211
211
|
expect(componentSchema(Camera).autoAspect).toBe('bool');
|
|
212
212
|
});
|
|
213
213
|
|
|
214
|
-
it('DirectionalLight has
|
|
214
|
+
it('DirectionalLight has 14 fields: 3 light + castShadow bool + closed shadow quality fields', () => {
|
|
215
215
|
// feat-20260621: DirectionalLightShadow merged into DirectionalLight via castShadow toggle.
|
|
216
216
|
// shadowDistance replaced the nearPlane/farPlane pair (near derives from camera).
|
|
217
217
|
// feat-20260709 M2: direction/color collapsed from 6 per-axis scalars to
|
|
218
218
|
// two array<f32,3> columns (3 light fields: direction + color + intensity).
|
|
219
219
|
expect(DirectionalLight.name).toBe('DirectionalLight');
|
|
220
|
-
expect(Object.keys(componentSchema(DirectionalLight)).length).toBe(
|
|
220
|
+
expect(Object.keys(componentSchema(DirectionalLight)).length).toBe(14);
|
|
221
221
|
// 3 light fields
|
|
222
222
|
expect(componentSchema(DirectionalLight).direction).toBe('array<f32, 3>');
|
|
223
223
|
expect(componentSchema(DirectionalLight).color).toBe('array<f32, 3>');
|
|
@@ -231,7 +231,10 @@ import { extractFrame, prepareExtractContext } from '../../../render/src/render-
|
|
|
231
231
|
expect(componentSchema(DirectionalLight).depthBias).toBe('f32');
|
|
232
232
|
expect(componentSchema(DirectionalLight).normalBias).toBe('f32');
|
|
233
233
|
expect(componentSchema(DirectionalLight).shadowDistance).toBe('f32');
|
|
234
|
-
expect(componentSchema(DirectionalLight).
|
|
234
|
+
expect(componentSchema(DirectionalLight).shadowFilter).toBe('enum');
|
|
235
|
+
expect(componentSchema(DirectionalLight).shadowAngularRadius).toBe('f32');
|
|
236
|
+
expect(componentSchema(DirectionalLight).maxPenumbraTexels).toBe('f32');
|
|
237
|
+
expect('pcfKernelSize' in componentSchema(DirectionalLight)).toBe(false);
|
|
235
238
|
});
|
|
236
239
|
|
|
237
240
|
it('all 5 components are frozen tokens with owner-assigned identities', () => {
|
|
@@ -1408,7 +1411,7 @@ import { extractFrame, prepareExtractContext } from '../../../render/src/render-
|
|
|
1408
1411
|
// ────────────────────────────────────────────────────────────────────────────
|
|
1409
1412
|
|
|
1410
1413
|
describe('Camera.fields reflection (w14 AC-07 SSOT)', () => {
|
|
1411
|
-
it('Camera.fields has exactly
|
|
1414
|
+
it('Camera.fields has exactly 21 keys matching the Camera column set', () => {
|
|
1412
1415
|
const keys = Object.keys(Camera.fields).sort();
|
|
1413
1416
|
expect(keys).toEqual([
|
|
1414
1417
|
'antialias',
|
|
@@ -1428,6 +1431,7 @@ import { extractFrame, prepareExtractContext } from '../../../render/src/render-
|
|
|
1428
1431
|
'near',
|
|
1429
1432
|
'projection',
|
|
1430
1433
|
'right',
|
|
1434
|
+
'target',
|
|
1431
1435
|
'tonemap',
|
|
1432
1436
|
'top',
|
|
1433
1437
|
'whitePoint',
|
|
@@ -1443,7 +1447,9 @@ import { extractFrame, prepareExtractContext } from '../../../render/src/render-
|
|
|
1443
1447
|
? 'array<f32, 4>'
|
|
1444
1448
|
: key === 'historyVersion'
|
|
1445
1449
|
? 'u32'
|
|
1446
|
-
: '
|
|
1450
|
+
: key === 'target'
|
|
1451
|
+
? 'shared<RenderTarget>'
|
|
1452
|
+
: 'f32';
|
|
1447
1453
|
expect(Camera.fields[key].type).toBe(expected);
|
|
1448
1454
|
}
|
|
1449
1455
|
});
|
|
@@ -79,13 +79,11 @@ function compressedPod(): TextureAsset {
|
|
|
79
79
|
bytes.fill(level.level + 1, level.byteOffset, level.byteOffset + level.byteLength);
|
|
80
80
|
return {
|
|
81
81
|
kind: 'texture',
|
|
82
|
-
width: 2085,
|
|
83
|
-
height: 1573,
|
|
82
|
+
shape: { viewDimension: '2d', extent: { width: 2085, height: 1573 } },
|
|
84
83
|
format: 'bc7-rgba-unorm',
|
|
85
84
|
data: bytes,
|
|
86
85
|
colorSpace: 'linear',
|
|
87
|
-
|
|
88
|
-
mipLevelCount: layout.length,
|
|
86
|
+
mips: { kind: 'packed', levelCount: layout.length },
|
|
89
87
|
};
|
|
90
88
|
}
|
|
91
89
|
|
|
@@ -93,13 +91,18 @@ describe('compressed texture physical extent recorded-RHI witness [w36]', () =>
|
|
|
93
91
|
it('allocates and uploads BC7 physical extents while preserving logical asset metadata', () => {
|
|
94
92
|
const calls = { create: [] as TextureCall[], writes: [] as WriteCall[] };
|
|
95
93
|
const pod = compressedPod();
|
|
96
|
-
const expected = deriveMipUploadLayout(
|
|
94
|
+
const expected = deriveMipUploadLayout(
|
|
95
|
+
pod.format,
|
|
96
|
+
pod.shape.extent.width,
|
|
97
|
+
pod.shape.extent.height,
|
|
98
|
+
pod.mips.kind === 'packed' ? pod.mips.levelCount : 1,
|
|
99
|
+
);
|
|
97
100
|
|
|
98
101
|
const result = makeStore(calls).ensureResident(toShared<'TextureAsset'>(36), pod);
|
|
99
102
|
if (!result.ok) throw result.error;
|
|
100
103
|
|
|
101
|
-
expect(pod.width).toBe(2085);
|
|
102
|
-
expect(pod.height).toBe(1573);
|
|
104
|
+
expect(pod.shape.extent.width).toBe(2085);
|
|
105
|
+
expect(pod.shape.extent.height).toBe(1573);
|
|
103
106
|
expect(calls.create).toHaveLength(1);
|
|
104
107
|
expect(calls.create[0]?.size).toMatchObject({ width: 2088, height: 1576 });
|
|
105
108
|
expect(calls.writes).toHaveLength(expected.length);
|
|
@@ -122,12 +125,11 @@ describe('compressed texture physical extent recorded-RHI witness [w36]', () =>
|
|
|
122
125
|
const calls = { create: [] as TextureCall[], writes: [] as WriteCall[] };
|
|
123
126
|
const pod: TextureAsset = {
|
|
124
127
|
kind: 'texture',
|
|
125
|
-
width: 17,
|
|
126
|
-
height: 9,
|
|
128
|
+
shape: { viewDimension: '2d', extent: { width: 17, height: 9 } },
|
|
127
129
|
format: 'rgba8unorm',
|
|
128
130
|
data: new Uint8Array(17 * 9 * 4),
|
|
129
131
|
colorSpace: 'linear',
|
|
130
|
-
|
|
132
|
+
mips: { kind: 'none' },
|
|
131
133
|
};
|
|
132
134
|
|
|
133
135
|
expect(makeStore(calls).ensureResident(toShared<'TextureAsset'>(37), pod).ok).toBe(true);
|
|
@@ -36,6 +36,30 @@ describe('runtime renderer host assembly', () => {
|
|
|
36
36
|
host.value.renderer.dispose();
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it('repeats null-device assembly and frame observation without device loss', async () => {
|
|
40
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
41
|
+
const host = await constructRendererHost(canvas(), { rhi }, { shaderManifestUrl: manifest });
|
|
42
|
+
expect(host.ok).toBe(true);
|
|
43
|
+
if (!host.ok) continue;
|
|
44
|
+
const world = new World();
|
|
45
|
+
const attached = host.value.renderer.attach(world);
|
|
46
|
+
expect(attached.ok).toBe(true);
|
|
47
|
+
if (!attached.ok) {
|
|
48
|
+
host.value.renderer.dispose();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
expect(world.update().ok).toBe(true);
|
|
52
|
+
const frame = host.value.renderer.draw(frameRequest(attached.value));
|
|
53
|
+
expect(frame.ok).toBe(true);
|
|
54
|
+
if (frame.ok) {
|
|
55
|
+
expect((await host.value.renderer.observe(frame.value, { include: ['timings'] })).ok).toBe(
|
|
56
|
+
true,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
host.value.renderer.dispose();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
39
63
|
it('keeps runtime construction errors structured', async () => {
|
|
40
64
|
const result = await createRenderer(canvas(), { rhi: undefined });
|
|
41
65
|
expect(result.ok).toBe(false);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import type { GpuPassTimingOptions, RendererOptions } from '@forgeax/engine-render';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
describe('runtime GPU pass timing forwarding', () => {
|
|
7
|
+
it('keeps one option object and projects the exact field to Render', () => {
|
|
8
|
+
const gpuPassTiming = { maxPassesPerFrame: 8 } satisfies GpuPassTimingOptions;
|
|
9
|
+
const options = { gpuPassTiming } satisfies RendererOptions;
|
|
10
|
+
expect(options.gpuPassTiming).toBe(gpuPassTiming);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('forwards without owning admission, sessions, handles, or CPU capture', () => {
|
|
14
|
+
const source = readFileSync(
|
|
15
|
+
fileURLToPath(new URL('../createRenderer.ts', import.meta.url)),
|
|
16
|
+
'utf8',
|
|
17
|
+
);
|
|
18
|
+
expect(source).toContain('gpuPassTiming: options.gpuPassTiming');
|
|
19
|
+
expect(source).not.toContain('createGpuPassTimingSession');
|
|
20
|
+
expect(source).not.toContain('ProfileCapture');
|
|
21
|
+
expect(source).not.toContain('passCatalog');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('keeps the no-option path free of a timing field', () => {
|
|
25
|
+
const source = readFileSync(
|
|
26
|
+
fileURLToPath(new URL('../createRenderer.ts', import.meta.url)),
|
|
27
|
+
'utf8',
|
|
28
|
+
);
|
|
29
|
+
expect(source).toContain('options === undefined');
|
|
30
|
+
expect(source).toContain('gpuPassTiming: options.gpuPassTiming');
|
|
31
|
+
});
|
|
32
|
+
});
|