@plasius/gpu-worker 0.1.10 → 0.1.12
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/CHANGELOG.md +37 -1
- package/README.md +33 -0
- package/dist/index.cjs +314 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +311 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.js +379 -0
package/src/index.js
CHANGED
|
@@ -727,3 +727,382 @@ export function createWorkerLoop(options = {}) {
|
|
|
727
727
|
},
|
|
728
728
|
};
|
|
729
729
|
}
|
|
730
|
+
|
|
731
|
+
export const scenePreparationRepresentationBands = Object.freeze([
|
|
732
|
+
"near",
|
|
733
|
+
"mid",
|
|
734
|
+
"far",
|
|
735
|
+
"horizon",
|
|
736
|
+
]);
|
|
737
|
+
|
|
738
|
+
export const scenePreparationStageFamilies = Object.freeze([
|
|
739
|
+
"snapshotSelection",
|
|
740
|
+
"transformPropagation",
|
|
741
|
+
"animationPose",
|
|
742
|
+
"proceduralAnimation",
|
|
743
|
+
"skinningOrDeformation",
|
|
744
|
+
"boundsUpdate",
|
|
745
|
+
"lodSelection",
|
|
746
|
+
"rtRepresentationSelection",
|
|
747
|
+
"visibility",
|
|
748
|
+
"lightAssignment",
|
|
749
|
+
"renderProxyBuild",
|
|
750
|
+
"rtInstancePreparation",
|
|
751
|
+
]);
|
|
752
|
+
|
|
753
|
+
const scenePreparationDefaultStageDependencies = Object.freeze({
|
|
754
|
+
snapshotSelection: Object.freeze([]),
|
|
755
|
+
transformPropagation: Object.freeze(["snapshotSelection"]),
|
|
756
|
+
animationPose: Object.freeze(["transformPropagation"]),
|
|
757
|
+
proceduralAnimation: Object.freeze(["animationPose"]),
|
|
758
|
+
skinningOrDeformation: Object.freeze([
|
|
759
|
+
"animationPose",
|
|
760
|
+
"proceduralAnimation",
|
|
761
|
+
]),
|
|
762
|
+
boundsUpdate: Object.freeze(["skinningOrDeformation"]),
|
|
763
|
+
lodSelection: Object.freeze(["boundsUpdate"]),
|
|
764
|
+
rtRepresentationSelection: Object.freeze(["lodSelection"]),
|
|
765
|
+
visibility: Object.freeze(["boundsUpdate", "lodSelection"]),
|
|
766
|
+
lightAssignment: Object.freeze(["visibility"]),
|
|
767
|
+
renderProxyBuild: Object.freeze(["visibility", "lodSelection"]),
|
|
768
|
+
rtInstancePreparation: Object.freeze([
|
|
769
|
+
"visibility",
|
|
770
|
+
"rtRepresentationSelection",
|
|
771
|
+
]),
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
const scenePreparationBandPriorityWeights = Object.freeze({
|
|
775
|
+
near: 400,
|
|
776
|
+
mid: 300,
|
|
777
|
+
far: 200,
|
|
778
|
+
horizon: 100,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
const scenePreparationImportanceWeights = Object.freeze({
|
|
782
|
+
low: 0,
|
|
783
|
+
medium: 15,
|
|
784
|
+
high: 30,
|
|
785
|
+
critical: 60,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
const scenePreparationStagePriorityWeights = Object.freeze({
|
|
789
|
+
snapshotSelection: 60,
|
|
790
|
+
transformPropagation: 54,
|
|
791
|
+
animationPose: 50,
|
|
792
|
+
proceduralAnimation: 46,
|
|
793
|
+
skinningOrDeformation: 42,
|
|
794
|
+
boundsUpdate: 38,
|
|
795
|
+
lodSelection: 32,
|
|
796
|
+
rtRepresentationSelection: 28,
|
|
797
|
+
visibility: 34,
|
|
798
|
+
lightAssignment: 24,
|
|
799
|
+
renderProxyBuild: 22,
|
|
800
|
+
rtInstancePreparation: 26,
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
function assertScenePreparationIdentifier(name, value) {
|
|
804
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
805
|
+
throw new Error(`${name} must be a non-empty string.`);
|
|
806
|
+
}
|
|
807
|
+
return value.trim();
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function assertScenePreparationEnum(name, value, allowed) {
|
|
811
|
+
const normalized = assertScenePreparationIdentifier(name, value);
|
|
812
|
+
if (!allowed.includes(normalized)) {
|
|
813
|
+
throw new Error(`${name} must be one of: ${allowed.join(", ")}.`);
|
|
814
|
+
}
|
|
815
|
+
return normalized;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function normalizeScenePreparationStages(stages, chunkLabel) {
|
|
819
|
+
const requested =
|
|
820
|
+
stages === undefined ? scenePreparationStageFamilies : stages;
|
|
821
|
+
if (!Array.isArray(requested) || requested.length === 0) {
|
|
822
|
+
throw new Error(`${chunkLabel}.stages must be a non-empty array when provided.`);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const normalized = [...new Set(
|
|
826
|
+
requested.map((stage, index) =>
|
|
827
|
+
assertScenePreparationEnum(
|
|
828
|
+
`${chunkLabel}.stages[${index}]`,
|
|
829
|
+
stage,
|
|
830
|
+
scenePreparationStageFamilies
|
|
831
|
+
)
|
|
832
|
+
)
|
|
833
|
+
)];
|
|
834
|
+
|
|
835
|
+
return normalized.sort(
|
|
836
|
+
(left, right) =>
|
|
837
|
+
scenePreparationStageFamilies.indexOf(left) -
|
|
838
|
+
scenePreparationStageFamilies.indexOf(right)
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function collectScenePreparationDependencies(
|
|
843
|
+
stageFamily,
|
|
844
|
+
includedStages,
|
|
845
|
+
seen = new Set()
|
|
846
|
+
) {
|
|
847
|
+
const dependencies =
|
|
848
|
+
scenePreparationDefaultStageDependencies[stageFamily] ?? [];
|
|
849
|
+
for (const dependency of dependencies) {
|
|
850
|
+
if (includedStages.has(dependency)) {
|
|
851
|
+
seen.add(dependency);
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
collectScenePreparationDependencies(dependency, includedStages, seen);
|
|
855
|
+
}
|
|
856
|
+
return [...seen].sort(
|
|
857
|
+
(left, right) =>
|
|
858
|
+
scenePreparationStageFamilies.indexOf(left) -
|
|
859
|
+
scenePreparationStageFamilies.indexOf(right)
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function buildScenePreparationPriority(chunk, stageFamily) {
|
|
864
|
+
const bandWeight =
|
|
865
|
+
scenePreparationBandPriorityWeights[chunk.representationBand] ?? 0;
|
|
866
|
+
const importanceWeight =
|
|
867
|
+
scenePreparationImportanceWeights[chunk.gameplayImportance] ?? 0;
|
|
868
|
+
const stageWeight =
|
|
869
|
+
scenePreparationStagePriorityWeights[stageFamily] ?? 0;
|
|
870
|
+
|
|
871
|
+
return (
|
|
872
|
+
bandWeight +
|
|
873
|
+
importanceWeight +
|
|
874
|
+
stageWeight +
|
|
875
|
+
(chunk.visible ? 20 : 0) +
|
|
876
|
+
(chunk.playerRelevant ? 20 : 0) +
|
|
877
|
+
(chunk.imageCritical ? 15 : 0)
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function buildScenePreparationPriorityLanes(jobs) {
|
|
882
|
+
const lanes = new Map();
|
|
883
|
+
for (const job of jobs) {
|
|
884
|
+
const lane = lanes.get(job.priority) ?? {
|
|
885
|
+
priority: job.priority,
|
|
886
|
+
jobIds: [],
|
|
887
|
+
chunkIds: [],
|
|
888
|
+
};
|
|
889
|
+
lane.jobIds.push(job.id);
|
|
890
|
+
if (!lane.chunkIds.includes(job.chunkId)) {
|
|
891
|
+
lane.chunkIds.push(job.chunkId);
|
|
892
|
+
}
|
|
893
|
+
lanes.set(job.priority, lane);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return Object.freeze(
|
|
897
|
+
[...lanes.values()]
|
|
898
|
+
.sort((left, right) => right.priority - left.priority)
|
|
899
|
+
.map((lane) =>
|
|
900
|
+
Object.freeze({
|
|
901
|
+
priority: lane.priority,
|
|
902
|
+
jobIds: Object.freeze([...lane.jobIds]),
|
|
903
|
+
chunkIds: Object.freeze([...lane.chunkIds]),
|
|
904
|
+
jobCount: lane.jobIds.length,
|
|
905
|
+
})
|
|
906
|
+
)
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function buildScenePreparationTopologicalOrder(jobs) {
|
|
911
|
+
const indegree = new Map(jobs.map((job) => [job.id, job.dependencies.length]));
|
|
912
|
+
const dependentsById = new Map(jobs.map((job) => [job.id, []]));
|
|
913
|
+
|
|
914
|
+
for (const job of jobs) {
|
|
915
|
+
for (const dependency of job.dependencies) {
|
|
916
|
+
dependentsById.get(dependency)?.push(job.id);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const queue = jobs
|
|
921
|
+
.filter((job) => job.dependencies.length === 0)
|
|
922
|
+
.sort((left, right) => right.priority - left.priority)
|
|
923
|
+
.map((job) => job.id);
|
|
924
|
+
const jobById = new Map(jobs.map((job) => [job.id, job]));
|
|
925
|
+
const order = [];
|
|
926
|
+
|
|
927
|
+
while (queue.length > 0) {
|
|
928
|
+
const currentId = queue.shift();
|
|
929
|
+
if (!currentId) {
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
order.push(currentId);
|
|
933
|
+
const unlocked = [];
|
|
934
|
+
for (const dependentId of dependentsById.get(currentId) ?? []) {
|
|
935
|
+
const next = (indegree.get(dependentId) ?? 0) - 1;
|
|
936
|
+
indegree.set(dependentId, next);
|
|
937
|
+
if (next === 0) {
|
|
938
|
+
unlocked.push(dependentId);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
unlocked
|
|
942
|
+
.sort(
|
|
943
|
+
(left, right) =>
|
|
944
|
+
(jobById.get(right)?.priority ?? 0) -
|
|
945
|
+
(jobById.get(left)?.priority ?? 0)
|
|
946
|
+
)
|
|
947
|
+
.forEach((jobId) => {
|
|
948
|
+
queue.push(jobId);
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (order.length !== jobs.length) {
|
|
953
|
+
throw new Error("Scene-preparation manifest contains a cycle.");
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return Object.freeze(order);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
export function createScenePreparationManifest(options = {}) {
|
|
960
|
+
const snapshotId = assertScenePreparationIdentifier(
|
|
961
|
+
"snapshotId",
|
|
962
|
+
options.snapshotId
|
|
963
|
+
);
|
|
964
|
+
const chunkEntries = Array.isArray(options.chunks) ? options.chunks : [];
|
|
965
|
+
if (chunkEntries.length === 0) {
|
|
966
|
+
throw new Error("createScenePreparationManifest requires at least one chunk.");
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const normalizedChunks = chunkEntries.map((chunk, index) => {
|
|
970
|
+
if (!chunk || typeof chunk !== "object" || Array.isArray(chunk)) {
|
|
971
|
+
throw new Error(`chunks[${index}] must be an object.`);
|
|
972
|
+
}
|
|
973
|
+
const chunkLabel = `chunks[${index}]`;
|
|
974
|
+
if (chunk.mutatesSimulation === true) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
`${chunkLabel}.mutatesSimulation cannot be true for render preparation.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return Object.freeze({
|
|
981
|
+
chunkId: assertScenePreparationIdentifier(`${chunkLabel}.chunkId`, chunk.chunkId),
|
|
982
|
+
representationBand: assertScenePreparationEnum(
|
|
983
|
+
`${chunkLabel}.representationBand`,
|
|
984
|
+
chunk.representationBand ?? "mid",
|
|
985
|
+
scenePreparationRepresentationBands
|
|
986
|
+
),
|
|
987
|
+
gameplayImportance: assertScenePreparationEnum(
|
|
988
|
+
`${chunkLabel}.gameplayImportance`,
|
|
989
|
+
chunk.gameplayImportance ?? "medium",
|
|
990
|
+
Object.keys(scenePreparationImportanceWeights)
|
|
991
|
+
),
|
|
992
|
+
visible: chunk.visible !== false,
|
|
993
|
+
playerRelevant: chunk.playerRelevant === true,
|
|
994
|
+
imageCritical: chunk.imageCritical === true,
|
|
995
|
+
stages: normalizeScenePreparationStages(chunk.stages, chunkLabel),
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
const chunkIds = new Set();
|
|
1000
|
+
for (const chunk of normalizedChunks) {
|
|
1001
|
+
if (chunkIds.has(chunk.chunkId)) {
|
|
1002
|
+
throw new Error(`Duplicate scene-preparation chunk id detected: ${chunk.chunkId}`);
|
|
1003
|
+
}
|
|
1004
|
+
chunkIds.add(chunk.chunkId);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const jobs = [];
|
|
1008
|
+
for (const chunk of normalizedChunks) {
|
|
1009
|
+
const includedStages = new Set(chunk.stages);
|
|
1010
|
+
for (const stageFamily of chunk.stages) {
|
|
1011
|
+
const dependencies = collectScenePreparationDependencies(
|
|
1012
|
+
stageFamily,
|
|
1013
|
+
includedStages
|
|
1014
|
+
).map(
|
|
1015
|
+
(dependency) =>
|
|
1016
|
+
`${snapshotId}:${chunk.chunkId}:${chunk.representationBand}:${dependency}`
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
jobs.push(
|
|
1020
|
+
Object.freeze({
|
|
1021
|
+
id: `${snapshotId}:${chunk.chunkId}:${chunk.representationBand}:${stageFamily}`,
|
|
1022
|
+
snapshotId,
|
|
1023
|
+
chunkId: chunk.chunkId,
|
|
1024
|
+
representationBand: chunk.representationBand,
|
|
1025
|
+
stageFamily,
|
|
1026
|
+
priority: buildScenePreparationPriority(chunk, stageFamily),
|
|
1027
|
+
dependencies: Object.freeze(dependencies),
|
|
1028
|
+
dependencyCount: dependencies.length,
|
|
1029
|
+
root: dependencies.length === 0,
|
|
1030
|
+
authority: "visual",
|
|
1031
|
+
mutatesSimulation: false,
|
|
1032
|
+
gameplayImportance: chunk.gameplayImportance,
|
|
1033
|
+
visible: chunk.visible,
|
|
1034
|
+
playerRelevant: chunk.playerRelevant,
|
|
1035
|
+
imageCritical: chunk.imageCritical,
|
|
1036
|
+
})
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const jobById = new Map(jobs.map((job) => [job.id, job]));
|
|
1042
|
+
let crossChunkDependencyCount = 0;
|
|
1043
|
+
let localJoinCount = 0;
|
|
1044
|
+
|
|
1045
|
+
const finalizedJobs = Object.freeze(
|
|
1046
|
+
jobs.map((job) => {
|
|
1047
|
+
const dependents = jobs
|
|
1048
|
+
.filter((candidate) => candidate.dependencies.includes(job.id))
|
|
1049
|
+
.map((candidate) => candidate.id);
|
|
1050
|
+
const crossChunkDependencies = job.dependencies.filter((dependency) => {
|
|
1051
|
+
const parent = jobById.get(dependency);
|
|
1052
|
+
return parent && parent.chunkId !== job.chunkId;
|
|
1053
|
+
});
|
|
1054
|
+
crossChunkDependencyCount += crossChunkDependencies.length;
|
|
1055
|
+
if (
|
|
1056
|
+
job.dependencies.length > 1 &&
|
|
1057
|
+
crossChunkDependencies.length === 0
|
|
1058
|
+
) {
|
|
1059
|
+
localJoinCount += 1;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return Object.freeze({
|
|
1063
|
+
...job,
|
|
1064
|
+
dependents: Object.freeze(dependents),
|
|
1065
|
+
dependentCount: dependents.length,
|
|
1066
|
+
unresolvedDependencyCount: job.dependencies.length,
|
|
1067
|
+
localJoin:
|
|
1068
|
+
job.dependencies.length > 1 && crossChunkDependencies.length === 0,
|
|
1069
|
+
});
|
|
1070
|
+
})
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
const graph = Object.freeze({
|
|
1074
|
+
schedulerMode: "dag",
|
|
1075
|
+
jobCount: finalizedJobs.length,
|
|
1076
|
+
chunkCount: normalizedChunks.length,
|
|
1077
|
+
chunkIds: Object.freeze(normalizedChunks.map((chunk) => chunk.chunkId)),
|
|
1078
|
+
representationBands: Object.freeze(
|
|
1079
|
+
[...new Set(normalizedChunks.map((chunk) => chunk.representationBand))]
|
|
1080
|
+
),
|
|
1081
|
+
roots: Object.freeze(
|
|
1082
|
+
finalizedJobs.filter((job) => job.root).map((job) => job.id)
|
|
1083
|
+
),
|
|
1084
|
+
chunkRoots: Object.freeze(
|
|
1085
|
+
Object.fromEntries(
|
|
1086
|
+
normalizedChunks.map((chunk) => [
|
|
1087
|
+
chunk.chunkId,
|
|
1088
|
+
finalizedJobs
|
|
1089
|
+
.filter((job) => job.chunkId === chunk.chunkId && job.root)
|
|
1090
|
+
.map((job) => job.id),
|
|
1091
|
+
])
|
|
1092
|
+
)
|
|
1093
|
+
),
|
|
1094
|
+
topologicalOrder: buildScenePreparationTopologicalOrder(finalizedJobs),
|
|
1095
|
+
priorityLanes: buildScenePreparationPriorityLanes(finalizedJobs),
|
|
1096
|
+
localJoinCount,
|
|
1097
|
+
crossChunkDependencyCount,
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
return Object.freeze({
|
|
1101
|
+
schemaVersion: 1,
|
|
1102
|
+
owner: "scene-preparation",
|
|
1103
|
+
schedulerMode: "dag",
|
|
1104
|
+
snapshotId,
|
|
1105
|
+
jobs: finalizedJobs,
|
|
1106
|
+
graph,
|
|
1107
|
+
});
|
|
1108
|
+
}
|