@forgeax/engine-vfx-render 0.1.4 → 0.1.7
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 +6 -12
- package/dist/.tsbuildinfo +1 -1
- package/dist/feature/gpu-particle-feature.d.ts +20 -26
- package/dist/feature/gpu-particle-feature.d.ts.map +1 -1
- package/dist/feature/particle-resources.d.ts +0 -3
- package/dist/feature/particle-resources.d.ts.map +1 -1
- package/dist/feature/stage-plan.d.ts +1 -1
- package/dist/feature/stage-plan.d.ts.map +1 -1
- package/dist/host/vfx-runtime-host.d.ts +20 -4
- package/dist/host/vfx-runtime-host.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +458 -722
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
- package/src/__tests__/billboard-advanced.integration.test.ts +0 -23
- package/src/__tests__/fog-producer.integration.test.ts +4 -3
- package/src/__tests__/gpu-host.integration.test.ts +133 -115
- package/src/__tests__/particle-resources.unit.test.ts +0 -9
- package/src/__tests__/stage-plan.integration.test.ts +132 -0
- package/src/feature/gpu-particle-feature.ts +482 -888
- package/src/feature/particle-resources.ts +0 -8
- package/src/feature/stage-plan.ts +4 -1
- package/src/host/vfx-runtime-host.ts +81 -24
- package/src/index.ts +1 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { RenderFeatureMaterialShaderBindingContract } from '@forgeax/engine-render';
|
|
2
1
|
import {
|
|
3
2
|
err,
|
|
4
3
|
type MaterialAsset,
|
|
@@ -157,13 +156,6 @@ export function particleMaterialPass(
|
|
|
157
156
|
};
|
|
158
157
|
}
|
|
159
158
|
|
|
160
|
-
/** Scene-depth is a shader binding contract, not a renderer-kind convention. */
|
|
161
|
-
export function particleMaterialUsesSceneDepth(
|
|
162
|
-
contract: RenderFeatureMaterialShaderBindingContract | 'view-with-resource',
|
|
163
|
-
): boolean {
|
|
164
|
-
return contract === 'group-0-resource' || contract === 'view-with-resource';
|
|
165
|
-
}
|
|
166
|
-
|
|
167
159
|
/** True when the authored particle shader consumes the standard material bind group. */
|
|
168
160
|
export function particleMaterialUsesBindings(material: MaterialAsset | undefined): boolean {
|
|
169
161
|
return (material?.parameters?.length ?? 0) > 0;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type { RenderFeatureGpuBindingsRef, RenderFeatureGpuDispatch } from '@forgeax/engine-render';
|
|
2
1
|
import type { VfxGpuStageReflection } from '@forgeax/engine-vfx';
|
|
2
|
+
import type {
|
|
3
|
+
RenderFeatureGpuBindingsRef,
|
|
4
|
+
RenderFeatureGpuDispatch,
|
|
5
|
+
} from '../../../render/src/features/prepared-gpu-work.js';
|
|
3
6
|
|
|
4
7
|
const MANAGED_STAGES = new Set(['spawn', 'update', 'scan', 'compact']);
|
|
5
8
|
const RESOURCE_NAMES = new Set([
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from '@forgeax/engine-assets-runtime
|
|
1
|
+
import type {
|
|
2
|
+
AssetRegistryResolver,
|
|
3
|
+
AssetRegistry as LegacyAssetRegistry,
|
|
4
|
+
RuntimeAssetRegistry,
|
|
5
|
+
} from '@forgeax/engine-assets-runtime';
|
|
6
|
+
import { getAssetRegistryResolver } from '@forgeax/engine-assets-runtime';
|
|
6
7
|
import { createWorldContext, type EntityHandle, type World } from '@forgeax/engine-ecs';
|
|
7
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
Asset,
|
|
10
|
+
AssetDecoderLease,
|
|
11
|
+
MaterialAsset,
|
|
12
|
+
MeshAsset,
|
|
13
|
+
Result,
|
|
14
|
+
} from '@forgeax/engine-types';
|
|
8
15
|
import { err, ok } from '@forgeax/engine-types';
|
|
9
16
|
import type {
|
|
10
17
|
VfxDataInterfaceError,
|
|
@@ -21,6 +28,7 @@ import {
|
|
|
21
28
|
VFX_GPU_RUNTIME_RESOURCE_KEY,
|
|
22
29
|
type VfxGpuRuntime,
|
|
23
30
|
vfxGpuEffectContribution,
|
|
31
|
+
vfxGpuEffectPackLoader,
|
|
24
32
|
vfxGpuRuntimePlugin,
|
|
25
33
|
} from '@forgeax/engine-vfx';
|
|
26
34
|
import type { ParticleRenderCameraSource } from '../feature/camera.js';
|
|
@@ -30,6 +38,23 @@ import {
|
|
|
30
38
|
type VfxDataInterfaceRegistry,
|
|
31
39
|
} from './data-interface-providers.js';
|
|
32
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Public authoring projection accepted by the VFX host. Editor owns the
|
|
43
|
+
* projection, while the Engine App still owns bytes/decoders/cache. Keeping
|
|
44
|
+
* this small structural seam public lets the Editor pass that projection
|
|
45
|
+
* without importing the runtime registry's internal module or fabricating a
|
|
46
|
+
* second registry identity.
|
|
47
|
+
*/
|
|
48
|
+
export interface VfxAuthoringAssetRegistry {
|
|
49
|
+
readonly loaders: {
|
|
50
|
+
registerPackLoader(loader: unknown): void;
|
|
51
|
+
};
|
|
52
|
+
lookup<T extends Asset = Asset>(guid: string): T | undefined;
|
|
53
|
+
readonly load?: (guid: string, kind: string) => Promise<Result<unknown, unknown>>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type AssetRegistry = LegacyAssetRegistry | RuntimeAssetRegistry | VfxAuthoringAssetRegistry;
|
|
57
|
+
|
|
33
58
|
export interface VfxRuntimeHostOptions {
|
|
34
59
|
readonly camera: ParticleRenderCameraSource;
|
|
35
60
|
readonly maxQueuedTicks?: number;
|
|
@@ -108,8 +133,8 @@ export interface VfxRuntimeHost {
|
|
|
108
133
|
}): Promise<Result<{ readonly state: 'detached' | 'not-attached' }, VfxRuntimeHostError>>;
|
|
109
134
|
}
|
|
110
135
|
|
|
111
|
-
/** Install the VFX owner decoder into the
|
|
112
|
-
export function installVfxRuntimeDecoder(registry:
|
|
136
|
+
/** Install the VFX owner decoder into the core registry. */
|
|
137
|
+
export function installVfxRuntimeDecoder(registry: RuntimeAssetRegistry): AssetDecoderLease {
|
|
113
138
|
return registry.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
|
|
114
139
|
}
|
|
115
140
|
|
|
@@ -142,7 +167,7 @@ function controlFailure(
|
|
|
142
167
|
}
|
|
143
168
|
|
|
144
169
|
export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntimeHost {
|
|
145
|
-
const
|
|
170
|
+
const registries = new WeakSet<AssetRegistry>();
|
|
146
171
|
const worlds = new WeakMap<
|
|
147
172
|
World,
|
|
148
173
|
{
|
|
@@ -169,15 +194,34 @@ export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntime
|
|
|
169
194
|
const key = `${kind}:${guid.toLowerCase()}`;
|
|
170
195
|
const cached = attached.renderAssets.get(key);
|
|
171
196
|
if (cached?.kind === kind) return cached as Kind extends 'material' ? MaterialAsset : MeshAsset;
|
|
197
|
+
if ('lookup' in attached.assets) {
|
|
198
|
+
const lookup = attached.assets.lookup as (guid: string) => Asset | undefined;
|
|
199
|
+
// Preserve the registry receiver: the Engine-owned legacy class uses
|
|
200
|
+
// `this` to read its load-state/catalogue maps. The Editor facade may
|
|
201
|
+
// expose an arrow function, but calling through the owner is safe for
|
|
202
|
+
// both shapes and avoids turning a lookup into a swallowed plan error.
|
|
203
|
+
const legacy = lookup.call(attached.assets, guid);
|
|
204
|
+
if (legacy?.kind === kind) {
|
|
205
|
+
attached.renderAssets.set(key, legacy);
|
|
206
|
+
return legacy as Kind extends 'material' ? MaterialAsset : MeshAsset;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
172
209
|
const resolved = attached.resolver?.lookup<MaterialAsset | MeshAsset>(guid);
|
|
173
210
|
if (resolved?.kind === kind) {
|
|
174
211
|
attached.renderAssets.set(key, resolved);
|
|
175
212
|
return resolved as Kind extends 'material' ? MaterialAsset : MeshAsset;
|
|
176
213
|
}
|
|
177
|
-
if (
|
|
214
|
+
if (
|
|
215
|
+
'load' in attached.assets &&
|
|
216
|
+
typeof attached.assets.load === 'function' &&
|
|
217
|
+
!attached.pendingRenderAssets.has(key)
|
|
218
|
+
) {
|
|
178
219
|
const epoch = attached.catalogEpoch;
|
|
179
|
-
const
|
|
180
|
-
|
|
220
|
+
const load = attached.assets.load as (
|
|
221
|
+
guid: string,
|
|
222
|
+
kind: string,
|
|
223
|
+
) => Promise<Result<MaterialAsset | MeshAsset, unknown>>;
|
|
224
|
+
const request = load(guid, kind)
|
|
181
225
|
.then((result) => {
|
|
182
226
|
if (!result.ok || worlds.get(world) !== attached || attached.catalogEpoch !== epoch)
|
|
183
227
|
return;
|
|
@@ -314,15 +358,27 @@ export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntime
|
|
|
314
358
|
dataInterfaces.resolve(requirements, generation),
|
|
315
359
|
attachWorld: async ({ world, assets }) => {
|
|
316
360
|
if (worlds.has(world)) return ok({ state: 'already-attached' });
|
|
317
|
-
if (!
|
|
361
|
+
if (!registries.has(assets)) {
|
|
318
362
|
try {
|
|
319
|
-
|
|
363
|
+
// The Editor facade deliberately exposes the legacy LoaderRegistry
|
|
364
|
+
// while projecting the Engine-owned App.assets instance. Prefer
|
|
365
|
+
// that public authoring seam before looking for the five-action
|
|
366
|
+
// runtime registry; otherwise a facade that happens to expose
|
|
367
|
+
// `load` would be misclassified as a decoder owner.
|
|
368
|
+
if ('loaders' in assets) {
|
|
369
|
+
assets.loaders.registerPackLoader(vfxGpuEffectPackLoader);
|
|
370
|
+
} else if ('installDecoder' in assets) {
|
|
371
|
+
assets.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
|
|
372
|
+
} else {
|
|
373
|
+
throw new TypeError('VFX assets registry exposes neither loaders nor installDecoder');
|
|
374
|
+
}
|
|
375
|
+
registries.add(assets);
|
|
320
376
|
} catch (cause) {
|
|
321
377
|
return err(
|
|
322
378
|
failure(
|
|
323
379
|
'vfx-host-loader-install-failed',
|
|
324
|
-
'the VFX
|
|
325
|
-
'remove a conflicting particle-effect
|
|
380
|
+
'the v2 VFX loader to be registered once',
|
|
381
|
+
'remove a conflicting particle-effect loader and retry attachWorld',
|
|
326
382
|
cause,
|
|
327
383
|
),
|
|
328
384
|
);
|
|
@@ -346,10 +402,12 @@ export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntime
|
|
|
346
402
|
);
|
|
347
403
|
}
|
|
348
404
|
let resolver: AssetRegistryResolver | undefined;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
405
|
+
if (!('loaders' in assets) && 'installDecoder' in assets) {
|
|
406
|
+
try {
|
|
407
|
+
resolver = getAssetRegistryResolver(assets);
|
|
408
|
+
} catch {
|
|
409
|
+
resolver = undefined;
|
|
410
|
+
}
|
|
353
411
|
}
|
|
354
412
|
const attached = {
|
|
355
413
|
assets,
|
|
@@ -359,14 +417,13 @@ export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntime
|
|
|
359
417
|
renderAssets: new Map<string, MaterialAsset | MeshAsset>(),
|
|
360
418
|
pendingRenderAssets: new Map<string, Promise<void>>(),
|
|
361
419
|
catalogEpoch:
|
|
362
|
-
|
|
420
|
+
'snapshot' in assets && typeof assets.snapshot === 'function'
|
|
363
421
|
? assets.snapshot().epoch
|
|
364
422
|
: 0,
|
|
365
423
|
unsubscribeAssets: () => {},
|
|
366
424
|
};
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
attached.unsubscribeAssets = subscribe((snapshot) => {
|
|
425
|
+
if ('subscribe' in assets && typeof assets.subscribe === 'function') {
|
|
426
|
+
attached.unsubscribeAssets = assets.subscribe((snapshot) => {
|
|
370
427
|
if (snapshot.epoch === attached.catalogEpoch) return;
|
|
371
428
|
attached.catalogEpoch = snapshot.epoch;
|
|
372
429
|
attached.renderAssets.clear();
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,6 @@ export {
|
|
|
10
10
|
VFX_EVENT_COUNTER_BYTES,
|
|
11
11
|
VFX_EVENT_INPUT_BYTES,
|
|
12
12
|
} from './feature/event-resources.js';
|
|
13
|
-
export type { VfxGpuRenderObservation } from './feature/gpu-particle-feature.js';
|
|
14
13
|
export {
|
|
15
14
|
createVfxRenderInspectSnapshot,
|
|
16
15
|
gpuParticleRenderFeature,
|
|
@@ -47,6 +46,7 @@ export {
|
|
|
47
46
|
createVfxDataInterfaceRegistry,
|
|
48
47
|
} from './host/data-interface-providers.js';
|
|
49
48
|
export type {
|
|
49
|
+
VfxAuthoringAssetRegistry,
|
|
50
50
|
VfxRuntimeHost,
|
|
51
51
|
VfxRuntimeHostControl,
|
|
52
52
|
VfxRuntimeHostControlError,
|