@plasius/gpu-shared 0.1.14 → 0.1.16

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +15 -0
  3. package/dist/{chunk-4ZJ24VRS.js → chunk-6SOHFUOE.js} +4 -3
  4. package/dist/{chunk-4ZJ24VRS.js.map → chunk-6SOHFUOE.js.map} +1 -1
  5. package/dist/{chunk-3ARPGHCQ.js → chunk-CH3ZS5TQ.js} +8 -8
  6. package/dist/chunk-CH3ZS5TQ.js.map +1 -0
  7. package/dist/chunk-DGUM43GV.js +11 -0
  8. package/dist/{chunk-W5GA3VA6.js → chunk-QVNRTWHB.js} +8 -5
  9. package/dist/chunk-QVNRTWHB.js.map +1 -0
  10. package/dist/dist-B5R2GZQR.js +1433 -0
  11. package/dist/dist-B5R2GZQR.js.map +1 -0
  12. package/dist/gltf-loader-B6VOWGBV.js +9 -0
  13. package/dist/index.cjs +1483 -20
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +30 -6
  16. package/dist/index.js.map +1 -1
  17. package/dist/{product-studio-runtime-HDAUDWYO.js → product-studio-runtime-BYVBUWIN.js} +4 -3
  18. package/dist/{showcase-inline-assets-WT4PSNKI.js → showcase-inline-assets-QRQKXGVX.js} +2 -1
  19. package/dist/showcase-inline-assets-QRQKXGVX.js.map +1 -0
  20. package/dist/{showcase-runtime-SNCUFSSC.js → showcase-runtime-M6TEUYOG.js} +4 -3
  21. package/dist/{showcase-runtime-SNCUFSSC.js.map → showcase-runtime-M6TEUYOG.js.map} +1 -1
  22. package/package.json +3 -4
  23. package/src/gltf-loader.js +7 -3
  24. package/src/index.d.ts +1 -0
  25. package/src/index.js +35 -1
  26. package/src/product-studio-runtime.js +1 -0
  27. package/dist/chunk-3ARPGHCQ.js.map +0 -1
  28. package/dist/chunk-W5GA3VA6.js.map +0 -1
  29. package/dist/gltf-loader-YDPLZS5Q.js +0 -8
  30. /package/dist/{gltf-loader-YDPLZS5Q.js.map → chunk-DGUM43GV.js.map} +0 -0
  31. /package/dist/{product-studio-runtime-HDAUDWYO.js.map → gltf-loader-B6VOWGBV.js.map} +0 -0
  32. /package/dist/{showcase-inline-assets-WT4PSNKI.js.map → product-studio-runtime-BYVBUWIN.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -29,6 +29,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  ));
30
30
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
31
 
32
+ // src/feature-flags.js
33
+ var GPU_SHOWCASE_REALISTIC_MODELS_FEATURE, GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE;
34
+ var init_feature_flags = __esm({
35
+ "src/feature-flags.js"() {
36
+ GPU_SHOWCASE_REALISTIC_MODELS_FEATURE = "gpu_showcase_realistic_models_v1";
37
+ GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE = "gpu_showcase_product_studio_wavefront_v1";
38
+ }
39
+ });
40
+
32
41
  // src/showcase-inline-assets.js
33
42
  var showcase_inline_assets_exports = {};
34
43
  __export(showcase_inline_assets_exports, {
@@ -231,15 +240,6 @@ var init_i18n = __esm({
231
240
  }
232
241
  });
233
242
 
234
- // src/feature-flags.js
235
- var GPU_SHOWCASE_REALISTIC_MODELS_FEATURE, GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE;
236
- var init_feature_flags = __esm({
237
- "src/feature-flags.js"() {
238
- GPU_SHOWCASE_REALISTIC_MODELS_FEATURE = "gpu_showcase_realistic_models_v1";
239
- GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE = "gpu_showcase_product_studio_wavefront_v1";
240
- }
241
- });
242
-
243
243
  // src/gltf-loader.js
244
244
  var gltf_loader_exports = {};
245
245
  __export(gltf_loader_exports, {
@@ -367,6 +367,11 @@ function appendValues(target, values) {
367
367
  target.push(values[index]);
368
368
  }
369
369
  }
370
+ function appendIndicesWithOffset(target, values, vertexOffset) {
371
+ for (let index = 0; index < values.length; index += 1) {
372
+ target.push(values[index] + vertexOffset);
373
+ }
374
+ }
370
375
  function resolveBrowserRequestBaseUrl() {
371
376
  if (typeof document !== "undefined" && typeof document.baseURI === "string" && document.baseURI.length > 0) {
372
377
  return document.baseURI;
@@ -556,7 +561,7 @@ async function loadInlineShowcaseDocument() {
556
561
  const module2 = await Promise.resolve().then(() => (init_showcase_inline_assets(), showcase_inline_assets_exports));
557
562
  return loadGltfDocument(new URL(module2.INLINE_SHOWCASE_ASSET_URLS.brigantine));
558
563
  }
559
- async function buildGltfModel(document2, baseUrl) {
564
+ async function buildGltfModel(document2, baseUrl2) {
560
565
  const buffers = await Promise.all(
561
566
  (document2.buffers ?? []).map(async (buffer) => {
562
567
  if (typeof buffer.uri !== "string") {
@@ -565,7 +570,7 @@ async function buildGltfModel(document2, baseUrl) {
565
570
  if (buffer.uri.startsWith("data:")) {
566
571
  return decodeDataUri(buffer.uri);
567
572
  }
568
- const nested = await fetch(new URL(buffer.uri, baseUrl));
573
+ const nested = await fetch(new URL(buffer.uri, baseUrl2));
569
574
  if (!nested.ok) {
570
575
  throw new Error(`Failed to load glTF buffer: ${nested.status} ${nested.statusText}`);
571
576
  }
@@ -578,9 +583,7 @@ async function buildGltfModel(document2, baseUrl) {
578
583
  for (const primitive of scene.primitives) {
579
584
  const vertexOffset = aggregatePositions.length / 3;
580
585
  appendValues(aggregatePositions, primitive.positions);
581
- for (const index of primitive.indices) {
582
- aggregateIndices.push(index + vertexOffset);
583
- }
586
+ appendIndicesWithOffset(aggregateIndices, primitive.indices, vertexOffset);
584
587
  }
585
588
  const color = scene.primitives[0]?.material?.color ?? { r: 0.56, g: 0.33, b: 0.22, a: 1 };
586
589
  return Object.freeze({
@@ -601,14 +604,14 @@ function shouldRetryWithInlineShowcaseFallback(url, error) {
601
604
  }
602
605
  async function loadGltfModel(url) {
603
606
  try {
604
- const { document: document2, baseUrl } = await loadGltfDocument(url);
605
- return buildGltfModel(document2, baseUrl);
607
+ const { document: document2, baseUrl: baseUrl2 } = await loadGltfDocument(url);
608
+ return buildGltfModel(document2, baseUrl2);
606
609
  } catch (error) {
607
610
  if (!shouldRetryWithInlineShowcaseFallback(url, error)) {
608
611
  throw error;
609
612
  }
610
- const { document: document2, baseUrl } = await loadInlineShowcaseDocument();
611
- return buildGltfModel(document2, baseUrl);
613
+ const { document: document2, baseUrl: baseUrl2 } = await loadInlineShowcaseDocument();
614
+ return buildGltfModel(document2, baseUrl2);
612
615
  }
613
616
  }
614
617
  var init_gltf_loader = __esm({
@@ -617,6 +620,1442 @@ var init_gltf_loader = __esm({
617
620
  }
618
621
  });
619
622
 
623
+ // node_modules/@plasius/gpu-lighting/dist/index.js
624
+ var dist_exports = {};
625
+ __export(dist_exports, {
626
+ createEnvironmentLightingConfig: () => createEnvironmentLightingConfig,
627
+ createLightingBandPlan: () => createLightingBandPlan,
628
+ createLightingProfileModeLadder: () => createLightingProfileModeLadder,
629
+ createWavefrontEnvironmentLightingOptions: () => createWavefrontEnvironmentLightingOptions,
630
+ defaultAdaptiveLightingProfilePolicy: () => defaultAdaptiveLightingProfilePolicy,
631
+ defaultLightingProfile: () => defaultLightingProfile,
632
+ defaultLightingTechnique: () => defaultLightingTechnique,
633
+ getLightingProfile: () => getLightingProfile,
634
+ getLightingProfileWorkerManifest: () => getLightingProfileWorkerManifest,
635
+ getLightingTechnique: () => getLightingTechnique,
636
+ getLightingTechniqueWorkerManifest: () => getLightingTechniqueWorkerManifest,
637
+ lightingDebugOwner: () => lightingDebugOwner,
638
+ lightingDistanceBands: () => lightingDistanceBands,
639
+ lightingEnvironmentPresetNames: () => lightingEnvironmentPresetNames,
640
+ lightingJobLabels: () => lightingJobLabels,
641
+ lightingJobs: () => lightingJobs,
642
+ lightingPreludeWgslUrl: () => lightingPreludeWgslUrl,
643
+ lightingProfileModeOrder: () => lightingProfileModeOrder,
644
+ lightingProfileNames: () => lightingProfileNames,
645
+ lightingProfiles: () => lightingProfiles,
646
+ lightingTechniqueNames: () => lightingTechniqueNames,
647
+ lightingTechniques: () => lightingTechniques,
648
+ lightingWorkerManifests: () => lightingWorkerManifests,
649
+ lightingWorkerQueueClass: () => lightingWorkerQueueClass,
650
+ loadLightingJobs: () => loadLightingJobs,
651
+ loadLightingPreludeWgsl: () => loadLightingPreludeWgsl,
652
+ loadLightingProfile: () => loadLightingProfile,
653
+ loadLightingProfileWorkerPlan: () => loadLightingProfileWorkerPlan,
654
+ loadLightingTechniqueJobWgsl: () => loadLightingTechniqueJobWgsl,
655
+ loadLightingTechniqueJobs: () => loadLightingTechniqueJobs,
656
+ loadLightingTechniquePreludeWgsl: () => loadLightingTechniquePreludeWgsl,
657
+ loadLightingTechniqueWorkerBundle: () => loadLightingTechniqueWorkerBundle
658
+ });
659
+ function createModuleBaseUrl(metaUrl) {
660
+ return Reflect.construct(URL, [String(metaUrl)]);
661
+ }
662
+ function buildTechnique(name, spec) {
663
+ const preludeUrl = new URL(`./techniques/${name}/${spec.prelude}`, baseUrl);
664
+ const jobs = Object.entries(spec.jobs).map(([key, file]) => {
665
+ const label = `lighting.${name}.${key}`;
666
+ return {
667
+ key,
668
+ label,
669
+ url: new URL(`./techniques/${name}/${file}`, baseUrl),
670
+ sourceName: label
671
+ };
672
+ });
673
+ return {
674
+ name,
675
+ description: spec.description,
676
+ preludeUrl,
677
+ jobs
678
+ };
679
+ }
680
+ function buildProfile(name, spec) {
681
+ return {
682
+ name,
683
+ description: spec.description,
684
+ techniques: [...spec.techniques]
685
+ };
686
+ }
687
+ function freezeVec4(value) {
688
+ return Object.freeze([value[0], value[1], value[2], value[3] ?? 1]);
689
+ }
690
+ function normalizeVector3(value, fallback) {
691
+ if (!Array.isArray(value) || value.length < 3) {
692
+ return [...fallback];
693
+ }
694
+ const vector = [
695
+ Number.isFinite(value[0]) ? value[0] : fallback[0],
696
+ Number.isFinite(value[1]) ? value[1] : fallback[1],
697
+ Number.isFinite(value[2]) ? value[2] : fallback[2]
698
+ ];
699
+ const length = Math.hypot(vector[0], vector[1], vector[2]);
700
+ if (!Number.isFinite(length) || length <= 1e-6) {
701
+ return [...fallback];
702
+ }
703
+ return vector.map((component) => component / length);
704
+ }
705
+ function readColor(value, fallback) {
706
+ if (!Array.isArray(value) || value.length < 3) {
707
+ return freezeVec4(fallback);
708
+ }
709
+ return freezeVec4([
710
+ Number.isFinite(value[0]) ? Math.max(0, value[0]) : fallback[0],
711
+ Number.isFinite(value[1]) ? Math.max(0, value[1]) : fallback[1],
712
+ Number.isFinite(value[2]) ? Math.max(0, value[2]) : fallback[2],
713
+ Number.isFinite(value[3]) ? Math.max(0, Math.min(1, value[3])) : fallback[3] ?? 1
714
+ ]);
715
+ }
716
+ function readFinite(value, fallback) {
717
+ return Number.isFinite(value) ? value : fallback;
718
+ }
719
+ function resolveEnvironmentPreset(name) {
720
+ const presetName = typeof name === "string" && name.length > 0 ? name : "product-studio";
721
+ const preset = environmentLightingPresets[presetName];
722
+ if (!preset) {
723
+ throw new Error(
724
+ `Unknown lighting environment preset "${presetName}". Expected one of: ${lightingEnvironmentPresetNames.join(", ")}.`
725
+ );
726
+ }
727
+ return preset;
728
+ }
729
+ function estimateEnvironmentColor(config) {
730
+ const horizonWeight = 0.58;
731
+ const zenithWeight = 1 - horizonWeight;
732
+ const glowWeight = 0.055;
733
+ const intensity = Math.max(config.environmentIntensity, 1e-4);
734
+ return freezeVec4([
735
+ (config.horizonColor[0] * horizonWeight + config.zenithColor[0] * zenithWeight + config.sunColor[0] * glowWeight) * intensity,
736
+ (config.horizonColor[1] * horizonWeight + config.zenithColor[1] * zenithWeight + config.sunColor[1] * glowWeight) * intensity,
737
+ (config.horizonColor[2] * horizonWeight + config.zenithColor[2] * zenithWeight + config.sunColor[2] * glowWeight) * intensity,
738
+ 1
739
+ ]);
740
+ }
741
+ function createEnvironmentLightingConfig(options = {}) {
742
+ const preset = resolveEnvironmentPreset(options.preset ?? options.name);
743
+ const environmentIntensity = Math.max(
744
+ readFinite(options.environmentIntensity ?? options.intensity, preset.environmentIntensity),
745
+ 1e-4
746
+ );
747
+ const config = {
748
+ preset: preset.preset,
749
+ profile: typeof options.profile === "string" ? options.profile : defaultLightingProfile,
750
+ environmentMode: Math.max(0, Math.trunc(readFinite(options.environmentMode, preset.environmentMode))),
751
+ environmentIntensity,
752
+ exposure: Math.max(1e-4, readFinite(options.exposure, preset.exposure)),
753
+ horizonColor: readColor(options.horizonColor, preset.horizonColor),
754
+ zenithColor: readColor(options.zenithColor, preset.zenithColor),
755
+ sunDirection: Object.freeze(
756
+ normalizeVector3(options.sunDirection, preset.sunDirection)
757
+ ),
758
+ sunColor: readColor(options.sunColor, preset.sunColor),
759
+ ambientColor: readColor(options.ambientColor, preset.ambientColor)
760
+ };
761
+ const environmentColor = estimateEnvironmentColor(config);
762
+ return Object.freeze({
763
+ ...config,
764
+ environmentColor,
765
+ wavefront: Object.freeze({
766
+ environmentColor,
767
+ ambientColor: config.ambientColor,
768
+ environmentLighting: Object.freeze({
769
+ horizonColor: config.horizonColor,
770
+ zenithColor: config.zenithColor,
771
+ sunDirection: Object.freeze([...config.sunDirection]),
772
+ sunColor: config.sunColor,
773
+ intensity: config.environmentIntensity,
774
+ mode: config.environmentMode,
775
+ exposure: config.exposure
776
+ })
777
+ })
778
+ });
779
+ }
780
+ function createWavefrontEnvironmentLightingOptions(options = {}) {
781
+ const config = createEnvironmentLightingConfig(options);
782
+ return Object.freeze({
783
+ environmentColor: config.wavefront.environmentColor,
784
+ ambientColor: config.wavefront.ambientColor,
785
+ environmentLighting: config.wavefront.environmentLighting,
786
+ lightingEnvironment: config
787
+ });
788
+ }
789
+ function assertLightingImportance(name, value) {
790
+ if (!lightingImportanceLevels.includes(value)) {
791
+ throw new Error(
792
+ `${name} must be one of: ${lightingImportanceLevels.join(", ")}.`
793
+ );
794
+ }
795
+ return value;
796
+ }
797
+ function readPositiveIntegerOption(name, value, fallback) {
798
+ if (value === void 0) {
799
+ return fallback;
800
+ }
801
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || Math.round(value) !== value) {
802
+ throw new Error(`${name} must be a positive integer.`);
803
+ }
804
+ return value;
805
+ }
806
+ function resolveBandParticipation(profileName, band, importance) {
807
+ const referenceProfile = profileName === "reference";
808
+ const premiumImportance = importance === "critical" || importance === "high";
809
+ if (band === "near") {
810
+ return Object.freeze({
811
+ directShadows: premiumImportance ? "premium" : "selective",
812
+ reflections: premiumImportance ? "premium" : "selective",
813
+ globalIllumination: referenceProfile || importance === "critical" ? "premium" : "selective"
814
+ });
815
+ }
816
+ if (band === "mid") {
817
+ return Object.freeze({
818
+ directShadows: premiumImportance ? "selective" : "proxy",
819
+ reflections: referenceProfile && premiumImportance ? "selective" : "proxy",
820
+ globalIllumination: referenceProfile && importance === "critical" ? "selective" : "disabled"
821
+ });
822
+ }
823
+ if (band === "far") {
824
+ return Object.freeze({
825
+ directShadows: "proxy",
826
+ reflections: referenceProfile && importance === "critical" ? "proxy" : "disabled",
827
+ globalIllumination: "disabled"
828
+ });
829
+ }
830
+ return Object.freeze({
831
+ directShadows: "disabled",
832
+ reflections: "disabled",
833
+ globalIllumination: "disabled"
834
+ });
835
+ }
836
+ function createLightingBandPlan(options = {}) {
837
+ const profileName = options.profile ?? defaultLightingProfile;
838
+ const profile = getLightingProfile(profileName);
839
+ const importance = assertLightingImportance(
840
+ "importance",
841
+ options.importance ?? "high"
842
+ );
843
+ const bands = Object.freeze(
844
+ lightingDistanceBands.map(
845
+ (band) => Object.freeze({
846
+ band,
847
+ profile: profile.name,
848
+ importance,
849
+ primaryShadowSource: lightingBandPolicySpecs[band].primaryShadowSource,
850
+ assistShadowSources: Object.freeze([
851
+ ...lightingBandPolicySpecs[band].assistShadowSources
852
+ ]),
853
+ rtParticipation: resolveBandParticipation(profile.name, band, importance),
854
+ temporalReuse: lightingBandPolicySpecs[band].temporalReuse,
855
+ updateCadenceDivisor: lightingBandPolicySpecs[band].updateCadenceDivisor,
856
+ liveObjectShadows: lightingBandPolicySpecs[band].liveObjectShadows,
857
+ impressionOnly: lightingBandPolicySpecs[band].impressionOnly
858
+ })
859
+ )
860
+ );
861
+ return Object.freeze({
862
+ schemaVersion: 1,
863
+ owner: lightingDebugOwner,
864
+ profile: profile.name,
865
+ importance,
866
+ techniques: Object.freeze([...profile.techniques]),
867
+ bands
868
+ });
869
+ }
870
+ function createLightingProfileModeLadder(options = {}) {
871
+ const moduleId = typeof options.id === "string" && options.id.trim().length > 0 ? options.id.trim() : "lighting-profile-mode";
872
+ const preferredProfile = getLightingProfile(
873
+ options.preferredProfile ?? defaultAdaptiveLightingProfilePolicy.preferredProfile
874
+ ).name;
875
+ const initialProfile = getLightingProfile(
876
+ options.initialProfile ?? preferredProfile
877
+ ).name;
878
+ const minimumFrameRate = readPositiveIntegerOption(
879
+ "minimumFrameRate",
880
+ options.minimumFrameRate,
881
+ defaultAdaptiveLightingProfilePolicy.minimumFrameRate
882
+ );
883
+ const sampleWindowSize = readPositiveIntegerOption(
884
+ "sampleWindowSize",
885
+ options.sampleWindowSize,
886
+ defaultAdaptiveLightingProfilePolicy.sampleWindowSize
887
+ );
888
+ const importance = assertLightingImportance(
889
+ "importance",
890
+ options.importance ?? "high"
891
+ );
892
+ const moduleImportance = assertLightingImportance(
893
+ "moduleImportance",
894
+ options.moduleImportance ?? "critical"
895
+ );
896
+ const levels = Object.freeze(
897
+ lightingProfileModeOrder.map((profileName) => {
898
+ const profile = getLightingProfile(profileName);
899
+ return Object.freeze({
900
+ id: profile.name,
901
+ estimatedCostMs: lightingProfileModeEstimatedCostMs[profile.name],
902
+ config: Object.freeze({
903
+ profile: profile.name,
904
+ description: profile.description,
905
+ techniques: Object.freeze([...profile.techniques]),
906
+ lightingBandPlan: createLightingBandPlan({
907
+ profile: profile.name,
908
+ importance
909
+ }),
910
+ policy: Object.freeze({
911
+ preferredProfile,
912
+ minimumFrameRate,
913
+ sampleWindowSize
914
+ })
915
+ })
916
+ });
917
+ })
918
+ );
919
+ return Object.freeze({
920
+ id: moduleId,
921
+ domain: "lighting",
922
+ authority: "visual",
923
+ importance: moduleImportance,
924
+ initialLevel: initialProfile,
925
+ levels,
926
+ target: Object.freeze({
927
+ minimumFrameRate,
928
+ maximumFrameRate: minimumFrameRate,
929
+ preferredFrameRates: Object.freeze([minimumFrameRate])
930
+ }),
931
+ adaptation: Object.freeze({
932
+ sampleWindowSize,
933
+ minimumSamplesBeforeAdjustment: sampleWindowSize,
934
+ degradeCooldownFrames: 1,
935
+ upgradeCooldownFrames: sampleWindowSize,
936
+ minStableFramesForRecovery: sampleWindowSize
937
+ }),
938
+ policy: Object.freeze({
939
+ preferredProfile,
940
+ minimumFrameRate,
941
+ sampleWindowSize
942
+ })
943
+ });
944
+ }
945
+ function buildWorkerBudgetLevels(jobType, queueClass, presets) {
946
+ return Object.freeze([
947
+ Object.freeze({
948
+ id: "low",
949
+ estimatedCostMs: presets.low.estimatedCostMs,
950
+ config: Object.freeze({
951
+ maxDispatchesPerFrame: presets.low.maxDispatchesPerFrame,
952
+ maxJobsPerDispatch: presets.low.maxJobsPerDispatch,
953
+ cadenceDivisor: presets.low.cadenceDivisor,
954
+ workgroupScale: presets.low.workgroupScale,
955
+ maxQueueDepth: presets.low.maxQueueDepth,
956
+ metadata: Object.freeze({
957
+ owner: lightingDebugOwner,
958
+ queueClass,
959
+ jobType,
960
+ quality: "low"
961
+ })
962
+ })
963
+ }),
964
+ Object.freeze({
965
+ id: "medium",
966
+ estimatedCostMs: presets.medium.estimatedCostMs,
967
+ config: Object.freeze({
968
+ maxDispatchesPerFrame: presets.medium.maxDispatchesPerFrame,
969
+ maxJobsPerDispatch: presets.medium.maxJobsPerDispatch,
970
+ cadenceDivisor: presets.medium.cadenceDivisor,
971
+ workgroupScale: presets.medium.workgroupScale,
972
+ maxQueueDepth: presets.medium.maxQueueDepth,
973
+ metadata: Object.freeze({
974
+ owner: lightingDebugOwner,
975
+ queueClass,
976
+ jobType,
977
+ quality: "medium"
978
+ })
979
+ })
980
+ }),
981
+ Object.freeze({
982
+ id: "high",
983
+ estimatedCostMs: presets.high.estimatedCostMs,
984
+ config: Object.freeze({
985
+ maxDispatchesPerFrame: presets.high.maxDispatchesPerFrame,
986
+ maxJobsPerDispatch: presets.high.maxJobsPerDispatch,
987
+ cadenceDivisor: presets.high.cadenceDivisor,
988
+ workgroupScale: presets.high.workgroupScale,
989
+ maxQueueDepth: presets.high.maxQueueDepth,
990
+ metadata: Object.freeze({
991
+ owner: lightingDebugOwner,
992
+ queueClass,
993
+ jobType,
994
+ quality: "high"
995
+ })
996
+ })
997
+ })
998
+ ]);
999
+ }
1000
+ function resolveLightingQualityDimensions(techniqueName, jobKey) {
1001
+ const key = `${techniqueName}.${jobKey}`;
1002
+ return Object.freeze(
1003
+ {
1004
+ "hybrid.directLighting": { shadows: 1, lightingSamples: 0.7 },
1005
+ "hybrid.screenTrace": { rayTracing: 1, temporalReuse: 0.4 },
1006
+ "hybrid.radianceCache": {
1007
+ lightingSamples: 0.8,
1008
+ updateCadence: 0.7,
1009
+ temporalReuse: 1
1010
+ },
1011
+ "hybrid.finalGather": { lightingSamples: 1, rayTracing: 0.6 },
1012
+ "hybrid.reflectionResolve": {
1013
+ rayTracing: 0.5,
1014
+ temporalReuse: 1,
1015
+ shading: 0.3
1016
+ },
1017
+ "pathtracer.pathTrace": { rayTracing: 1, lightingSamples: 1 },
1018
+ "pathtracer.accumulate": { temporalReuse: 1, updateCadence: 0.4 },
1019
+ "pathtracer.denoise": { temporalReuse: 1, shading: 0.4 },
1020
+ "volumetrics.froxelIntegrate": {
1021
+ lightingSamples: 0.6,
1022
+ shading: 0.4,
1023
+ updateCadence: 0.3
1024
+ },
1025
+ "volumetrics.volumetricShadow": { shadows: 0.8, updateCadence: 0.5 },
1026
+ "hdri.irradianceConvolution": {
1027
+ lightingSamples: 0.4,
1028
+ temporalReuse: 1,
1029
+ updateCadence: 1
1030
+ },
1031
+ "hdri.specularPrefilter": {
1032
+ lightingSamples: 0.5,
1033
+ temporalReuse: 1,
1034
+ updateCadence: 1
1035
+ },
1036
+ "hdri.brdfLut": {
1037
+ shading: 0.4,
1038
+ temporalReuse: 1,
1039
+ updateCadence: 1
1040
+ }
1041
+ }[key] ?? {}
1042
+ );
1043
+ }
1044
+ function resolveLightingImportanceSignals(techniqueName, jobKey) {
1045
+ const key = `${techniqueName}.${jobKey}`;
1046
+ return Object.freeze(
1047
+ {
1048
+ "hybrid.directLighting": { visible: true, shadowSignificance: "high" },
1049
+ "hybrid.screenTrace": { visible: true, reflectionSignificance: "high" },
1050
+ "hybrid.radianceCache": { visible: true },
1051
+ "hybrid.finalGather": {
1052
+ visible: true,
1053
+ shadowSignificance: "critical",
1054
+ reflectionSignificance: "high"
1055
+ },
1056
+ "hybrid.reflectionResolve": { visible: true, reflectionSignificance: "high" },
1057
+ "pathtracer.pathTrace": {
1058
+ visible: true,
1059
+ shadowSignificance: "high",
1060
+ reflectionSignificance: "high"
1061
+ },
1062
+ "pathtracer.accumulate": { visible: true },
1063
+ "pathtracer.denoise": { visible: true },
1064
+ "volumetrics.froxelIntegrate": { visible: true },
1065
+ "volumetrics.volumetricShadow": { visible: true, shadowSignificance: "high" },
1066
+ "hdri.irradianceConvolution": { visible: false },
1067
+ "hdri.specularPrefilter": { visible: false, reflectionSignificance: "medium" },
1068
+ "hdri.brdfLut": { visible: false }
1069
+ }[key] ?? {}
1070
+ );
1071
+ }
1072
+ function buildWorkerManifestJob(techniqueName, job) {
1073
+ const spec = lightingWorkerSpecPresets[techniqueName].jobs[job.key];
1074
+ const dag = lightingWorkerDagSpecs[techniqueName][job.key];
1075
+ const dependencies = dag.dependencies.map(
1076
+ (dependency) => `lighting.${techniqueName}.${dependency}`
1077
+ );
1078
+ return Object.freeze({
1079
+ key: job.key,
1080
+ label: job.label,
1081
+ worker: Object.freeze({
1082
+ jobType: job.label,
1083
+ queueClass: lightingWorkerQueueClass,
1084
+ priority: dag.priority,
1085
+ dependencies: Object.freeze(dependencies),
1086
+ schedulerMode: "dag"
1087
+ }),
1088
+ performance: Object.freeze({
1089
+ id: job.label,
1090
+ jobType: job.label,
1091
+ queueClass: lightingWorkerQueueClass,
1092
+ domain: spec.domain,
1093
+ authority: "visual",
1094
+ importance: spec.importance,
1095
+ qualityDimensions: resolveLightingQualityDimensions(techniqueName, job.key),
1096
+ importanceSignals: resolveLightingImportanceSignals(techniqueName, job.key),
1097
+ levels: spec.levels
1098
+ }),
1099
+ debug: Object.freeze({
1100
+ owner: lightingDebugOwner,
1101
+ queueClass: lightingWorkerQueueClass,
1102
+ jobType: job.label,
1103
+ tags: Object.freeze([
1104
+ "lighting",
1105
+ techniqueName,
1106
+ job.key,
1107
+ spec.domain
1108
+ ]),
1109
+ suggestedAllocationIds: Object.freeze([...spec.suggestedAllocationIds])
1110
+ })
1111
+ });
1112
+ }
1113
+ function buildLightingWorkerManifest(name, technique) {
1114
+ const spec = lightingWorkerSpecPresets[name];
1115
+ return Object.freeze({
1116
+ schemaVersion: 1,
1117
+ owner: lightingDebugOwner,
1118
+ technique: name,
1119
+ description: technique.description,
1120
+ queueClass: lightingWorkerQueueClass,
1121
+ schedulerMode: "dag",
1122
+ suggestedAllocationIds: Object.freeze([...spec.suggestedAllocationIds]),
1123
+ jobs: Object.freeze(
1124
+ technique.jobs.map((job) => buildWorkerManifestJob(name, job))
1125
+ )
1126
+ });
1127
+ }
1128
+ function getTechniqueJob(technique, key) {
1129
+ const job = technique.jobs.find((entry) => entry.key === key);
1130
+ if (!job) {
1131
+ const available = technique.jobs.map((entry) => entry.key).join(", ");
1132
+ throw new Error(
1133
+ `Unknown job "${key}" for technique "${technique.name}". Available: ${available}.`
1134
+ );
1135
+ }
1136
+ return job;
1137
+ }
1138
+ function getLightingTechnique(name = defaultLightingTechnique) {
1139
+ const technique = lightingTechniques[name];
1140
+ if (!technique) {
1141
+ const available = lightingTechniqueNames.join(", ");
1142
+ throw new Error(`Unknown lighting technique "${name}". Available: ${available}.`);
1143
+ }
1144
+ return technique;
1145
+ }
1146
+ function getLightingProfile(name = defaultLightingProfile) {
1147
+ const profile = lightingProfiles[name];
1148
+ if (!profile) {
1149
+ const available = lightingProfileNames.join(", ");
1150
+ throw new Error(`Unknown lighting profile "${name}". Available: ${available}.`);
1151
+ }
1152
+ return profile;
1153
+ }
1154
+ function getLightingTechniqueWorkerManifest(name = defaultLightingTechnique) {
1155
+ const manifest = lightingWorkerManifests[name];
1156
+ if (!manifest) {
1157
+ const available = lightingTechniqueNames.join(", ");
1158
+ throw new Error(
1159
+ `Unknown lighting technique "${name}". Available: ${available}.`
1160
+ );
1161
+ }
1162
+ return manifest;
1163
+ }
1164
+ function getLightingProfileWorkerManifest(name = defaultLightingProfile) {
1165
+ const profile = getLightingProfile(name);
1166
+ const techniques = profile.techniques.map(
1167
+ (techniqueName) => getLightingTechniqueWorkerManifest(techniqueName)
1168
+ );
1169
+ const lightingBandPlan = createLightingBandPlan({ profile: profile.name });
1170
+ return Object.freeze({
1171
+ schemaVersion: 1,
1172
+ owner: lightingDebugOwner,
1173
+ profile: profile.name,
1174
+ description: profile.description,
1175
+ schedulerMode: "dag",
1176
+ techniques: Object.freeze(techniques),
1177
+ lightingBands: lightingBandPlan.bands,
1178
+ jobs: Object.freeze(techniques.flatMap((technique) => technique.jobs))
1179
+ });
1180
+ }
1181
+ function assertNotHtmlWgsl(source, context) {
1182
+ const sample = source.slice(0, 200).toLowerCase();
1183
+ if (sample.includes("<!doctype") || sample.includes("<html") || sample.includes("<meta")) {
1184
+ const label = context ? ` for ${context}` : "";
1185
+ throw new Error(
1186
+ `Expected WGSL${label} but received HTML. Check the URL or server root.`
1187
+ );
1188
+ }
1189
+ }
1190
+ async function loadWgslSource(options = {}) {
1191
+ const { wgsl, url, fetcher = globalThis.fetch, base } = options ?? {};
1192
+ if (typeof wgsl === "string") {
1193
+ assertNotHtmlWgsl(wgsl, "inline WGSL");
1194
+ return wgsl;
1195
+ }
1196
+ if (!url) {
1197
+ return null;
1198
+ }
1199
+ const resolved = url instanceof URL ? url : new URL(url, base ?? baseUrl);
1200
+ if (!fetcher || resolved.protocol === "file:") {
1201
+ const { readFile } = await import("fs/promises");
1202
+ const { fileURLToPath } = await import("url");
1203
+ const source2 = await readFile(fileURLToPath(resolved), "utf8");
1204
+ assertNotHtmlWgsl(source2, resolved.href);
1205
+ return source2;
1206
+ }
1207
+ const response = await fetcher(resolved);
1208
+ if (!response.ok) {
1209
+ const status = "status" in response ? response.status : "unknown";
1210
+ const statusText = "statusText" in response ? response.statusText : "";
1211
+ const detail = statusText ? `${status} ${statusText}` : `${status}`;
1212
+ throw new Error(`Failed to load WGSL (${detail})`);
1213
+ }
1214
+ const source = await response.text();
1215
+ assertNotHtmlWgsl(source, resolved.href);
1216
+ return source;
1217
+ }
1218
+ async function loadTechniquePrelude(technique, fetcher) {
1219
+ const source = await loadWgslSource({ url: technique.preludeUrl, fetcher });
1220
+ if (typeof source !== "string") {
1221
+ throw new Error(`Failed to load ${technique.name} prelude WGSL source.`);
1222
+ }
1223
+ return source;
1224
+ }
1225
+ async function loadTechniqueJob(technique, job, fetcher) {
1226
+ const source = await loadWgslSource({ url: job.url, fetcher });
1227
+ if (typeof source !== "string") {
1228
+ throw new Error(
1229
+ `Failed to load ${technique.name} job "${job.key}" WGSL source.`
1230
+ );
1231
+ }
1232
+ return source;
1233
+ }
1234
+ async function loadLightingTechniquePreludeWgsl(techniqueName, options = {}) {
1235
+ const { fetcher } = options ?? {};
1236
+ const technique = getLightingTechnique(techniqueName);
1237
+ return loadTechniquePrelude(technique, fetcher);
1238
+ }
1239
+ async function loadLightingTechniqueJobWgsl(techniqueName, jobKey, options = {}) {
1240
+ const { fetcher } = options ?? {};
1241
+ const technique = getLightingTechnique(techniqueName);
1242
+ const job = getTechniqueJob(technique, jobKey);
1243
+ return loadTechniqueJob(technique, job, fetcher);
1244
+ }
1245
+ async function loadLightingTechniqueJobs(techniqueName, options = {}) {
1246
+ const { fetcher } = options ?? {};
1247
+ const technique = getLightingTechnique(techniqueName);
1248
+ const preludeWgsl = await loadTechniquePrelude(technique, fetcher);
1249
+ const jobSources = await Promise.all(
1250
+ technique.jobs.map((job) => loadTechniqueJob(technique, job, fetcher))
1251
+ );
1252
+ const jobs = technique.jobs.map((job, index) => ({
1253
+ wgsl: jobSources[index],
1254
+ label: job.label,
1255
+ sourceName: job.sourceName
1256
+ }));
1257
+ return { preludeWgsl, jobs };
1258
+ }
1259
+ async function loadLightingTechniqueWorkerBundle(techniqueName = defaultLightingTechnique, options = {}) {
1260
+ const technique = getLightingTechnique(techniqueName);
1261
+ const { preludeWgsl, jobs } = await loadLightingTechniqueJobs(
1262
+ technique.name,
1263
+ options
1264
+ );
1265
+ return {
1266
+ technique: technique.name,
1267
+ preludeWgsl,
1268
+ jobs,
1269
+ workerManifest: getLightingTechniqueWorkerManifest(technique.name)
1270
+ };
1271
+ }
1272
+ async function loadLightingPreludeWgsl(options = {}) {
1273
+ const { fetcher } = options ?? {};
1274
+ return loadTechniquePrelude(defaultTechnique, fetcher);
1275
+ }
1276
+ async function loadLightingJobs(options = {}) {
1277
+ return loadLightingTechniqueJobs(defaultLightingTechnique, options);
1278
+ }
1279
+ async function loadLightingProfile(profileName, options = {}) {
1280
+ const profile = getLightingProfile(profileName);
1281
+ const techniques = await Promise.all(
1282
+ profile.techniques.map(async (techniqueName) => {
1283
+ const { preludeWgsl, jobs } = await loadLightingTechniqueJobs(
1284
+ techniqueName,
1285
+ options
1286
+ );
1287
+ return {
1288
+ technique: techniqueName,
1289
+ preludeWgsl,
1290
+ jobs
1291
+ };
1292
+ })
1293
+ );
1294
+ return { profile, techniques };
1295
+ }
1296
+ async function loadLightingProfileWorkerPlan(profileName = defaultLightingProfile, options = {}) {
1297
+ const profile = getLightingProfile(profileName);
1298
+ const techniques = await Promise.all(
1299
+ profile.techniques.map(
1300
+ (techniqueName) => loadLightingTechniqueWorkerBundle(techniqueName, options)
1301
+ )
1302
+ );
1303
+ return {
1304
+ profile,
1305
+ techniques,
1306
+ lightingBandPlan: createLightingBandPlan({ profile: profile.name }),
1307
+ workerManifest: getLightingProfileWorkerManifest(profile.name)
1308
+ };
1309
+ }
1310
+ var import_meta2, __require, baseUrl, techniqueSpecs, lightingTechniques, lightingTechniqueNames, defaultLightingTechnique, profileSpecs, lightingProfiles, lightingProfileNames, defaultLightingProfile, lightingProfileModeOrder, lightingEnvironmentPresetNames, defaultAdaptiveLightingProfilePolicy, lightingDistanceBands, lightingWorkerQueueClass, lightingDebugOwner, environmentLightingPresets, lightingImportanceLevels, lightingBandPolicySpecs, lightingProfileModeEstimatedCostMs, lightingWorkerSpecPresets, lightingWorkerDagSpecs, lightingWorkerManifests, defaultTechnique, lightingPreludeWgslUrl, lightingJobLabels, lightingJobs;
1311
+ var init_dist = __esm({
1312
+ "node_modules/@plasius/gpu-lighting/dist/index.js"() {
1313
+ import_meta2 = {};
1314
+ __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
1315
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
1316
+ }) : x)(function(x) {
1317
+ if (typeof require !== "undefined") return require.apply(this, arguments);
1318
+ throw Error('Dynamic require of "' + x + '" is not supported');
1319
+ });
1320
+ baseUrl = (() => {
1321
+ if (typeof import_meta2.url !== "undefined") {
1322
+ return createModuleBaseUrl(import_meta2.url);
1323
+ }
1324
+ if (typeof __filename !== "undefined" && typeof __require !== "undefined") {
1325
+ const { pathToFileURL } = __require("url");
1326
+ return pathToFileURL(__filename);
1327
+ }
1328
+ const base = typeof process !== "undefined" && process.cwd ? `file://${process.cwd()}/` : "file:///";
1329
+ return new URL("./index.js", base);
1330
+ })();
1331
+ techniqueSpecs = {
1332
+ hybrid: {
1333
+ description: "Lumen-inspired hybrid realtime GI and reflections with radiance cache final gather.",
1334
+ prelude: "prelude.wgsl",
1335
+ jobs: {
1336
+ directLighting: "direct-lighting.job.wgsl",
1337
+ screenTrace: "screen-trace.job.wgsl",
1338
+ radianceCache: "radiance-cache.job.wgsl",
1339
+ finalGather: "final-gather.job.wgsl",
1340
+ reflectionResolve: "reflection-resolve.job.wgsl"
1341
+ }
1342
+ },
1343
+ pathtracer: {
1344
+ description: "Monte Carlo path-traced reference mode with progressive accumulation and denoise stage.",
1345
+ prelude: "prelude.wgsl",
1346
+ jobs: {
1347
+ pathTrace: "pathtrace.job.wgsl",
1348
+ accumulate: "accumulate.job.wgsl",
1349
+ denoise: "denoise.job.wgsl"
1350
+ }
1351
+ },
1352
+ volumetrics: {
1353
+ description: "Froxel volumetric lighting for fog, shafts, and participating media shadows.",
1354
+ prelude: "prelude.wgsl",
1355
+ jobs: {
1356
+ froxelIntegrate: "froxel-integrate.job.wgsl",
1357
+ volumetricShadow: "volumetric-shadow.job.wgsl"
1358
+ }
1359
+ },
1360
+ hdri: {
1361
+ description: "HDRI and IBL precompute passes including irradiance, specular prefilter, and BRDF LUT.",
1362
+ prelude: "prelude.wgsl",
1363
+ jobs: {
1364
+ irradianceConvolution: "irradiance-convolution.job.wgsl",
1365
+ specularPrefilter: "specular-prefilter.job.wgsl",
1366
+ brdfLut: "brdf-lut.job.wgsl"
1367
+ }
1368
+ }
1369
+ };
1370
+ lightingTechniques = Object.freeze(
1371
+ Object.fromEntries(
1372
+ Object.entries(techniqueSpecs).map(([name, spec]) => [
1373
+ name,
1374
+ buildTechnique(name, spec)
1375
+ ])
1376
+ )
1377
+ );
1378
+ lightingTechniqueNames = Object.freeze(Object.keys(lightingTechniques));
1379
+ defaultLightingTechnique = "hybrid";
1380
+ profileSpecs = {
1381
+ realtime: {
1382
+ description: "Primary runtime profile: hybrid GI/reflections with volumetrics and HDRI/IBL.",
1383
+ techniques: ["hybrid", "volumetrics", "hdri"]
1384
+ },
1385
+ hybrid: {
1386
+ description: "Hybrid-focused profile for direct tuning of Lumen-inspired realtime passes.",
1387
+ techniques: ["hybrid", "hdri"]
1388
+ },
1389
+ reference: {
1390
+ description: "Reference quality profile: path tracing plus volumetrics and HDRI/IBL validation.",
1391
+ techniques: ["pathtracer", "volumetrics", "hdri"]
1392
+ }
1393
+ };
1394
+ lightingProfiles = Object.freeze(
1395
+ Object.fromEntries(
1396
+ Object.entries(profileSpecs).map(([name, spec]) => [
1397
+ name,
1398
+ buildProfile(name, spec)
1399
+ ])
1400
+ )
1401
+ );
1402
+ lightingProfileNames = Object.freeze(Object.keys(lightingProfiles));
1403
+ defaultLightingProfile = "realtime";
1404
+ lightingProfileModeOrder = Object.freeze([
1405
+ "realtime",
1406
+ "hybrid",
1407
+ "reference"
1408
+ ]);
1409
+ lightingEnvironmentPresetNames = Object.freeze([
1410
+ "moonlit-harbor",
1411
+ "product-studio",
1412
+ "neutral-studio"
1413
+ ]);
1414
+ defaultAdaptiveLightingProfilePolicy = Object.freeze({
1415
+ preferredProfile: "reference",
1416
+ minimumFrameRate: 30,
1417
+ sampleWindowSize: 4
1418
+ });
1419
+ lightingDistanceBands = Object.freeze([
1420
+ "near",
1421
+ "mid",
1422
+ "far",
1423
+ "horizon"
1424
+ ]);
1425
+ lightingWorkerQueueClass = "lighting";
1426
+ lightingDebugOwner = "lighting";
1427
+ environmentLightingPresets = Object.freeze({
1428
+ "moonlit-harbor": Object.freeze({
1429
+ preset: "moonlit-harbor",
1430
+ environmentMode: 0,
1431
+ environmentIntensity: 0.86,
1432
+ exposure: 1,
1433
+ horizonColor: freezeVec4([0.33, 0.43, 0.53, 1]),
1434
+ zenithColor: freezeVec4([0.035, 0.07, 0.14, 1]),
1435
+ sunDirection: Object.freeze(normalizeVector3([0.22, 0.88, 0.42], [0, 1, 0])),
1436
+ sunColor: freezeVec4([2.1, 2.25, 2.65, 1]),
1437
+ ambientColor: freezeVec4([0.018, 0.023, 0.03, 1])
1438
+ }),
1439
+ "product-studio": Object.freeze({
1440
+ preset: "product-studio",
1441
+ environmentMode: 1,
1442
+ environmentIntensity: 1.05,
1443
+ exposure: 1,
1444
+ horizonColor: freezeVec4([0.52, 0.61, 0.65, 1]),
1445
+ zenithColor: freezeVec4([0.18, 0.22, 0.26, 1]),
1446
+ sunDirection: Object.freeze(normalizeVector3([0.18, 0.93, 0.24], [0, 1, 0])),
1447
+ sunColor: freezeVec4([3.8, 3.55, 2.85, 1]),
1448
+ ambientColor: freezeVec4([0.024, 0.027, 0.03, 1])
1449
+ }),
1450
+ "neutral-studio": Object.freeze({
1451
+ preset: "neutral-studio",
1452
+ environmentMode: 2,
1453
+ environmentIntensity: 0.95,
1454
+ exposure: 1,
1455
+ horizonColor: freezeVec4([0.48, 0.53, 0.55, 1]),
1456
+ zenithColor: freezeVec4([0.24, 0.26, 0.29, 1]),
1457
+ sunDirection: Object.freeze(normalizeVector3([-0.24, 0.86, 0.36], [0, 1, 0])),
1458
+ sunColor: freezeVec4([2.4, 2.35, 2.2, 1]),
1459
+ ambientColor: freezeVec4([0.028, 0.029, 0.03, 1])
1460
+ })
1461
+ });
1462
+ lightingImportanceLevels = Object.freeze([
1463
+ "low",
1464
+ "medium",
1465
+ "high",
1466
+ "critical"
1467
+ ]);
1468
+ lightingBandPolicySpecs = Object.freeze({
1469
+ near: Object.freeze({
1470
+ primaryShadowSource: "ray-traced-primary",
1471
+ assistShadowSources: Object.freeze(["visibility-raster", "shadow-map-assist"]),
1472
+ temporalReuse: "balanced",
1473
+ updateCadenceDivisor: 1,
1474
+ liveObjectShadows: true,
1475
+ impressionOnly: false
1476
+ }),
1477
+ mid: Object.freeze({
1478
+ primaryShadowSource: "selective-raster-and-proxy",
1479
+ assistShadowSources: Object.freeze([
1480
+ "regional-shadow-map",
1481
+ "proxy-caster",
1482
+ "temporal-history"
1483
+ ]),
1484
+ temporalReuse: "aggressive",
1485
+ updateCadenceDivisor: 2,
1486
+ liveObjectShadows: true,
1487
+ impressionOnly: false
1488
+ }),
1489
+ far: Object.freeze({
1490
+ primaryShadowSource: "merged-proxy-casters",
1491
+ assistShadowSources: Object.freeze([
1492
+ "coarse-directional",
1493
+ "semi-static-occlusion"
1494
+ ]),
1495
+ temporalReuse: "high",
1496
+ updateCadenceDivisor: 8,
1497
+ liveObjectShadows: false,
1498
+ impressionOnly: false
1499
+ }),
1500
+ horizon: Object.freeze({
1501
+ primaryShadowSource: "baked-impression",
1502
+ assistShadowSources: Object.freeze(["atmospheric-gradient", "skyline-darkening"]),
1503
+ temporalReuse: "baked",
1504
+ updateCadenceDivisor: 60,
1505
+ liveObjectShadows: false,
1506
+ impressionOnly: true
1507
+ })
1508
+ });
1509
+ lightingProfileModeEstimatedCostMs = Object.freeze({
1510
+ realtime: 4.5,
1511
+ hybrid: 7.5,
1512
+ reference: 12.5
1513
+ });
1514
+ lightingWorkerSpecPresets = {
1515
+ hybrid: {
1516
+ suggestedAllocationIds: [
1517
+ "lighting.hybrid.radiance-cache",
1518
+ "lighting.hybrid.reflection-history",
1519
+ "lighting.hybrid.shadow-atlas"
1520
+ ],
1521
+ jobs: {
1522
+ directLighting: {
1523
+ domain: "lighting",
1524
+ importance: "high",
1525
+ levels: buildWorkerBudgetLevels(
1526
+ "lighting.hybrid.directLighting",
1527
+ lightingWorkerQueueClass,
1528
+ {
1529
+ low: {
1530
+ estimatedCostMs: 0.5,
1531
+ maxDispatchesPerFrame: 1,
1532
+ maxJobsPerDispatch: 64,
1533
+ cadenceDivisor: 2,
1534
+ workgroupScale: 0.5,
1535
+ maxQueueDepth: 128
1536
+ },
1537
+ medium: {
1538
+ estimatedCostMs: 0.9,
1539
+ maxDispatchesPerFrame: 1,
1540
+ maxJobsPerDispatch: 128,
1541
+ cadenceDivisor: 1,
1542
+ workgroupScale: 0.75,
1543
+ maxQueueDepth: 256
1544
+ },
1545
+ high: {
1546
+ estimatedCostMs: 1.2,
1547
+ maxDispatchesPerFrame: 2,
1548
+ maxJobsPerDispatch: 256,
1549
+ cadenceDivisor: 1,
1550
+ workgroupScale: 1,
1551
+ maxQueueDepth: 512
1552
+ }
1553
+ }
1554
+ ),
1555
+ suggestedAllocationIds: ["lighting.hybrid.shadow-atlas"]
1556
+ },
1557
+ screenTrace: {
1558
+ domain: "reflections",
1559
+ importance: "high",
1560
+ levels: buildWorkerBudgetLevels(
1561
+ "lighting.hybrid.screenTrace",
1562
+ lightingWorkerQueueClass,
1563
+ {
1564
+ low: {
1565
+ estimatedCostMs: 0.8,
1566
+ maxDispatchesPerFrame: 1,
1567
+ maxJobsPerDispatch: 32,
1568
+ cadenceDivisor: 3,
1569
+ workgroupScale: 0.4,
1570
+ maxQueueDepth: 96
1571
+ },
1572
+ medium: {
1573
+ estimatedCostMs: 1.5,
1574
+ maxDispatchesPerFrame: 1,
1575
+ maxJobsPerDispatch: 96,
1576
+ cadenceDivisor: 2,
1577
+ workgroupScale: 0.7,
1578
+ maxQueueDepth: 192
1579
+ },
1580
+ high: {
1581
+ estimatedCostMs: 2.2,
1582
+ maxDispatchesPerFrame: 2,
1583
+ maxJobsPerDispatch: 192,
1584
+ cadenceDivisor: 1,
1585
+ workgroupScale: 1,
1586
+ maxQueueDepth: 384
1587
+ }
1588
+ }
1589
+ ),
1590
+ suggestedAllocationIds: ["lighting.hybrid.reflection-history"]
1591
+ },
1592
+ radianceCache: {
1593
+ domain: "lighting",
1594
+ importance: "high",
1595
+ levels: buildWorkerBudgetLevels(
1596
+ "lighting.hybrid.radianceCache",
1597
+ lightingWorkerQueueClass,
1598
+ {
1599
+ low: {
1600
+ estimatedCostMs: 0.7,
1601
+ maxDispatchesPerFrame: 1,
1602
+ maxJobsPerDispatch: 32,
1603
+ cadenceDivisor: 2,
1604
+ workgroupScale: 0.5,
1605
+ maxQueueDepth: 96
1606
+ },
1607
+ medium: {
1608
+ estimatedCostMs: 1.2,
1609
+ maxDispatchesPerFrame: 1,
1610
+ maxJobsPerDispatch: 64,
1611
+ cadenceDivisor: 1,
1612
+ workgroupScale: 0.75,
1613
+ maxQueueDepth: 192
1614
+ },
1615
+ high: {
1616
+ estimatedCostMs: 1.8,
1617
+ maxDispatchesPerFrame: 2,
1618
+ maxJobsPerDispatch: 128,
1619
+ cadenceDivisor: 1,
1620
+ workgroupScale: 1,
1621
+ maxQueueDepth: 256
1622
+ }
1623
+ }
1624
+ ),
1625
+ suggestedAllocationIds: ["lighting.hybrid.radiance-cache"]
1626
+ },
1627
+ finalGather: {
1628
+ domain: "lighting",
1629
+ importance: "critical",
1630
+ levels: buildWorkerBudgetLevels(
1631
+ "lighting.hybrid.finalGather",
1632
+ lightingWorkerQueueClass,
1633
+ {
1634
+ low: {
1635
+ estimatedCostMs: 1,
1636
+ maxDispatchesPerFrame: 1,
1637
+ maxJobsPerDispatch: 48,
1638
+ cadenceDivisor: 2,
1639
+ workgroupScale: 0.5,
1640
+ maxQueueDepth: 128
1641
+ },
1642
+ medium: {
1643
+ estimatedCostMs: 1.8,
1644
+ maxDispatchesPerFrame: 1,
1645
+ maxJobsPerDispatch: 96,
1646
+ cadenceDivisor: 1,
1647
+ workgroupScale: 0.75,
1648
+ maxQueueDepth: 256
1649
+ },
1650
+ high: {
1651
+ estimatedCostMs: 2.6,
1652
+ maxDispatchesPerFrame: 2,
1653
+ maxJobsPerDispatch: 192,
1654
+ cadenceDivisor: 1,
1655
+ workgroupScale: 1,
1656
+ maxQueueDepth: 384
1657
+ }
1658
+ }
1659
+ ),
1660
+ suggestedAllocationIds: [
1661
+ "lighting.hybrid.radiance-cache",
1662
+ "lighting.hybrid.reflection-history"
1663
+ ]
1664
+ },
1665
+ reflectionResolve: {
1666
+ domain: "reflections",
1667
+ importance: "high",
1668
+ levels: buildWorkerBudgetLevels(
1669
+ "lighting.hybrid.reflectionResolve",
1670
+ lightingWorkerQueueClass,
1671
+ {
1672
+ low: {
1673
+ estimatedCostMs: 0.4,
1674
+ maxDispatchesPerFrame: 1,
1675
+ maxJobsPerDispatch: 32,
1676
+ cadenceDivisor: 2,
1677
+ workgroupScale: 0.5,
1678
+ maxQueueDepth: 96
1679
+ },
1680
+ medium: {
1681
+ estimatedCostMs: 0.8,
1682
+ maxDispatchesPerFrame: 1,
1683
+ maxJobsPerDispatch: 64,
1684
+ cadenceDivisor: 1,
1685
+ workgroupScale: 0.75,
1686
+ maxQueueDepth: 192
1687
+ },
1688
+ high: {
1689
+ estimatedCostMs: 1.2,
1690
+ maxDispatchesPerFrame: 1,
1691
+ maxJobsPerDispatch: 128,
1692
+ cadenceDivisor: 1,
1693
+ workgroupScale: 1,
1694
+ maxQueueDepth: 256
1695
+ }
1696
+ }
1697
+ ),
1698
+ suggestedAllocationIds: ["lighting.hybrid.reflection-history"]
1699
+ }
1700
+ }
1701
+ },
1702
+ pathtracer: {
1703
+ suggestedAllocationIds: [
1704
+ "lighting.pathtracer.path-state",
1705
+ "lighting.pathtracer.accumulation",
1706
+ "lighting.pathtracer.denoise-history"
1707
+ ],
1708
+ jobs: {
1709
+ pathTrace: {
1710
+ domain: "lighting",
1711
+ importance: "critical",
1712
+ levels: buildWorkerBudgetLevels(
1713
+ "lighting.pathtracer.pathTrace",
1714
+ lightingWorkerQueueClass,
1715
+ {
1716
+ low: {
1717
+ estimatedCostMs: 1.2,
1718
+ maxDispatchesPerFrame: 1,
1719
+ maxJobsPerDispatch: 16,
1720
+ cadenceDivisor: 3,
1721
+ workgroupScale: 0.45,
1722
+ maxQueueDepth: 48
1723
+ },
1724
+ medium: {
1725
+ estimatedCostMs: 2.3,
1726
+ maxDispatchesPerFrame: 1,
1727
+ maxJobsPerDispatch: 32,
1728
+ cadenceDivisor: 2,
1729
+ workgroupScale: 0.7,
1730
+ maxQueueDepth: 96
1731
+ },
1732
+ high: {
1733
+ estimatedCostMs: 3.8,
1734
+ maxDispatchesPerFrame: 2,
1735
+ maxJobsPerDispatch: 64,
1736
+ cadenceDivisor: 1,
1737
+ workgroupScale: 1,
1738
+ maxQueueDepth: 128
1739
+ }
1740
+ }
1741
+ ),
1742
+ suggestedAllocationIds: ["lighting.pathtracer.path-state"]
1743
+ },
1744
+ accumulate: {
1745
+ domain: "lighting",
1746
+ importance: "medium",
1747
+ levels: buildWorkerBudgetLevels(
1748
+ "lighting.pathtracer.accumulate",
1749
+ lightingWorkerQueueClass,
1750
+ {
1751
+ low: {
1752
+ estimatedCostMs: 0.4,
1753
+ maxDispatchesPerFrame: 1,
1754
+ maxJobsPerDispatch: 16,
1755
+ cadenceDivisor: 2,
1756
+ workgroupScale: 0.5,
1757
+ maxQueueDepth: 32
1758
+ },
1759
+ medium: {
1760
+ estimatedCostMs: 0.8,
1761
+ maxDispatchesPerFrame: 1,
1762
+ maxJobsPerDispatch: 32,
1763
+ cadenceDivisor: 1,
1764
+ workgroupScale: 0.75,
1765
+ maxQueueDepth: 64
1766
+ },
1767
+ high: {
1768
+ estimatedCostMs: 1.1,
1769
+ maxDispatchesPerFrame: 1,
1770
+ maxJobsPerDispatch: 64,
1771
+ cadenceDivisor: 1,
1772
+ workgroupScale: 1,
1773
+ maxQueueDepth: 96
1774
+ }
1775
+ }
1776
+ ),
1777
+ suggestedAllocationIds: ["lighting.pathtracer.accumulation"]
1778
+ },
1779
+ denoise: {
1780
+ domain: "post-processing",
1781
+ importance: "high",
1782
+ levels: buildWorkerBudgetLevels(
1783
+ "lighting.pathtracer.denoise",
1784
+ lightingWorkerQueueClass,
1785
+ {
1786
+ low: {
1787
+ estimatedCostMs: 0.4,
1788
+ maxDispatchesPerFrame: 1,
1789
+ maxJobsPerDispatch: 16,
1790
+ cadenceDivisor: 2,
1791
+ workgroupScale: 0.5,
1792
+ maxQueueDepth: 32
1793
+ },
1794
+ medium: {
1795
+ estimatedCostMs: 0.9,
1796
+ maxDispatchesPerFrame: 1,
1797
+ maxJobsPerDispatch: 32,
1798
+ cadenceDivisor: 1,
1799
+ workgroupScale: 0.75,
1800
+ maxQueueDepth: 64
1801
+ },
1802
+ high: {
1803
+ estimatedCostMs: 1.4,
1804
+ maxDispatchesPerFrame: 1,
1805
+ maxJobsPerDispatch: 64,
1806
+ cadenceDivisor: 1,
1807
+ workgroupScale: 1,
1808
+ maxQueueDepth: 96
1809
+ }
1810
+ }
1811
+ ),
1812
+ suggestedAllocationIds: ["lighting.pathtracer.denoise-history"]
1813
+ }
1814
+ }
1815
+ },
1816
+ volumetrics: {
1817
+ suggestedAllocationIds: [
1818
+ "lighting.volumetrics.froxel-grid",
1819
+ "lighting.volumetrics.shadow-history"
1820
+ ],
1821
+ jobs: {
1822
+ froxelIntegrate: {
1823
+ domain: "volumetrics",
1824
+ importance: "high",
1825
+ levels: buildWorkerBudgetLevels(
1826
+ "lighting.volumetrics.froxelIntegrate",
1827
+ lightingWorkerQueueClass,
1828
+ {
1829
+ low: {
1830
+ estimatedCostMs: 0.6,
1831
+ maxDispatchesPerFrame: 1,
1832
+ maxJobsPerDispatch: 32,
1833
+ cadenceDivisor: 2,
1834
+ workgroupScale: 0.5,
1835
+ maxQueueDepth: 96
1836
+ },
1837
+ medium: {
1838
+ estimatedCostMs: 1.1,
1839
+ maxDispatchesPerFrame: 1,
1840
+ maxJobsPerDispatch: 64,
1841
+ cadenceDivisor: 1,
1842
+ workgroupScale: 0.75,
1843
+ maxQueueDepth: 192
1844
+ },
1845
+ high: {
1846
+ estimatedCostMs: 1.7,
1847
+ maxDispatchesPerFrame: 2,
1848
+ maxJobsPerDispatch: 128,
1849
+ cadenceDivisor: 1,
1850
+ workgroupScale: 1,
1851
+ maxQueueDepth: 256
1852
+ }
1853
+ }
1854
+ ),
1855
+ suggestedAllocationIds: ["lighting.volumetrics.froxel-grid"]
1856
+ },
1857
+ volumetricShadow: {
1858
+ domain: "volumetrics",
1859
+ importance: "high",
1860
+ levels: buildWorkerBudgetLevels(
1861
+ "lighting.volumetrics.volumetricShadow",
1862
+ lightingWorkerQueueClass,
1863
+ {
1864
+ low: {
1865
+ estimatedCostMs: 0.5,
1866
+ maxDispatchesPerFrame: 1,
1867
+ maxJobsPerDispatch: 24,
1868
+ cadenceDivisor: 2,
1869
+ workgroupScale: 0.5,
1870
+ maxQueueDepth: 64
1871
+ },
1872
+ medium: {
1873
+ estimatedCostMs: 0.9,
1874
+ maxDispatchesPerFrame: 1,
1875
+ maxJobsPerDispatch: 48,
1876
+ cadenceDivisor: 1,
1877
+ workgroupScale: 0.75,
1878
+ maxQueueDepth: 128
1879
+ },
1880
+ high: {
1881
+ estimatedCostMs: 1.3,
1882
+ maxDispatchesPerFrame: 1,
1883
+ maxJobsPerDispatch: 96,
1884
+ cadenceDivisor: 1,
1885
+ workgroupScale: 1,
1886
+ maxQueueDepth: 192
1887
+ }
1888
+ }
1889
+ ),
1890
+ suggestedAllocationIds: ["lighting.volumetrics.shadow-history"]
1891
+ }
1892
+ }
1893
+ },
1894
+ hdri: {
1895
+ suggestedAllocationIds: [
1896
+ "lighting.hdri.cubemap",
1897
+ "lighting.hdri.prefilter",
1898
+ "lighting.hdri.brdf-lut"
1899
+ ],
1900
+ jobs: {
1901
+ irradianceConvolution: {
1902
+ domain: "lighting",
1903
+ importance: "medium",
1904
+ levels: buildWorkerBudgetLevels(
1905
+ "lighting.hdri.irradianceConvolution",
1906
+ lightingWorkerQueueClass,
1907
+ {
1908
+ low: {
1909
+ estimatedCostMs: 0.3,
1910
+ maxDispatchesPerFrame: 1,
1911
+ maxJobsPerDispatch: 8,
1912
+ cadenceDivisor: 3,
1913
+ workgroupScale: 0.5,
1914
+ maxQueueDepth: 16
1915
+ },
1916
+ medium: {
1917
+ estimatedCostMs: 0.5,
1918
+ maxDispatchesPerFrame: 1,
1919
+ maxJobsPerDispatch: 16,
1920
+ cadenceDivisor: 2,
1921
+ workgroupScale: 0.75,
1922
+ maxQueueDepth: 32
1923
+ },
1924
+ high: {
1925
+ estimatedCostMs: 0.8,
1926
+ maxDispatchesPerFrame: 1,
1927
+ maxJobsPerDispatch: 32,
1928
+ cadenceDivisor: 1,
1929
+ workgroupScale: 1,
1930
+ maxQueueDepth: 48
1931
+ }
1932
+ }
1933
+ ),
1934
+ suggestedAllocationIds: ["lighting.hdri.cubemap"]
1935
+ },
1936
+ specularPrefilter: {
1937
+ domain: "lighting",
1938
+ importance: "medium",
1939
+ levels: buildWorkerBudgetLevels(
1940
+ "lighting.hdri.specularPrefilter",
1941
+ lightingWorkerQueueClass,
1942
+ {
1943
+ low: {
1944
+ estimatedCostMs: 0.4,
1945
+ maxDispatchesPerFrame: 1,
1946
+ maxJobsPerDispatch: 8,
1947
+ cadenceDivisor: 3,
1948
+ workgroupScale: 0.5,
1949
+ maxQueueDepth: 16
1950
+ },
1951
+ medium: {
1952
+ estimatedCostMs: 0.7,
1953
+ maxDispatchesPerFrame: 1,
1954
+ maxJobsPerDispatch: 16,
1955
+ cadenceDivisor: 2,
1956
+ workgroupScale: 0.75,
1957
+ maxQueueDepth: 32
1958
+ },
1959
+ high: {
1960
+ estimatedCostMs: 1,
1961
+ maxDispatchesPerFrame: 1,
1962
+ maxJobsPerDispatch: 32,
1963
+ cadenceDivisor: 1,
1964
+ workgroupScale: 1,
1965
+ maxQueueDepth: 48
1966
+ }
1967
+ }
1968
+ ),
1969
+ suggestedAllocationIds: ["lighting.hdri.prefilter"]
1970
+ },
1971
+ brdfLut: {
1972
+ domain: "lighting",
1973
+ importance: "low",
1974
+ levels: buildWorkerBudgetLevels(
1975
+ "lighting.hdri.brdfLut",
1976
+ lightingWorkerQueueClass,
1977
+ {
1978
+ low: {
1979
+ estimatedCostMs: 0.2,
1980
+ maxDispatchesPerFrame: 1,
1981
+ maxJobsPerDispatch: 4,
1982
+ cadenceDivisor: 3,
1983
+ workgroupScale: 0.5,
1984
+ maxQueueDepth: 8
1985
+ },
1986
+ medium: {
1987
+ estimatedCostMs: 0.4,
1988
+ maxDispatchesPerFrame: 1,
1989
+ maxJobsPerDispatch: 8,
1990
+ cadenceDivisor: 2,
1991
+ workgroupScale: 0.75,
1992
+ maxQueueDepth: 16
1993
+ },
1994
+ high: {
1995
+ estimatedCostMs: 0.6,
1996
+ maxDispatchesPerFrame: 1,
1997
+ maxJobsPerDispatch: 16,
1998
+ cadenceDivisor: 1,
1999
+ workgroupScale: 1,
2000
+ maxQueueDepth: 24
2001
+ }
2002
+ }
2003
+ ),
2004
+ suggestedAllocationIds: ["lighting.hdri.brdf-lut"]
2005
+ }
2006
+ }
2007
+ }
2008
+ };
2009
+ lightingWorkerDagSpecs = {
2010
+ hybrid: {
2011
+ directLighting: { priority: 4, dependencies: [] },
2012
+ screenTrace: { priority: 3, dependencies: [] },
2013
+ radianceCache: { priority: 4, dependencies: ["directLighting"] },
2014
+ finalGather: { priority: 2, dependencies: ["radianceCache", "screenTrace"] },
2015
+ reflectionResolve: {
2016
+ priority: 1,
2017
+ dependencies: ["screenTrace", "finalGather"]
2018
+ }
2019
+ },
2020
+ pathtracer: {
2021
+ pathTrace: { priority: 4, dependencies: [] },
2022
+ accumulate: { priority: 3, dependencies: ["pathTrace"] },
2023
+ denoise: { priority: 2, dependencies: ["accumulate"] }
2024
+ },
2025
+ volumetrics: {
2026
+ volumetricShadow: { priority: 3, dependencies: [] },
2027
+ froxelIntegrate: { priority: 2, dependencies: ["volumetricShadow"] }
2028
+ },
2029
+ hdri: {
2030
+ irradianceConvolution: { priority: 3, dependencies: [] },
2031
+ specularPrefilter: { priority: 3, dependencies: [] },
2032
+ brdfLut: {
2033
+ priority: 2,
2034
+ dependencies: ["irradianceConvolution", "specularPrefilter"]
2035
+ }
2036
+ }
2037
+ };
2038
+ lightingWorkerManifests = Object.freeze(
2039
+ Object.fromEntries(
2040
+ Object.entries(lightingTechniques).map(([name, technique]) => [
2041
+ name,
2042
+ buildLightingWorkerManifest(name, technique)
2043
+ ])
2044
+ )
2045
+ );
2046
+ defaultTechnique = getLightingTechnique(defaultLightingTechnique);
2047
+ lightingPreludeWgslUrl = defaultTechnique.preludeUrl;
2048
+ lightingJobLabels = Object.freeze(
2049
+ Object.fromEntries(defaultTechnique.jobs.map((job) => [job.key, job.label]))
2050
+ );
2051
+ lightingJobs = defaultTechnique.jobs.map((job) => ({
2052
+ label: job.label,
2053
+ url: job.url,
2054
+ sourceName: job.sourceName
2055
+ }));
2056
+ }
2057
+ });
2058
+
620
2059
  // src/product-studio-runtime.js
621
2060
  var product_studio_runtime_exports = {};
622
2061
  __export(product_studio_runtime_exports, {
@@ -931,7 +2370,7 @@ async function resolveWavefrontLightingOptions(options) {
931
2370
  environmentColor: [0.35, 0.43, 0.49, 1],
932
2371
  ambientColor: [0.02, 0.024, 0.028, 1]
933
2372
  };
934
- const lightingLoader = typeof options.__lightingLoader === "function" ? options.__lightingLoader : () => import("@plasius/gpu-lighting").catch(() => null);
2373
+ const lightingLoader = typeof options.__lightingLoader === "function" ? options.__lightingLoader : () => Promise.resolve().then(() => (init_dist(), dist_exports)).catch(() => null);
935
2374
  const lightingModule = await lightingLoader();
936
2375
  if (typeof lightingModule?.createWavefrontEnvironmentLightingOptions !== "function") {
937
2376
  return fallback;
@@ -999,6 +2438,7 @@ async function mountGpuProductStudio(options = {}, featureFlags = null) {
999
2438
  return Object.freeze({
1000
2439
  state,
1001
2440
  model,
2441
+ productModel: model,
1002
2442
  canvas,
1003
2443
  renderer,
1004
2444
  meshes,
@@ -4824,10 +6264,10 @@ __export(index_exports, {
4824
6264
  translateGpuSharedText: () => translateGpuSharedText
4825
6265
  });
4826
6266
  module.exports = __toCommonJS(index_exports);
6267
+ init_feature_flags();
4827
6268
  init_asset_url();
4828
6269
  init_i18n();
4829
6270
  init_en_GB();
4830
- init_feature_flags();
4831
6271
  init_product_studio_runtime();
4832
6272
  var showcaseFocusModes2 = Object.freeze([
4833
6273
  "integrated",
@@ -4843,9 +6283,32 @@ async function loadGltfModel2(url) {
4843
6283
  const module2 = await Promise.resolve().then(() => (init_gltf_loader(), gltf_loader_exports));
4844
6284
  return module2.loadGltfModel(url);
4845
6285
  }
6286
+ function isProductStudioFeatureEnabled(featureFlags) {
6287
+ if (typeof featureFlags?.get === "function") {
6288
+ return featureFlags.get(GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE) === true;
6289
+ }
6290
+ const direct = featureFlags?.[GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE];
6291
+ if (typeof direct === "boolean") {
6292
+ return direct;
6293
+ }
6294
+ const flagsValue = featureFlags?.flags?.[GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE];
6295
+ if (typeof flagsValue === "boolean") {
6296
+ return flagsValue;
6297
+ }
6298
+ const enabledValue = featureFlags?.enabled?.[GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE];
6299
+ if (typeof enabledValue === "boolean") {
6300
+ return enabledValue;
6301
+ }
6302
+ return false;
6303
+ }
4846
6304
  async function mountGpuShowcase2(options = {}) {
4847
6305
  const demoMode = options.demoMode ?? options.mode;
4848
6306
  if (demoMode === "product-studio" || demoMode === "product" || demoMode === "studio" || demoMode === "eames") {
6307
+ if (!isProductStudioFeatureEnabled(options.__featureFlags)) {
6308
+ throw new Error(
6309
+ `${GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE} must be enabled before Product Studio can mount.`
6310
+ );
6311
+ }
4849
6312
  const productRuntimeLoader = typeof options.__productRuntimeLoader === "function" ? options.__productRuntimeLoader : () => Promise.resolve().then(() => (init_product_studio_runtime(), product_studio_runtime_exports));
4850
6313
  const productModule = await productRuntimeLoader();
4851
6314
  if (typeof productModule.mountGpuProductStudio !== "function") {