@forgeax/engine-vfx 0.1.28 → 0.1.29
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 +62 -14
- package/dist/assets/particle-effect-decoder.d.ts.map +1 -1
- package/dist/authoring-descriptor.d.ts +4 -4
- package/dist/authoring-descriptor.d.ts.map +1 -1
- package/dist/code-source-v3.d.ts +101 -0
- package/dist/code-source-v3.d.ts.map +1 -0
- package/dist/code-source.d.ts +12 -11
- package/dist/code-source.d.ts.map +1 -1
- package/dist/data-interface.d.ts +17 -1
- package/dist/data-interface.d.ts.map +1 -1
- package/dist/effect-contract.d.ts +11 -1
- package/dist/effect-contract.d.ts.map +1 -1
- package/dist/gpu-loader.d.ts +5 -5
- package/dist/gpu-loader.d.ts.map +1 -1
- package/dist/gpu-program.d.ts +52 -27
- package/dist/gpu-program.d.ts.map +1 -1
- package/dist/gpu-runtime.d.ts +32 -7
- package/dist/gpu-runtime.d.ts.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +647 -117
- package/dist/index.mjs.map +1 -1
- package/dist/particle-layout.d.ts +62 -0
- package/dist/particle-layout.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/__tests__/authoring-descriptor.unit.test.ts +18 -8
- package/src/__tests__/channel-input.unit.test.ts +4 -1
- package/src/__tests__/channel-overflow-policy-owner.test-d.ts +2 -2
- package/src/__tests__/code-source-v2.unit.test.ts +15 -14
- package/src/__tests__/data-interface-contract.unit.test.ts +41 -2
- package/src/__tests__/effect-contract.unit.test.ts +6 -3
- package/src/__tests__/gpu-program-simulation-culling-owner.test-d.ts +6 -6
- package/src/__tests__/gpu-reflection-vocabulary-owner.test-d.ts +2 -2
- package/src/__tests__/gpu-runtime.integration.test.ts +244 -11
- package/src/__tests__/instance-public-api.unit.test.ts +4 -1
- package/src/__tests__/instance.unit.test.ts +4 -1
- package/src/__tests__/loader-v1-rejection.unit.test.ts +14 -14
- package/src/__tests__/player-reflection.unit.test.ts +2 -2
- package/src/__tests__/player-type.test-d.ts +4 -1
- package/src/__tests__/renderer-source.unit.test.ts +6 -6
- package/src/__tests__/replay-canonical.unit.test.ts +4 -1
- package/src/__tests__/vfx-data-interface-vocabulary-owner.test-d.ts +3 -8
- package/src/assets/particle-effect-decoder.ts +4 -3
- package/src/authoring-descriptor.ts +26 -28
- package/src/code-source-v3.ts +460 -0
- package/src/code-source.ts +23 -27
- package/src/data-interface.ts +66 -1
- package/src/effect-contract.ts +73 -20
- package/src/gpu-loader.ts +61 -35
- package/src/gpu-program.ts +57 -35
- package/src/gpu-runtime.ts +115 -42
- package/src/index.ts +53 -9
- package/src/particle-layout.ts +214 -0
package/dist/index.mjs
CHANGED
|
@@ -8,13 +8,13 @@ var particleEffectContribution = {
|
|
|
8
8
|
decoder: {
|
|
9
9
|
async decode({ envelope }) {
|
|
10
10
|
const payload = envelope.payload;
|
|
11
|
-
if (payload.kind === "particle-effect" && payload.schemaVersion ===
|
|
11
|
+
if (payload.kind === "particle-effect" && payload.schemaVersion === 3 && payload.program.format === "forgeax-vfx-program-3" && payload.emitters.length === payload.program.emitters.length && payload.programFingerprint === payload.program.fingerprint) {
|
|
12
12
|
return ok(payload);
|
|
13
13
|
}
|
|
14
14
|
return err({
|
|
15
15
|
code: "asset-package-invalid",
|
|
16
|
-
expected: "a
|
|
17
|
-
hint: "
|
|
16
|
+
expected: "a schemaVersion 3 particle payload matching its cooked Program v3",
|
|
17
|
+
hint: "cold-cook the legacy payload with the Program v3 compiler and publish atomically",
|
|
18
18
|
detail: { guid: envelope.guid, reason: "particle owner validation failed" }
|
|
19
19
|
});
|
|
20
20
|
}
|
|
@@ -22,31 +22,26 @@ var particleEffectContribution = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
// src/authoring-descriptor.ts
|
|
25
|
-
var
|
|
25
|
+
var V3_CAPABILITIES = Object.freeze([
|
|
26
26
|
Object.freeze({ id: "wgsl-behavior", state: "executable" }),
|
|
27
27
|
Object.freeze({ id: "multi-emitter", state: "executable" }),
|
|
28
28
|
Object.freeze({ id: "deterministic-replay", state: "executable" }),
|
|
29
29
|
Object.freeze({ id: "emitter-visibility", state: "executable" }),
|
|
30
|
-
Object.freeze({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}),
|
|
35
|
-
Object.freeze({
|
|
36
|
-
id: "custom-attributes",
|
|
37
|
-
state: "partial",
|
|
38
|
-
reason: "reflected without executable per-particle custom storage"
|
|
39
|
-
}),
|
|
30
|
+
Object.freeze({ id: "runtime-parameters", state: "executable" }),
|
|
31
|
+
Object.freeze({ id: "custom-attributes", state: "executable" }),
|
|
32
|
+
Object.freeze({ id: "renderer-semantics", state: "executable" }),
|
|
33
|
+
Object.freeze({ id: "material-particle-inputs", state: "executable" }),
|
|
40
34
|
Object.freeze({
|
|
41
35
|
id: "data-interfaces",
|
|
42
36
|
state: "partial",
|
|
43
|
-
reason: "
|
|
37
|
+
reason: "camera, single-sample scene depth, and noise require generation-owned providers"
|
|
44
38
|
})
|
|
45
39
|
]);
|
|
46
40
|
function isVfxGpuEffectAsset(value) {
|
|
47
41
|
if (typeof value !== "object" || value === null) return false;
|
|
48
42
|
const candidate = value;
|
|
49
|
-
|
|
43
|
+
const program = candidate.program;
|
|
44
|
+
return candidate.kind === "particle-effect" && candidate.schemaVersion === 3 && typeof candidate.guid === "string" && candidate.guid.length > 0 && typeof program === "object" && program !== null && program.format === "forgeax-vfx-program-3" && typeof program.fingerprint === "string" && Array.isArray(program.emitters);
|
|
50
45
|
}
|
|
51
46
|
function label(path) {
|
|
52
47
|
const leaf = path.split(".").at(-1) ?? path;
|
|
@@ -243,7 +238,7 @@ function describeVfxGpuEffect(effect) {
|
|
|
243
238
|
return Object.freeze({
|
|
244
239
|
version: 1,
|
|
245
240
|
assetGuid: effect.guid,
|
|
246
|
-
schemaVersion:
|
|
241
|
+
schemaVersion: effect.schemaVersion,
|
|
247
242
|
artifactFingerprint: effect.program.fingerprint,
|
|
248
243
|
emitters: Object.freeze(effect.program.emitters.map(emitterNode)),
|
|
249
244
|
timeline: Object.freeze(
|
|
@@ -257,7 +252,7 @@ function describeVfxGpuEffect(effect) {
|
|
|
257
252
|
)
|
|
258
253
|
),
|
|
259
254
|
dependencies: dependencies(effect),
|
|
260
|
-
capabilities:
|
|
255
|
+
capabilities: V3_CAPABILITIES
|
|
261
256
|
});
|
|
262
257
|
}
|
|
263
258
|
var PARTICLE_CODE_DEFAULT_MODULE_ID = "forgeax_vfx::default";
|
|
@@ -326,8 +321,8 @@ function parseStageResources(value, path, stageId) {
|
|
|
326
321
|
const resources = [];
|
|
327
322
|
const names = /* @__PURE__ */ new Set();
|
|
328
323
|
for (const item of value.split(",")) {
|
|
329
|
-
const [name, access,
|
|
330
|
-
if (name === void 0 || access === void 0 ||
|
|
324
|
+
const [name, access, extra2] = item.split(":");
|
|
325
|
+
if (name === void 0 || access === void 0 || extra2 !== void 0 || !STAGE_RESOURCE_NAMES.has(name) || access !== "read" && access !== "write" && access !== "read-write" || names.has(name)) {
|
|
331
326
|
return stageInvalid(
|
|
332
327
|
path,
|
|
333
328
|
"known resources with unique read, write, or read-write access",
|
|
@@ -408,8 +403,8 @@ function parseVfxStageDeclarations(source) {
|
|
|
408
403
|
}
|
|
409
404
|
function parseEmitter(value, index, ids) {
|
|
410
405
|
const path = `emitters[${index}]`;
|
|
411
|
-
if (!record(value)) return invalid(path, "a
|
|
412
|
-
const
|
|
406
|
+
if (!record(value)) return invalid(path, "a Program v3 emitter object");
|
|
407
|
+
const extra2 = allowed(value, [
|
|
413
408
|
"id",
|
|
414
409
|
"capacity",
|
|
415
410
|
"backend",
|
|
@@ -422,7 +417,7 @@ function parseEmitter(value, index, ids) {
|
|
|
422
417
|
"events",
|
|
423
418
|
"simulationWhenCulled"
|
|
424
419
|
]);
|
|
425
|
-
if (
|
|
420
|
+
if (extra2 !== void 0) return invalid(`${path}.${extra2}`, "a Program v3 emitter field");
|
|
426
421
|
if (!text(value.id) || ids.has(value.id)) return invalid(`${path}.id`, "a unique non-empty id");
|
|
427
422
|
const id = value.id;
|
|
428
423
|
ids.add(id);
|
|
@@ -722,20 +717,12 @@ function parseEmitter(value, index, ids) {
|
|
|
722
717
|
}
|
|
723
718
|
return ok(value);
|
|
724
719
|
}
|
|
725
|
-
function
|
|
720
|
+
function parseParticleEffectSourceStructure(value) {
|
|
726
721
|
if (!record(value)) return invalid("$", "a particle effect source object");
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
code: "vfx-source-version-unsupported",
|
|
730
|
-
expected: "ParticleEffectSource schemaVersion 2",
|
|
731
|
-
hint: "migrate code behavior to WGSL and recook; v1 is not interpreted at runtime",
|
|
732
|
-
detail: { path: "schemaVersion" }
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
const extra = allowed(value, ["schemaVersion", "emitters"]);
|
|
736
|
-
if (extra !== void 0) return invalid(extra, "a root field: emitters");
|
|
722
|
+
const extra2 = allowed(value, ["schemaVersion", "emitters"]);
|
|
723
|
+
if (extra2 !== void 0) return invalid(extra2, "a root field: emitters");
|
|
737
724
|
if (!Array.isArray(value.emitters) || value.emitters.length === 0) {
|
|
738
|
-
return invalid("emitters", "at least one
|
|
725
|
+
return invalid("emitters", "at least one Program v3 emitter");
|
|
739
726
|
}
|
|
740
727
|
const ids = /* @__PURE__ */ new Set();
|
|
741
728
|
const emitters = [];
|
|
@@ -768,16 +755,297 @@ function parseParticleEffectSourceV2(value) {
|
|
|
768
755
|
}
|
|
769
756
|
return ok(
|
|
770
757
|
Object.freeze({
|
|
771
|
-
schemaVersion:
|
|
758
|
+
schemaVersion: 3,
|
|
772
759
|
emitters: Object.freeze(emitters)
|
|
773
760
|
})
|
|
774
761
|
);
|
|
775
762
|
}
|
|
776
|
-
|
|
777
|
-
|
|
763
|
+
var PARTICLE_RENDERER_SEMANTICS = Object.freeze({
|
|
764
|
+
billboard: Object.freeze([
|
|
765
|
+
"position",
|
|
766
|
+
"color",
|
|
767
|
+
"size",
|
|
768
|
+
"rotation",
|
|
769
|
+
"age",
|
|
770
|
+
"subImage",
|
|
771
|
+
"sort",
|
|
772
|
+
"visibility"
|
|
773
|
+
]),
|
|
774
|
+
mesh: Object.freeze(["position", "color", "orientation", "scale", "visibility"]),
|
|
775
|
+
ribbon: Object.freeze(["position", "color", "width"]),
|
|
776
|
+
trail: Object.freeze(["position", "color", "width", "taper"]),
|
|
777
|
+
beam: Object.freeze(["position", "endpoint", "color", "width"])
|
|
778
|
+
});
|
|
779
|
+
function coreAttribute(name) {
|
|
780
|
+
return { source: "core", name };
|
|
781
|
+
}
|
|
782
|
+
function defaultParticleRendererAttributes(kind) {
|
|
783
|
+
switch (kind) {
|
|
784
|
+
case "billboard":
|
|
785
|
+
return {
|
|
786
|
+
position: coreAttribute("position"),
|
|
787
|
+
color: coreAttribute("color"),
|
|
788
|
+
size: coreAttribute("sprite_size"),
|
|
789
|
+
rotation: coreAttribute("sprite_rotation"),
|
|
790
|
+
age: coreAttribute("age"),
|
|
791
|
+
visibility: coreAttribute("alive")
|
|
792
|
+
};
|
|
793
|
+
case "mesh":
|
|
794
|
+
return {
|
|
795
|
+
position: coreAttribute("position"),
|
|
796
|
+
color: coreAttribute("color"),
|
|
797
|
+
orientation: coreAttribute("mesh_orientation"),
|
|
798
|
+
scale: coreAttribute("mesh_scale"),
|
|
799
|
+
visibility: coreAttribute("alive")
|
|
800
|
+
};
|
|
801
|
+
case "ribbon":
|
|
802
|
+
return {
|
|
803
|
+
position: coreAttribute("position"),
|
|
804
|
+
color: coreAttribute("color")
|
|
805
|
+
};
|
|
806
|
+
case "trail":
|
|
807
|
+
return {
|
|
808
|
+
position: coreAttribute("position"),
|
|
809
|
+
color: coreAttribute("color")
|
|
810
|
+
};
|
|
811
|
+
case "beam":
|
|
812
|
+
return {
|
|
813
|
+
position: coreAttribute("position"),
|
|
814
|
+
endpoint: coreAttribute("velocity"),
|
|
815
|
+
color: coreAttribute("color")
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function record2(value) {
|
|
820
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
821
|
+
}
|
|
822
|
+
function fail(path, expected) {
|
|
823
|
+
return err({
|
|
824
|
+
code: "vfx-source-invalid",
|
|
825
|
+
expected,
|
|
826
|
+
hint: `repair ${path} and recook the Program v3 particle effect`,
|
|
827
|
+
detail: { path }
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
function extra(value, allowed2) {
|
|
831
|
+
const keys = new Set(allowed2);
|
|
832
|
+
return Object.keys(value).find((key) => !keys.has(key));
|
|
833
|
+
}
|
|
834
|
+
var CORE_NAMES = /* @__PURE__ */ new Set([
|
|
835
|
+
"position",
|
|
836
|
+
"age",
|
|
837
|
+
"velocity",
|
|
838
|
+
"lifetime",
|
|
839
|
+
"color",
|
|
840
|
+
"sprite_size",
|
|
841
|
+
"sprite_rotation",
|
|
842
|
+
"sub_image",
|
|
843
|
+
"mesh_orientation",
|
|
844
|
+
"mesh_scale",
|
|
845
|
+
"material_random",
|
|
846
|
+
"id",
|
|
847
|
+
"alive"
|
|
848
|
+
]);
|
|
849
|
+
function validateAttributes(value, path) {
|
|
850
|
+
if (value === void 0) return ok(void 0);
|
|
851
|
+
if (!record2(value)) return fail(path, "a semantic-to-attribute object");
|
|
852
|
+
for (const [semantic, reference] of Object.entries(value)) {
|
|
853
|
+
if (!record2(reference) || reference.source !== "core" && reference.source !== "custom") {
|
|
854
|
+
return fail(`${path}.${semantic}`, "{ source: 'core' | 'custom', name: string }");
|
|
855
|
+
}
|
|
856
|
+
if (typeof reference.name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(reference.name)) {
|
|
857
|
+
return fail(`${path}.${semantic}.name`, "a non-empty identifier");
|
|
858
|
+
}
|
|
859
|
+
if (reference.source === "core" && !CORE_NAMES.has(reference.name)) {
|
|
860
|
+
return fail(`${path}.${semantic}.name`, "a VfxParticle core attribute");
|
|
861
|
+
}
|
|
862
|
+
const referenceExtra = extra(reference, ["source", "name"]);
|
|
863
|
+
if (referenceExtra !== void 0)
|
|
864
|
+
return fail(`${path}.${semantic}.${referenceExtra}`, "source and name only");
|
|
865
|
+
}
|
|
866
|
+
return ok(value);
|
|
867
|
+
}
|
|
868
|
+
function validateMaterialInputs(value, path) {
|
|
869
|
+
if (value === void 0) return ok(void 0);
|
|
870
|
+
if (!Array.isArray(value) || value.length > 4 || value.some((entry) => typeof entry !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(entry))) {
|
|
871
|
+
return fail(path, "at most four unique particle input names");
|
|
872
|
+
}
|
|
873
|
+
if (new Set(value).size !== value.length) return fail(path, "unique particle input names");
|
|
874
|
+
return ok(Object.freeze([...value]));
|
|
875
|
+
}
|
|
876
|
+
function rendererAllowed(kind) {
|
|
877
|
+
const common = [
|
|
878
|
+
"kind",
|
|
879
|
+
"material",
|
|
880
|
+
"enabled",
|
|
881
|
+
"capacity",
|
|
882
|
+
"overflow",
|
|
883
|
+
"width",
|
|
884
|
+
"attributes",
|
|
885
|
+
"materialInputs"
|
|
886
|
+
];
|
|
887
|
+
switch (kind) {
|
|
888
|
+
case "billboard":
|
|
889
|
+
return [...common, "blend", "textureSheet", "pivot", "softParticle", "sorting"];
|
|
890
|
+
case "mesh":
|
|
891
|
+
return [
|
|
892
|
+
"kind",
|
|
893
|
+
"material",
|
|
894
|
+
"mesh",
|
|
895
|
+
"submesh",
|
|
896
|
+
"enabled",
|
|
897
|
+
"attributes",
|
|
898
|
+
"materialInputs",
|
|
899
|
+
"lighting",
|
|
900
|
+
"castShadows",
|
|
901
|
+
"receiveShadows"
|
|
902
|
+
];
|
|
903
|
+
case "ribbon":
|
|
904
|
+
return [...common, "stripKey", "twist", "facing"];
|
|
905
|
+
case "trail":
|
|
906
|
+
return [...common, "historyLength", "taper"];
|
|
907
|
+
case "beam":
|
|
908
|
+
return [...common, "endpointField", "taper"];
|
|
909
|
+
default:
|
|
910
|
+
return ["kind"];
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function asStructureRenderer(value) {
|
|
914
|
+
const common = ["kind", "material", "enabled", "capacity", "overflow", "width"];
|
|
915
|
+
const result = {};
|
|
916
|
+
for (const key of common) if (value[key] !== void 0) result[key] = value[key];
|
|
917
|
+
switch (value.kind) {
|
|
918
|
+
case "billboard":
|
|
919
|
+
for (const key of ["blend", "textureSheet", "pivot", "softParticle"])
|
|
920
|
+
if (value[key] !== void 0) result[key] = value[key];
|
|
921
|
+
if (value.sorting === "view-depth" || value.sorting === "view-distance")
|
|
922
|
+
result.sorting = "back-to-front";
|
|
923
|
+
else if (value.sorting === "custom-ascending" || value.sorting === "custom-descending")
|
|
924
|
+
result.sorting = "emitter";
|
|
925
|
+
else if (value.sorting !== void 0) result.sorting = value.sorting;
|
|
926
|
+
break;
|
|
927
|
+
case "mesh":
|
|
928
|
+
for (const key of ["mesh", "submesh"])
|
|
929
|
+
if (value[key] !== void 0) result[key] = value[key];
|
|
930
|
+
break;
|
|
931
|
+
case "ribbon":
|
|
932
|
+
result.stripKey = value.stripKey;
|
|
933
|
+
break;
|
|
934
|
+
case "trail":
|
|
935
|
+
result.historyLength = value.historyLength;
|
|
936
|
+
break;
|
|
937
|
+
case "beam":
|
|
938
|
+
result.endpointField = value.endpointField;
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
return result;
|
|
942
|
+
}
|
|
943
|
+
function validateRenderer(value, emitterIndex, index) {
|
|
944
|
+
const path = `emitters[${emitterIndex}].renderers[${index}]`;
|
|
945
|
+
if (!record2(value) || typeof value.kind !== "string" || typeof value.material !== "string") {
|
|
946
|
+
return fail(path, "a Program v3 renderer object");
|
|
947
|
+
}
|
|
948
|
+
const unknown = extra(value, rendererAllowed(value.kind));
|
|
949
|
+
if (unknown !== void 0)
|
|
950
|
+
return fail(`${path}.${unknown}`, "a supported Program v3 renderer field");
|
|
951
|
+
if (value.lighting !== void 0 && value.lighting !== "unlit" && value.lighting !== "standard")
|
|
952
|
+
return fail(`${path}.lighting`, "unlit or standard");
|
|
953
|
+
for (const field2 of ["castShadows", "receiveShadows"]) {
|
|
954
|
+
if (value[field2] !== void 0 && typeof value[field2] !== "boolean")
|
|
955
|
+
return fail(`${path}.${field2}`, "a boolean");
|
|
956
|
+
}
|
|
957
|
+
for (const field2 of ["twist", "taper"]) {
|
|
958
|
+
if (value[field2] !== void 0 && (typeof value[field2] !== "number" || !Number.isFinite(value[field2])))
|
|
959
|
+
return fail(`${path}.${field2}`, "a finite number");
|
|
960
|
+
}
|
|
961
|
+
if (value.facing !== void 0 && value.facing !== "camera" && value.facing !== "velocity" && value.facing !== "custom")
|
|
962
|
+
return fail(`${path}.facing`, "camera, velocity, or custom");
|
|
963
|
+
if (value.sorting !== void 0 && value.kind === "billboard" && !["none", "view-depth", "view-distance", "custom-ascending", "custom-descending"].includes(
|
|
964
|
+
value.sorting
|
|
965
|
+
))
|
|
966
|
+
return fail(`${path}.sorting`, "a Program v3 sorting mode");
|
|
967
|
+
const attrs = validateAttributes(value.attributes, `${path}.attributes`);
|
|
968
|
+
if (!attrs.ok) return attrs;
|
|
969
|
+
if (attrs.value !== void 0) {
|
|
970
|
+
for (const semantic of Object.keys(attrs.value)) {
|
|
971
|
+
if (!PARTICLE_RENDERER_SEMANTICS[value.kind]?.includes(
|
|
972
|
+
semantic
|
|
973
|
+
)) {
|
|
974
|
+
return fail(
|
|
975
|
+
`${path}.attributes.${semantic}`,
|
|
976
|
+
"a semantic supported by this renderer topology"
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const inputs = validateMaterialInputs(value.materialInputs, `${path}.materialInputs`);
|
|
982
|
+
if (!inputs.ok) return inputs;
|
|
983
|
+
return ok({
|
|
984
|
+
...value,
|
|
985
|
+
...attrs.value === void 0 ? {} : { attributes: attrs.value },
|
|
986
|
+
...inputs.value === void 0 ? {} : { materialInputs: inputs.value }
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
function parseParticleEffectSourceV3(value) {
|
|
990
|
+
if (!record2(value)) return fail("$", "a Program v3 particle effect source object");
|
|
991
|
+
if (value.schemaVersion !== 3) {
|
|
992
|
+
return err({
|
|
993
|
+
code: "vfx-source-version-unsupported",
|
|
994
|
+
expected: "ParticleEffectSource schemaVersion 3",
|
|
995
|
+
hint: "cold-cook the source with the Program v3 compiler; older versions are not executable",
|
|
996
|
+
detail: { path: "schemaVersion" }
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
const rootExtra = extra(value, ["schemaVersion", "emitters"]);
|
|
1000
|
+
if (rootExtra !== void 0) return fail(rootExtra, "schemaVersion and emitters only");
|
|
1001
|
+
if (!Array.isArray(value.emitters) || value.emitters.length === 0)
|
|
1002
|
+
return fail("emitters", "at least one Program v3 emitter");
|
|
1003
|
+
const renderers = /* @__PURE__ */ new Map();
|
|
1004
|
+
for (const [emitterIndex, emitter] of value.emitters.entries()) {
|
|
1005
|
+
if (record2(emitter) && Array.isArray(emitter.renderers)) {
|
|
1006
|
+
const parsed2 = [];
|
|
1007
|
+
for (const [index, renderer] of emitter.renderers.entries()) {
|
|
1008
|
+
const result = validateRenderer(renderer, emitterIndex, index);
|
|
1009
|
+
if (!result.ok) return result;
|
|
1010
|
+
parsed2.push(result.value);
|
|
1011
|
+
}
|
|
1012
|
+
renderers.set(typeof emitter.id === "string" ? emitter.id : String(renderers.size), parsed2);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const structuralInput = {
|
|
1016
|
+
schemaVersion: 3,
|
|
1017
|
+
emitters: value.emitters.map((emitter) => {
|
|
1018
|
+
if (!record2(emitter)) return emitter;
|
|
1019
|
+
return {
|
|
1020
|
+
...emitter,
|
|
1021
|
+
renderers: Array.isArray(emitter.renderers) ? emitter.renderers.map(
|
|
1022
|
+
(renderer) => record2(renderer) ? asStructureRenderer(renderer) : renderer
|
|
1023
|
+
) : emitter.renderers
|
|
1024
|
+
};
|
|
1025
|
+
})
|
|
1026
|
+
};
|
|
1027
|
+
const parsed = parseParticleEffectSourceStructure(structuralInput);
|
|
1028
|
+
if (!parsed.ok) return parsed;
|
|
1029
|
+
const emitters = parsed.value.emitters.map((emitter) => ({
|
|
1030
|
+
...emitter,
|
|
1031
|
+
renderers: renderers.get(emitter.id) ?? []
|
|
1032
|
+
}));
|
|
1033
|
+
return ok(Object.freeze({ schemaVersion: 3, emitters: Object.freeze(emitters) }));
|
|
1034
|
+
}
|
|
1035
|
+
function defineParticleEffectSourceV3(source) {
|
|
1036
|
+
const parsed = parseParticleEffectSourceV3(source);
|
|
778
1037
|
if (!parsed.ok) throw new TypeError(`${parsed.error.code}: ${parsed.error.expected}`);
|
|
779
1038
|
return source;
|
|
780
1039
|
}
|
|
1040
|
+
function expectedResourceKind(kind) {
|
|
1041
|
+
switch (kind) {
|
|
1042
|
+
case "camera":
|
|
1043
|
+
return "buffer";
|
|
1044
|
+
case "scene-depth":
|
|
1045
|
+
case "noise":
|
|
1046
|
+
return "texture-view";
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
781
1049
|
function failure(code, requirement, expected, hint, detail = {}) {
|
|
782
1050
|
return { code, expected, hint, detail: { token: requirement.token, ...detail } };
|
|
783
1051
|
}
|
|
@@ -859,6 +1127,44 @@ function resolveVfxDataInterfaces(requirements, providers, generation) {
|
|
|
859
1127
|
)
|
|
860
1128
|
);
|
|
861
1129
|
}
|
|
1130
|
+
const expectedKind = expectedResourceKind(requirement.kind);
|
|
1131
|
+
if (resource.resource === void 0) {
|
|
1132
|
+
return err(
|
|
1133
|
+
failure(
|
|
1134
|
+
"vfx-data-interface-missing",
|
|
1135
|
+
requirement,
|
|
1136
|
+
`${requirement.kind} backed by a resident ${expectedKind} resource`,
|
|
1137
|
+
`refresh provider ${provider.id} with a generation-owned resource before dispatch`,
|
|
1138
|
+
{ providerId: provider.id, expectedResourceKind: expectedKind }
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
if (resource.resource.kind !== expectedKind) {
|
|
1143
|
+
return err(
|
|
1144
|
+
failure(
|
|
1145
|
+
"vfx-data-interface-wrong-type",
|
|
1146
|
+
requirement,
|
|
1147
|
+
`${requirement.kind} backed by a ${expectedKind} resource`,
|
|
1148
|
+
`repair provider ${provider.id} so its opaque resource matches the reflected binding kind`,
|
|
1149
|
+
{
|
|
1150
|
+
providerId: provider.id,
|
|
1151
|
+
expectedResourceKind: expectedKind,
|
|
1152
|
+
actualResourceKind: resource.resource.kind
|
|
1153
|
+
}
|
|
1154
|
+
)
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
if (requirement.sampleCount !== void 0 && resource.sampleCount !== requirement.sampleCount) {
|
|
1158
|
+
return err(
|
|
1159
|
+
failure(
|
|
1160
|
+
"vfx-data-interface-wrong-type",
|
|
1161
|
+
requirement,
|
|
1162
|
+
`a scene-depth resource with sampleCount=${requirement.sampleCount}`,
|
|
1163
|
+
`provide a single-sample scene depth target for ${provider.id}; MSAA resolve is not implicit`,
|
|
1164
|
+
{ providerId: provider.id }
|
|
1165
|
+
)
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
862
1168
|
if (resource.generation !== generation) {
|
|
863
1169
|
return err(
|
|
864
1170
|
failure(
|
|
@@ -886,8 +1192,8 @@ var VECTOR_LENGTH = {
|
|
|
886
1192
|
"vec3<f32>": 3,
|
|
887
1193
|
"vec4<f32>": 4
|
|
888
1194
|
};
|
|
889
|
-
function fieldList(reflection) {
|
|
890
|
-
return [...reflection.parameters.fields, ...reflection.custom.fields];
|
|
1195
|
+
function fieldList(reflection, includeCustom = false) {
|
|
1196
|
+
return includeCustom ? [...reflection.parameters.fields, ...reflection.custom.fields] : reflection.parameters.fields;
|
|
891
1197
|
}
|
|
892
1198
|
function zeroValue(type) {
|
|
893
1199
|
const length = VECTOR_LENGTH[type];
|
|
@@ -898,7 +1204,7 @@ function valueMatches(type, value) {
|
|
|
898
1204
|
if (length === 1) return typeof value === "number" && Number.isFinite(value);
|
|
899
1205
|
return Array.isArray(value) && value.length === length && value.every((component) => typeof component === "number" && Number.isFinite(component));
|
|
900
1206
|
}
|
|
901
|
-
function
|
|
1207
|
+
function fail2(code, path, expected, hint, actual) {
|
|
902
1208
|
return err({
|
|
903
1209
|
code,
|
|
904
1210
|
expected,
|
|
@@ -907,18 +1213,26 @@ function fail(code, path, expected, hint, actual) {
|
|
|
907
1213
|
});
|
|
908
1214
|
}
|
|
909
1215
|
function validateReflection(reflection) {
|
|
910
|
-
if (reflection.version !==
|
|
911
|
-
return
|
|
1216
|
+
if (reflection.version !== 3 || typeof reflection.fingerprint !== "string" || !reflection.fingerprint.startsWith("sha256:")) {
|
|
1217
|
+
return fail2(
|
|
912
1218
|
"vfx-reflection-invalid",
|
|
913
1219
|
"reflection",
|
|
914
|
-
"a version
|
|
1220
|
+
"a version 3 reflection with a sha256 fingerprint",
|
|
1221
|
+
"recook the effect with the current VFX compiler"
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
if (reflection.core.name !== "VfxParticle" || reflection.core.stride !== 112 || reflection.core.fields.length === 0 || reflection.customLayout.name !== "VfxCustom" || reflection.customLayout.stride < 0 || reflection.customLayout.lanes < 0) {
|
|
1225
|
+
return fail2(
|
|
1226
|
+
"vfx-reflection-invalid",
|
|
1227
|
+
"reflection.core",
|
|
1228
|
+
"Program v3 Core and Custom layouts derived from the particle schema",
|
|
915
1229
|
"recook the effect with the current VFX compiler"
|
|
916
1230
|
);
|
|
917
1231
|
}
|
|
918
1232
|
const names = /* @__PURE__ */ new Set();
|
|
919
1233
|
for (const field2 of fieldList(reflection)) {
|
|
920
1234
|
if (names.has(field2.name)) {
|
|
921
|
-
return
|
|
1235
|
+
return fail2(
|
|
922
1236
|
"vfx-reflection-invalid",
|
|
923
1237
|
field2.name,
|
|
924
1238
|
"unique field names across parameters and custom data",
|
|
@@ -937,20 +1251,23 @@ function defaultsFor(reflection) {
|
|
|
937
1251
|
return Object.freeze(defaults);
|
|
938
1252
|
}
|
|
939
1253
|
function validateMap(reflection, values) {
|
|
940
|
-
|
|
1254
|
+
return validateMapAgainstFields(fieldList(reflection), values);
|
|
1255
|
+
}
|
|
1256
|
+
function validateMapAgainstFields(reflectedFields, values) {
|
|
1257
|
+
const fields2 = new Map(reflectedFields.map((field2) => [field2.name, field2]));
|
|
941
1258
|
for (const name of Object.keys(values)) {
|
|
942
1259
|
const field2 = fields2.get(name);
|
|
943
1260
|
if (field2 === void 0) {
|
|
944
|
-
return
|
|
1261
|
+
return fail2(
|
|
945
1262
|
"vfx-value-unknown-field",
|
|
946
1263
|
name,
|
|
947
|
-
"a field declared by
|
|
1264
|
+
"a field declared by the selected VFX data scope",
|
|
948
1265
|
`remove ${name} or declare it in the authored WGSL struct`,
|
|
949
1266
|
values[name]
|
|
950
1267
|
);
|
|
951
1268
|
}
|
|
952
1269
|
if (!valueMatches(field2.type, values[name])) {
|
|
953
|
-
return
|
|
1270
|
+
return fail2(
|
|
954
1271
|
"vfx-value-type-mismatch",
|
|
955
1272
|
name,
|
|
956
1273
|
field2.type,
|
|
@@ -974,13 +1291,28 @@ function createVfxEffectContract(reflection) {
|
|
|
974
1291
|
const checked = validateReflection(reflection);
|
|
975
1292
|
if (!checked.ok) throw new TypeError(checked.error.hint);
|
|
976
1293
|
const defaults = defaultsFor(reflection);
|
|
977
|
-
const fields2 = fieldList(reflection);
|
|
978
1294
|
const customBase = reflection.parameters.size;
|
|
979
|
-
const
|
|
1295
|
+
const parameterSize = reflection.parameters.size;
|
|
1296
|
+
const customStride = reflection.customLayout.stride;
|
|
1297
|
+
const packedSize = customBase;
|
|
1298
|
+
const packFields = (values, selected, size, baseOffset) => {
|
|
1299
|
+
const checkedValues = validateMapAgainstFields(selected, values);
|
|
1300
|
+
if (!checkedValues.ok) return checkedValues;
|
|
1301
|
+
const bytes = new Uint8Array(size);
|
|
1302
|
+
const view = new DataView(bytes.buffer);
|
|
1303
|
+
for (const field2 of selected) {
|
|
1304
|
+
const value = checkedValues.value[field2.name];
|
|
1305
|
+
if (value === void 0) continue;
|
|
1306
|
+
writeValue(view, baseOffset + field2.offset, field2.type, value);
|
|
1307
|
+
}
|
|
1308
|
+
return ok(bytes);
|
|
1309
|
+
};
|
|
980
1310
|
return {
|
|
981
1311
|
reflection,
|
|
982
1312
|
fingerprint: reflection.fingerprint,
|
|
983
1313
|
packedSize,
|
|
1314
|
+
parameterSize: reflection.parameters.size,
|
|
1315
|
+
customStride,
|
|
984
1316
|
defaults,
|
|
985
1317
|
createValues(initial = {}) {
|
|
986
1318
|
const merged = { ...defaults, ...initial };
|
|
@@ -990,17 +1322,13 @@ function createVfxEffectContract(reflection) {
|
|
|
990
1322
|
return validateMap(reflection, values);
|
|
991
1323
|
},
|
|
992
1324
|
pack(values) {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const base = reflection.parameters.fields.includes(field2) ? 0 : customBase;
|
|
1001
|
-
writeValue(view, base + field2.offset, field2.type, value);
|
|
1002
|
-
}
|
|
1003
|
-
return ok(bytes);
|
|
1325
|
+
return packFields(values, reflection.parameters.fields, parameterSize, 0);
|
|
1326
|
+
},
|
|
1327
|
+
packParameters(values) {
|
|
1328
|
+
return packFields(values, reflection.parameters.fields, parameterSize, 0);
|
|
1329
|
+
},
|
|
1330
|
+
packCustom(values) {
|
|
1331
|
+
return packFields(values, reflection.custom.fields, customStride, 0);
|
|
1004
1332
|
}
|
|
1005
1333
|
};
|
|
1006
1334
|
}
|
|
@@ -1009,14 +1337,15 @@ function validateVfxEffectValues(reflection, values) {
|
|
|
1009
1337
|
}
|
|
1010
1338
|
|
|
1011
1339
|
// src/gpu-program.ts
|
|
1012
|
-
var VFX_GPU_PROGRAM_FORMAT = "forgeax-vfx-program-
|
|
1340
|
+
var VFX_GPU_PROGRAM_FORMAT = "forgeax-vfx-program-3";
|
|
1341
|
+
var VFX_GPU_PROGRAM_FORMAT_V3 = VFX_GPU_PROGRAM_FORMAT;
|
|
1013
1342
|
var VFX_GPU_PROGRAM_ARTIFACT_KEY = "particle-effect/program.json";
|
|
1014
1343
|
|
|
1015
1344
|
// src/gpu-loader.ts
|
|
1016
1345
|
function failure2(code, guid, path, expected, hint) {
|
|
1017
1346
|
return err({ code, expected, hint, detail: { guid, path } });
|
|
1018
1347
|
}
|
|
1019
|
-
function
|
|
1348
|
+
function record3(value) {
|
|
1020
1349
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1021
1350
|
}
|
|
1022
1351
|
function hex(bytes) {
|
|
@@ -1031,9 +1360,15 @@ async function fingerprint(bytes) {
|
|
|
1031
1360
|
}
|
|
1032
1361
|
function validReflectionLayout(value) {
|
|
1033
1362
|
if (value === void 0) return true;
|
|
1034
|
-
if (!
|
|
1035
|
-
if (!
|
|
1036
|
-
if (!
|
|
1363
|
+
if (!record3(value) || value.version !== 3) return false;
|
|
1364
|
+
if (!record3(value.parameters) || !Array.isArray(value.parameters.fields)) return false;
|
|
1365
|
+
if (!record3(value.custom) || !Array.isArray(value.custom.fields)) return false;
|
|
1366
|
+
if (value.version === 3) {
|
|
1367
|
+
if (!record3(value.core) || value.core.name !== "VfxParticle" || value.core.stride !== 112) {
|
|
1368
|
+
return false;
|
|
1369
|
+
}
|
|
1370
|
+
if (!record3(value.customLayout) || value.customLayout.name !== "VfxCustom") return false;
|
|
1371
|
+
}
|
|
1037
1372
|
return typeof value.fingerprint === "string" && value.fingerprint.startsWith("sha256:");
|
|
1038
1373
|
}
|
|
1039
1374
|
function stableLayouts(emitters) {
|
|
@@ -1041,26 +1376,40 @@ function stableLayouts(emitters) {
|
|
|
1041
1376
|
return fingerprints.every((fingerprint2) => fingerprint2 === fingerprints[0]);
|
|
1042
1377
|
}
|
|
1043
1378
|
function validEmitter(value) {
|
|
1044
|
-
const reflection =
|
|
1379
|
+
const reflection = record3(value) && record3(value.reflection) ? value.reflection : void 0;
|
|
1045
1380
|
const layout = reflection !== void 0 ? reflection.layout : void 0;
|
|
1046
|
-
return
|
|
1381
|
+
return record3(value) && typeof value.id === "string" && typeof value.module === "string" && value.module.length > 0 && Number.isInteger(value.capacity) && value.capacity > 0 && record3(value.backend) && value.backend.required === "gpu" && Object.keys(value.backend).length === 1 && typeof value.wgsl === "string" && value.wgsl.length > 0 && record3(value.reflection) && validReflectionLayout(layout) && Array.isArray(value.reflection.entryPoints) && value.reflection.entryPoints.includes("forgeax_vfx_spawn_main") && value.reflection.entryPoints.includes("forgeax_vfx_compact_main") && value.reflection.entryPoints.includes("forgeax_vfx_billboard_main") && value.reflection.entryPoints.includes("forgeax_vfx_mesh_main") && value.reflection.entryPoints.includes("forgeax_vfx_ribbon_main") && value.reflection.entryPoints.includes("forgeax_vfx_trail_main") && value.reflection.entryPoints.includes("forgeax_vfx_beam_main") && layout !== void 0 && record3(layout) && Array.isArray(reflection?.bindings) && Array.isArray(reflection?.dataInterfaces) && Array.isArray(reflection?.renderers) && Array.isArray(reflection?.resources);
|
|
1047
1382
|
}
|
|
1048
1383
|
var vfxGpuEffectPackLoader = {
|
|
1049
1384
|
kind: "particle-effect",
|
|
1050
1385
|
async load(input, _context) {
|
|
1051
|
-
|
|
1386
|
+
const schemaVersion = input.payload.schemaVersion;
|
|
1387
|
+
if (schemaVersion !== 3) {
|
|
1052
1388
|
return failure2(
|
|
1053
|
-
"vfx-asset-
|
|
1389
|
+
"vfx-asset-version-unsupported",
|
|
1390
|
+
input.guid,
|
|
1391
|
+
"schemaVersion",
|
|
1392
|
+
"schemaVersion 3 / forgeax-vfx-program-3",
|
|
1393
|
+
"cold-cook the older source and publish a Program v3 payload; older versions are not executable"
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
const invalidCode = "vfx-asset-v3-invalid";
|
|
1397
|
+
const missingCode = "vfx-asset-v3-program-missing";
|
|
1398
|
+
const fingerprintCode = "vfx-asset-v3-fingerprint-mismatch";
|
|
1399
|
+
const expectedFormat = VFX_GPU_PROGRAM_FORMAT_V3;
|
|
1400
|
+
if (input.payload.kind !== "particle-effect" || !Array.isArray(input.payload.emitters) || typeof input.payload.programFingerprint !== "string" || !record3(input.payload.program)) {
|
|
1401
|
+
return failure2(
|
|
1402
|
+
invalidCode,
|
|
1054
1403
|
input.guid,
|
|
1055
1404
|
"payload",
|
|
1056
|
-
"a schemaVersion
|
|
1057
|
-
"
|
|
1405
|
+
"a schemaVersion 3 particle payload with its complete Program v3 and fingerprint",
|
|
1406
|
+
"cold-cook the source with the current VFX compiler and retry the load"
|
|
1058
1407
|
);
|
|
1059
1408
|
}
|
|
1060
1409
|
const artifact = input.artifacts[VFX_GPU_PROGRAM_ARTIFACT_KEY];
|
|
1061
1410
|
if (artifact === void 0) {
|
|
1062
1411
|
return failure2(
|
|
1063
|
-
|
|
1412
|
+
missingCode,
|
|
1064
1413
|
input.guid,
|
|
1065
1414
|
VFX_GPU_PROGRAM_ARTIFACT_KEY,
|
|
1066
1415
|
"the asset-local cooked VFX program",
|
|
@@ -1072,26 +1421,26 @@ var vfxGpuEffectPackLoader = {
|
|
|
1072
1421
|
decoded = JSON.parse(new TextDecoder().decode(artifact.bytes));
|
|
1073
1422
|
} catch {
|
|
1074
1423
|
return failure2(
|
|
1075
|
-
|
|
1424
|
+
invalidCode,
|
|
1076
1425
|
input.guid,
|
|
1077
1426
|
VFX_GPU_PROGRAM_ARTIFACT_KEY,
|
|
1078
1427
|
"canonical JSON program bytes",
|
|
1079
1428
|
"recook the particle effect and retry the load"
|
|
1080
1429
|
);
|
|
1081
1430
|
}
|
|
1082
|
-
if (!
|
|
1431
|
+
if (!record3(decoded) || decoded.format !== expectedFormat || !Array.isArray(decoded.emitters) || !decoded.emitters.every((emitter) => validEmitter(emitter)) || !stableLayouts(decoded.emitters)) {
|
|
1083
1432
|
return failure2(
|
|
1084
|
-
|
|
1433
|
+
invalidCode,
|
|
1085
1434
|
input.guid,
|
|
1086
1435
|
VFX_GPU_PROGRAM_ARTIFACT_KEY,
|
|
1087
|
-
`a ${
|
|
1436
|
+
`a ${expectedFormat} managed GPU program`,
|
|
1088
1437
|
"recook with the current VFX compiler ABI"
|
|
1089
1438
|
);
|
|
1090
1439
|
}
|
|
1091
1440
|
const decodedEmitters = decoded.emitters;
|
|
1092
|
-
if (input.payload.program.format !==
|
|
1441
|
+
if (input.payload.program.format !== expectedFormat || input.payload.program.fingerprint !== input.payload.programFingerprint || !Array.isArray(input.payload.program.emitters)) {
|
|
1093
1442
|
return failure2(
|
|
1094
|
-
|
|
1443
|
+
invalidCode,
|
|
1095
1444
|
input.guid,
|
|
1096
1445
|
"payload.program",
|
|
1097
1446
|
"the complete canonical program matching payload.programFingerprint",
|
|
@@ -1101,7 +1450,7 @@ var vfxGpuEffectPackLoader = {
|
|
|
1101
1450
|
const actualFingerprint = await fingerprint(artifact.bytes);
|
|
1102
1451
|
if (actualFingerprint !== input.payload.programFingerprint) {
|
|
1103
1452
|
return failure2(
|
|
1104
|
-
|
|
1453
|
+
fingerprintCode,
|
|
1105
1454
|
input.guid,
|
|
1106
1455
|
"payload.programFingerprint",
|
|
1107
1456
|
"payload and program artifact fingerprints to match",
|
|
@@ -1109,7 +1458,7 @@ var vfxGpuEffectPackLoader = {
|
|
|
1109
1458
|
);
|
|
1110
1459
|
}
|
|
1111
1460
|
const emitters = input.payload.emitters.map((value) => {
|
|
1112
|
-
if (!
|
|
1461
|
+
if (!record3(value) || typeof value.id !== "string" || !Number.isInteger(value.capacity)) {
|
|
1113
1462
|
return void 0;
|
|
1114
1463
|
}
|
|
1115
1464
|
return { id: value.id, capacity: value.capacity };
|
|
@@ -1118,7 +1467,7 @@ var vfxGpuEffectPackLoader = {
|
|
|
1118
1467
|
(value, index) => value?.id !== decodedEmitters[index]?.id || value?.capacity !== decodedEmitters[index]?.capacity
|
|
1119
1468
|
)) {
|
|
1120
1469
|
return failure2(
|
|
1121
|
-
|
|
1470
|
+
invalidCode,
|
|
1122
1471
|
input.guid,
|
|
1123
1472
|
"payload.emitters",
|
|
1124
1473
|
"payload emitter identities and capacities to match the program",
|
|
@@ -1129,11 +1478,11 @@ var vfxGpuEffectPackLoader = {
|
|
|
1129
1478
|
Object.freeze({
|
|
1130
1479
|
guid: input.guid,
|
|
1131
1480
|
kind: "particle-effect",
|
|
1132
|
-
schemaVersion:
|
|
1481
|
+
schemaVersion: 3,
|
|
1133
1482
|
programFingerprint: actualFingerprint,
|
|
1134
1483
|
emitters: Object.freeze(emitters),
|
|
1135
1484
|
program: Object.freeze({
|
|
1136
|
-
format:
|
|
1485
|
+
format: expectedFormat,
|
|
1137
1486
|
fingerprint: actualFingerprint,
|
|
1138
1487
|
emitters: Object.freeze(decodedEmitters)
|
|
1139
1488
|
})
|
|
@@ -1489,9 +1838,27 @@ var VfxGpuRuntime = class {
|
|
|
1489
1838
|
diagnostics() {
|
|
1490
1839
|
return this.#diagnostics;
|
|
1491
1840
|
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Visit every live emitter source. The renderer uses this on render frames
|
|
1843
|
+
* where pause/restart culling has no pending simulation intent, so a camera
|
|
1844
|
+
* move back into the bounds can refresh visibility before the next tick.
|
|
1845
|
+
*/
|
|
1846
|
+
forEachEmitterSource(visitor) {
|
|
1847
|
+
for (const [player, state] of this.#players) {
|
|
1848
|
+
for (const emitter of state.emitters) visitor({ player, emitter });
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1492
1851
|
lastCommitted(player) {
|
|
1493
1852
|
return this.#lastCommitted.get(player);
|
|
1494
1853
|
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Return the last submitted fixed-tick state for one emitter. Renderers
|
|
1856
|
+
* retain this projection between fixed ticks; simulation intents are a
|
|
1857
|
+
* queue, while a drawable emitter remains live until the player is reset.
|
|
1858
|
+
*/
|
|
1859
|
+
lastCommittedEmitter(player, emitterId) {
|
|
1860
|
+
return this.#lastCommittedByEmitter.get(player)?.get(emitterId);
|
|
1861
|
+
}
|
|
1495
1862
|
inspectPlayers() {
|
|
1496
1863
|
return Object.freeze(
|
|
1497
1864
|
[...this.#players.keys()].sort((left, right) => Number(left) - Number(right)).flatMap((player) => {
|
|
@@ -1646,24 +2013,46 @@ var VfxGpuRuntime = class {
|
|
|
1646
2013
|
}
|
|
1647
2014
|
this.#replayRequests.add(player);
|
|
1648
2015
|
}
|
|
1649
|
-
|
|
2016
|
+
/**
|
|
2017
|
+
* Acknowledge queued intents. A scalar acknowledges the contiguous prefix;
|
|
2018
|
+
* an array acknowledges only those terminal sequences, which lets unrelated
|
|
2019
|
+
* player/emitter streams progress while another stream is deferred. When the
|
|
2020
|
+
* renderer supplies `publishedSequences`, only intents that reached GPU
|
|
2021
|
+
* submission update the retained last-committed projection; skipped intents
|
|
2022
|
+
* are queue acknowledgements, not new GPU state.
|
|
2023
|
+
*/
|
|
2024
|
+
commit(sequence, publishedSequences) {
|
|
2025
|
+
const prefix = typeof sequence === "number" ? sequence : void 0;
|
|
2026
|
+
const acknowledged = typeof sequence === "number" ? void 0 : new Set(sequence);
|
|
2027
|
+
const published = publishedSequences === void 0 ? void 0 : new Set(publishedSequences);
|
|
1650
2028
|
const committedPlayers = /* @__PURE__ */ new Set();
|
|
2029
|
+
const retained = [];
|
|
1651
2030
|
for (const intent of this.#intents) {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
let emitters = this.#lastCommittedByEmitter.get(intent.player);
|
|
1657
|
-
if (emitters === void 0) {
|
|
1658
|
-
emitters = /* @__PURE__ */ new Map();
|
|
1659
|
-
this.#lastCommittedByEmitter.set(intent.player, emitters);
|
|
2031
|
+
const shouldAcknowledge = prefix === void 0 ? acknowledged?.has(intent.sequence) === true : intent.sequence <= prefix;
|
|
2032
|
+
if (!shouldAcknowledge) {
|
|
2033
|
+
retained.push(intent);
|
|
2034
|
+
continue;
|
|
1660
2035
|
}
|
|
1661
|
-
|
|
2036
|
+
const state = this.#players.get(intent.player);
|
|
1662
2037
|
committedPlayers.add(intent.player);
|
|
2038
|
+
if (published === void 0 || published.has(intent.sequence)) {
|
|
2039
|
+
if (state !== void 0) state.hasCommitted = true;
|
|
2040
|
+
const priorCommitted = this.#lastCommitted.get(intent.player);
|
|
2041
|
+
if (priorCommitted === void 0 || priorCommitted.sequence < intent.sequence) {
|
|
2042
|
+
this.#lastCommitted.set(intent.player, intent);
|
|
2043
|
+
}
|
|
2044
|
+
let emitters = this.#lastCommittedByEmitter.get(intent.player);
|
|
2045
|
+
if (emitters === void 0) {
|
|
2046
|
+
emitters = /* @__PURE__ */ new Map();
|
|
2047
|
+
this.#lastCommittedByEmitter.set(intent.player, emitters);
|
|
2048
|
+
}
|
|
2049
|
+
const priorEmitter = emitters.get(intent.emitter.id);
|
|
2050
|
+
if (priorEmitter === void 0 || priorEmitter.sequence < intent.sequence) {
|
|
2051
|
+
emitters.set(intent.emitter.id, intent);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
1663
2054
|
}
|
|
1664
|
-
|
|
1665
|
-
if (retained < 0) this.#intents.length = 0;
|
|
1666
|
-
else if (retained > 0) this.#intents.splice(0, retained);
|
|
2055
|
+
this.#intents.splice(0, this.#intents.length, ...retained);
|
|
1667
2056
|
for (const player of committedPlayers) {
|
|
1668
2057
|
this.#clearDiagnostics(player, "vfx-intent-queue-overflow");
|
|
1669
2058
|
}
|
|
@@ -1749,11 +2138,11 @@ var VfxGpuRuntime = class {
|
|
|
1749
2138
|
const resolved = world.sharedRefs.resolve(
|
|
1750
2139
|
input.effect
|
|
1751
2140
|
);
|
|
1752
|
-
if (!resolved.ok || resolved.value.schemaVersion !==
|
|
2141
|
+
if (!resolved.ok || resolved.value.schemaVersion !== 3) {
|
|
1753
2142
|
this.#report({
|
|
1754
2143
|
code: "vfx-effect-unavailable",
|
|
1755
|
-
expected: "a loaded schemaVersion
|
|
1756
|
-
hint: "
|
|
2144
|
+
expected: "a loaded schemaVersion 3 GPU particle effect",
|
|
2145
|
+
hint: "cold-cook the legacy effect as Program v3 before the first FixedUpdate",
|
|
1757
2146
|
detail: { player: input.player }
|
|
1758
2147
|
});
|
|
1759
2148
|
continue;
|
|
@@ -1761,8 +2150,9 @@ var VfxGpuRuntime = class {
|
|
|
1761
2150
|
this.#clearDiagnostics(input.player, "vfx-effect-unavailable");
|
|
1762
2151
|
const previous = this.#players.get(input.player);
|
|
1763
2152
|
const replayRequested = this.#replayRequests.delete(input.player);
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
2153
|
+
const restartRequested = previous === void 0 || previous.effect !== input.effect || previous.seed !== input.seed || replayRequested || !previous.playing && input.playing;
|
|
2154
|
+
const restart = restartRequested;
|
|
2155
|
+
const state = restartRequested ? {
|
|
1766
2156
|
effect: input.effect,
|
|
1767
2157
|
assetGuid: resolved.value.guid,
|
|
1768
2158
|
programFingerprint: resolved.value.program.fingerprint,
|
|
@@ -1776,16 +2166,21 @@ var VfxGpuRuntime = class {
|
|
|
1776
2166
|
playCycles: resolved.value.program.emitters.map(() => (previous?.playCycle ?? -1) + 1),
|
|
1777
2167
|
cameraVisible: resolved.value.program.emitters.map(() => true),
|
|
1778
2168
|
phaseTicks: resolved.value.program.emitters.map(() => 0),
|
|
1779
|
-
hasCommitted: false
|
|
2169
|
+
hasCommitted: false,
|
|
2170
|
+
pendingResets: resolved.value.program.emitters.map(() => false)
|
|
1780
2171
|
} : previous;
|
|
1781
2172
|
this.#players.set(input.player, state);
|
|
1782
2173
|
state.playing = input.playing;
|
|
1783
|
-
if (!input.playing && !replayRequested)
|
|
2174
|
+
if (!input.playing && !replayRequested) {
|
|
2175
|
+
if (restart) state.pendingResets.fill(true);
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
1784
2178
|
const queuedForPlayer = this.#intents.reduce(
|
|
1785
2179
|
(count, intent) => count + (intent.player === input.player ? 1 : 0),
|
|
1786
2180
|
0
|
|
1787
2181
|
);
|
|
1788
2182
|
if (queuedForPlayer >= this.#maxQueuedTicks * Math.max(1, resolved.value.program.emitters.length)) {
|
|
2183
|
+
if (restart) state.pendingResets.fill(true);
|
|
1789
2184
|
if (state.hasCommitted) {
|
|
1790
2185
|
this.#report({
|
|
1791
2186
|
code: "vfx-intent-queue-overflow",
|
|
@@ -1797,13 +2192,17 @@ var VfxGpuRuntime = class {
|
|
|
1797
2192
|
continue;
|
|
1798
2193
|
}
|
|
1799
2194
|
const instance = this.#instances.get(input.player) ?? this.#createInstance(input.player, resolved.value);
|
|
1800
|
-
if (instance === void 0)
|
|
2195
|
+
if (instance === void 0) {
|
|
2196
|
+
if (restart) state.pendingResets.fill(true);
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
1801
2199
|
const hasActiveEmitter = resolved.value.program.emitters.some((emitter) => {
|
|
1802
2200
|
if (!this.isEmitterSessionEnabled(input.player, emitter.id)) return false;
|
|
1803
2201
|
const cameraVisible = this.#cameraVisibility.get(`${input.player}:${emitter.id}`) ?? true;
|
|
1804
2202
|
return cameraVisible || emitter.simulationWhenCulled === "continue";
|
|
1805
2203
|
});
|
|
1806
2204
|
if (!hasActiveEmitter) {
|
|
2205
|
+
if (restart) state.pendingResets.fill(true);
|
|
1807
2206
|
for (const [index, emitter] of resolved.value.program.emitters.entries()) {
|
|
1808
2207
|
state.cameraVisible[index] = this.#cameraVisibility.get(`${input.player}:${emitter.id}`) ?? true;
|
|
1809
2208
|
}
|
|
@@ -1813,6 +2212,7 @@ var VfxGpuRuntime = class {
|
|
|
1813
2212
|
this.#replayInputs.delete(input.player);
|
|
1814
2213
|
const committed = replayInput === void 0 ? instance.commit({ seed: input.seed, tick }) : instance.replay(replayInput);
|
|
1815
2214
|
if (!committed.ok) {
|
|
2215
|
+
if (restart) state.pendingResets.fill(true);
|
|
1816
2216
|
this.#report({
|
|
1817
2217
|
code: "vfx-instance-commit-failed",
|
|
1818
2218
|
expected: "the current typed instance values to pack into the reflected GPU block",
|
|
@@ -1827,8 +2227,15 @@ var VfxGpuRuntime = class {
|
|
|
1827
2227
|
const cameraVisible = this.#cameraVisibility.get(`${input.player}:${emitter.id}`) ?? true;
|
|
1828
2228
|
const becameVisible = cameraVisible && state.cameraVisible[index] === false;
|
|
1829
2229
|
state.cameraVisible[index] = cameraVisible;
|
|
1830
|
-
|
|
1831
|
-
if (!
|
|
2230
|
+
const emitterRestart = restart || state.pendingResets[index] === true;
|
|
2231
|
+
if (!this.isEmitterSessionEnabled(input.player, emitter.id)) {
|
|
2232
|
+
if (emitterRestart) state.pendingResets[index] = true;
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
if (!cameraVisible && emitter.simulationWhenCulled !== "continue") {
|
|
2236
|
+
if (emitterRestart) state.pendingResets[index] = true;
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
1832
2239
|
const visibilityRestart = becameVisible && emitter.simulationWhenCulled === "restart-on-visible";
|
|
1833
2240
|
if (visibilityRestart) {
|
|
1834
2241
|
state.elapsed[index] = 0;
|
|
@@ -1842,7 +2249,7 @@ var VfxGpuRuntime = class {
|
|
|
1842
2249
|
emitter,
|
|
1843
2250
|
previousElapsed,
|
|
1844
2251
|
previousElapsed + delta,
|
|
1845
|
-
|
|
2252
|
+
emitterRestart || visibilityRestart,
|
|
1846
2253
|
state.rateRemainders[index] ?? 0
|
|
1847
2254
|
);
|
|
1848
2255
|
state.rateRemainders[index] = scheduled.remainder;
|
|
@@ -1862,7 +2269,7 @@ var VfxGpuRuntime = class {
|
|
|
1862
2269
|
player: input.player,
|
|
1863
2270
|
emitter,
|
|
1864
2271
|
programFingerprint: resolved.value.program.fingerprint,
|
|
1865
|
-
reset:
|
|
2272
|
+
reset: emitterRestart || visibilityRestart,
|
|
1866
2273
|
fixedDelta: delta,
|
|
1867
2274
|
phaseTick,
|
|
1868
2275
|
tick,
|
|
@@ -1885,6 +2292,7 @@ var VfxGpuRuntime = class {
|
|
|
1885
2292
|
);
|
|
1886
2293
|
state.elapsed[index] = previousElapsed + delta;
|
|
1887
2294
|
state.phaseTicks[index] = phaseTick + 1;
|
|
2295
|
+
state.pendingResets[index] = false;
|
|
1888
2296
|
}
|
|
1889
2297
|
}
|
|
1890
2298
|
for (const player of this.#players.keys()) {
|
|
@@ -1895,12 +2303,16 @@ var VfxGpuRuntime = class {
|
|
|
1895
2303
|
const layout = effect.program.emitters.find(
|
|
1896
2304
|
(emitter) => emitter.reflection.layout !== void 0
|
|
1897
2305
|
)?.reflection.layout;
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2306
|
+
if (layout === void 0) {
|
|
2307
|
+
this.#report({
|
|
2308
|
+
code: "vfx-effect-unavailable",
|
|
2309
|
+
expected: "a Program v3 emitter reflection layout",
|
|
2310
|
+
hint: "recook the effect with Program v3 reflection before starting the player",
|
|
2311
|
+
detail: { player }
|
|
2312
|
+
});
|
|
2313
|
+
return void 0;
|
|
2314
|
+
}
|
|
2315
|
+
const reflection = layout;
|
|
1904
2316
|
try {
|
|
1905
2317
|
const instance = new ParticleEffectInstance(createVfxEffectContract(reflection), {
|
|
1906
2318
|
channels: effect.program.emitters.flatMap((emitter) => emitter.channels ?? [])
|
|
@@ -1990,6 +2402,124 @@ function vfxGpuRuntimePlugin(options = {}) {
|
|
|
1990
2402
|
};
|
|
1991
2403
|
}
|
|
1992
2404
|
|
|
1993
|
-
|
|
2405
|
+
// src/particle-layout.ts
|
|
2406
|
+
var CORE_FIELDS = Object.freeze([
|
|
2407
|
+
{ name: "position", type: "vec3<f32>", offset: 0, size: 12, alignment: 16 },
|
|
2408
|
+
{ name: "age", type: "f32", offset: 12, size: 4, alignment: 4 },
|
|
2409
|
+
{ name: "velocity", type: "vec3<f32>", offset: 16, size: 12, alignment: 16 },
|
|
2410
|
+
{ name: "lifetime", type: "f32", offset: 28, size: 4, alignment: 4 },
|
|
2411
|
+
{ name: "color", type: "vec4<f32>", offset: 32, size: 16, alignment: 16 },
|
|
2412
|
+
{ name: "sprite_size", type: "vec2<f32>", offset: 48, size: 8, alignment: 8 },
|
|
2413
|
+
{ name: "sprite_rotation", type: "f32", offset: 56, size: 4, alignment: 4 },
|
|
2414
|
+
{ name: "sub_image", type: "f32", offset: 60, size: 4, alignment: 4 },
|
|
2415
|
+
{ name: "mesh_orientation", type: "vec4<f32>", offset: 64, size: 16, alignment: 16 },
|
|
2416
|
+
{ name: "mesh_scale", type: "vec3<f32>", offset: 80, size: 12, alignment: 16 },
|
|
2417
|
+
{ name: "material_random", type: "f32", offset: 92, size: 4, alignment: 4 },
|
|
2418
|
+
{ name: "id", type: "u32", offset: 96, size: 4, alignment: 4 },
|
|
2419
|
+
{ name: "alive", type: "u32", offset: 100, size: 4, alignment: 4 }
|
|
2420
|
+
]);
|
|
2421
|
+
var VFX_PARTICLE_CORE_LAYOUT = Object.freeze({
|
|
2422
|
+
name: "VfxParticle",
|
|
2423
|
+
fields: CORE_FIELDS,
|
|
2424
|
+
size: 112,
|
|
2425
|
+
alignment: 16,
|
|
2426
|
+
stride: 112,
|
|
2427
|
+
fingerprint: "sha256:9e3c1566f6d0600cedb94bd3c4b76c4e7c456fd563b37065220d4e33cf83c777"
|
|
2428
|
+
});
|
|
2429
|
+
var VFX_PARTICLE_CORE_STRIDE = VFX_PARTICLE_CORE_LAYOUT.stride;
|
|
2430
|
+
var CORE_FIELD_BY_NAME = new Map(CORE_FIELDS.map((field2) => [field2.name, field2]));
|
|
2431
|
+
function vfxParticleCoreField(name) {
|
|
2432
|
+
const field2 = CORE_FIELD_BY_NAME.get(name);
|
|
2433
|
+
if (field2 === void 0) throw new Error(`unknown VFX particle core field ${name}`);
|
|
2434
|
+
return field2;
|
|
2435
|
+
}
|
|
2436
|
+
function vfxParticleCoreWgsl() {
|
|
2437
|
+
return [
|
|
2438
|
+
"struct VfxParticle {",
|
|
2439
|
+
...CORE_FIELDS.map((field2) => ` ${field2.name}: ${field2.type},`),
|
|
2440
|
+
"}"
|
|
2441
|
+
].join("\n");
|
|
2442
|
+
}
|
|
2443
|
+
var VALUE_LAYOUT = {
|
|
2444
|
+
f32: { alignment: 4, size: 4 },
|
|
2445
|
+
i32: { alignment: 4, size: 4 },
|
|
2446
|
+
u32: { alignment: 4, size: 4 },
|
|
2447
|
+
"vec2<f32>": { alignment: 8, size: 8 },
|
|
2448
|
+
"vec3<f32>": { alignment: 16, size: 12 },
|
|
2449
|
+
"vec4<f32>": { alignment: 16, size: 16 }
|
|
2450
|
+
};
|
|
2451
|
+
function alignUp(value, alignment) {
|
|
2452
|
+
return Math.ceil(value / alignment) * alignment;
|
|
2453
|
+
}
|
|
2454
|
+
function deriveVfxCustomLayout(struct, maxLanes = 4) {
|
|
2455
|
+
if (struct.name !== "VfxCustom") {
|
|
2456
|
+
throw new TypeError(`expected VfxCustom reflection, received ${struct.name}`);
|
|
2457
|
+
}
|
|
2458
|
+
const fields2 = [];
|
|
2459
|
+
let offset = 0;
|
|
2460
|
+
let alignment = 1;
|
|
2461
|
+
for (const field2 of struct.fields) {
|
|
2462
|
+
const layout = VALUE_LAYOUT[field2.type];
|
|
2463
|
+
offset = alignUp(offset, layout.alignment);
|
|
2464
|
+
fields2.push({ ...field2, offset, size: layout.size, alignment: layout.alignment });
|
|
2465
|
+
offset += layout.size;
|
|
2466
|
+
alignment = Math.max(alignment, layout.alignment);
|
|
2467
|
+
}
|
|
2468
|
+
const size = fields2.length === 0 ? 0 : alignUp(offset, alignment);
|
|
2469
|
+
const stride = size;
|
|
2470
|
+
const lanes = Math.ceil(stride / 16);
|
|
2471
|
+
if (lanes > maxLanes) {
|
|
2472
|
+
throw new RangeError(`VfxCustom consumes ${lanes} vec4 lanes; maximum is ${maxLanes}`);
|
|
2473
|
+
}
|
|
2474
|
+
return Object.freeze({
|
|
2475
|
+
name: "VfxCustom",
|
|
2476
|
+
fields: Object.freeze(fields2),
|
|
2477
|
+
size,
|
|
2478
|
+
alignment,
|
|
2479
|
+
stride,
|
|
2480
|
+
lanes
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
function finite2(value) {
|
|
2484
|
+
return Number.isFinite(value);
|
|
2485
|
+
}
|
|
2486
|
+
function encodeVfxParticleCore(value) {
|
|
2487
|
+
const bytes = new Uint8Array(VFX_PARTICLE_CORE_STRIDE);
|
|
2488
|
+
const view = new DataView(bytes.buffer);
|
|
2489
|
+
const write = (name, values) => {
|
|
2490
|
+
const field2 = vfxParticleCoreField(name);
|
|
2491
|
+
for (const component of values) {
|
|
2492
|
+
if (!finite2(component)) throw new TypeError(`non-finite VFX particle field ${name}`);
|
|
2493
|
+
if (field2.type === "u32" && (!Number.isInteger(component) || component < 0 || component > 4294967295)) {
|
|
2494
|
+
throw new RangeError(`u32 VFX particle field ${name} is outside its valid range`);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
2498
|
+
const component = values[index] ?? 0;
|
|
2499
|
+
if (field2.type === "u32") view.setUint32(field2.offset + index * 4, component, true);
|
|
2500
|
+
else view.setFloat32(field2.offset + index * 4, component, true);
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
write("position", value.position);
|
|
2504
|
+
write("age", [value.age]);
|
|
2505
|
+
write("velocity", value.velocity);
|
|
2506
|
+
write("lifetime", [value.lifetime]);
|
|
2507
|
+
write("color", value.color);
|
|
2508
|
+
write("sprite_size", value.sprite_size);
|
|
2509
|
+
write("sprite_rotation", [value.sprite_rotation]);
|
|
2510
|
+
write("sub_image", [value.sub_image]);
|
|
2511
|
+
write("mesh_orientation", value.mesh_orientation);
|
|
2512
|
+
write("mesh_scale", value.mesh_scale);
|
|
2513
|
+
write("material_random", [value.material_random]);
|
|
2514
|
+
write("id", [value.id]);
|
|
2515
|
+
write("alive", [value.alive]);
|
|
2516
|
+
return bytes;
|
|
2517
|
+
}
|
|
2518
|
+
function normalizedVfxParticleAge(age, lifetime) {
|
|
2519
|
+
if (!finite2(age) || !finite2(lifetime) || lifetime <= 0) return 0;
|
|
2520
|
+
return Math.min(1, Math.max(0, age / lifetime));
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
export { PARTICLE_CODE_DEFAULT_MODULE_ID, PARTICLE_RENDERER_SEMANTICS, PARTICLE_STAGE_RESOURCE_NAMES, ParticleEffectInstance, ParticleEffectPlayer, VFX_GPU_PROGRAM_ARTIFACT_KEY, VFX_GPU_PROGRAM_FORMAT, VFX_GPU_PROGRAM_FORMAT_V3, VFX_GPU_RUNTIME_RESOURCE_KEY, VFX_PARTICLE_CORE_LAYOUT, VFX_PARTICLE_CORE_STRIDE, VfxGpuRuntime, createParticleEffectInstance, createVfxEffectContract, createVfxInspectSnapshot, defaultParticleRendererAttributes, defineParticleEffectSourceV3, deriveVfxCustomLayout, describeVfxGpuEffect, encodeVfxParticleCore, isVfxGpuEffectAsset, loadVfxGpuEffect, normalizedVfxParticleAge, parseParticleEffectSourceV3, parseVfxStageDeclarations, particleEffectContribution, resolveVfxDataInterfaces, validateVfxEffectValues, vfxGpuEffectContribution, vfxGpuEffectPackLoader, vfxGpuRuntimePlugin, vfxParticleCoreField, vfxParticleCoreWgsl };
|
|
1994
2524
|
//# sourceMappingURL=index.mjs.map
|
|
1995
2525
|
//# sourceMappingURL=index.mjs.map
|