@forgeax/engine-render 0.1.34 → 0.1.35
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 +52 -3
- package/dist/assembly/device-feature-admission.d.ts +2 -2
- package/dist/assembly/host-contract.d.ts +3 -2
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +5 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts +1 -0
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/authoring.mjs +2 -4
- package/dist/authoring.mjs.map +1 -1
- package/dist/chunk-3XUBWRY2.mjs +1842 -0
- package/dist/chunk-3XUBWRY2.mjs.map +1 -0
- package/dist/{chunk-UI5SFODX.mjs → chunk-G6HDNGY7.mjs} +2329 -1776
- package/dist/chunk-G6HDNGY7.mjs.map +1 -0
- package/dist/{chunk-HEB564WF.mjs → chunk-LM5IE7GF.mjs} +4 -4
- package/dist/{chunk-HEB564WF.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
- package/dist/{chunk-MKDIT2EM.mjs → chunk-LNIUIKQ7.mjs} +143 -246
- package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
- package/dist/chunk-MXTWKZO4.mjs +3268 -0
- package/dist/chunk-MXTWKZO4.mjs.map +1 -0
- package/dist/{chunk-ODZWR6JQ.mjs → chunk-MZ4C7EHF.mjs} +2042 -27
- package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
- package/dist/chunk-RIPAEGI6.mjs +48 -0
- package/dist/chunk-RIPAEGI6.mjs.map +1 -0
- package/dist/{chunk-OCDLEI6I.mjs → chunk-SGPUJW3F.mjs} +117 -5
- package/dist/chunk-SGPUJW3F.mjs.map +1 -0
- package/dist/{chunk-ELJFJ2BU.mjs → chunk-X2KA6WHM.mjs} +29 -4
- package/dist/chunk-X2KA6WHM.mjs.map +1 -0
- package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
- package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
- package/dist/cloud/frame-context.d.ts +2 -2
- package/dist/cloud/frame-context.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +810 -2073
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -19
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/errors/render.d.ts +3 -2
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/material-context.d.ts +7 -0
- package/dist/extract/material-context.d.ts.map +1 -0
- package/dist/features/host.d.ts +5 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/plan.d.ts +2 -0
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/types.d.ts +4 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/glyph-text-layout-system.d.ts +1 -1
- package/dist/glyph-text-layout-system.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-ownership.d.ts +3 -3
- package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +4 -4
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +530 -9
- package/dist/index.mjs.map +1 -1
- package/dist/instances.d.ts +3 -4
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +7 -8
- package/dist/internal.mjs.map +1 -1
- package/dist/pbr-pipeline.d.ts +3 -3
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
- package/dist/pipeline/standard-output/resources.d.ts +2 -2
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
- package/dist/publication/contract.d.ts +77 -0
- package/dist/publication/contract.d.ts.map +1 -0
- package/dist/publication/dependencies.d.ts +7 -0
- package/dist/publication/dependencies.d.ts.map +1 -0
- package/dist/publication/prepare-geometry.d.ts +9 -0
- package/dist/publication/prepare-geometry.d.ts.map +1 -0
- package/dist/publication/programs.d.ts +5 -0
- package/dist/publication/programs.d.ts.map +1 -0
- package/dist/publication/publisher.d.ts +33 -0
- package/dist/publication/publisher.d.ts.map +1 -0
- package/dist/publication/receiver.d.ts +29 -0
- package/dist/publication/receiver.d.ts.map +1 -0
- package/dist/publication/resource-scope.d.ts +18 -0
- package/dist/publication/resource-scope.d.ts.map +1 -0
- package/dist/publication/targets.d.ts +33 -0
- package/dist/publication/targets.d.ts.map +1 -0
- package/dist/publication/video.d.ts +5 -0
- package/dist/publication/video.d.ts.map +1 -0
- package/dist/record/fold-buckets.d.ts +3 -3
- package/dist/record/fold-buckets.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +6 -6
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +5 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +2 -2
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +2 -2
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -9
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +3 -16
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts +3 -3
- package/dist/record/prepared-material-bindings.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +2 -2
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/sprite-instance-buffer.d.ts +31 -0
- package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
- package/dist/record/target-capture-graph.d.ts +19 -0
- package/dist/record/target-capture-graph.d.ts.map +1 -0
- package/dist/record/typed-frame-graph.d.ts +2 -13
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/record/volume-params.d.ts +2 -2
- package/dist/record/volume-params.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +5 -5
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +4 -1
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +2 -0
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-presentation.d.ts +2 -2
- package/dist/render-system-presentation.d.ts.map +1 -1
- package/dist/render-system-projections.d.ts +3 -2
- package/dist/render-system-projections.d.ts.map +1 -1
- package/dist/render-system-residency.d.ts +2 -2
- package/dist/render-system-residency.d.ts.map +1 -1
- package/dist/render-system.d.ts +3 -7
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +8 -4
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/render-source.d.ts +42 -0
- package/dist/scene/render-source.d.ts.map +1 -0
- package/dist/scene/source-systems.d.ts +6 -0
- package/dist/scene/source-systems.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +10 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +2 -2
- package/dist/systems/transparent-dispatch.d.ts.map +1 -1
- package/dist/systems/transparent-sort-config.d.ts +2 -1
- package/dist/systems/transparent-sort-config.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -7
- package/package.json +21 -21
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
- package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +3 -4
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
- package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +0 -1
- package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
- package/src/__tests__/physical-clearcoat.integration.test.ts +23 -23
- package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +1 -1
- package/src/assembly/device-feature-admission.ts +2 -2
- package/src/assembly/host-contract.ts +5 -2
- package/src/assembly/recovery/renderer-recover.ts +9 -0
- package/src/assembly/render-target-host.ts +57 -47
- package/src/assembly/renderer-facade.ts +3 -1
- package/src/assembly/webgpu-ready.ts +1 -1
- package/src/assembly/webgpu-renderer.ts +89 -61
- package/src/cloud/frame-context.ts +2 -2
- package/src/device/gpu-residency.ts +26 -25
- package/src/errors/render.ts +3 -1
- package/src/extract/material-context.ts +21 -0
- package/src/features/host.ts +13 -5
- package/src/features/plan.ts +2 -0
- package/src/features/types.ts +4 -0
- package/src/glyph-text-layout-system.ts +2 -2
- package/src/gpu-driven/shadow-ownership.ts +3 -3
- package/src/ibl/frame-resources.ts +0 -1
- package/src/ibl/skylight-bind-group.ts +13 -25
- package/src/index.ts +9 -0
- package/src/instances.ts +9 -6
- package/src/pbr-pipeline.ts +11 -17
- package/src/pipeline/standard-output/lut-gpu.ts +3 -3
- package/src/pipeline/standard-output/resources.ts +2 -2
- package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
- package/src/publication/__tests__/features.integration.test.ts +122 -0
- package/src/publication/__tests__/programs.unit.test.ts +25 -0
- package/src/publication/__tests__/publication.integration.test.ts +370 -0
- package/src/publication/__tests__/targets.integration.test.ts +120 -0
- package/src/publication/__tests__/video.browser.test.ts +50 -0
- package/src/publication/contract.ts +104 -0
- package/src/publication/dependencies.ts +54 -0
- package/src/publication/prepare-geometry.ts +94 -0
- package/src/publication/programs.ts +45 -0
- package/src/publication/publisher.ts +618 -0
- package/src/publication/receiver.ts +297 -0
- package/src/publication/resource-scope.ts +31 -0
- package/src/publication/targets.ts +182 -0
- package/src/publication/video.ts +33 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +8 -10
- package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
- package/src/record/fold-buckets.ts +8 -5
- package/src/record/frame-snapshot.ts +6 -6
- package/src/record/frame.ts +42 -22
- package/src/record/helpers.ts +2 -2
- package/src/record/main-pass-geometry.ts +21 -2
- package/src/record/main-pass-material.ts +37 -33
- package/src/record/main-pass-sprite-draws.ts +6 -152
- package/src/record/main-pass.ts +8 -7
- package/src/record/prepared-material-bindings.ts +11 -6
- package/src/record/render-context.ts +2 -2
- package/src/record/shadow-pass.ts +0 -3
- package/src/record/sprite-instance-buffer.ts +155 -0
- package/src/record/target-capture-graph.ts +258 -0
- package/src/record/typed-frame-graph.ts +6 -206
- package/src/record/view-ubo.ts +1 -1
- package/src/record/volume-params.ts +6 -6
- package/src/recovery/render-system-candidate.ts +10 -10
- package/src/render-contract.ts +6 -1
- package/src/render-system-extract-tail.ts +33 -17
- package/src/render-system-extract.ts +2 -0
- package/src/render-system-presentation.ts +2 -2
- package/src/render-system-projections.ts +3 -2
- package/src/render-system-residency.ts +2 -2
- package/src/render-system.ts +82 -53
- package/src/scene/render-scene.ts +110 -100
- package/src/scene/render-source.ts +90 -0
- package/src/scene/source-systems.ts +33 -0
- package/src/systems/skin-palette-allocator.ts +1 -0
- package/src/systems/skin-palette-types.ts +11 -0
- package/src/systems/transparent-dispatch.ts +2 -2
- package/src/systems/transparent-sort-config.ts +3 -1
- package/dist/chunk-BLXGONR2.mjs.map +0 -1
- package/dist/chunk-ELJFJ2BU.mjs.map +0 -1
- package/dist/chunk-ILQ5MEZF.mjs +0 -32
- package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
- package/dist/chunk-MKDIT2EM.mjs.map +0 -1
- package/dist/chunk-OCDLEI6I.mjs.map +0 -1
- package/dist/chunk-ODZWR6JQ.mjs.map +0 -1
- package/dist/chunk-RXZ3RGOU.mjs +0 -53
- package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
- package/dist/chunk-SHFUFJ5N.mjs +0 -1781
- package/dist/chunk-SHFUFJ5N.mjs.map +0 -1
- package/dist/chunk-UI5SFODX.mjs.map +0 -1
- package/dist/chunk-UINHLCZA.mjs +0 -2389
- package/dist/chunk-UINHLCZA.mjs.map +0 -1
- package/dist/chunk-WEX45HKH.mjs +0 -1524
- package/dist/chunk-WEX45HKH.mjs.map +0 -1
package/src/errors/render.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RenderPublicationError } from '../publication/contract';
|
|
1
2
|
// @forgeax/engine-render -- render cluster error classes.
|
|
2
3
|
//
|
|
3
4
|
// feat-20260704-runtime-tier1-decomposition M2 / w8 (D-3): the monolithic
|
|
@@ -562,7 +563,7 @@ export class PointShadowAtlasBoundsViolationError extends Error {
|
|
|
562
563
|
* Fired by the per-frame record stage (`videoTextureView`) when a VideoPlayer
|
|
563
564
|
* entity can reach neither video upload path this frame: the general
|
|
564
565
|
* `copyExternalImageToTexture` path (no host HTMLVideoElement resolved via
|
|
565
|
-
* `
|
|
566
|
+
* `VideoSourceProvider`) AND the high-perf `GPUExternalTexture` path
|
|
566
567
|
* (capability absent). The engine surfaces this explicit failure rather than
|
|
567
568
|
* silently rendering a stale/garbage texture (charter P3, plan-strategy D-6).
|
|
568
569
|
* - `.code = 'video-upload-unsupported'`
|
|
@@ -1980,6 +1981,7 @@ export type RenderErrorCode = RenderError['code'];
|
|
|
1980
1981
|
* `RenderErrorCode` discriminant on `.code`.
|
|
1981
1982
|
*/
|
|
1982
1983
|
export type RenderError =
|
|
1984
|
+
| RenderPublicationError
|
|
1983
1985
|
| LifecycleConstructionError
|
|
1984
1986
|
| AutoExposureError
|
|
1985
1987
|
| RendererExpectedOperationError
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MaterialCookProgramContext } from '@forgeax/engine-pack/material-cook';
|
|
2
|
+
import type { RhiCaps } from '@forgeax/engine-rhi';
|
|
3
|
+
|
|
4
|
+
/** The receiving device selects the immutable material program domain. */
|
|
5
|
+
export function renderMaterialContext(caps: Pick<RhiCaps, 'backendKind' | 'storageBuffer'>): {
|
|
6
|
+
readonly materialContext?: MaterialCookProgramContext;
|
|
7
|
+
} {
|
|
8
|
+
if (caps.backendKind === 'null') return {};
|
|
9
|
+
return {
|
|
10
|
+
materialContext: {
|
|
11
|
+
backend: caps.backendKind === 'wgpu-webgl2' ? 'webgl2' : caps.backendKind,
|
|
12
|
+
capability: caps.storageBuffer ? 'storage-buffer' : 'uniform-fallback',
|
|
13
|
+
pipeline: 'forward',
|
|
14
|
+
geometry: 'mesh',
|
|
15
|
+
pass: 'forward',
|
|
16
|
+
profile: 'forgeax-material-wgsl-v1',
|
|
17
|
+
toolchain: 'naga-oil',
|
|
18
|
+
instrumentation: 'none',
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
package/src/features/host.ts
CHANGED
|
@@ -133,6 +133,8 @@ export interface RenderFeatureStageEvent {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
export interface RenderFeatureFrameInput {
|
|
136
|
+
readonly publishedFeatures?: readonly { readonly identity: string; readonly data: unknown }[];
|
|
137
|
+
readonly onFeatureSourceSubmitted?: (identity: string, feedback: unknown) => void;
|
|
136
138
|
readonly worlds: readonly import('@forgeax/engine-ecs').World[];
|
|
137
139
|
readonly owner: number;
|
|
138
140
|
readonly frameNumber: number;
|
|
@@ -1454,7 +1456,10 @@ export function runRenderFeatureFrame(
|
|
|
1454
1456
|
const extracted = invokeStage(
|
|
1455
1457
|
slot,
|
|
1456
1458
|
'extract',
|
|
1457
|
-
() =>
|
|
1459
|
+
() => {
|
|
1460
|
+
const published = input.publishedFeatures?.find((row) => row.identity === identity);
|
|
1461
|
+
return published === undefined ? slot.feature.extract(extractContext) : ok(published.data);
|
|
1462
|
+
},
|
|
1458
1463
|
stageEvents,
|
|
1459
1464
|
errors,
|
|
1460
1465
|
);
|
|
@@ -1655,11 +1660,14 @@ export function runRenderFeatureFrame(
|
|
|
1655
1660
|
continue;
|
|
1656
1661
|
}
|
|
1657
1662
|
planExecutionProjections.set(plannedFrame, projected.value);
|
|
1658
|
-
if (feature.onFrameSubmitted !== undefined) {
|
|
1663
|
+
if (feature.onFrameSubmitted !== undefined || feature.onSourceFrameSubmitted !== undefined) {
|
|
1659
1664
|
submissionSensitivePlans.add(plannedFrame);
|
|
1660
|
-
submissionAcknowledgments.set(plannedFrame, () =>
|
|
1661
|
-
feature.onFrameSubmitted?.(extracted.value, projected.value)
|
|
1662
|
-
|
|
1665
|
+
submissionAcknowledgments.set(plannedFrame, () => {
|
|
1666
|
+
feature.onFrameSubmitted?.(extracted.value, projected.value);
|
|
1667
|
+
if (input.publishedFeatures?.some((row) => row.identity === identity))
|
|
1668
|
+
input.onFeatureSourceSubmitted?.(identity, declared.value.sourceFeedback);
|
|
1669
|
+
else feature.onSourceFrameSubmitted?.(extracted.value, declared.value.sourceFeedback);
|
|
1670
|
+
});
|
|
1663
1671
|
}
|
|
1664
1672
|
plans.push(plannedFrame);
|
|
1665
1673
|
if (feature.onFrameAborted !== undefined) {
|
package/src/features/plan.ts
CHANGED
|
@@ -237,6 +237,8 @@ export type RenderFeaturePassDeclaration =
|
|
|
237
237
|
* access is derived from these roles rather than duplicated as reads/writes.
|
|
238
238
|
*/
|
|
239
239
|
export interface RenderFeaturePlan {
|
|
240
|
+
/** Source acknowledgment facts; never part of GPU resource or graph identity. */
|
|
241
|
+
readonly sourceFeedback?: unknown;
|
|
240
242
|
readonly resources: readonly RenderFeatureResourceDeclaration[];
|
|
241
243
|
readonly passes: readonly RenderFeaturePassDeclaration[];
|
|
242
244
|
}
|
package/src/features/types.ts
CHANGED
|
@@ -171,6 +171,8 @@ export interface RenderFeature<FrameData> {
|
|
|
171
171
|
readonly source: string;
|
|
172
172
|
}[];
|
|
173
173
|
extract(context: RenderFeatureExtractContext): Result<FrameData, RenderError>;
|
|
174
|
+
/** GUID roots needed by the declarative plan; the publisher carries their asset closure. */
|
|
175
|
+
assetDependencies?(data: FrameData): readonly string[];
|
|
174
176
|
plan(data: FrameData, context: RenderFeaturePlanContext): Result<RenderFeaturePlan, RenderError>;
|
|
175
177
|
/**
|
|
176
178
|
* Consume frame-owned producer state only after the compiled graph reached
|
|
@@ -178,6 +180,8 @@ export interface RenderFeature<FrameData> {
|
|
|
178
180
|
* extracted frame and never during declarative planning.
|
|
179
181
|
*/
|
|
180
182
|
onFrameSubmitted?(data: FrameData, submission?: RenderFeatureSubmission): void;
|
|
183
|
+
/** Consume source-owned intents after submission; feedback must be structured-cloneable. */
|
|
184
|
+
onSourceFrameSubmitted?(data: FrameData, feedback: unknown): void;
|
|
181
185
|
/**
|
|
182
186
|
* Discard frame-owned producer state when graph admission or submission is
|
|
183
187
|
* rejected. This is the symmetric recovery path for onFrameSubmitted.
|
|
@@ -158,7 +158,7 @@ interface GlyphTextData {
|
|
|
158
158
|
*/
|
|
159
159
|
export function glyphTextLayoutSystem(
|
|
160
160
|
world: World,
|
|
161
|
-
gpuStore: GpuResidencyCache,
|
|
161
|
+
gpuStore: Pick<GpuResidencyCache, 'updateMesh'>,
|
|
162
162
|
): Result<void, TextError> {
|
|
163
163
|
resetFontConcurrency();
|
|
164
164
|
|
|
@@ -200,7 +200,7 @@ function collectGlyphEntities(world: World): EntityHandle[] {
|
|
|
200
200
|
*/
|
|
201
201
|
function processEntity(
|
|
202
202
|
world: World,
|
|
203
|
-
gpuStore: GpuResidencyCache,
|
|
203
|
+
gpuStore: Pick<GpuResidencyCache, 'updateMesh'>,
|
|
204
204
|
entity: EntityHandle,
|
|
205
205
|
): TextError | null {
|
|
206
206
|
const gtRes = world.get(entity, GlyphText);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { World } from '@forgeax/engine-ecs';
|
|
2
1
|
import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
|
|
2
|
+
import type { RenderResourceScope } from '../publication/resource-scope';
|
|
3
3
|
import { worldEntityKey } from '../record/frame-snapshot';
|
|
4
4
|
import type { ShadowCasterMembership } from '../render-system-extract';
|
|
5
5
|
|
|
@@ -7,12 +7,12 @@ const WORLD_ENTITY_KEY_STRIDE = 4294967296;
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Extract emits world/entity keys in the transient `worlds[]` index domain,
|
|
10
|
-
* while PersistentRenderScene owns stable renderer-local
|
|
10
|
+
* while PersistentRenderScene owns stable renderer-local RenderResourceScope keys for the
|
|
11
11
|
* retained GPU scene. Rebase ShadowCaster ownership at this orchestration
|
|
12
12
|
* boundary before both GPU projection and CPU residual recording consume it.
|
|
13
13
|
*/
|
|
14
14
|
export function rebaseShadowCasterOwnership(
|
|
15
|
-
worlds: readonly
|
|
15
|
+
worlds: readonly RenderResourceScope[],
|
|
16
16
|
stableWorldKeys: readonly number[],
|
|
17
17
|
entityKeys: ReadonlySet<number>,
|
|
18
18
|
drawKeys: ReadonlySet<string>,
|
|
@@ -32,7 +32,6 @@ export function resolveFrameSkylightResources(
|
|
|
32
32
|
prefilterView: resident?.prefilterView ?? fallback.prefilterView,
|
|
33
33
|
prefilterSampler: fallback.sampler,
|
|
34
34
|
brdfLutView: resident?.brdfLutView ?? fallback.brdfLutView,
|
|
35
|
-
brdfLutSampler: fallback.sampler,
|
|
36
35
|
intensityBuffer: fallback.intensityBuffer,
|
|
37
36
|
});
|
|
38
37
|
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
// skylight-bind-group.ts -- Skylight resources merged into @group(1) PBR
|
|
2
|
-
// material BGL (binding 7..
|
|
2
|
+
// material BGL (binding 7..12) + fallback identity resource bundle. The
|
|
3
3
|
// complete Standard material group appends transmission entries.
|
|
4
4
|
//
|
|
5
5
|
// Plan-strategy D-5 (round-4 REVISED): the round-2 stand-alone @group(4)
|
|
6
6
|
// Skylight BindGroupLayout collided with WebGPU's default maxBindGroups=4
|
|
7
7
|
// limit, blocking pbr-pl pipeline-layout creation in chrome-beta. Round-4
|
|
8
|
-
// rewrites the contract: Skylight
|
|
9
|
-
// existing PBR material BindGroupLayout at bindings 7..
|
|
8
|
+
// rewrites the contract: Skylight 6 entries are **appended** to the
|
|
9
|
+
// existing PBR material BindGroupLayout at bindings 7..12; pipeline layout
|
|
10
10
|
// stays at 4 slots `[view, material, mesh, instances]`; unlit pipeline is
|
|
11
11
|
// untouched (it uses its own material BGL).
|
|
12
12
|
//
|
|
13
13
|
// Surface (M2 round-4 / t40 amend):
|
|
14
14
|
// - mergeSkylightIntoMaterialBgl(materialBglEntries): given the existing
|
|
15
15
|
// 7 PBR material BindGroupLayout entries (binding 0..6), returns the
|
|
16
|
-
// merged
|
|
17
|
-
// [irrTex, irrSampler, prefTex, prefSampler, brdfTex,
|
|
16
|
+
// merged 13-entry array with Skylight resources at binding 7..12
|
|
17
|
+
// [irrTex, irrSampler, prefTex, prefSampler, brdfTex,
|
|
18
18
|
// uniform { intensity: f32 }]. The merge is a pure function -- caller
|
|
19
19
|
// (createRenderer) is responsible for passing the result to
|
|
20
20
|
// `device.createBindGroupLayout`.
|
|
@@ -124,13 +124,13 @@ export interface SkylightQueue {
|
|
|
124
124
|
*/
|
|
125
125
|
export interface SkylightBindGroupResources {
|
|
126
126
|
readonly irradianceView: TextureView;
|
|
127
|
+
/** Shared by diffuse irradiance and the BRDF lookup table. */
|
|
127
128
|
readonly irradianceSampler: Sampler;
|
|
128
129
|
readonly prefilterView: TextureView;
|
|
129
130
|
/** Present only when the per-draw prefilter slot was replaced by a local probe. */
|
|
130
131
|
readonly skylightPrefilterView?: TextureView;
|
|
131
132
|
readonly prefilterSampler: Sampler;
|
|
132
133
|
readonly brdfLutView: TextureView;
|
|
133
|
-
readonly brdfLutSampler: Sampler;
|
|
134
134
|
readonly intensityBuffer: Buffer;
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -143,7 +143,7 @@ export interface SkylightBindGroupResources {
|
|
|
143
143
|
*
|
|
144
144
|
* No stand-alone `bindGroup` field: the round-2 stand-alone Skylight BG
|
|
145
145
|
* is gone (D-5 round-4); the fallback resources flow into the PBR
|
|
146
|
-
* material BG at binding 7..
|
|
146
|
+
* material BG at binding 7..12.
|
|
147
147
|
*/
|
|
148
148
|
export interface SkylightFallback {
|
|
149
149
|
readonly irradianceTexture: Texture;
|
|
@@ -159,7 +159,7 @@ export interface SkylightFallback {
|
|
|
159
159
|
// ─── Pure merger: BindGroupLayout entries (D-5 round-4) ─────────────────────
|
|
160
160
|
|
|
161
161
|
/**
|
|
162
|
-
* Append the
|
|
162
|
+
* Append the 6 Skylight BindGroupLayout entries after the existing PBR
|
|
163
163
|
* user-region entries. Pure function -- the
|
|
164
164
|
* caller passes the result to `device.createBindGroupLayout`. Charter P4
|
|
165
165
|
* consistent abstraction: one BGL holds material + Skylight together,
|
|
@@ -208,16 +208,10 @@ export function mergeSkylightIntoMaterialBgl(
|
|
|
208
208
|
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
209
209
|
texture: { sampleType: 'float', viewDimension: '2d' },
|
|
210
210
|
} as BglEntry);
|
|
211
|
-
// binding 12:
|
|
211
|
+
// binding 12: uniform { intensity: f32 }
|
|
212
212
|
merged.push({
|
|
213
213
|
binding: skylightBindingStart + 5,
|
|
214
214
|
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
215
|
-
sampler: { type: 'filtering' },
|
|
216
|
-
} as BglEntry);
|
|
217
|
-
// binding 13: uniform { intensity: f32 }
|
|
218
|
-
merged.push({
|
|
219
|
-
binding: skylightBindingStart + 6,
|
|
220
|
-
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
221
215
|
buffer: { type: 'uniform' },
|
|
222
216
|
} as BglEntry);
|
|
223
217
|
return merged;
|
|
@@ -226,7 +220,7 @@ export function mergeSkylightIntoMaterialBgl(
|
|
|
226
220
|
// ─── Pure merger: BindGroupEntry values (assembly site) ─────────────────────
|
|
227
221
|
|
|
228
222
|
/**
|
|
229
|
-
* Append the
|
|
223
|
+
* Append the 6 Skylight BindGroupEntry resources (binding 7..12) onto the
|
|
230
224
|
* existing PBR material BindGroupEntry values (binding 0..6). The caller
|
|
231
225
|
* passes the result to `device.createBindGroup` as part of the merged
|
|
232
226
|
* material BG.
|
|
@@ -301,10 +295,6 @@ export function assembleMaterialWithSkylightEntries(
|
|
|
301
295
|
},
|
|
302
296
|
{
|
|
303
297
|
binding: iblStart + 5,
|
|
304
|
-
resource: { kind: 'sampler', value: skylight.brdfLutSampler },
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
binding: iblStart + 6,
|
|
308
298
|
resource: { kind: 'buffer', value: { buffer: skylight.intensityBuffer } },
|
|
309
299
|
},
|
|
310
300
|
];
|
|
@@ -413,11 +403,9 @@ export function createSkylightFallback(
|
|
|
413
403
|
device: SkylightDevice,
|
|
414
404
|
queue: SkylightQueue,
|
|
415
405
|
): SkylightFallback {
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
// in assembleMaterialWithSkylightEntries; WebGPU spec permits sampler
|
|
420
|
-
// reuse across BindGroupEntry slots.
|
|
406
|
+
// Diffuse IBL and the BRDF LUT share one binding; prefilter remains a
|
|
407
|
+
// separate binding so per-draw reflection probes can replace it. Both
|
|
408
|
+
// bindings use this linear-clamp sampler for the global environment.
|
|
421
409
|
const samplerResult = device.createSampler({
|
|
422
410
|
label: 'skylight-fallback-sampler',
|
|
423
411
|
magFilter: 'linear',
|
package/src/index.ts
CHANGED
|
@@ -560,6 +560,15 @@ export {
|
|
|
560
560
|
type PointsStyleInput,
|
|
561
561
|
} from './points-lines/admission.js';
|
|
562
562
|
export type { PointsLinesInspection } from './points-lines/inspection.js';
|
|
563
|
+
export {
|
|
564
|
+
type PublishedRenderFrameInput,
|
|
565
|
+
type RenderPublication,
|
|
566
|
+
RenderPublicationError,
|
|
567
|
+
type RenderPublicationIdentity,
|
|
568
|
+
renderPublicationTransfers,
|
|
569
|
+
} from './publication/contract';
|
|
570
|
+
export { createRenderPublisher, type RenderPublicationCandidate } from './publication/publisher';
|
|
571
|
+
export { RenderPublicationTargetOwner, type RenderTargetAuthoring } from './publication/targets';
|
|
563
572
|
/**
|
|
564
573
|
* Public bounded GPU pass facts. Use the single `gpuPassTiming` opt-in,
|
|
565
574
|
* `draw()` receipt, and `observe(receipt, { include: ['timings'] })` route.
|
package/src/instances.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { World } from '@forgeax/engine-ecs';
|
|
2
1
|
import { InstanceTransformsStrideMismatchError } from '@forgeax/engine-ecs/projection';
|
|
3
2
|
import type { RhiErrorCode } from '@forgeax/engine-rhi';
|
|
4
3
|
|
|
@@ -93,7 +92,7 @@ export function instanceUploadRangesForResident(
|
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
interface Projection extends InstanceCollectionSnapshot {
|
|
96
|
-
readonly world:
|
|
95
|
+
readonly world: object;
|
|
97
96
|
readonly entity: number;
|
|
98
97
|
}
|
|
99
98
|
interface Residency {
|
|
@@ -112,11 +111,15 @@ interface Residency {
|
|
|
112
111
|
export class InstanceProjectionStore {
|
|
113
112
|
private nextId = 1;
|
|
114
113
|
private nextGeneration = 1;
|
|
115
|
-
private identities = new WeakMap<
|
|
114
|
+
private identities = new WeakMap<object, Map<number, InstanceCollectionId>>();
|
|
116
115
|
private readonly records = new Map<InstanceCollectionId, Projection>();
|
|
117
116
|
private readonly residency = new Map<InstanceCollectionId, Residency>();
|
|
118
117
|
|
|
119
|
-
project(
|
|
118
|
+
project(
|
|
119
|
+
world: object,
|
|
120
|
+
entity: number,
|
|
121
|
+
transforms: ArrayLike<number>,
|
|
122
|
+
): InstanceCollectionSnapshot {
|
|
120
123
|
const collectionId =
|
|
121
124
|
this.identities.get(world)?.get(entity) ?? (this.nextId++ as InstanceCollectionId);
|
|
122
125
|
const previous = this.records.get(collectionId);
|
|
@@ -147,7 +150,7 @@ export class InstanceProjectionStore {
|
|
|
147
150
|
|
|
148
151
|
/** Called only after the owning RenderScene accepts its prepared changes. */
|
|
149
152
|
accept(
|
|
150
|
-
world:
|
|
153
|
+
world: object,
|
|
151
154
|
entity: number,
|
|
152
155
|
snapshot: {
|
|
153
156
|
readonly collectionId?: InstanceCollectionId;
|
|
@@ -175,7 +178,7 @@ export class InstanceProjectionStore {
|
|
|
175
178
|
});
|
|
176
179
|
}
|
|
177
180
|
|
|
178
|
-
release(world:
|
|
181
|
+
release(world: object, entity: number): void {
|
|
179
182
|
const entities = this.identities.get(world);
|
|
180
183
|
const id = entities?.get(entity);
|
|
181
184
|
if (id === undefined) return;
|
package/src/pbr-pipeline.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// material BGL grows from the derived user region by appending the 7
|
|
7
7
|
// Skylight resources via `mergeSkylightIntoMaterialBgl`. The
|
|
8
8
|
// unlit pipeline keeps its 7-entry material BGL (no Skylight binding
|
|
9
|
-
// 7..
|
|
9
|
+
// 7..12 contamination) so unlit demos don't carry IBL state.
|
|
10
10
|
// - charter P4: same pipeline layout shape drives Skylight present +
|
|
11
11
|
// absent paths -- AI users do not branch on Skylight existence.
|
|
12
12
|
// - feat-20260520-skylight-ibl-cubemap M4 / t59 (round-4): the
|
|
@@ -209,8 +209,8 @@ export function buildPbrMaterialUserRegionEntries(
|
|
|
209
209
|
// (group(0) bindings 3..7); this kind is the seam for
|
|
210
210
|
// each per-material shadow override surface a future feat
|
|
211
211
|
// wires onto group(1).
|
|
212
|
-
// - 'ibl' the
|
|
213
|
-
// cube + brdfLut 2d +
|
|
212
|
+
// - 'ibl' the 6 IBL / Skylight entries (irradiance / prefilter
|
|
213
|
+
// cube + brdfLut 2d + 2 samplers + intensity uniform).
|
|
214
214
|
// Used by `buildPbrPipelineLayouts` after the user-region
|
|
215
215
|
// user paramSchema entries are emitted.
|
|
216
216
|
// - 'lightmap' the 4 emissive + occlusion entries (sampler + texture
|
|
@@ -224,7 +224,7 @@ export function buildPbrMaterialUserRegionEntries(
|
|
|
224
224
|
// placeholders because their shaders do not read these slots.
|
|
225
225
|
export type InjectionKind = keyof typeof INJECTION_KIND_LENGTHS;
|
|
226
226
|
|
|
227
|
-
const IBL_INJECTION_LENGTH =
|
|
227
|
+
const IBL_INJECTION_LENGTH = 6;
|
|
228
228
|
const LIGHTMAP_INJECTION_LENGTH = 4;
|
|
229
229
|
const SHADOW_INJECTION_LENGTH = 2;
|
|
230
230
|
const TRANSMISSION_INJECTION_LENGTH = 2;
|
|
@@ -283,16 +283,10 @@ export function appendInjection(
|
|
|
283
283
|
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
284
284
|
texture: { sampleType: 'float', viewDimension: '2d' },
|
|
285
285
|
},
|
|
286
|
-
// binding start+5:
|
|
286
|
+
// binding start+5: uniform { intensity: f32 }
|
|
287
287
|
{
|
|
288
288
|
binding: start + 5,
|
|
289
289
|
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
290
|
-
sampler: { type: 'filtering' },
|
|
291
|
-
},
|
|
292
|
-
// binding start+6: uniform { intensity: f32 }
|
|
293
|
-
{
|
|
294
|
-
binding: start + 6,
|
|
295
|
-
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
296
290
|
buffer: { type: 'uniform' },
|
|
297
291
|
},
|
|
298
292
|
];
|
|
@@ -1119,13 +1113,13 @@ export function pbrSkinMeshDynamicOffsets(
|
|
|
1119
1113
|
|
|
1120
1114
|
/**
|
|
1121
1115
|
* Build a stand-alone 7-entry unlit material BGL. Round-4 D-5 keeps unlit
|
|
1122
|
-
* material BG isolated from Skylight binding 7..
|
|
1116
|
+
* material BG isolated from Skylight binding 7..12 -- unlit demos do not
|
|
1123
1117
|
* pay for IBL state. The unlit pipeline still binds material at slot 1,
|
|
1124
1118
|
* just with a 7-entry layout.
|
|
1125
1119
|
*
|
|
1126
1120
|
* Note: at the moment the runtime still routes both unlit + standard
|
|
1127
1121
|
* through a single 14-entry pipeline layout; the unlit material BG
|
|
1128
|
-
* carries fallback identity resources at binding 7..
|
|
1122
|
+
* carries fallback identity resources at binding 7..12. This factory is
|
|
1129
1123
|
* exported as a future-proof seam for the moment when unlit demos own
|
|
1130
1124
|
* their own pipeline layout (t57 (e) test pins the contract today so the
|
|
1131
1125
|
* eventual split has a green target).
|
|
@@ -1241,7 +1235,7 @@ export const SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET = '';
|
|
|
1241
1235
|
*
|
|
1242
1236
|
* Three groups by derivation source:
|
|
1243
1237
|
* 1. Shader-derived (paramSchema reflection + injection chain):
|
|
1244
|
-
* - `'pbr-material-merged'` — derived user region + Skylight
|
|
1238
|
+
* - `'pbr-material-merged'` — derived user region + Skylight 6 + transmission 2
|
|
1245
1239
|
* - `'unlit-material'` — 7 entries: base PBR material only (no inject)
|
|
1246
1240
|
* - `'hdrp-7-slot'` — 7 entries (binding 0 + 3..8): HDRP cluster + SSAO group(2) BGL
|
|
1247
1241
|
* 2. Caps-driven literal shapes (no shader):
|
|
@@ -1638,11 +1632,11 @@ export function buildBindGroupLayoutDescriptor(
|
|
|
1638
1632
|
}
|
|
1639
1633
|
case 'pbr-material-merged': {
|
|
1640
1634
|
// Material BGL: per-shader user-region (derive(paramSchema).bglEntries)
|
|
1641
|
-
// + IBL injection (
|
|
1635
|
+
// + IBL injection (6) + transmission injection (2). The user-region
|
|
1642
1636
|
// size is the only variable; injection start = userRegion.length so a
|
|
1643
1637
|
// custom schema shifts IBL/transmission by its sampler/texture pairs.
|
|
1644
|
-
// The built-in standard-PBR
|
|
1645
|
-
//
|
|
1638
|
+
// The built-in standard-PBR user region has 15 entries;
|
|
1639
|
+
// scene rows follow injection, and physical texture pairs follow the effective root schema.
|
|
1646
1640
|
//
|
|
1647
1641
|
// Schema source priority (D-1): explicit materialParamSchema option >
|
|
1648
1642
|
// registry lookup of spec.shader.id > built-in standard-PBR fallback.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
|
|
2
2
|
import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
|
|
3
|
-
import type { World } from '@forgeax/engine-ecs';
|
|
4
3
|
import type { RenderGraphBuilder, RenderGraphError } from '@forgeax/engine-render-graph';
|
|
5
4
|
import type {
|
|
6
5
|
BindGroup,
|
|
@@ -13,6 +12,7 @@ import type {
|
|
|
13
12
|
} from '@forgeax/engine-rhi';
|
|
14
13
|
import { err, type Handle, ok, type Result, type TextureAsset } from '@forgeax/engine-types';
|
|
15
14
|
import type { GpuResidencyCache, TextureGpuEntry } from '../../device/gpu-residency';
|
|
15
|
+
import type { RenderResourceScope } from '../../publication/resource-scope';
|
|
16
16
|
import type { RenderPipelineFrame, RenderPipelineTarget } from '../../render-pipeline';
|
|
17
17
|
import {
|
|
18
18
|
admitStandardColorLut,
|
|
@@ -41,7 +41,7 @@ export interface StandardLutGraphProjection {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export interface StandardLutGpuPreparationInput {
|
|
44
|
-
readonly world:
|
|
44
|
+
readonly world: RenderResourceScope;
|
|
45
45
|
readonly handle: Handle<'TextureAsset', 'shared'>;
|
|
46
46
|
readonly strength: number;
|
|
47
47
|
readonly assets: AssetRegistry;
|
|
@@ -125,7 +125,7 @@ export function prepareStandardLutGpu(
|
|
|
125
125
|
if (!asset.ok) {
|
|
126
126
|
return failure(
|
|
127
127
|
'standard-lut-residency-failed',
|
|
128
|
-
'the authored TextureAsset handle resolves in the active
|
|
128
|
+
'the authored TextureAsset handle resolves in the active RenderResourceScope',
|
|
129
129
|
'register or rebuild the TextureAsset, then retry the same camera generation',
|
|
130
130
|
causeDetail(asset.error),
|
|
131
131
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { World } from '@forgeax/engine-ecs';
|
|
2
1
|
import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
|
|
3
2
|
import { toShared } from '@forgeax/engine-types';
|
|
3
|
+
import type { RenderResourceScope } from '../../publication/resource-scope';
|
|
4
4
|
import type { RenderFrameState } from '../../record/frame-snapshot';
|
|
5
5
|
import type { RenderSystemInternals } from '../../record/render-context';
|
|
6
6
|
import type { CameraSnapshot } from '../../render-contract';
|
|
@@ -17,7 +17,7 @@ export function prepareStandardOutputResources(
|
|
|
17
17
|
state: RenderFrameState,
|
|
18
18
|
internals: RenderSystemInternals,
|
|
19
19
|
camera: CameraSnapshot | undefined,
|
|
20
|
-
world:
|
|
20
|
+
world: RenderResourceScope,
|
|
21
21
|
): Result<void, RhiError> {
|
|
22
22
|
const output = camera?.output;
|
|
23
23
|
if (output?.exposure.kind === 'auto') {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { AssetRegistry, HANDLE_CUBE } from '@forgeax/engine-assets-runtime';
|
|
2
|
+
import { World } from '@forgeax/engine-ecs';
|
|
3
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
4
|
+
import { rhi } from '@forgeax/engine-rhi-null';
|
|
5
|
+
import { Transform } from '@forgeax/engine-scene';
|
|
6
|
+
import { ShaderRegistry } from '@forgeax/engine-shader';
|
|
7
|
+
import { Skin } from '@forgeax/engine-skinning';
|
|
8
|
+
import { expect, it } from 'vitest';
|
|
9
|
+
import { createSkinPaletteOwner } from '../../assembly/skin-palette-owner';
|
|
10
|
+
import { Instances, MeshFilter, MeshRenderer } from '../../components';
|
|
11
|
+
import { GlyphText } from '../../components/glyph-text';
|
|
12
|
+
import { InstanceProjectionStore } from '../../instances';
|
|
13
|
+
import { PersistentRenderScene } from '../../scene/render-scene';
|
|
14
|
+
import { renderPublicationTransfers } from '../contract';
|
|
15
|
+
import { preparePublicationGeometry } from '../prepare-geometry';
|
|
16
|
+
import { createRenderPublisher } from '../publisher';
|
|
17
|
+
import { RenderPublicationReceiver } from '../receiver';
|
|
18
|
+
|
|
19
|
+
function required<T>(value: T | undefined): T {
|
|
20
|
+
if (value === undefined) throw new Error('Missing expected render projection');
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fixture() {
|
|
25
|
+
const world = new World();
|
|
26
|
+
const assets = new AssetRegistry(new ShaderRegistry({ manifestUrl: undefined }));
|
|
27
|
+
const identity = { source: 'derived-geometry', epoch: 1 };
|
|
28
|
+
const publisher = createRenderPublisher(world, assets, identity);
|
|
29
|
+
const receiver = new RenderPublicationReceiver(identity);
|
|
30
|
+
const scene = new PersistentRenderScene();
|
|
31
|
+
const instances = new InstanceProjectionStore();
|
|
32
|
+
const publish = (palette?: ReturnType<typeof createSkinPaletteOwner>) => {
|
|
33
|
+
world.update(0).unwrap();
|
|
34
|
+
const candidate = publisher.prepare(0).unwrap();
|
|
35
|
+
const packet = structuredClone(candidate.packet);
|
|
36
|
+
candidate.accept();
|
|
37
|
+
const received = receiver.accept(packet).unwrap();
|
|
38
|
+
const prepared = preparePublicationGeometry(received, scene, palette, assets, instances);
|
|
39
|
+
const frame = scene.consumePublication(prepared);
|
|
40
|
+
publisher.recycle(packet.revision, renderPublicationTransfers(packet)).unwrap();
|
|
41
|
+
return { packet, frame };
|
|
42
|
+
};
|
|
43
|
+
const spawn = () =>
|
|
44
|
+
world
|
|
45
|
+
.spawn(
|
|
46
|
+
{ component: Transform, data: {} },
|
|
47
|
+
{ component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
|
|
48
|
+
{ component: MeshRenderer, data: {} },
|
|
49
|
+
)
|
|
50
|
+
.unwrap();
|
|
51
|
+
return { world, assets, publisher, receiver, scene, instances, publish, spawn };
|
|
52
|
+
}
|
|
53
|
+
const matrix = () => new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
54
|
+
|
|
55
|
+
it('derives glyph meshes in the source World and republishes edits under the same mesh handle', () => {
|
|
56
|
+
const f = fixture();
|
|
57
|
+
const font = f.world.allocSharedRef('FontAsset', {
|
|
58
|
+
kind: 'font',
|
|
59
|
+
atlas: AssetGuid.random(),
|
|
60
|
+
sampler: AssetGuid.random(),
|
|
61
|
+
glyphs: {
|
|
62
|
+
65: {
|
|
63
|
+
advance: 10,
|
|
64
|
+
bearingX: 0,
|
|
65
|
+
bearingY: 8,
|
|
66
|
+
size: { w: 8, h: 8 },
|
|
67
|
+
region: { x: 0, y: 0, w: 8, h: 8 },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
common: {
|
|
71
|
+
lineHeight: 12,
|
|
72
|
+
base: 8,
|
|
73
|
+
distanceRange: 4,
|
|
74
|
+
pxRange: 4,
|
|
75
|
+
atlasWidth: 8,
|
|
76
|
+
atlasHeight: 8,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const label = f.world
|
|
80
|
+
.spawn(
|
|
81
|
+
{ component: Transform, data: {} },
|
|
82
|
+
{ component: GlyphText, data: { fontHandle: font, text: 'A', fontSize: 1 } },
|
|
83
|
+
)
|
|
84
|
+
.unwrap();
|
|
85
|
+
const initial = f.publish();
|
|
86
|
+
expect(initial.frame.renderables.map((row) => row.entityKey)).toContain(label);
|
|
87
|
+
const handle = f.world.get(label, MeshFilter).unwrap().assetHandle;
|
|
88
|
+
const mesh = initial.packet.assets.find((row) => row.handle === handle)?.value;
|
|
89
|
+
expect(mesh?.kind).toBe('mesh');
|
|
90
|
+
f.world.set(label, GlyphText, { text: 'AAA' }).unwrap();
|
|
91
|
+
const edited = f.publish();
|
|
92
|
+
expect(f.world.get(label, MeshFilter).unwrap().assetHandle).toBe(handle);
|
|
93
|
+
expect(edited.packet.invalidatedAssets).toContain(handle);
|
|
94
|
+
const next = edited.packet.assets.find((row) => row.handle === handle)?.value;
|
|
95
|
+
expect(
|
|
96
|
+
next?.kind === 'mesh' && mesh?.kind === 'mesh' && next.vertices.length > mesh.vertices.length,
|
|
97
|
+
).toBe(true);
|
|
98
|
+
f.publisher.dispose();
|
|
99
|
+
const later = f.world
|
|
100
|
+
.spawn({ component: GlyphText, data: { fontHandle: font, text: 'A' } })
|
|
101
|
+
.unwrap();
|
|
102
|
+
f.world.update(0).unwrap();
|
|
103
|
+
expect(f.world.hasComponent(later, MeshFilter)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('retains instance history on pose edits and retires it across removal and topology changes', () => {
|
|
107
|
+
const f = fixture();
|
|
108
|
+
const entity = f.spawn();
|
|
109
|
+
f.world.addComponent(entity, { component: Instances, data: { transforms: matrix() } }).unwrap();
|
|
110
|
+
const initial = required(f.publish().frame.renderables[0]?.instances);
|
|
111
|
+
const moved = matrix();
|
|
112
|
+
moved[12] = 2;
|
|
113
|
+
f.world.set(entity, Instances, { transforms: moved }).unwrap();
|
|
114
|
+
const edited = required(f.publish().frame.renderables[0]?.instances);
|
|
115
|
+
expect(edited.collectionId).toBe(initial.collectionId);
|
|
116
|
+
expect(edited.generations).toEqual(initial.generations);
|
|
117
|
+
expect(edited.revision).toBe(required(initial.revision) + 1);
|
|
118
|
+
f.world
|
|
119
|
+
.set(entity, Instances, { transforms: new Float32Array([...matrix(), ...matrix()]) })
|
|
120
|
+
.unwrap();
|
|
121
|
+
const resized = required(f.publish().frame.renderables[0]?.instances);
|
|
122
|
+
expect(resized.generations?.[0]).not.toBe(initial.generations?.[0]);
|
|
123
|
+
f.world.removeComponent(entity, Instances).unwrap();
|
|
124
|
+
f.publish();
|
|
125
|
+
f.world.addComponent(entity, { component: Instances, data: { transforms: matrix() } }).unwrap();
|
|
126
|
+
const restored = required(f.publish().frame.renderables[0]?.instances);
|
|
127
|
+
expect(restored.collectionId).not.toBe(initial.collectionId);
|
|
128
|
+
f.publisher.dispose();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it.each([
|
|
132
|
+
true,
|
|
133
|
+
false,
|
|
134
|
+
])('keeps untouched skin palettes and reclaims removed slices (storage=%s)', async (storage) => {
|
|
135
|
+
const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
|
|
136
|
+
const palette = createSkinPaletteOwner(device, storage);
|
|
137
|
+
const f = fixture();
|
|
138
|
+
const skeleton = f.world.allocSharedRef('SkeletonAsset', {
|
|
139
|
+
kind: 'skeleton',
|
|
140
|
+
jointCount: 1,
|
|
141
|
+
inverseBindMatrices: matrix(),
|
|
142
|
+
});
|
|
143
|
+
const joint = f.world.spawn({ component: Transform, data: {} }).unwrap();
|
|
144
|
+
const first = f.spawn(),
|
|
145
|
+
second = f.spawn();
|
|
146
|
+
for (const entity of [first, second])
|
|
147
|
+
f.world
|
|
148
|
+
.addComponent(entity, {
|
|
149
|
+
component: Skin,
|
|
150
|
+
data: { skeleton, joints: new Uint32Array([joint]) },
|
|
151
|
+
})
|
|
152
|
+
.unwrap();
|
|
153
|
+
const initial = f.publish(palette).frame;
|
|
154
|
+
const one = required(initial.renderables.find((row) => row.entityKey === first)?.skin);
|
|
155
|
+
const two = required(initial.renderables.find((row) => row.entityKey === second)?.skin);
|
|
156
|
+
expect(one).toBeDefined();
|
|
157
|
+
expect(two).toBeDefined();
|
|
158
|
+
f.world.removeComponent(first, Skin).unwrap();
|
|
159
|
+
const removed = f.publish(palette).frame;
|
|
160
|
+
expect(removed.renderables.find((row) => row.entityKey === first)?.skin).toBeUndefined();
|
|
161
|
+
expect(removed.renderables.find((row) => row.entityKey === second)?.skin?.fence).toBe(two.fence);
|
|
162
|
+
f.world.set(joint, Transform, { pos: [1, 0, 0] }).unwrap();
|
|
163
|
+
const moved = required(
|
|
164
|
+
f.publish(palette).frame.renderables.find((row) => row.entityKey === second)?.skin,
|
|
165
|
+
);
|
|
166
|
+
expect(moved.fence).toBe(two.fence);
|
|
167
|
+
expect(moved.uploadBytes).toBe(64);
|
|
168
|
+
f.world
|
|
169
|
+
.addComponent(first, { component: Skin, data: { skeleton, joints: new Uint32Array([joint]) } })
|
|
170
|
+
.unwrap();
|
|
171
|
+
const restored = required(
|
|
172
|
+
f.publish(palette).frame.renderables.find((row) => row.entityKey === first)?.skin,
|
|
173
|
+
);
|
|
174
|
+
expect(restored.fence).not.toBe(one.fence);
|
|
175
|
+
f.publisher.dispose();
|
|
176
|
+
palette.dispose();
|
|
177
|
+
});
|