@plasius/gpu-shared 0.1.13 → 0.1.14
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 +22 -0
- package/README.md +55 -3
- package/dist/{chunk-NCPJWLX3.js → chunk-2GM64LB6.js} +1 -9
- package/dist/{chunk-NCPJWLX3.js.map → chunk-2GM64LB6.js.map} +1 -1
- package/dist/{chunk-DABW627O.js → chunk-3ARPGHCQ.js} +8 -2
- package/dist/chunk-3ARPGHCQ.js.map +1 -0
- package/dist/chunk-4ZJ24VRS.js +402 -0
- package/dist/chunk-4ZJ24VRS.js.map +1 -0
- package/dist/{chunk-DQX4DXBR.js → chunk-W5GA3VA6.js} +79 -6
- package/dist/chunk-W5GA3VA6.js.map +1 -0
- package/dist/gltf-loader-YDPLZS5Q.js +8 -0
- package/dist/index.cjs +1230 -6198
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +31 -5
- package/dist/index.js.map +1 -1
- package/dist/product-studio-runtime-HDAUDWYO.js +11 -0
- package/dist/showcase-inline-assets-WT4PSNKI.js +7 -0
- package/dist/showcase-inline-assets-WT4PSNKI.js.map +1 -0
- package/dist/showcase-runtime-SNCUFSSC.js +3785 -0
- package/dist/showcase-runtime-SNCUFSSC.js.map +1 -0
- package/package.json +6 -8
- package/src/feature-flags.js +1 -0
- package/src/gltf-loader.js +10 -2
- package/src/index.d.ts +72 -1
- package/src/index.js +33 -0
- package/src/product-studio-runtime.js +465 -0
- package/src/showcase-runtime.js +875 -72
- package/dist/chunk-2FIFSBB4.js +0 -74
- package/dist/chunk-2FIFSBB4.js.map +0 -1
- package/dist/chunk-DABW627O.js.map +0 -1
- package/dist/chunk-DQX4DXBR.js.map +0 -1
- package/dist/gltf-loader-WAM23F37.js +0 -9
- package/dist/showcase-inline-assets-B7U7VX5H.js +0 -7
- package/dist/showcase-runtime-PN7N3FZY.js +0 -9164
- package/dist/showcase-runtime-PN7N3FZY.js.map +0 -1
- /package/dist/{gltf-loader-WAM23F37.js.map → gltf-loader-YDPLZS5Q.js.map} +0 -0
- /package/dist/{showcase-inline-assets-B7U7VX5H.js.map → product-studio-runtime-HDAUDWYO.js.map} +0 -0
package/src/showcase-runtime.js
CHANGED
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
clothGarmentKinds,
|
|
3
|
-
clothProfileNames,
|
|
4
|
-
createClothRepresentationPlan,
|
|
5
|
-
selectClothRepresentationBand,
|
|
6
|
-
} from "@plasius/gpu-cloth";
|
|
7
|
-
import {
|
|
8
|
-
fluidBodyKinds,
|
|
9
|
-
fluidProfileNames,
|
|
10
|
-
createFluidContinuityEnvelope,
|
|
11
|
-
createFluidRepresentationPlan,
|
|
12
|
-
selectFluidRepresentationBand,
|
|
13
|
-
} from "@plasius/gpu-fluid";
|
|
14
|
-
import {
|
|
15
|
-
createLightingBandPlan,
|
|
16
|
-
defaultLightingProfile,
|
|
17
|
-
getLightingProfile,
|
|
18
|
-
lightingDistanceBands,
|
|
19
|
-
} from "@plasius/gpu-lighting";
|
|
20
|
-
import {
|
|
21
|
-
createDeviceProfile,
|
|
22
|
-
createGpuPerformanceGovernor,
|
|
23
|
-
createQualityLadderAdapter,
|
|
24
|
-
} from "@plasius/gpu-performance";
|
|
25
|
-
import { createGpuDebugSession } from "@plasius/gpu-debug";
|
|
26
|
-
import {
|
|
27
|
-
createPhysicsSimulationPlan,
|
|
28
|
-
createPhysicsWorldSnapshot,
|
|
29
|
-
defaultPhysicsWorkerProfile,
|
|
30
|
-
getPhysicsWorkerManifest,
|
|
31
|
-
} from "@plasius/gpu-physics/browser";
|
|
32
|
-
|
|
33
1
|
import { resolveShowcaseAssetUrl } from "./asset-url.js";
|
|
34
2
|
import { loadGltfModel } from "./gltf-loader.js";
|
|
35
3
|
import { GPU_SHOWCASE_REALISTIC_MODELS_FEATURE } from "./feature-flags.js";
|
|
@@ -60,6 +28,772 @@ const CAMERA_PRESETS = Object.freeze({
|
|
|
60
28
|
performance: Object.freeze({ yaw: -0.65, pitch: 0.36, distance: 24, target: [0, 2.2, 0] }),
|
|
61
29
|
debug: Object.freeze({ yaw: -0.7, pitch: 0.32, distance: 24, target: [0, 2.2, 0] }),
|
|
62
30
|
});
|
|
31
|
+
|
|
32
|
+
const FALLBACK_LIGHTING_DISTANCE_BANDS = Object.freeze([
|
|
33
|
+
Object.freeze({
|
|
34
|
+
band: "near",
|
|
35
|
+
primaryShadowSource: "ray-traced-primary",
|
|
36
|
+
rtParticipation: Object.freeze({
|
|
37
|
+
reflections: "full",
|
|
38
|
+
globalIllumination: "high",
|
|
39
|
+
directShadows: "premium",
|
|
40
|
+
}),
|
|
41
|
+
updateCadenceDivisor: 1,
|
|
42
|
+
}),
|
|
43
|
+
Object.freeze({
|
|
44
|
+
band: "mid",
|
|
45
|
+
primaryShadowSource: "ray-traced-secondary",
|
|
46
|
+
rtParticipation: Object.freeze({
|
|
47
|
+
reflections: "medium",
|
|
48
|
+
globalIllumination: "medium",
|
|
49
|
+
directShadows: "selective",
|
|
50
|
+
}),
|
|
51
|
+
updateCadenceDivisor: 2,
|
|
52
|
+
}),
|
|
53
|
+
Object.freeze({
|
|
54
|
+
band: "far",
|
|
55
|
+
primaryShadowSource: "baked",
|
|
56
|
+
rtParticipation: Object.freeze({
|
|
57
|
+
reflections: "low",
|
|
58
|
+
globalIllumination: "low",
|
|
59
|
+
directShadows: "none",
|
|
60
|
+
}),
|
|
61
|
+
updateCadenceDivisor: 4,
|
|
62
|
+
}),
|
|
63
|
+
Object.freeze({
|
|
64
|
+
band: "horizon",
|
|
65
|
+
primaryShadowSource: "impression",
|
|
66
|
+
rtParticipation: Object.freeze({
|
|
67
|
+
reflections: "off",
|
|
68
|
+
globalIllumination: "off",
|
|
69
|
+
directShadows: "none",
|
|
70
|
+
}),
|
|
71
|
+
updateCadenceDivisor: 8,
|
|
72
|
+
}),
|
|
73
|
+
]);
|
|
74
|
+
const FALLBACK_LIGHTING_PROFILE = "cinematic";
|
|
75
|
+
const FALLBACK_PHYSICS_PROFILE = "cinematic";
|
|
76
|
+
const FALLBACK_PERFORMANCE_LEVELS = Object.freeze({
|
|
77
|
+
fluid: Object.freeze([
|
|
78
|
+
Object.freeze({
|
|
79
|
+
id: "low",
|
|
80
|
+
config: Object.freeze({ nearResolution: 10, midResolution: 6, splashCount: 10 }),
|
|
81
|
+
estimatedCostMs: 0.8,
|
|
82
|
+
}),
|
|
83
|
+
Object.freeze({
|
|
84
|
+
id: "medium",
|
|
85
|
+
config: Object.freeze({ nearResolution: 16, midResolution: 8, splashCount: 18 }),
|
|
86
|
+
estimatedCostMs: 1.4,
|
|
87
|
+
}),
|
|
88
|
+
Object.freeze({
|
|
89
|
+
id: "high",
|
|
90
|
+
config: Object.freeze({ nearResolution: 24, midResolution: 12, splashCount: 28 }),
|
|
91
|
+
estimatedCostMs: 2.4,
|
|
92
|
+
}),
|
|
93
|
+
]),
|
|
94
|
+
cloth: Object.freeze([
|
|
95
|
+
Object.freeze({
|
|
96
|
+
id: "low",
|
|
97
|
+
config: Object.freeze({ cols: 10, rows: 7 }),
|
|
98
|
+
estimatedCostMs: 0.7,
|
|
99
|
+
}),
|
|
100
|
+
Object.freeze({
|
|
101
|
+
id: "medium",
|
|
102
|
+
config: Object.freeze({ cols: 16, rows: 11 }),
|
|
103
|
+
estimatedCostMs: 1.3,
|
|
104
|
+
}),
|
|
105
|
+
Object.freeze({
|
|
106
|
+
id: "high",
|
|
107
|
+
config: Object.freeze({ cols: 24, rows: 16 }),
|
|
108
|
+
estimatedCostMs: 2.1,
|
|
109
|
+
}),
|
|
110
|
+
]),
|
|
111
|
+
lighting: Object.freeze([
|
|
112
|
+
Object.freeze({
|
|
113
|
+
id: "low",
|
|
114
|
+
config: Object.freeze({ shadowStrength: 0.18, reflectionStrength: 0.08 }),
|
|
115
|
+
estimatedCostMs: 0.5,
|
|
116
|
+
}),
|
|
117
|
+
Object.freeze({
|
|
118
|
+
id: "medium",
|
|
119
|
+
config: Object.freeze({ shadowStrength: 0.34, reflectionStrength: 0.16 }),
|
|
120
|
+
estimatedCostMs: 1.0,
|
|
121
|
+
}),
|
|
122
|
+
Object.freeze({
|
|
123
|
+
id: "high",
|
|
124
|
+
config: Object.freeze({ shadowStrength: 0.5, reflectionStrength: 0.24 }),
|
|
125
|
+
estimatedCostMs: 1.8,
|
|
126
|
+
}),
|
|
127
|
+
]),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
function createFallbackClothFeatureModule() {
|
|
131
|
+
const fallback = createFallbackClothFeatureAdapters();
|
|
132
|
+
return {
|
|
133
|
+
clothGarmentKinds: fallback.garmentKinds,
|
|
134
|
+
clothProfileNames: fallback.profileNames,
|
|
135
|
+
createClothRepresentationPlan: fallback.createPlan,
|
|
136
|
+
selectClothRepresentationBand: fallback.selectBand,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createFallbackFluidFeatureModule() {
|
|
141
|
+
const fallback = createFallbackFluidFeatureAdapters();
|
|
142
|
+
return {
|
|
143
|
+
fluidBodyKinds: fallback.bodyKinds,
|
|
144
|
+
fluidProfileNames: fallback.profileNames,
|
|
145
|
+
createFluidContinuityEnvelope: fallback.createContinuityEnvelope,
|
|
146
|
+
createFluidRepresentationPlan: fallback.createPlan,
|
|
147
|
+
selectFluidRepresentationBand: fallback.selectBand,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function createFallbackLightingFeatureModule() {
|
|
152
|
+
return {
|
|
153
|
+
createLightingBandPlan({ profile = FALLBACK_LIGHTING_PROFILE, importance = "high" } = {}) {
|
|
154
|
+
const defaultPlan = {
|
|
155
|
+
profile,
|
|
156
|
+
importance,
|
|
157
|
+
bands: FALLBACK_LIGHTING_DISTANCE_BANDS,
|
|
158
|
+
};
|
|
159
|
+
return defaultPlan;
|
|
160
|
+
},
|
|
161
|
+
defaultLightingProfile: FALLBACK_LIGHTING_PROFILE,
|
|
162
|
+
getLightingProfile(profile = FALLBACK_LIGHTING_PROFILE) {
|
|
163
|
+
return {
|
|
164
|
+
name: profile,
|
|
165
|
+
bands: FALLBACK_LIGHTING_DISTANCE_BANDS,
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
lightingDistanceBands: FALLBACK_LIGHTING_DISTANCE_BANDS,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function createFallbackPerformanceQualityState(levels = [], initialLevel = "high", profile = "") {
|
|
173
|
+
const fallbackLevels = levels.length ? levels : FALLBACK_PERFORMANCE_LEVELS[profile] ?? [];
|
|
174
|
+
const resolvedLevels = fallbackLevels.map((entry) => ({
|
|
175
|
+
id: String(entry?.id ?? "high"),
|
|
176
|
+
config: entry?.config ?? {},
|
|
177
|
+
estimatedCostMs: Number.isFinite(Number(entry?.estimatedCostMs))
|
|
178
|
+
? Number(entry.estimatedCostMs)
|
|
179
|
+
: 1.0,
|
|
180
|
+
}));
|
|
181
|
+
if (resolvedLevels.length === 0) {
|
|
182
|
+
return {
|
|
183
|
+
module: {
|
|
184
|
+
id: "high",
|
|
185
|
+
config: Object.freeze({}),
|
|
186
|
+
estimatedCostMs: 1.0,
|
|
187
|
+
},
|
|
188
|
+
getSnapshot() {
|
|
189
|
+
return {
|
|
190
|
+
currentLevel: {
|
|
191
|
+
id: "high",
|
|
192
|
+
config: Object.freeze({}),
|
|
193
|
+
estimatedCostMs: 1.0,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
id: "high",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const initial = resolvedLevels.find((entry) => entry.id === initialLevel) ?? resolvedLevels[0];
|
|
201
|
+
return {
|
|
202
|
+
id: initial.id,
|
|
203
|
+
getSnapshot() {
|
|
204
|
+
return {
|
|
205
|
+
currentLevel: {
|
|
206
|
+
id: initial.id,
|
|
207
|
+
config: initial.config,
|
|
208
|
+
estimatedCostMs: initial.estimatedCostMs,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function createFallbackPerformanceFeatureModule() {
|
|
216
|
+
const moduleIds = new Set(["fluid-detail", "cloth-detail", "lighting-detail"]);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
createDeviceProfile(profile = {}) {
|
|
220
|
+
return {
|
|
221
|
+
deviceClass: "desktop",
|
|
222
|
+
mode: "flat",
|
|
223
|
+
refreshRateHz: Number.isFinite(profile?.refreshRateHz)
|
|
224
|
+
? Number(profile.refreshRateHz)
|
|
225
|
+
: 60,
|
|
226
|
+
supportedFrameRates: Array.isArray(profile?.supportedFrameRates)
|
|
227
|
+
? profile.supportedFrameRates
|
|
228
|
+
: [60, 90],
|
|
229
|
+
supportsWebGpu: true,
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
createQualityLadderAdapter({ id, domain, levels, initialLevel }) {
|
|
233
|
+
return {
|
|
234
|
+
id: String(id ?? ""),
|
|
235
|
+
domain,
|
|
236
|
+
...createFallbackPerformanceQualityState(
|
|
237
|
+
domain === "fluid" ? FALLBACK_PERFORMANCE_LEVELS.fluid : domain === "cloth" ? FALLBACK_PERFORMANCE_LEVELS.cloth : FALLBACK_PERFORMANCE_LEVELS.lighting,
|
|
238
|
+
initialLevel,
|
|
239
|
+
domain
|
|
240
|
+
),
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
createGpuPerformanceGovernor({ device, modules = [], adaptation = {} } = {}) {
|
|
244
|
+
let pressureLevel = "stable";
|
|
245
|
+
let frameSamples = 0;
|
|
246
|
+
let averageMs = 16.67;
|
|
247
|
+
|
|
248
|
+
const clamp = (next = 16.67) => (Number.isFinite(next) ? Math.max(1, next) : 16.67);
|
|
249
|
+
const target = Object.freeze({
|
|
250
|
+
targetFrameTimeMs: 16.67,
|
|
251
|
+
downgradeFrameTimeMs: clamp(adaptation?.degradeThresholdMs ?? 20),
|
|
252
|
+
upgradeFrameTimeMs: clamp(adaptation?.upgradeThresholdMs ?? 14),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
recordFrame({ frameTimeMs = averageMs } = {}) {
|
|
257
|
+
const sample = Number.isFinite(Number(frameTimeMs)) ? Number(frameTimeMs) : averageMs;
|
|
258
|
+
frameSamples += 1;
|
|
259
|
+
averageMs = clamp((averageMs * (frameSamples - 1) + sample) / frameSamples);
|
|
260
|
+
const fps = 1000 / averageMs;
|
|
261
|
+
|
|
262
|
+
pressureLevel =
|
|
263
|
+
sample > target.downgradeFrameTimeMs
|
|
264
|
+
? "degrade"
|
|
265
|
+
: pressureLevel === "degrade" && sample <= target.upgradeFrameTimeMs
|
|
266
|
+
? "stable"
|
|
267
|
+
: pressureLevel;
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
pressureLevel,
|
|
271
|
+
metrics: {
|
|
272
|
+
fps,
|
|
273
|
+
averageFrameTimeMs: averageMs,
|
|
274
|
+
},
|
|
275
|
+
adjustments: pressureLevel === "degrade" ? [{ type: "capability-step-down" }] : [],
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
getTarget() {
|
|
279
|
+
return target;
|
|
280
|
+
},
|
|
281
|
+
getState() {
|
|
282
|
+
return {
|
|
283
|
+
modules: modules
|
|
284
|
+
.filter((entry) => entry != null && typeof entry.id === "string" && moduleIds.has(entry.id))
|
|
285
|
+
.map((entry) => ({
|
|
286
|
+
isAtMaximum: pressureLevel === "stable",
|
|
287
|
+
})),
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function createFallbackDebugFeatureModule() {
|
|
296
|
+
let queueSamples = 0;
|
|
297
|
+
let queuePeakDepth = 0;
|
|
298
|
+
let readyLaneSamples = 0;
|
|
299
|
+
let readyLanePeakDepth = 0;
|
|
300
|
+
let dispatchSamples = 0;
|
|
301
|
+
let dispatchDurationTotal = 0;
|
|
302
|
+
let dependencyUnlockSamples = 0;
|
|
303
|
+
let dependencyUnlockCount = 0;
|
|
304
|
+
let pipelineSamples = 0;
|
|
305
|
+
let pipelineAgeTotal = 0;
|
|
306
|
+
let frameSamples = 0;
|
|
307
|
+
let frameTimeTotal = 0;
|
|
308
|
+
let gpuBusyTotal = 0;
|
|
309
|
+
let frameDroppedSamples = 0;
|
|
310
|
+
let memoryTotalBytes = 0;
|
|
311
|
+
|
|
312
|
+
function ensureNumber(value, fallback = 0) {
|
|
313
|
+
const asNumber = Number(value);
|
|
314
|
+
return Number.isFinite(asNumber) ? asNumber : fallback;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function createDebugSession({ adapter } = {}) {
|
|
318
|
+
const memoryHint = Number.isFinite(Number(adapter?.memoryCapacityHintBytes))
|
|
319
|
+
? Number(adapter.memoryCapacityHintBytes)
|
|
320
|
+
: 0;
|
|
321
|
+
|
|
322
|
+
memoryTotalBytes = Math.max(0, memoryHint);
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
trackAllocation({ sizeBytes = 0 } = {}) {
|
|
326
|
+
memoryTotalBytes += ensureNumber(sizeBytes);
|
|
327
|
+
},
|
|
328
|
+
recordQueue({ depth = 0 } = {}) {
|
|
329
|
+
queueSamples += 1;
|
|
330
|
+
queuePeakDepth = Math.max(queuePeakDepth, ensureNumber(depth, 0));
|
|
331
|
+
},
|
|
332
|
+
recordReadyLane({ depth = 0 } = {}) {
|
|
333
|
+
readyLaneSamples += 1;
|
|
334
|
+
readyLanePeakDepth = Math.max(readyLanePeakDepth, ensureNumber(depth, 0));
|
|
335
|
+
},
|
|
336
|
+
recordDispatch({ durationMs = 0 } = {}) {
|
|
337
|
+
dispatchSamples += 1;
|
|
338
|
+
dispatchDurationTotal += ensureNumber(durationMs);
|
|
339
|
+
},
|
|
340
|
+
recordDependencyUnlock({ unlockCount = 0 } = {}) {
|
|
341
|
+
dependencyUnlockSamples += 1;
|
|
342
|
+
dependencyUnlockCount += ensureNumber(unlockCount);
|
|
343
|
+
},
|
|
344
|
+
recordPipelinePhase({ snapshotAgeMs = 0 } = {}) {
|
|
345
|
+
pipelineSamples += 1;
|
|
346
|
+
pipelineAgeTotal += ensureNumber(snapshotAgeMs);
|
|
347
|
+
},
|
|
348
|
+
recordFrame({
|
|
349
|
+
frameTimeMs = 16.67,
|
|
350
|
+
targetFrameTimeMs = 16.67,
|
|
351
|
+
gpuBusyMs = 0,
|
|
352
|
+
dropped = false,
|
|
353
|
+
} = {}) {
|
|
354
|
+
frameSamples += 1;
|
|
355
|
+
frameTimeTotal += ensureNumber(frameTimeMs);
|
|
356
|
+
gpuBusyTotal += ensureNumber(gpuBusyMs);
|
|
357
|
+
frameDroppedSamples += dropped === true ? 1 : 0;
|
|
358
|
+
targetFrameTimeMs;
|
|
359
|
+
},
|
|
360
|
+
getSnapshot() {
|
|
361
|
+
const queueAverageDepth = queueSamples > 0 ? queuePeakDepth / queueSamples : 0;
|
|
362
|
+
return {
|
|
363
|
+
frames: {
|
|
364
|
+
sampleCount: frameSamples,
|
|
365
|
+
averageFrameTimeMs: frameSamples > 0 ? frameTimeTotal / frameSamples : 0,
|
|
366
|
+
averageGpuBusyMs: frameSamples > 0 ? gpuBusyTotal / frameSamples : 0,
|
|
367
|
+
droppedCount: frameDroppedSamples,
|
|
368
|
+
},
|
|
369
|
+
queues: {
|
|
370
|
+
sampleCount: queueSamples,
|
|
371
|
+
peakDepth: queuePeakDepth,
|
|
372
|
+
averageDepth: queueAverageDepth,
|
|
373
|
+
},
|
|
374
|
+
dispatch: {
|
|
375
|
+
sampleCount: dispatchSamples,
|
|
376
|
+
averageDurationMs: dispatchSamples > 0 ? dispatchDurationTotal / dispatchSamples : 0,
|
|
377
|
+
},
|
|
378
|
+
dag: {
|
|
379
|
+
peakReadyLaneDepth: readyLanePeakDepth,
|
|
380
|
+
totalUnlockCount: dependencyUnlockCount,
|
|
381
|
+
unlockSamples: dependencyUnlockSamples,
|
|
382
|
+
},
|
|
383
|
+
pipeline: {
|
|
384
|
+
sampleCount: pipelineSamples,
|
|
385
|
+
averageSnapshotAgeMs: pipelineSamples > 0 ? pipelineAgeTotal / pipelineSamples : 0,
|
|
386
|
+
},
|
|
387
|
+
memory: {
|
|
388
|
+
totalTrackedBytes: memoryTotalBytes,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
createGpuDebugSession({ adapter } = {}) {
|
|
397
|
+
return createDebugSession({ adapter });
|
|
398
|
+
},
|
|
399
|
+
createSession({ adapter } = {}) {
|
|
400
|
+
return createDebugSession({ adapter });
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function createFallbackPhysicsFeatureModule() {
|
|
406
|
+
const fallbackPlan = Object.freeze({
|
|
407
|
+
snapshotStageId: "baseline",
|
|
408
|
+
stageOrder: Object.freeze(["authoritative"]),
|
|
409
|
+
secondarySimulationStageIds: Object.freeze(["visual"]),
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
createPhysicsSimulationPlan() {
|
|
413
|
+
return {
|
|
414
|
+
snapshotStageId: "baseline",
|
|
415
|
+
stageOrder: fallbackPlan.stageOrder,
|
|
416
|
+
secondarySimulationStageIds: fallbackPlan.secondarySimulationStageIds,
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
createPhysicsWorldSnapshot(input = {}) {
|
|
420
|
+
return {
|
|
421
|
+
stage: "baseline",
|
|
422
|
+
stability: "stable",
|
|
423
|
+
stageId: fallbackPlan.snapshotStageId,
|
|
424
|
+
metadata: input.metadata ?? {},
|
|
425
|
+
bodyCount: input.bodyCount ?? 0,
|
|
426
|
+
dynamicBodyCount: input.dynamicBodyCount ?? 0,
|
|
427
|
+
contactCount: input.contactCount ?? 0,
|
|
428
|
+
snapshotStageId: fallbackPlan.snapshotStageId,
|
|
429
|
+
};
|
|
430
|
+
},
|
|
431
|
+
defaultPhysicsWorkerProfile: FALLBACK_PHYSICS_PROFILE,
|
|
432
|
+
getPhysicsWorkerManifest() {
|
|
433
|
+
return {
|
|
434
|
+
jobs: [
|
|
435
|
+
Object.freeze({
|
|
436
|
+
worker: Object.freeze({ authority: "authoritative", jobType: "simulate" }),
|
|
437
|
+
}),
|
|
438
|
+
],
|
|
439
|
+
suggestedAllocationIds: Object.freeze(["physics-workspace"]),
|
|
440
|
+
};
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const SHOWCASE_FEATURE_LOADERS = Object.freeze({
|
|
446
|
+
cloth: () => Promise.resolve(createFallbackClothFeatureModule()),
|
|
447
|
+
fluid: () => Promise.resolve(createFallbackFluidFeatureModule()),
|
|
448
|
+
lighting: () => Promise.resolve(createFallbackLightingFeatureModule()),
|
|
449
|
+
performance: () => Promise.resolve(createFallbackPerformanceFeatureModule()),
|
|
450
|
+
debug: () => Promise.resolve(createFallbackDebugFeatureModule()),
|
|
451
|
+
physics: () => Promise.resolve(createFallbackPhysicsFeatureModule()),
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const DEFAULT_FLUID_BAND_THRESHOLDS = Object.freeze({
|
|
455
|
+
near: Object.freeze({ minDistance: 0, maxDistance: 22 }),
|
|
456
|
+
mid: Object.freeze({ minDistance: 22, maxDistance: 90 }),
|
|
457
|
+
far: Object.freeze({ minDistance: 90, maxDistance: 260 }),
|
|
458
|
+
horizon: Object.freeze({ minDistance: 260, maxDistance: Number.POSITIVE_INFINITY }),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const DEFAULT_CLOTH_BAND_THRESHOLDS = Object.freeze({
|
|
462
|
+
near: Object.freeze({ minDistance: 0, maxDistance: 24 }),
|
|
463
|
+
mid: Object.freeze({ minDistance: 24, maxDistance: 86 }),
|
|
464
|
+
far: Object.freeze({ minDistance: 86, maxDistance: 190 }),
|
|
465
|
+
horizon: Object.freeze({ minDistance: 190, maxDistance: Number.POSITIVE_INFINITY }),
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
function resolveFluidBandSelection(distance, thresholds = DEFAULT_FLUID_BAND_THRESHOLDS) {
|
|
469
|
+
if (!Number.isFinite(distance)) {
|
|
470
|
+
return "horizon";
|
|
471
|
+
}
|
|
472
|
+
if (distance <= (thresholds.near?.maxDistance ?? DEFAULT_FLUID_BAND_THRESHOLDS.near.maxDistance)) {
|
|
473
|
+
return "near";
|
|
474
|
+
}
|
|
475
|
+
if (distance <= (thresholds.mid?.maxDistance ?? DEFAULT_FLUID_BAND_THRESHOLDS.mid.maxDistance)) {
|
|
476
|
+
return "mid";
|
|
477
|
+
}
|
|
478
|
+
if (distance <= (thresholds.far?.maxDistance ?? DEFAULT_FLUID_BAND_THRESHOLDS.far.maxDistance)) {
|
|
479
|
+
return "far";
|
|
480
|
+
}
|
|
481
|
+
return "horizon";
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function createFallbackFluidFeatureAdapters() {
|
|
485
|
+
const defaultContinuityBand = Object.freeze({
|
|
486
|
+
amplitudeFloor: 0.22,
|
|
487
|
+
frequencyFloor: 0.05,
|
|
488
|
+
blendWindowMeters: 14,
|
|
489
|
+
retainFoamHistory: true,
|
|
490
|
+
retainDirectionality: true,
|
|
491
|
+
});
|
|
492
|
+
return {
|
|
493
|
+
bodyKinds: Object.freeze(["ocean"]),
|
|
494
|
+
profileNames: Object.freeze(["cinematic"]),
|
|
495
|
+
createContinuityEnvelope() {
|
|
496
|
+
return Object.freeze({
|
|
497
|
+
bands: Object.freeze({
|
|
498
|
+
near: defaultContinuityBand,
|
|
499
|
+
mid: Object.freeze({
|
|
500
|
+
...defaultContinuityBand,
|
|
501
|
+
blendWindowMeters: 22,
|
|
502
|
+
amplitudeFloor: 0.17,
|
|
503
|
+
}),
|
|
504
|
+
far: Object.freeze({
|
|
505
|
+
...defaultContinuityBand,
|
|
506
|
+
blendWindowMeters: 34,
|
|
507
|
+
amplitudeFloor: 0.12,
|
|
508
|
+
}),
|
|
509
|
+
horizon: Object.freeze({
|
|
510
|
+
...defaultContinuityBand,
|
|
511
|
+
blendWindowMeters: 42,
|
|
512
|
+
amplitudeFloor: 0.09,
|
|
513
|
+
}),
|
|
514
|
+
}),
|
|
515
|
+
});
|
|
516
|
+
},
|
|
517
|
+
createPlan({ fluidBodyId = "harbor", kind = "ocean", profile = "cinematic" }) {
|
|
518
|
+
return Object.freeze({
|
|
519
|
+
fluidBodyId,
|
|
520
|
+
kind,
|
|
521
|
+
profile,
|
|
522
|
+
thresholds: DEFAULT_FLUID_BAND_THRESHOLDS,
|
|
523
|
+
representations: Object.freeze([
|
|
524
|
+
Object.freeze({
|
|
525
|
+
band: "near",
|
|
526
|
+
output: "raster",
|
|
527
|
+
rtParticipation: "full",
|
|
528
|
+
shading: Object.freeze({ refraction: 0.14, reflectionMode: "full", caustics: true }),
|
|
529
|
+
}),
|
|
530
|
+
Object.freeze({
|
|
531
|
+
band: "mid",
|
|
532
|
+
output: "raster",
|
|
533
|
+
rtParticipation: "reduced",
|
|
534
|
+
shading: Object.freeze({ reflectionMode: "partial" }),
|
|
535
|
+
}),
|
|
536
|
+
Object.freeze({
|
|
537
|
+
band: "far",
|
|
538
|
+
output: "raster",
|
|
539
|
+
rtParticipation: "low",
|
|
540
|
+
shading: Object.freeze({ reflectionMode: "partial" }),
|
|
541
|
+
}),
|
|
542
|
+
Object.freeze({
|
|
543
|
+
band: "horizon",
|
|
544
|
+
output: "coarse",
|
|
545
|
+
rtParticipation: "off",
|
|
546
|
+
shading: Object.freeze({ reflectionMode: "reduced" }),
|
|
547
|
+
}),
|
|
548
|
+
]),
|
|
549
|
+
});
|
|
550
|
+
},
|
|
551
|
+
selectBand(distance, thresholds = DEFAULT_FLUID_BAND_THRESHOLDS) {
|
|
552
|
+
return resolveFluidBandSelection(distance, thresholds);
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function createFallbackClothFeatureAdapters() {
|
|
558
|
+
const defaultContinuity = Object.freeze({
|
|
559
|
+
amplitudeFloor: 0.22,
|
|
560
|
+
wrinkleFloor: 0.32,
|
|
561
|
+
damping: 0.58,
|
|
562
|
+
creaseBias: 0.14,
|
|
563
|
+
});
|
|
564
|
+
return {
|
|
565
|
+
garmentKinds: Object.freeze(["flag"]),
|
|
566
|
+
profileNames: Object.freeze(["cinematic"]),
|
|
567
|
+
createPlan() {
|
|
568
|
+
return Object.freeze({
|
|
569
|
+
thresholds: DEFAULT_CLOTH_BAND_THRESHOLDS,
|
|
570
|
+
representations: Object.freeze([
|
|
571
|
+
Object.freeze({
|
|
572
|
+
band: "near",
|
|
573
|
+
continuity: defaultContinuity,
|
|
574
|
+
}),
|
|
575
|
+
Object.freeze({
|
|
576
|
+
band: "mid",
|
|
577
|
+
continuity: defaultContinuity,
|
|
578
|
+
}),
|
|
579
|
+
Object.freeze({
|
|
580
|
+
band: "far",
|
|
581
|
+
continuity: defaultContinuity,
|
|
582
|
+
}),
|
|
583
|
+
Object.freeze({
|
|
584
|
+
band: "horizon",
|
|
585
|
+
continuity: defaultContinuity,
|
|
586
|
+
}),
|
|
587
|
+
]),
|
|
588
|
+
});
|
|
589
|
+
},
|
|
590
|
+
selectBand(distance, thresholds = DEFAULT_CLOTH_BAND_THRESHOLDS) {
|
|
591
|
+
if (!Number.isFinite(distance)) {
|
|
592
|
+
return "horizon";
|
|
593
|
+
}
|
|
594
|
+
if (distance <= (thresholds.near?.maxDistance ?? DEFAULT_CLOTH_BAND_THRESHOLDS.near.maxDistance)) {
|
|
595
|
+
return "near";
|
|
596
|
+
}
|
|
597
|
+
if (distance <= (thresholds.mid?.maxDistance ?? DEFAULT_CLOTH_BAND_THRESHOLDS.mid.maxDistance)) {
|
|
598
|
+
return "mid";
|
|
599
|
+
}
|
|
600
|
+
if (distance <= (thresholds.far?.maxDistance ?? DEFAULT_CLOTH_BAND_THRESHOLDS.far.maxDistance)) {
|
|
601
|
+
return "far";
|
|
602
|
+
}
|
|
603
|
+
return "horizon";
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function normalizeClothFeatureAdapters(clothFeatures) {
|
|
609
|
+
const fallback = createFallbackClothFeatureAdapters();
|
|
610
|
+
if (clothFeatures == null || typeof clothFeatures !== "object") {
|
|
611
|
+
return fallback;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
garmentKinds:
|
|
616
|
+
Array.isArray(clothFeatures.garmentKinds) && clothFeatures.garmentKinds.length > 0
|
|
617
|
+
? clothFeatures.garmentKinds
|
|
618
|
+
: fallback.garmentKinds,
|
|
619
|
+
profileNames:
|
|
620
|
+
Array.isArray(clothFeatures.profileNames) && clothFeatures.profileNames.length > 0
|
|
621
|
+
? clothFeatures.profileNames
|
|
622
|
+
: fallback.profileNames,
|
|
623
|
+
createPlan: assertRequiredFunction(clothFeatures, "cloth", "createPlan"),
|
|
624
|
+
selectBand: assertRequiredFunction(clothFeatures, "cloth", "selectBand"),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function normalizeFluidFeatureAdapters(fluidFeatures) {
|
|
629
|
+
const fallback = createFallbackFluidFeatureAdapters();
|
|
630
|
+
if (fluidFeatures == null || typeof fluidFeatures !== "object") {
|
|
631
|
+
return fallback;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
bodyKinds:
|
|
636
|
+
Array.isArray(fluidFeatures.bodyKinds) && fluidFeatures.bodyKinds.length > 0
|
|
637
|
+
? fluidFeatures.bodyKinds
|
|
638
|
+
: fallback.bodyKinds,
|
|
639
|
+
profileNames:
|
|
640
|
+
Array.isArray(fluidFeatures.profileNames) && fluidFeatures.profileNames.length > 0
|
|
641
|
+
? fluidFeatures.profileNames
|
|
642
|
+
: fallback.profileNames,
|
|
643
|
+
createContinuityEnvelope: assertRequiredFunction(
|
|
644
|
+
fluidFeatures,
|
|
645
|
+
"fluid",
|
|
646
|
+
"createContinuityEnvelope"
|
|
647
|
+
),
|
|
648
|
+
createPlan: assertRequiredFunction(fluidFeatures, "fluid", "createPlan"),
|
|
649
|
+
selectBand: assertRequiredFunction(fluidFeatures, "fluid", "selectBand"),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function assertRequiredFunction(module, featureLabel, exportName) {
|
|
654
|
+
const value = module?.[exportName];
|
|
655
|
+
if (typeof value !== "function") {
|
|
656
|
+
throw new Error(
|
|
657
|
+
`Showcase ${featureLabel} feature package must export "${exportName}" as a function.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
return value;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function assertRequiredArray(module, featureLabel, exportName) {
|
|
664
|
+
const value = module?.[exportName];
|
|
665
|
+
if (!Array.isArray(value)) {
|
|
666
|
+
throw new Error(
|
|
667
|
+
`Showcase ${featureLabel} feature package must export "${exportName}" as an array.`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
return value;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function loadShowcaseFeatureModule(featureLabel, loader) {
|
|
674
|
+
try {
|
|
675
|
+
const module = await loader();
|
|
676
|
+
if (module == null || typeof module !== "object") {
|
|
677
|
+
throw new Error("module is missing or not an object.");
|
|
678
|
+
}
|
|
679
|
+
return module;
|
|
680
|
+
} catch (error) {
|
|
681
|
+
const message = error?.message ?? String(error);
|
|
682
|
+
throw new Error(`Unable to load showcase ${featureLabel} feature package: ${message}`, {
|
|
683
|
+
cause: error,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function resolveShowcaseFeatureLoaders(options = {}) {
|
|
689
|
+
const overrides = options.__showcaseFeatureLoaders;
|
|
690
|
+
return {
|
|
691
|
+
cloth:
|
|
692
|
+
typeof overrides?.cloth === "function"
|
|
693
|
+
? overrides.cloth
|
|
694
|
+
: SHOWCASE_FEATURE_LOADERS.cloth,
|
|
695
|
+
fluid:
|
|
696
|
+
typeof overrides?.fluid === "function"
|
|
697
|
+
? overrides.fluid
|
|
698
|
+
: SHOWCASE_FEATURE_LOADERS.fluid,
|
|
699
|
+
lighting:
|
|
700
|
+
typeof overrides?.lighting === "function"
|
|
701
|
+
? overrides.lighting
|
|
702
|
+
: SHOWCASE_FEATURE_LOADERS.lighting,
|
|
703
|
+
performance:
|
|
704
|
+
typeof overrides?.performance === "function"
|
|
705
|
+
? overrides.performance
|
|
706
|
+
: SHOWCASE_FEATURE_LOADERS.performance,
|
|
707
|
+
debug:
|
|
708
|
+
typeof overrides?.debug === "function"
|
|
709
|
+
? overrides.debug
|
|
710
|
+
: SHOWCASE_FEATURE_LOADERS.debug,
|
|
711
|
+
physics:
|
|
712
|
+
typeof overrides?.physics === "function"
|
|
713
|
+
? overrides.physics
|
|
714
|
+
: SHOWCASE_FEATURE_LOADERS.physics,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async function resolveShowcaseFeatureAdapters(options = {}) {
|
|
719
|
+
const loaders = resolveShowcaseFeatureLoaders(options);
|
|
720
|
+
const [
|
|
721
|
+
clothModule,
|
|
722
|
+
fluidModule,
|
|
723
|
+
lightingModule,
|
|
724
|
+
performanceModule,
|
|
725
|
+
debugModule,
|
|
726
|
+
physicsModule,
|
|
727
|
+
] = await Promise.all([
|
|
728
|
+
loadShowcaseFeatureModule("cloth", loaders.cloth),
|
|
729
|
+
loadShowcaseFeatureModule("fluid", loaders.fluid),
|
|
730
|
+
loadShowcaseFeatureModule("lighting", loaders.lighting),
|
|
731
|
+
loadShowcaseFeatureModule("performance", loaders.performance),
|
|
732
|
+
loadShowcaseFeatureModule("debug", loaders.debug),
|
|
733
|
+
loadShowcaseFeatureModule("physics", loaders.physics),
|
|
734
|
+
]);
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
cloth: {
|
|
738
|
+
garmentKinds: assertRequiredArray(clothModule, "cloth", "clothGarmentKinds"),
|
|
739
|
+
profileNames: assertRequiredArray(clothModule, "cloth", "clothProfileNames"),
|
|
740
|
+
createPlan: assertRequiredFunction(clothModule, "cloth", "createClothRepresentationPlan"),
|
|
741
|
+
selectBand: assertRequiredFunction(clothModule, "cloth", "selectClothRepresentationBand"),
|
|
742
|
+
},
|
|
743
|
+
fluid: {
|
|
744
|
+
bodyKinds: assertRequiredArray(fluidModule, "fluid", "fluidBodyKinds"),
|
|
745
|
+
profileNames: assertRequiredArray(fluidModule, "fluid", "fluidProfileNames"),
|
|
746
|
+
createContinuityEnvelope: assertRequiredFunction(
|
|
747
|
+
fluidModule,
|
|
748
|
+
"fluid",
|
|
749
|
+
"createFluidContinuityEnvelope"
|
|
750
|
+
),
|
|
751
|
+
createPlan: assertRequiredFunction(fluidModule, "fluid", "createFluidRepresentationPlan"),
|
|
752
|
+
selectBand: assertRequiredFunction(fluidModule, "fluid", "selectFluidRepresentationBand"),
|
|
753
|
+
},
|
|
754
|
+
lighting: {
|
|
755
|
+
createBandPlan: assertRequiredFunction(lightingModule, "lighting", "createLightingBandPlan"),
|
|
756
|
+
defaultProfile: lightingModule.defaultLightingProfile,
|
|
757
|
+
getProfile: assertRequiredFunction(lightingModule, "lighting", "getLightingProfile"),
|
|
758
|
+
distanceBands: assertRequiredArray(lightingModule, "lighting", "lightingDistanceBands"),
|
|
759
|
+
},
|
|
760
|
+
performance: {
|
|
761
|
+
createDeviceProfile: assertRequiredFunction(
|
|
762
|
+
performanceModule,
|
|
763
|
+
"performance",
|
|
764
|
+
"createDeviceProfile"
|
|
765
|
+
),
|
|
766
|
+
createGovernor: assertRequiredFunction(
|
|
767
|
+
performanceModule,
|
|
768
|
+
"performance",
|
|
769
|
+
"createGpuPerformanceGovernor"
|
|
770
|
+
),
|
|
771
|
+
createQualityAdapter: assertRequiredFunction(
|
|
772
|
+
performanceModule,
|
|
773
|
+
"performance",
|
|
774
|
+
"createQualityLadderAdapter"
|
|
775
|
+
),
|
|
776
|
+
},
|
|
777
|
+
debug: {
|
|
778
|
+
createSession: assertRequiredFunction(debugModule, "debug", "createGpuDebugSession"),
|
|
779
|
+
},
|
|
780
|
+
physics: {
|
|
781
|
+
createSimulationPlan: assertRequiredFunction(
|
|
782
|
+
physicsModule,
|
|
783
|
+
"physics",
|
|
784
|
+
"createPhysicsSimulationPlan"
|
|
785
|
+
),
|
|
786
|
+
createWorldSnapshot: assertRequiredFunction(
|
|
787
|
+
physicsModule,
|
|
788
|
+
"physics",
|
|
789
|
+
"createPhysicsWorldSnapshot"
|
|
790
|
+
),
|
|
791
|
+
defaultProfile: physicsModule.defaultPhysicsWorkerProfile,
|
|
792
|
+
getManifest: assertRequiredFunction(physicsModule, "physics", "getPhysicsWorkerManifest"),
|
|
793
|
+
},
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
63
797
|
export const showcaseFocusModes = Object.freeze(Object.keys(CAMERA_PRESETS));
|
|
64
798
|
|
|
65
799
|
const FOCUS_MODE_TRANSLATION_KEYS = Object.freeze({
|
|
@@ -840,7 +1574,23 @@ function resolveShipModel(state, ship, fallbackModel = null) {
|
|
|
840
1574
|
);
|
|
841
1575
|
}
|
|
842
1576
|
|
|
843
|
-
function createPerformanceGovernor() {
|
|
1577
|
+
function createPerformanceGovernor(performanceFeatures) {
|
|
1578
|
+
const createQualityLadderAdapter = assertRequiredFunction(
|
|
1579
|
+
performanceFeatures,
|
|
1580
|
+
"performance",
|
|
1581
|
+
"createQualityAdapter"
|
|
1582
|
+
);
|
|
1583
|
+
const createDeviceProfile = assertRequiredFunction(
|
|
1584
|
+
performanceFeatures,
|
|
1585
|
+
"performance",
|
|
1586
|
+
"createDeviceProfile"
|
|
1587
|
+
);
|
|
1588
|
+
const createGpuPerformanceGovernor = assertRequiredFunction(
|
|
1589
|
+
performanceFeatures,
|
|
1590
|
+
"performance",
|
|
1591
|
+
"createGovernor"
|
|
1592
|
+
);
|
|
1593
|
+
|
|
844
1594
|
const fluidDetail = createQualityLadderAdapter({
|
|
845
1595
|
id: "fluid-detail",
|
|
846
1596
|
domain: "geometry",
|
|
@@ -1154,11 +1904,11 @@ function resizeCanvasToDisplaySize(canvas, state) {
|
|
|
1154
1904
|
state.renderScale = scale;
|
|
1155
1905
|
}
|
|
1156
1906
|
|
|
1157
|
-
function resolveClothPresentation(state, meshDetail) {
|
|
1158
|
-
const clothPlan =
|
|
1907
|
+
function resolveClothPresentation(state, meshDetail, clothFeatures) {
|
|
1908
|
+
const clothPlan = clothFeatures.createPlan({
|
|
1159
1909
|
garmentId: "shore-flag",
|
|
1160
|
-
kind: state.focus === "cloth" ? "flag" :
|
|
1161
|
-
profile: state.focus === "cloth" ? "cinematic" :
|
|
1910
|
+
kind: state.focus === "cloth" ? "flag" : clothFeatures.garmentKinds[0],
|
|
1911
|
+
profile: state.focus === "cloth" ? "cinematic" : clothFeatures.profileNames[0],
|
|
1162
1912
|
supportsRayTracing: true,
|
|
1163
1913
|
nearFieldMaxMeters: 18,
|
|
1164
1914
|
midFieldMaxMeters: 55,
|
|
@@ -1176,7 +1926,7 @@ function resolveClothPresentation(state, meshDetail) {
|
|
|
1176
1926
|
)
|
|
1177
1927
|
);
|
|
1178
1928
|
const cameraDistance = lengthVec3(subVec3(state.camera.target, fallbackEye));
|
|
1179
|
-
const band =
|
|
1929
|
+
const band = clothFeatures.selectBand(cameraDistance, clothPlan.thresholds);
|
|
1180
1930
|
const representation =
|
|
1181
1931
|
clothPlan.representations.find((entry) => entry.band === band) ?? clothPlan.representations[0];
|
|
1182
1932
|
return {
|
|
@@ -1538,8 +2288,9 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
1538
2288
|
};
|
|
1539
2289
|
}
|
|
1540
2290
|
|
|
1541
|
-
function buildClothSurface(model, state, meshDetail, visuals) {
|
|
1542
|
-
const
|
|
2291
|
+
function buildClothSurface(model, state, meshDetail, visuals, clothFeatures) {
|
|
2292
|
+
const resolvedClothFeatures = normalizeClothFeatureAdapters(clothFeatures);
|
|
2293
|
+
const clothPresentation = resolveClothPresentation(state, meshDetail, resolvedClothFeatures);
|
|
1543
2294
|
const clothState = ensureShowcaseClothState(state, meshDetail, clothPresentation);
|
|
1544
2295
|
|
|
1545
2296
|
return {
|
|
@@ -1708,11 +2459,12 @@ function buildWaterMotionEffects(state) {
|
|
|
1708
2459
|
});
|
|
1709
2460
|
}
|
|
1710
2461
|
|
|
1711
|
-
function buildWaterBands(state, fluidDetail, visuals) {
|
|
1712
|
-
const
|
|
2462
|
+
function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
2463
|
+
const resolvedFluidFeatures = normalizeFluidFeatureAdapters(fluidFeatures);
|
|
2464
|
+
const fluidPlan = resolvedFluidFeatures.createPlan({
|
|
1713
2465
|
fluidBodyId: "harbor",
|
|
1714
|
-
kind: state.focus === "fluid" ? "ocean" :
|
|
1715
|
-
profile: state.focus === "fluid" ? "cinematic" :
|
|
2466
|
+
kind: state.focus === "fluid" ? "ocean" : resolvedFluidFeatures.bodyKinds[0],
|
|
2467
|
+
profile: state.focus === "fluid" ? "cinematic" : resolvedFluidFeatures.profileNames[0],
|
|
1716
2468
|
supportsRayTracing: true,
|
|
1717
2469
|
nearFieldMaxMeters: 40,
|
|
1718
2470
|
midFieldMaxMeters: 150,
|
|
@@ -1731,7 +2483,7 @@ function buildWaterBands(state, fluidDetail, visuals) {
|
|
|
1731
2483
|
const representation =
|
|
1732
2484
|
fluidPlan.representations.find((entry) => entry.band === bandSpec.band) ??
|
|
1733
2485
|
fluidPlan.representations[0];
|
|
1734
|
-
const continuity =
|
|
2486
|
+
const continuity = resolvedFluidFeatures.createContinuityEnvelope({ fluidBodyId: "harbor" });
|
|
1735
2487
|
const bandContinuity = resolveFluidBandContinuity(continuity, bandSpec.band);
|
|
1736
2488
|
const bandResolution =
|
|
1737
2489
|
bandSpec.band === "near"
|
|
@@ -1801,10 +2553,28 @@ function buildWaterBands(state, fluidDetail, visuals) {
|
|
|
1801
2553
|
return { fluidPlan, bandMeshes };
|
|
1802
2554
|
}
|
|
1803
2555
|
|
|
1804
|
-
function createSceneState(options) {
|
|
2556
|
+
function createSceneState(options, featureAdapters) {
|
|
1805
2557
|
const translate = options.translate;
|
|
1806
|
-
const { governor, fluidDetail, clothDetail, lightingDetail } = createPerformanceGovernor(
|
|
1807
|
-
|
|
2558
|
+
const { governor, fluidDetail, clothDetail, lightingDetail } = createPerformanceGovernor(
|
|
2559
|
+
featureAdapters.performance
|
|
2560
|
+
);
|
|
2561
|
+
const physicsProfile = featureAdapters.physics.defaultProfile;
|
|
2562
|
+
const createPhysicsSimulationPlan = assertRequiredFunction(
|
|
2563
|
+
featureAdapters.physics,
|
|
2564
|
+
"physics",
|
|
2565
|
+
"createSimulationPlan"
|
|
2566
|
+
);
|
|
2567
|
+
const getPhysicsWorkerManifest = assertRequiredFunction(
|
|
2568
|
+
featureAdapters.physics,
|
|
2569
|
+
"physics",
|
|
2570
|
+
"getManifest"
|
|
2571
|
+
);
|
|
2572
|
+
const createGpuDebugSession = assertRequiredFunction(
|
|
2573
|
+
featureAdapters.debug,
|
|
2574
|
+
"debug",
|
|
2575
|
+
"createSession"
|
|
2576
|
+
);
|
|
2577
|
+
|
|
1808
2578
|
const physicsPlan = createPhysicsSimulationPlan(physicsProfile);
|
|
1809
2579
|
const physicsManifest = getPhysicsWorkerManifest(physicsProfile);
|
|
1810
2580
|
const debugSession = createGpuDebugSession({
|
|
@@ -3048,12 +3818,24 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3048
3818
|
ctx.restore();
|
|
3049
3819
|
}
|
|
3050
3820
|
|
|
3051
|
-
function renderScene(
|
|
3821
|
+
function renderScene(
|
|
3822
|
+
ctx,
|
|
3823
|
+
canvas,
|
|
3824
|
+
state,
|
|
3825
|
+
shipModel,
|
|
3826
|
+
dom,
|
|
3827
|
+
lightingFeatures,
|
|
3828
|
+
fluidFeatures,
|
|
3829
|
+
clothFeatures
|
|
3830
|
+
) {
|
|
3052
3831
|
const viewport = { width: canvas.width, height: canvas.height };
|
|
3053
3832
|
const camera = buildCamera(state, canvas);
|
|
3054
3833
|
state.camera.eye = camera.eye;
|
|
3055
|
-
const lightingPlan =
|
|
3056
|
-
profile:
|
|
3834
|
+
const lightingPlan = lightingFeatures.createBandPlan({
|
|
3835
|
+
profile:
|
|
3836
|
+
state.focus === "lighting"
|
|
3837
|
+
? lightingFeatures.defaultProfile
|
|
3838
|
+
: lightingFeatures.getProfile(lightingFeatures.defaultProfile).name,
|
|
3057
3839
|
importance: state.focus === "lighting" ? "critical" : "high",
|
|
3058
3840
|
});
|
|
3059
3841
|
const nearLighting = lightingPlan.bands.find((entry) => entry.band === "near") ?? lightingPlan.bands[0];
|
|
@@ -3082,7 +3864,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
3082
3864
|
const water = buildWaterBands(
|
|
3083
3865
|
state,
|
|
3084
3866
|
state.fluidDetail.getSnapshot().currentLevel.config,
|
|
3085
|
-
visuals
|
|
3867
|
+
visuals,
|
|
3868
|
+
fluidFeatures
|
|
3086
3869
|
);
|
|
3087
3870
|
for (const bandMesh of water.bandMeshes) {
|
|
3088
3871
|
const bandAccent = bandMesh.band === "near" ? 0.06 : bandMesh.band === "mid" ? 0.04 : 0;
|
|
@@ -3123,7 +3906,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
3123
3906
|
state,
|
|
3124
3907
|
state,
|
|
3125
3908
|
state.clothDetail.getSnapshot().currentLevel.config,
|
|
3126
|
-
visuals
|
|
3909
|
+
visuals,
|
|
3910
|
+
clothFeatures
|
|
3127
3911
|
);
|
|
3128
3912
|
for (let index = 0; index < cloth.indices.length; index += 3) {
|
|
3129
3913
|
const a = cloth.positions[cloth.indices[index]];
|
|
@@ -3226,7 +4010,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
3226
4010
|
`mass split: ${state.ships.map((ship) => `${ship.id} ${(getShipMass(ship, resolveShipModel(state, ship, shipModel)) / 1000).toFixed(1)}t`).join(" · ")}`,
|
|
3227
4011
|
`cloth band: ${cloth.band} -> ${cloth.representation.output}`,
|
|
3228
4012
|
`fluid near band: ${water.bandMeshes[0].representation.output}`,
|
|
3229
|
-
`lighting profile: ${lightingPlan.profile} (${
|
|
4013
|
+
`lighting profile: ${lightingPlan.profile} (${lightingFeatures.distanceBands.length} bands)`,
|
|
3230
4014
|
];
|
|
3231
4015
|
const qualityMetrics = [
|
|
3232
4016
|
`fluid detail: ${quality.fluid.currentLevel.id} (${quality.fluid.currentLevel.config.nearResolution} near cells)`,
|
|
@@ -3286,13 +4070,14 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
3286
4070
|
});
|
|
3287
4071
|
}
|
|
3288
4072
|
|
|
3289
|
-
function updateSceneState(state, dt, shipModel) {
|
|
4073
|
+
function updateSceneState(state, dt, shipModel, featureAdapters) {
|
|
3290
4074
|
updateShips(state, dt, shipModel);
|
|
3291
4075
|
updateWaveImpulses(state, dt);
|
|
3292
4076
|
updateSpray(state, dt);
|
|
3293
4077
|
const clothPresentation = resolveClothPresentation(
|
|
3294
4078
|
state,
|
|
3295
|
-
state.clothDetail.getSnapshot().currentLevel.config
|
|
4079
|
+
state.clothDetail.getSnapshot().currentLevel.config,
|
|
4080
|
+
featureAdapters.cloth
|
|
3296
4081
|
);
|
|
3297
4082
|
const clothState = ensureShowcaseClothState(
|
|
3298
4083
|
state,
|
|
@@ -3305,10 +4090,10 @@ function updateSceneState(state, dt, shipModel) {
|
|
|
3305
4090
|
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.92),
|
|
3306
4091
|
waveInfluence: sampleWave(state, FLAG_LAYOUT.origin.x + FLAG_LAYOUT.width * 0.55, FLAG_LAYOUT.origin.z + FLAG_LAYOUT.width * 0.48, state.time),
|
|
3307
4092
|
});
|
|
3308
|
-
updatePhysicsSnapshot(state, shipModel);
|
|
4093
|
+
updatePhysicsSnapshot(state, shipModel, featureAdapters.physics);
|
|
3309
4094
|
}
|
|
3310
4095
|
|
|
3311
|
-
function syncTextState(state, shipModel) {
|
|
4096
|
+
function syncTextState(state, shipModel, featureAdapters) {
|
|
3312
4097
|
const snapshot = {
|
|
3313
4098
|
coordinateSystem: "right-handed world; +x right, +y up, +z forward from the shore",
|
|
3314
4099
|
focus: state.focus,
|
|
@@ -3344,13 +4129,14 @@ function syncTextState(state, shipModel) {
|
|
|
3344
4129
|
for (let index = 0; index < step; index += 1) {
|
|
3345
4130
|
state.frame += 1;
|
|
3346
4131
|
state.time += 1 / 60;
|
|
3347
|
-
updateSceneState(state, 1 / 60, shipModel);
|
|
4132
|
+
updateSceneState(state, 1 / 60, shipModel, featureAdapters);
|
|
3348
4133
|
state.lastDecision = recordTelemetry(state, 16.67 + (state.stress ? 6.5 : 0));
|
|
3349
4134
|
}
|
|
3350
4135
|
};
|
|
3351
4136
|
}
|
|
3352
4137
|
|
|
3353
4138
|
export async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
4139
|
+
const featureAdapters = await resolveShowcaseFeatureAdapters(options);
|
|
3354
4140
|
injectStyles();
|
|
3355
4141
|
const root = options.root ?? document.body;
|
|
3356
4142
|
root.classList?.add?.(ROOT_CLASS);
|
|
@@ -3370,13 +4156,16 @@ export async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
|
3370
4156
|
translate,
|
|
3371
4157
|
});
|
|
3372
4158
|
dom.focusMode.value = focus;
|
|
3373
|
-
const state = createSceneState(
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
4159
|
+
const state = createSceneState(
|
|
4160
|
+
{
|
|
4161
|
+
focus,
|
|
4162
|
+
translate,
|
|
4163
|
+
realisticModelsEnabled: isFeatureEnabled(featureFlags, GPU_SHOWCASE_REALISTIC_MODELS_FEATURE, true),
|
|
4164
|
+
captureMode: captureSettings.captureMode,
|
|
4165
|
+
renderScale: captureSettings.renderScale,
|
|
4166
|
+
},
|
|
4167
|
+
featureAdapters
|
|
4168
|
+
);
|
|
3380
4169
|
const assetCatalog = await (state.showcaseRealisticModelsEnabled
|
|
3381
4170
|
? loadShowcaseAssetCatalog()
|
|
3382
4171
|
: createLegacyShowcaseAssetCatalog());
|
|
@@ -3386,10 +4175,10 @@ export async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
|
3386
4175
|
state.shipModel = shipModel;
|
|
3387
4176
|
state.packageState =
|
|
3388
4177
|
typeof options.createState === "function" ? options.createState() : undefined;
|
|
3389
|
-
updatePhysicsSnapshot(state, shipModel);
|
|
4178
|
+
updatePhysicsSnapshot(state, shipModel, featureAdapters.physics);
|
|
3390
4179
|
state.lastDecision = recordTelemetry(state, 16.4);
|
|
3391
4180
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
3392
|
-
syncTextState(state, shipModel);
|
|
4181
|
+
syncTextState(state, shipModel, featureAdapters);
|
|
3393
4182
|
|
|
3394
4183
|
const ctx = dom.canvas.getContext("2d");
|
|
3395
4184
|
if (!ctx) {
|
|
@@ -3411,7 +4200,7 @@ export async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
|
3411
4200
|
state.lastTimeMs = nowMs;
|
|
3412
4201
|
state.time += dt;
|
|
3413
4202
|
state.frame += 1;
|
|
3414
|
-
updateSceneState(state, dt, shipModel);
|
|
4203
|
+
updateSceneState(state, dt, shipModel, featureAdapters);
|
|
3415
4204
|
updatePackageState(state, options, shipModel, dt);
|
|
3416
4205
|
const syntheticFrame = 14.2 + state.sprays.length * 0.1 + (state.stress ? 6.4 : 0);
|
|
3417
4206
|
state.lastDecision = recordTelemetry(state, syntheticFrame);
|
|
@@ -3419,8 +4208,17 @@ export async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
|
3419
4208
|
|
|
3420
4209
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
3421
4210
|
resizeCanvasToDisplaySize(dom.canvas, state);
|
|
3422
|
-
renderScene(
|
|
3423
|
-
|
|
4211
|
+
renderScene(
|
|
4212
|
+
ctx,
|
|
4213
|
+
dom.canvas,
|
|
4214
|
+
state,
|
|
4215
|
+
shipModel,
|
|
4216
|
+
dom,
|
|
4217
|
+
featureAdapters.lighting,
|
|
4218
|
+
featureAdapters.fluid,
|
|
4219
|
+
featureAdapters.cloth
|
|
4220
|
+
);
|
|
4221
|
+
syncTextState(state, shipModel, featureAdapters);
|
|
3424
4222
|
animationFrameId = requestAnimationFrame(renderFrame);
|
|
3425
4223
|
};
|
|
3426
4224
|
|
|
@@ -3486,7 +4284,12 @@ export async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
|
3486
4284
|
};
|
|
3487
4285
|
}
|
|
3488
4286
|
|
|
3489
|
-
function updatePhysicsSnapshot(state, shipModel) {
|
|
4287
|
+
function updatePhysicsSnapshot(state, shipModel, physicsFeatures) {
|
|
4288
|
+
const createPhysicsWorldSnapshot = assertRequiredFunction(
|
|
4289
|
+
physicsFeatures,
|
|
4290
|
+
"physics",
|
|
4291
|
+
"createWorldSnapshot"
|
|
4292
|
+
);
|
|
3490
4293
|
const rigidBodyShapes = Object.fromEntries(
|
|
3491
4294
|
state.ships.map((ship) => [
|
|
3492
4295
|
ship.id,
|