@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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// @perf-budget-skip: intentional full Renderer submit-failure and LKG recovery integration gate.
|
|
2
|
+
import { World } from '@forgeax/engine-ecs';
|
|
3
|
+
import {
|
|
4
|
+
Camera,
|
|
5
|
+
DirectionalLight,
|
|
6
|
+
perspective,
|
|
7
|
+
TONEMAP_ACES_FILMIC,
|
|
8
|
+
VolumetricFog,
|
|
9
|
+
} from '@forgeax/engine-render';
|
|
10
|
+
import type { CommandBuffer, RhiQueue } from '@forgeax/engine-rhi';
|
|
11
|
+
import { RhiError } from '@forgeax/engine-rhi';
|
|
12
|
+
import { rhi as nullRhi, RhiNullAdapter } from '@forgeax/engine-rhi-null';
|
|
13
|
+
import { Transform } from '@forgeax/engine-scene';
|
|
14
|
+
import { err, type Result, type TextureAsset } from '@forgeax/engine-types';
|
|
15
|
+
import { describe, expect, it } from 'vitest';
|
|
16
|
+
import { createRenderer } from '../createRenderer';
|
|
17
|
+
|
|
18
|
+
const VOLUME_MANIFEST = `data:application/json,${encodeURIComponent(
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
entries: [
|
|
21
|
+
{ hash: 'pbr', wgsl: 'f_schlick', glsl: '', bindings: '' },
|
|
22
|
+
{ hash: 'unlit', wgsl: 'unlit', glsl: '', bindings: '' },
|
|
23
|
+
{ hash: 'tonemap', wgsl: 'struct TonemapParams {}', glsl: '', bindings: '' },
|
|
24
|
+
{ hash: 'volume-inject', wgsl: 'fn volume_inject() {}', glsl: '', bindings: '' },
|
|
25
|
+
{ hash: 'volume-integrate', wgsl: 'fn volume_integrate() {}', glsl: '', bindings: '' },
|
|
26
|
+
{ hash: 'volume-temporal', wgsl: 'fn volume_temporal() {}', glsl: '', bindings: '' },
|
|
27
|
+
{ hash: 'volume-composite', wgsl: 'fn volume_fs() {}', glsl: '', bindings: '' },
|
|
28
|
+
],
|
|
29
|
+
}),
|
|
30
|
+
)}`;
|
|
31
|
+
|
|
32
|
+
function canvas(): HTMLCanvasElement {
|
|
33
|
+
return { width: 64, height: 64, getContext: () => null } as unknown as HTMLCanvasElement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function density(): TextureAsset {
|
|
37
|
+
return {
|
|
38
|
+
kind: 'texture',
|
|
39
|
+
shape: { viewDimension: '3d', extent: { width: 64, height: 64, depth: 64 } },
|
|
40
|
+
format: 'r8unorm',
|
|
41
|
+
colorSpace: 'linear',
|
|
42
|
+
mips: { kind: 'none' },
|
|
43
|
+
data: new Uint8Array(64 * 64 * 64).fill(32),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function failingNullRhi(controller: { failNext: boolean }) {
|
|
48
|
+
const adapter = new RhiNullAdapter();
|
|
49
|
+
const requestDevice = adapter.requestDevice.bind(adapter);
|
|
50
|
+
adapter.requestDevice = async (options) => {
|
|
51
|
+
const result = await requestDevice(options);
|
|
52
|
+
if (!result.ok) return result;
|
|
53
|
+
const queue: RhiQueue = result.value.queue;
|
|
54
|
+
const submit = queue.submit.bind(queue);
|
|
55
|
+
queue.submit = (buffers: readonly CommandBuffer[]): Result<void, RhiError> => {
|
|
56
|
+
if (controller.failNext) {
|
|
57
|
+
controller.failNext = false;
|
|
58
|
+
return err(
|
|
59
|
+
new RhiError({
|
|
60
|
+
code: 'webgpu-runtime-error',
|
|
61
|
+
expected: 'queue submission to succeed',
|
|
62
|
+
hint: 'the recovery regression intentionally rejects one submit',
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return submit(buffers);
|
|
67
|
+
};
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
...nullRhi,
|
|
72
|
+
requestAdapter: async () => ({ ok: true, value: adapter }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('volumetric fog record recovery projection', () => {
|
|
77
|
+
it('keeps accepted LKG inspection on a failed submit and clears it after repair', async () => {
|
|
78
|
+
const controller = { failNext: false };
|
|
79
|
+
const created = await createRenderer(
|
|
80
|
+
canvas(),
|
|
81
|
+
{ rhi: failingNullRhi(controller) as never },
|
|
82
|
+
{ shaderManifestUrl: VOLUME_MANIFEST },
|
|
83
|
+
);
|
|
84
|
+
expect(created.ok).toBe(true);
|
|
85
|
+
if (!created.ok) return;
|
|
86
|
+
const renderer = created.value;
|
|
87
|
+
|
|
88
|
+
const world = new World();
|
|
89
|
+
const densityHandle = world.allocSharedRef('TextureAsset', density());
|
|
90
|
+
world
|
|
91
|
+
.spawn(
|
|
92
|
+
{ component: Transform, data: { pos: [0, 0, 3] } },
|
|
93
|
+
{
|
|
94
|
+
component: Camera,
|
|
95
|
+
data: {
|
|
96
|
+
...perspective({ fov: Math.PI / 4, aspect: 1 }),
|
|
97
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
.unwrap();
|
|
102
|
+
const directionalLight = world
|
|
103
|
+
.spawn({
|
|
104
|
+
component: DirectionalLight,
|
|
105
|
+
data: { direction: [-0.4, -0.8, -0.3], castShadow: true },
|
|
106
|
+
})
|
|
107
|
+
.unwrap();
|
|
108
|
+
const fogEntity = world
|
|
109
|
+
.spawn({
|
|
110
|
+
component: VolumetricFog,
|
|
111
|
+
data: {
|
|
112
|
+
light: directionalLight,
|
|
113
|
+
density: densityHandle,
|
|
114
|
+
boundsMin: [-1, -1, -1],
|
|
115
|
+
boundsMax: [1, 1, 1],
|
|
116
|
+
extinction: [0.2, 0.2, 0.2],
|
|
117
|
+
albedo: [0.8, 0.8, 0.8],
|
|
118
|
+
emission: [0, 0, 0],
|
|
119
|
+
anisotropy: 0,
|
|
120
|
+
maxDistance: 50,
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
.unwrap();
|
|
124
|
+
expect(world.update(1 / 60).ok).toBe(true);
|
|
125
|
+
|
|
126
|
+
const lease = renderer.attach(world);
|
|
127
|
+
expect(lease.ok).toBe(true);
|
|
128
|
+
if (!lease.ok) return;
|
|
129
|
+
const request = {
|
|
130
|
+
leases: [lease.value],
|
|
131
|
+
camera: { lease: lease.value },
|
|
132
|
+
environment: { lease: lease.value },
|
|
133
|
+
} as const;
|
|
134
|
+
|
|
135
|
+
expect(renderer.draw(request).ok).toBe(true);
|
|
136
|
+
expect(renderer.inspect().volumetricFog).toMatchObject({
|
|
137
|
+
status: 'available',
|
|
138
|
+
resourceStage: 'accepted',
|
|
139
|
+
generation: 1,
|
|
140
|
+
passCount: 4,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
controller.failNext = true;
|
|
144
|
+
expect(renderer.draw(request).ok).toBe(false);
|
|
145
|
+
expect(renderer.inspect().volumetricFog).toMatchObject({
|
|
146
|
+
status: 'degraded',
|
|
147
|
+
resourceStage: 'lkg',
|
|
148
|
+
generation: 1,
|
|
149
|
+
lkgGeneration: 1,
|
|
150
|
+
candidateGeneration: 1,
|
|
151
|
+
candidateFailure: 'submit-failed',
|
|
152
|
+
passCount: 4,
|
|
153
|
+
// The packed high profile uses a 64x64x16 physical froxel texture,
|
|
154
|
+
// representing 64 logical slices per pixel. Inspection reports the
|
|
155
|
+
// descriptor-derived allocation bytes for this recovery fixture.
|
|
156
|
+
sampleCount: 64 * 64 * 16,
|
|
157
|
+
memoryBytes: 71808,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(renderer.draw(request).ok).toBe(true);
|
|
161
|
+
expect(renderer.inspect().volumetricFog).toMatchObject({
|
|
162
|
+
status: 'available',
|
|
163
|
+
resourceStage: 'accepted',
|
|
164
|
+
generation: 1,
|
|
165
|
+
passCount: 4,
|
|
166
|
+
candidateFailure: undefined,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(world.removeComponent(fogEntity, VolumetricFog).ok).toBe(true);
|
|
170
|
+
expect(world.update(1 / 60).ok).toBe(true);
|
|
171
|
+
expect(renderer.draw(request).ok).toBe(true);
|
|
172
|
+
expect(renderer.inspect().volumetricFog).toMatchObject({
|
|
173
|
+
status: 'off',
|
|
174
|
+
passCount: 0,
|
|
175
|
+
sampleCount: 0,
|
|
176
|
+
memoryBytes: 0,
|
|
177
|
+
});
|
|
178
|
+
expect((await renderer.dispose()).ok).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
});
|
package/src/createRenderer.ts
CHANGED
|
@@ -30,12 +30,16 @@ export async function createRenderer(
|
|
|
30
30
|
...(options.rhi === undefined ? {} : { rhi: options.rhi }),
|
|
31
31
|
...(options.features === undefined ? {} : { features: options.features }),
|
|
32
32
|
...(options.profiler === undefined ? {} : { profiler: options.profiler }),
|
|
33
|
+
...(options.captureGpuTimings === undefined
|
|
34
|
+
? {}
|
|
35
|
+
: { captureGpuTimings: options.captureGpuTimings }),
|
|
33
36
|
...(options.rhiInstrumentation === undefined
|
|
34
37
|
? {}
|
|
35
38
|
: { rhiInstrumentation: options.rhiInstrumentation }),
|
|
36
39
|
...(options.standardProfile === undefined
|
|
37
40
|
? {}
|
|
38
41
|
: { standardProfile: options.standardProfile }),
|
|
42
|
+
...(options.gpuPassTiming === undefined ? {} : { gpuPassTiming: options.gpuPassTiming }),
|
|
39
43
|
};
|
|
40
44
|
try {
|
|
41
45
|
const constructed = await constructRuntimeRendererHost(canvas, rendererOptions, bundler);
|
package/src/index.ts
CHANGED
|
@@ -340,8 +340,7 @@ export {
|
|
|
340
340
|
// never rendered). One barrel entry => one module copy => one registry shared by
|
|
341
341
|
// createDebugDrawOnReady (the writer) and attachDebugOverlayPass (the reader).
|
|
342
342
|
// feat-20260704-runtime-tier1-decomposition M2 / w10 (D-3): the third error
|
|
343
|
-
// re-export block (
|
|
344
|
-
// HdrpLightBudgetExceededError) is folded into the render-cluster class
|
|
343
|
+
// re-export block (Standard Cluster error classes) is folded into the render-cluster class
|
|
345
344
|
// re-export block above -- they are render-cluster members sourced from
|
|
346
345
|
// ./errors/render.
|
|
347
346
|
// feat-20260608-cluster-lighting M2 / w10 + verify F-1/F-2: HDRP cluster-forward
|