@react-three/fiber 10.0.0-canary.2c50459 → 10.0.0-canary.4a21acb

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.
@@ -63,13 +63,16 @@ const WebGLRenderer = class WebGLRenderer2 {
63
63
  }
64
64
  };
65
65
  const WebGLRenderTarget = null;
66
+ const WebGLCubeRenderTarget = null;
66
67
 
67
68
  const THREE = /*#__PURE__*/_mergeNamespaces({
68
69
  __proto__: null,
70
+ CubeRenderTargetCompat: webgpu.CubeRenderTarget,
69
71
  Inspector: Inspector_js.Inspector,
70
72
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
71
73
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
72
74
  RenderTargetCompat: webgpu.RenderTarget,
75
+ WebGLCubeRenderTarget: WebGLCubeRenderTarget,
73
76
  WebGLRenderTarget: WebGLRenderTarget,
74
77
  WebGLRenderer: WebGLRenderer
75
78
  }, [webgpu__namespace]);
@@ -343,15 +346,16 @@ function EnvironmentPortal({
343
346
  preset = void 0,
344
347
  extensions
345
348
  }) {
346
- const gl = fiber.useThree((state) => state.gl);
349
+ const renderer = fiber.useThree((state) => state.renderer);
350
+ const isLegacy = fiber.useThree((state) => state.isLegacy);
347
351
  const defaultScene = fiber.useThree((state) => state.scene);
348
352
  const camera = React__namespace.useRef(null);
349
353
  const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
350
354
  const fbo = React__namespace.useMemo(() => {
351
- const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
355
+ const fbo2 = isLegacy ? new WebGLCubeRenderTarget(resolution) : new webgpu.CubeRenderTarget(resolution);
352
356
  fbo2.texture.type = webgpu.HalfFloatType;
353
357
  return fbo2;
354
- }, [resolution]);
358
+ }, [resolution, isLegacy]);
355
359
  React__namespace.useEffect(() => {
356
360
  return () => {
357
361
  fbo.dispose();
@@ -359,10 +363,10 @@ function EnvironmentPortal({
359
363
  }, [fbo]);
360
364
  React__namespace.useLayoutEffect(() => {
361
365
  if (frames === 1) {
362
- const autoClear = gl.autoClear;
363
- gl.autoClear = true;
364
- camera.current.update(gl, virtualScene);
365
- gl.autoClear = autoClear;
366
+ const autoClear = renderer.autoClear;
367
+ renderer.autoClear = true;
368
+ camera.current.update(renderer, virtualScene);
369
+ renderer.autoClear = autoClear;
366
370
  }
367
371
  return setEnvProps(background, scene, defaultScene, fbo.texture, {
368
372
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -379,7 +383,7 @@ function EnvironmentPortal({
379
383
  defaultScene,
380
384
  background,
381
385
  frames,
382
- gl,
386
+ renderer,
383
387
  blur,
384
388
  backgroundBlurriness,
385
389
  backgroundIntensity,
@@ -390,10 +394,10 @@ function EnvironmentPortal({
390
394
  let count = 1;
391
395
  fiber.useFrame(() => {
392
396
  if (frames === Infinity || count < frames) {
393
- const autoClear = gl.autoClear;
394
- gl.autoClear = true;
395
- camera.current.update(gl, virtualScene);
396
- gl.autoClear = autoClear;
397
+ const autoClear = renderer.autoClear;
398
+ renderer.autoClear = true;
399
+ camera.current.update(renderer, virtualScene);
400
+ renderer.autoClear = autoClear;
397
401
  count++;
398
402
  }
399
403
  });
@@ -636,8 +640,7 @@ function prepare(target, root, type, props) {
636
640
  object,
637
641
  eventCount: 0,
638
642
  handlers: {},
639
- isHidden: false,
640
- deferredRefs: []
643
+ isHidden: false
641
644
  };
642
645
  if (object) object.__r3f = instance;
643
646
  }
@@ -794,6 +797,17 @@ function unregisterVisibility(store, object) {
794
797
  internal.visibilityRegistry.delete(object.uuid);
795
798
  internal.occlusionCache.delete(object);
796
799
  }
800
+ function updateVisibilityHandlers(store, object, handlers) {
801
+ const { internal } = store.getState();
802
+ const entry = internal.visibilityRegistry.get(object.uuid);
803
+ if (!entry) return false;
804
+ entry.handlers = handlers;
805
+ if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
806
+ object.occlusionTest = true;
807
+ enableOcclusion(store);
808
+ }
809
+ return true;
810
+ }
797
811
  function checkVisibility(state) {
798
812
  const { internal, camera } = state;
799
813
  const registry = internal.visibilityRegistry;
@@ -981,10 +995,7 @@ function applyProps(object, props) {
981
995
  continue;
982
996
  }
983
997
  if (value === void 0) continue;
984
- if (isFromRef(value)) {
985
- instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
986
- continue;
987
- }
998
+ if (isFromRef(value)) continue;
988
999
  if (isOnce(value)) {
989
1000
  if (instance?.appliedOnce?.has(prop)) continue;
990
1001
  if (instance) {
@@ -1020,6 +1031,19 @@ function applyProps(object, props) {
1020
1031
  } else if (isVectorLike(target) && is.num(value)) {
1021
1032
  if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
1022
1033
  else target.set(value);
1034
+ } else if (root instanceof webgpu.ShaderMaterial && key === "uniforms" && is.obj(value)) {
1035
+ if (!is.obj(root.uniforms)) root.uniforms = {};
1036
+ const uniforms = root.uniforms;
1037
+ const nextUniforms = value;
1038
+ for (const name in nextUniforms) {
1039
+ const uniform = nextUniforms[name];
1040
+ const targetUniform = uniforms[name];
1041
+ if (targetUniform) Object.assign(targetUniform, uniform);
1042
+ else {
1043
+ const nextUniform = uniform instanceof webgpu.Uniform ? uniform.clone() : new webgpu.Uniform(uniform.value);
1044
+ uniforms[name] = nextUniform;
1045
+ }
1046
+ }
1023
1047
  } else {
1024
1048
  root[key] = value;
1025
1049
  if (key.endsWith("Node") && root.isMaterial) {
@@ -1038,6 +1062,9 @@ function applyProps(object, props) {
1038
1062
  if (instance.eventCount && object2.raycast !== null) {
1039
1063
  rootState.internal.interaction.push(object2);
1040
1064
  }
1065
+ }
1066
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
1067
+ const object2 = instance.object;
1041
1068
  const root = findInitialRoot(instance);
1042
1069
  const visibilityHandlers = {
1043
1070
  onFramed: instance.handlers.onFramed,
@@ -1045,7 +1072,9 @@ function applyProps(object, props) {
1045
1072
  onVisible: instance.handlers.onVisible
1046
1073
  };
1047
1074
  if (hasVisibilityHandlers(visibilityHandlers)) {
1048
- registerVisibility(root, object2, visibilityHandlers);
1075
+ if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
1076
+ registerVisibility(root, object2, visibilityHandlers);
1077
+ }
1049
1078
  } else {
1050
1079
  unregisterVisibility(root, object2);
1051
1080
  }
@@ -1573,6 +1602,7 @@ function createPointerEvents(store) {
1573
1602
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1574
1603
  }
1575
1604
  },
1605
+ // PREVIEW (A8): the XR pointer registration API may change before stable.
1576
1606
  registerPointer: (config) => {
1577
1607
  const pointerId = nextXRPointerId++;
1578
1608
  xrPointers.set(pointerId, config);
@@ -1659,6 +1689,13 @@ function notifyAlpha({ message, link }) {
1659
1689
  }
1660
1690
  }
1661
1691
 
1692
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
1693
+ function isInternalRendererAccess(stack, callerDepth = 2) {
1694
+ if (!stack) return false;
1695
+ const caller = stack.split("\n")[callerDepth] ?? "";
1696
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
1697
+ }
1698
+
1662
1699
  const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1663
1700
  const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
1664
1701
  const createStore = (invalidate, advance) => {
@@ -1791,6 +1828,7 @@ const createStore = (invalidate, advance) => {
1791
1828
  }),
1792
1829
  setFrameloop: (frameloop = "always") => {
1793
1830
  set(() => ({ frameloop }));
1831
+ scheduler.getScheduler().frameloop = frameloop;
1794
1832
  },
1795
1833
  setError: (error) => set(() => ({ error })),
1796
1834
  error: null,
@@ -1858,8 +1896,7 @@ const createStore = (invalidate, advance) => {
1858
1896
  const currentState = rootStore.getState();
1859
1897
  if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1860
1898
  const stack = new Error().stack || "";
1861
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1862
- if (!isInternalAccess) {
1899
+ if (!isInternalRendererAccess(stack)) {
1863
1900
  const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1864
1901
  notifyDepreciated({
1865
1902
  heading: "Accessing state.gl in WebGPU mode",
@@ -1972,19 +2009,25 @@ function loadingFn(extensions, onProgress) {
1972
2009
  );
1973
2010
  };
1974
2011
  }
1975
- function useLoader(loader, input, extensions, onProgress) {
1976
- const keys = Array.isArray(input) ? input : [input];
2012
+ function useLoader(loader, input, extensions, onProgress, cacheKey) {
2013
+ const inputs = Array.isArray(input) ? input : [input];
2014
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1977
2015
  const fn = loadingFn(extensions, onProgress);
1978
- const results = keys.map((key) => suspendReact.suspend(fn, [loader, key], { equal: is.equ }));
2016
+ const results = cacheKeys.map(
2017
+ (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
2018
+ );
1979
2019
  return Array.isArray(input) ? results : results[0];
1980
2020
  }
1981
- useLoader.preload = function(loader, input, extensions, onProgress) {
1982
- const keys = Array.isArray(input) ? input : [input];
1983
- keys.forEach((key) => suspendReact.preload(loadingFn(extensions, onProgress), [loader, key]));
2021
+ useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
2022
+ const inputs = Array.isArray(input) ? input : [input];
2023
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2024
+ cacheKeys.forEach(
2025
+ (key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
2026
+ );
1984
2027
  };
1985
- useLoader.clear = function(loader, input) {
1986
- const keys = Array.isArray(input) ? input : [input];
1987
- keys.forEach((key) => suspendReact.clear([loader, key]));
2028
+ useLoader.clear = function(loader, input, cacheKey) {
2029
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2030
+ cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
1988
2031
  };
1989
2032
  useLoader.loader = getLoader;
1990
2033
 
@@ -2404,7 +2447,7 @@ function advance(timestamp) {
2404
2447
  scheduler.getScheduler().step(timestamp);
2405
2448
  }
2406
2449
 
2407
- const version = "10.0.0-alpha.2";
2450
+ const version = "10.0.0-alpha.3";
2408
2451
  const packageData = {
2409
2452
  version: version};
2410
2453
 
@@ -13843,6 +13886,7 @@ function swapInstances() {
13843
13886
  }
13844
13887
  }
13845
13888
  if (instance.isHidden) unhideInstance(instance);
13889
+ if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
13846
13890
  if (instance.object.__r3f) delete instance.object.__r3f;
13847
13891
  if (instance.type !== "primitive") disposeOnIdle(instance.object);
13848
13892
  }
@@ -13855,6 +13899,8 @@ function swapInstances() {
13855
13899
  instance.object.__r3f = instance;
13856
13900
  setFiberRef(fiber, instance.object);
13857
13901
  delete instance.appliedOnce;
13902
+ instance.eventCount = 0;
13903
+ instance.handlers = {};
13858
13904
  applyProps(instance.object, instance.props);
13859
13905
  if (instance.props.attach) {
13860
13906
  attach(parent, instance);
@@ -14070,11 +14116,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
14070
14116
  cloneMutableTextInstance(textInstance) {
14071
14117
  return textInstance;
14072
14118
  },
14119
+ // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14120
+ // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14121
+ // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14122
+ // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14123
+ // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14124
+ // Guarded by a test asserting a full render + unmount never reaches them.
14073
14125
  cloneRootViewTransitionContainer(_rootContainer) {
14074
- throw new Error("Not implemented.");
14075
14126
  },
14076
14127
  removeRootViewTransitionClone(_rootContainer, _clone) {
14077
- throw new Error("Not implemented.");
14078
14128
  },
14079
14129
  // https://github.com/facebook/react/pull/32465
14080
14130
  createFragmentInstance: (_fiber) => null,
@@ -14159,6 +14209,7 @@ function createRoot(canvas) {
14159
14209
  const lastConfiguredProps = {};
14160
14210
  let configured = false;
14161
14211
  let pending = null;
14212
+ let rendererSetup = null;
14162
14213
  return {
14163
14214
  async configure(props = {}) {
14164
14215
  let resolve;
@@ -14189,7 +14240,7 @@ function createRoot(canvas) {
14189
14240
  forceEven
14190
14241
  } = props;
14191
14242
  const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
14192
- const state = store.getState();
14243
+ let state = store.getState();
14193
14244
  const defaultGPUProps = {
14194
14245
  canvas,
14195
14246
  antialias: true
@@ -14203,52 +14254,64 @@ function createRoot(canvas) {
14203
14254
  throw new Error("Cannot use both gl and renderer props at the same time");
14204
14255
  }
14205
14256
  let renderer = state.internal.actualRenderer;
14206
- if (primaryCanvas && !state.internal.actualRenderer) {
14207
- const primary = await waitForPrimary(primaryCanvas);
14208
- renderer = primary.renderer;
14209
- state.internal.actualRenderer = renderer;
14210
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14211
- primary.store.setState((prev) => ({
14212
- internal: { ...prev.internal, isMultiCanvas: true }
14213
- }));
14214
- state.set((prev) => ({
14215
- webGPUSupported: primary.store.getState().webGPUSupported,
14216
- renderer,
14217
- primaryStore: primary.store,
14218
- internal: {
14219
- ...prev.internal,
14220
- canvasTarget,
14221
- isMultiCanvas: true,
14222
- isSecondary: true,
14223
- targetId: primaryCanvas
14224
- }
14225
- }));
14226
- } else if (!state.internal.actualRenderer) {
14227
- renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
14228
- if (!renderer.hasInitialized?.()) {
14229
- const size2 = computeInitialSize(canvas, propsSize);
14230
- if (size2.width > 0 && size2.height > 0) {
14231
- const pixelRatio = calculateDpr(dpr);
14232
- canvas.width = size2.width * pixelRatio;
14233
- canvas.height = size2.height * pixelRatio;
14234
- }
14235
- await renderer.init();
14236
- }
14237
- const backend = renderer.backend;
14238
- const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14239
- state.internal.actualRenderer = renderer;
14240
- state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14241
- if (canvasId && !state.internal.isSecondary) {
14242
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14243
- const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14244
- state.set((prev) => ({
14245
- internal: {
14246
- ...prev.internal,
14247
- canvasTarget,
14248
- unregisterPrimary
14257
+ if (!state.internal.actualRenderer) {
14258
+ if (!rendererSetup) {
14259
+ rendererSetup = (async () => {
14260
+ if (primaryCanvas) {
14261
+ const primary = await waitForPrimary(primaryCanvas);
14262
+ renderer = primary.renderer;
14263
+ state.internal.actualRenderer = renderer;
14264
+ const canvasTarget = new webgpu.CanvasTarget(canvas);
14265
+ primary.store.setState((prev) => ({
14266
+ internal: { ...prev.internal, isMultiCanvas: true }
14267
+ }));
14268
+ state.set((prev) => ({
14269
+ webGPUSupported: primary.store.getState().webGPUSupported,
14270
+ renderer,
14271
+ primaryStore: primary.store,
14272
+ internal: {
14273
+ ...prev.internal,
14274
+ canvasTarget,
14275
+ isMultiCanvas: true,
14276
+ isSecondary: true,
14277
+ targetId: primaryCanvas
14278
+ }
14279
+ }));
14280
+ } else {
14281
+ renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
14282
+ if (!renderer.hasInitialized?.()) {
14283
+ const size2 = computeInitialSize(canvas, propsSize);
14284
+ if (size2.width > 0 && size2.height > 0) {
14285
+ const pixelRatio = calculateDpr(dpr);
14286
+ canvas.width = size2.width * pixelRatio;
14287
+ canvas.height = size2.height * pixelRatio;
14288
+ }
14289
+ await renderer.init();
14290
+ }
14291
+ const backend = renderer.backend;
14292
+ const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14293
+ state.internal.actualRenderer = renderer;
14294
+ state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14295
+ if (canvasId && !state.internal.isSecondary) {
14296
+ const canvasTarget = new webgpu.CanvasTarget(canvas);
14297
+ const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14298
+ state.set((prev) => ({
14299
+ internal: {
14300
+ ...prev.internal,
14301
+ canvasTarget,
14302
+ unregisterPrimary
14303
+ }
14304
+ }));
14305
+ }
14249
14306
  }
14250
- }));
14307
+ })().catch((err) => {
14308
+ rendererSetup = null;
14309
+ throw err;
14310
+ });
14251
14311
  }
14312
+ await rendererSetup;
14313
+ state = store.getState();
14314
+ renderer = state.internal.actualRenderer;
14252
14315
  }
14253
14316
  let raycaster = state.raycaster;
14254
14317
  if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
@@ -14598,7 +14661,8 @@ function unmountComponentAtNode(canvas, callback) {
14598
14661
  dispose(state.scene);
14599
14662
  _roots.delete(canvas);
14600
14663
  if (callback) callback(canvas);
14601
- } catch {
14664
+ } catch (error) {
14665
+ console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
14602
14666
  }
14603
14667
  }, 500);
14604
14668
  }
@@ -14661,6 +14725,7 @@ function PortalInner({ state = {}, children, container }) {
14661
14725
  viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
14662
14726
  if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14663
14727
  }
14728
+ const set = injectState.set;
14664
14729
  return {
14665
14730
  // The intersect consists of the previous root state
14666
14731
  ...rootState,
@@ -14679,7 +14744,7 @@ function PortalInner({ state = {}, children, container }) {
14679
14744
  size: resolvedSize,
14680
14745
  viewport: { ...rootState.viewport, ...viewport },
14681
14746
  // Layers are allowed to override events
14682
- setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14747
+ setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14683
14748
  // Container for child attachment - the portalScene (injected or container itself)
14684
14749
  internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
14685
14750
  };
@@ -14738,14 +14803,146 @@ function parseBackground(background) {
14738
14803
  return null;
14739
14804
  }
14740
14805
 
14806
+ function parseRendererConfig(rendererProp) {
14807
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14808
+ if (isRendererConfig) {
14809
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14810
+ return {
14811
+ primaryCanvas: pc,
14812
+ scheduler: sc,
14813
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14814
+ };
14815
+ }
14816
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14817
+ }
14818
+
14819
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14820
+ const ROOT_SCOPE = "";
14821
+ const registries = /* @__PURE__ */ new WeakMap();
14822
+ function getKindRegistry(store, kind) {
14823
+ let registry = registries.get(store);
14824
+ if (!registry) {
14825
+ registry = {
14826
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14827
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14828
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14829
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14830
+ };
14831
+ registries.set(store, registry);
14832
+ }
14833
+ return registry[kind];
14834
+ }
14835
+ function resolvePrimary$1(store) {
14836
+ return store.getState().primaryStore ?? store;
14837
+ }
14838
+ function isScopeValid(store, kind, scopeKey) {
14839
+ return getKindRegistry(store, kind).valid.has(scopeKey);
14840
+ }
14841
+ function peekStaged(store, kind, scopeKey) {
14842
+ return getKindRegistry(store, kind).staged.get(scopeKey);
14843
+ }
14844
+ function stageEntries(store, kind, scopeKey, entries) {
14845
+ const { staged } = getKindRegistry(store, kind);
14846
+ const current = staged.get(scopeKey);
14847
+ if (current) Object.assign(current, entries);
14848
+ else staged.set(scopeKey, { ...entries });
14849
+ }
14850
+ function readCommittedScope(map, scopeKey, isLeaf) {
14851
+ if (scopeKey === ROOT_SCOPE) {
14852
+ const leaves = {};
14853
+ for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
14854
+ return leaves;
14855
+ }
14856
+ const scopeData = map[scopeKey];
14857
+ if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
14858
+ return {};
14859
+ }
14860
+ function withStagedOverlay(store, kind, committed) {
14861
+ const { staged } = getKindRegistry(store, kind);
14862
+ if (staged.size === 0) return committed;
14863
+ const view = { ...committed };
14864
+ for (const [scopeKey, entries] of staged) {
14865
+ if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
14866
+ else view[scopeKey] = { ...view[scopeKey], ...entries };
14867
+ }
14868
+ return view;
14869
+ }
14870
+ function flushStagedScope(store, kind, scopeKey, isLeaf) {
14871
+ const registry = getKindRegistry(store, kind);
14872
+ const staged = registry.staged.get(scopeKey);
14873
+ if (!staged) return;
14874
+ registry.staged.delete(scopeKey);
14875
+ const replace = !registry.valid.has(scopeKey);
14876
+ store.setState((state) => {
14877
+ const map = state[kind];
14878
+ if (scopeKey === ROOT_SCOPE) {
14879
+ if (!replace) return { [kind]: { ...map, ...staged } };
14880
+ const next = {};
14881
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
14882
+ return { [kind]: { ...next, ...staged } };
14883
+ }
14884
+ const current = replace ? {} : map[scopeKey] ?? {};
14885
+ return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
14886
+ });
14887
+ registry.valid.add(scopeKey);
14888
+ }
14889
+ function invalidateResource(store, kind, scopeKey) {
14890
+ const registry = getKindRegistry(store, kind);
14891
+ if (scopeKey === void 0) {
14892
+ registry.valid.clear();
14893
+ registry.staged.clear();
14894
+ } else {
14895
+ registry.valid.delete(scopeKey);
14896
+ registry.staged.delete(scopeKey);
14897
+ }
14898
+ }
14899
+ function rebuildResource(store, kind, userScope) {
14900
+ const primary = resolvePrimary$1(store);
14901
+ invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
14902
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14903
+ }
14904
+ function invalidateAllResources(store) {
14905
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14906
+ }
14907
+ function removeResourceEntries(store, kind, names, scope, isLeaf) {
14908
+ const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
14909
+ if (staged) for (const name of names) delete staged[name];
14910
+ store.setState((state) => {
14911
+ const map = state[kind];
14912
+ if (scope) {
14913
+ const currentScope = { ...map[scope] };
14914
+ for (const name of names) delete currentScope[name];
14915
+ return { [kind]: { ...map, [scope]: currentScope } };
14916
+ }
14917
+ const next = { ...map };
14918
+ for (const name of names) if (isLeaf(next[name])) delete next[name];
14919
+ return { [kind]: next };
14920
+ });
14921
+ }
14922
+ function clearResourceEntries(store, kind, scope, isLeaf) {
14923
+ const registry = getKindRegistry(store, kind);
14924
+ if (scope && scope !== "root") registry.staged.delete(scope);
14925
+ else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
14926
+ else registry.staged.clear();
14927
+ store.setState((state) => {
14928
+ const map = state[kind];
14929
+ if (scope && scope !== "root") {
14930
+ const { [scope]: _, ...rest } = map;
14931
+ return { [kind]: rest };
14932
+ }
14933
+ if (scope === "root") {
14934
+ const next = {};
14935
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
14936
+ return { [kind]: next };
14937
+ }
14938
+ return { [kind]: {} };
14939
+ });
14940
+ }
14941
+
14741
14942
  function clearHmrCaches(store) {
14742
- store.setState((state) => ({
14743
- nodes: {},
14744
- uniforms: {},
14745
- buffers: {},
14746
- gpuStorage: {},
14747
- _hmrVersion: state._hmrVersion + 1
14748
- }));
14943
+ const primary = store.getState().primaryStore ?? store;
14944
+ invalidateAllResources(primary);
14945
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14749
14946
  }
14750
14947
 
14751
14948
  function CanvasImpl({
@@ -14781,18 +14978,7 @@ function CanvasImpl({
14781
14978
  forceEven,
14782
14979
  ...props
14783
14980
  }) {
14784
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14785
- let primaryCanvas;
14786
- let scheduler;
14787
- let renderer;
14788
- if (isRendererConfig) {
14789
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14790
- primaryCanvas = pc;
14791
- scheduler = sc;
14792
- renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
14793
- } else {
14794
- renderer = rendererProp;
14795
- }
14981
+ const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
14796
14982
  React__namespace.useMemo(() => extend(THREE), []);
14797
14983
  const Bridge = useBridge();
14798
14984
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
@@ -14837,6 +15023,7 @@ function CanvasImpl({
14837
15023
  const handleDropMissed = useMutableCallback(onDropMissed);
14838
15024
  const [block, setBlock] = React__namespace.useState(false);
14839
15025
  const [error, setError] = React__namespace.useState(false);
15026
+ const [fallbackVisible, setFallbackVisible] = React__namespace.useState(false);
14840
15027
  if (block) throw block;
14841
15028
  if (error) throw error;
14842
15029
  const root = React__namespace.useRef(null);
@@ -14844,6 +15031,7 @@ function CanvasImpl({
14844
15031
  const unsubscribeErrorRef = React__namespace.useRef(null);
14845
15032
  useIsomorphicLayoutEffect(() => {
14846
15033
  effectActiveRef.current = true;
15034
+ if (fallbackVisible) return;
14847
15035
  const canvas = canvasRef.current;
14848
15036
  if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
14849
15037
  if (!root.current) {
@@ -14864,7 +15052,7 @@ function CanvasImpl({
14864
15052
  }
14865
15053
  async function run() {
14866
15054
  if (!effectActiveRef.current || !root.current) return;
14867
- await root.current.configure({
15055
+ const configured = await root.current.configure({
14868
15056
  id,
14869
15057
  primaryCanvas,
14870
15058
  scheduler,
@@ -14905,8 +15093,20 @@ function CanvasImpl({
14905
15093
  }
14906
15094
  onCreated?.(state);
14907
15095
  }
14908
- });
14909
- if (!effectActiveRef.current || !root.current) return;
15096
+ }).then(
15097
+ () => true,
15098
+ // Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
15099
+ // inside <canvas>, which browsers don't display, so surface it as visible DOM
15100
+ // instead; with no fallback, rethrow to an external error boundary. (#3757)
15101
+ (setupError) => {
15102
+ if (effectActiveRef.current) {
15103
+ if (fallback != null) setFallbackVisible(true);
15104
+ else setError(setupError);
15105
+ }
15106
+ return false;
15107
+ }
15108
+ );
15109
+ if (!configured || !effectActiveRef.current || !root.current) return;
14910
15110
  root.current.render(
14911
15111
  /* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
14912
15112
  backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
@@ -14969,7 +15169,11 @@ function CanvasImpl({
14969
15169
  ...style
14970
15170
  },
14971
15171
  ...props,
14972
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
15172
+ children: fallbackVisible ? (
15173
+ // Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
15174
+ // it exists in the tree but browsers never paint it, which is the whole bug (#3757).
15175
+ fallback
15176
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
14973
15177
  "canvas",
14974
15178
  {
14975
15179
  ref: canvasRef,
@@ -15051,30 +15255,31 @@ let ScopedStore = _ScopedStore;
15051
15255
  function createScopedStore(data) {
15052
15256
  return new ScopedStore(data);
15053
15257
  }
15054
- function createLazyCreatorState(state) {
15258
+ function createLazyCreatorState(state, store) {
15055
15259
  let _uniforms = null;
15056
15260
  let _nodes = null;
15057
15261
  let _buffers = null;
15058
15262
  let _gpuStorage = null;
15263
+ const view = (kind) => store ? withStagedOverlay(store, kind, state[kind]) : state[kind];
15059
15264
  return Object.create(state, {
15060
15265
  uniforms: {
15061
15266
  get() {
15062
- return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
15267
+ return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
15063
15268
  }
15064
15269
  },
15065
15270
  nodes: {
15066
15271
  get() {
15067
- return _nodes ?? (_nodes = createScopedStore(state.nodes));
15272
+ return _nodes ?? (_nodes = createScopedStore(view("nodes")));
15068
15273
  }
15069
15274
  },
15070
15275
  buffers: {
15071
15276
  get() {
15072
- return _buffers ?? (_buffers = createScopedStore(state.buffers));
15277
+ return _buffers ?? (_buffers = createScopedStore(view("buffers")));
15073
15278
  }
15074
15279
  },
15075
15280
  gpuStorage: {
15076
15281
  get() {
15077
- return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
15282
+ return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
15078
15283
  }
15079
15284
  }
15080
15285
  });
@@ -15197,65 +15402,56 @@ function useCompareMemoize(value, deep) {
15197
15402
  return ref.current;
15198
15403
  }
15199
15404
 
15405
+ function useScopedResource(options) {
15406
+ const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
15407
+ const scopeKey = scope ?? ROOT_SCOPE;
15408
+ const version = store((s) => s._hmrVersion);
15409
+ const entries = React.useMemo(() => {
15410
+ const staged = peekStaged(store, kind, scopeKey);
15411
+ const reusable = isScopeValid(store, kind, scopeKey);
15412
+ const map = store.getState()[kind];
15413
+ const committed = reusable ? readCommittedScope(map, scopeKey, isLeaf) : void 0;
15414
+ const result = {};
15415
+ let fresh = null;
15416
+ for (const [name, candidate] of Object.entries(create())) {
15417
+ const existing = staged?.[name] ?? committed?.[name];
15418
+ if (existing !== void 0) {
15419
+ result[name] = existing;
15420
+ reconcile?.(existing, candidate);
15421
+ } else {
15422
+ const value = prepare ? prepare(name, candidate) : candidate;
15423
+ result[name] = value;
15424
+ (fresh ?? (fresh = {}))[name] = value;
15425
+ }
15426
+ }
15427
+ if (fresh) stageEntries(store, kind, scopeKey, fresh);
15428
+ return result;
15429
+ }, [store, kind, scopeKey, version, input]);
15430
+ useIsomorphicLayoutEffect(() => {
15431
+ flushStagedScope(store, kind, scopeKey, isLeaf);
15432
+ });
15433
+ return entries;
15434
+ }
15435
+
15200
15436
  const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15201
15437
  function useUniforms(creatorOrScope, scope) {
15202
15438
  const store = usePrimaryStore();
15203
15439
  const removeUniforms2 = React.useCallback(
15204
- (names, targetScope) => {
15205
- const nameArray = Array.isArray(names) ? names : [names];
15206
- store.setState((state) => {
15207
- if (targetScope) {
15208
- const currentScope = { ...state.uniforms[targetScope] };
15209
- for (const name of nameArray) delete currentScope[name];
15210
- return { uniforms: { ...state.uniforms, [targetScope]: currentScope } };
15211
- }
15212
- const uniforms2 = { ...state.uniforms };
15213
- for (const name of nameArray) if (isUniformNode$1(uniforms2[name])) delete uniforms2[name];
15214
- return { uniforms: uniforms2 };
15215
- });
15216
- },
15440
+ (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
15217
15441
  [store]
15218
15442
  );
15219
15443
  const clearUniforms = React.useCallback(
15220
- (targetScope) => {
15221
- store.setState((state) => {
15222
- if (targetScope && targetScope !== "root") {
15223
- const { [targetScope]: _, ...rest } = state.uniforms;
15224
- return { uniforms: rest };
15225
- }
15226
- if (targetScope === "root") {
15227
- const uniforms2 = {};
15228
- for (const [key, value] of Object.entries(state.uniforms)) {
15229
- if (!isUniformNode$1(value)) uniforms2[key] = value;
15230
- }
15231
- return { uniforms: uniforms2 };
15232
- }
15233
- return { uniforms: {} };
15234
- });
15235
- },
15444
+ (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
15236
15445
  [store]
15237
15446
  );
15238
15447
  const rebuildUniforms = React.useCallback(
15239
- (targetScope) => {
15240
- store.setState((state) => {
15241
- let newUniforms = {};
15242
- if (targetScope && targetScope !== "root") {
15243
- const { [targetScope]: _, ...rest } = state.uniforms;
15244
- newUniforms = rest;
15245
- } else if (targetScope === "root") {
15246
- for (const [key, value] of Object.entries(state.uniforms)) {
15247
- if (!isUniformNode$1(value)) newUniforms[key] = value;
15248
- }
15249
- }
15250
- return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15251
- });
15252
- },
15448
+ (targetScope) => rebuildResource(store, "uniforms", targetScope),
15253
15449
  [store]
15254
15450
  );
15255
15451
  const inputForMemoization = React.useMemo(() => {
15256
15452
  let raw = creatorOrScope;
15257
15453
  if (is.fun(creatorOrScope)) {
15258
- const wrappedState = createLazyCreatorState(store.getState());
15454
+ const wrappedState = createLazyCreatorState(store.getState(), store);
15259
15455
  raw = creatorOrScope(wrappedState);
15260
15456
  }
15261
15457
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
@@ -15270,80 +15466,43 @@ function useUniforms(creatorOrScope, scope) {
15270
15466
  const memoizedInput = useCompareMemoize(inputForMemoization);
15271
15467
  const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15272
15468
  const storeUniforms = usePrimaryThree((s) => s.uniforms);
15273
- const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15274
- const readerDep = isReader ? storeUniforms : null;
15275
- const creatorDep = isReader ? null : hmrVersion;
15276
- const uniforms = React.useMemo(() => {
15277
- if (memoizedInput === void 0) {
15278
- return storeUniforms;
15279
- }
15280
- if (typeof memoizedInput === "string") {
15281
- const scopeData = storeUniforms[memoizedInput];
15282
- if (scopeData && !isUniformNode$1(scopeData)) return scopeData;
15283
- return {};
15284
- }
15285
- const state = store.getState();
15286
- const set = store.setState;
15287
- if (typeof memoizedInput !== "object" || memoizedInput === null) {
15288
- throw new Error("Invalid uniform input");
15289
- }
15290
- const created = memoizedInput;
15291
- const result = {};
15292
- let hasNewUniforms = false;
15293
- let targetRecord = state.uniforms;
15294
- if (scope) {
15295
- if (!state.uniforms[scope]) state.uniforms[scope] = {};
15296
- targetRecord = state.uniforms[scope];
15297
- }
15298
- for (const [name, node] of Object.entries(created)) {
15299
- if (targetRecord[name]) {
15300
- result[name] = targetRecord[name];
15301
- const existingVal = result[name].value;
15302
- const newVal = vectorize(node);
15303
- let equals = newVal === existingVal;
15304
- if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
15305
- if (isThreeVector(existingVal) && isThreeVector(newVal)) {
15306
- equals = vectorEquals(existingVal, newVal);
15307
- } else if (isSameThreeType(existingVal, newVal)) {
15308
- equals = existingVal.equals(newVal);
15309
- }
15310
- }
15311
- if (!equals) result[name].value = newVal;
15312
- } else {
15313
- result[name] = createUniform(name, node, scope);
15314
- hasNewUniforms = true;
15315
- }
15316
- }
15317
- if (hasNewUniforms) {
15318
- if (scope) {
15319
- set((s) => ({
15320
- uniforms: {
15321
- ...s.uniforms,
15322
- [scope]: { ...s.uniforms[scope], ...result }
15323
- }
15324
- }));
15325
- } else {
15326
- set((s) => ({ uniforms: { ...s.uniforms, ...result } }));
15469
+ const created = useScopedResource({
15470
+ store,
15471
+ kind: "uniforms",
15472
+ scope: isReader ? void 0 : scope,
15473
+ isLeaf: isUniformNode$1,
15474
+ input: memoizedInput,
15475
+ create: () => {
15476
+ if (isReader) return {};
15477
+ if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
15478
+ return memoizedInput;
15479
+ },
15480
+ prepare: (name, candidate) => createUniform(name, candidate, scope),
15481
+ reconcile: (existing, candidate) => {
15482
+ const existingVal = existing.value;
15483
+ const newVal = vectorize(candidate);
15484
+ let equals = newVal === existingVal;
15485
+ if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
15486
+ if (isThreeVector(existingVal) && isThreeVector(newVal)) {
15487
+ equals = vectorEquals(existingVal, newVal);
15488
+ } else if (isSameThreeType(existingVal, newVal)) {
15489
+ equals = existingVal.equals(newVal);
15490
+ }
15327
15491
  }
15492
+ if (!equals) existing.value = newVal;
15328
15493
  }
15329
- return result;
15330
- }, [store, memoizedInput, scope, readerDep, creatorDep]);
15494
+ });
15495
+ let uniforms = created;
15496
+ if (memoizedInput === void 0) {
15497
+ uniforms = storeUniforms;
15498
+ } else if (typeof memoizedInput === "string") {
15499
+ const scopeData = storeUniforms[memoizedInput];
15500
+ uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
15501
+ }
15331
15502
  return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15332
15503
  }
15333
15504
  function rebuildAllUniforms(store, scope) {
15334
- store = store.getState().primaryStore ?? store;
15335
- store.setState((state) => {
15336
- let newUniforms = {};
15337
- if (scope && scope !== "root") {
15338
- const { [scope]: _, ...rest } = state.uniforms;
15339
- newUniforms = rest;
15340
- } else if (scope === "root") {
15341
- for (const [key, value] of Object.entries(state.uniforms)) {
15342
- if (!isUniformNode$1(value)) newUniforms[key] = value;
15343
- }
15344
- }
15345
- return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15346
- });
15505
+ rebuildResource(store, "uniforms", scope);
15347
15506
  }
15348
15507
  function removeUniforms(set, names, scope) {
15349
15508
  set((state) => {
@@ -15407,192 +15566,91 @@ function isSameThreeType(a, b) {
15407
15566
  return obj_a.isColor && obj_b.isColor || obj_a.isMatrix3 && obj_b.isMatrix3 || obj_a.isMatrix4 && obj_b.isMatrix4 || obj_a.isEuler && obj_b.isEuler || obj_a.isQuaternion && obj_b.isQuaternion;
15408
15567
  }
15409
15568
 
15569
+ const uniformOf = tsl.uniform;
15410
15570
  const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15411
15571
  const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15412
15572
  function useUniform(name, value) {
15413
15573
  const store = useStore();
15414
- const hmrVersion = useThree((s) => s._hmrVersion);
15415
- return React.useMemo(() => {
15416
- const state = store.getState();
15417
- const set = store.setState;
15418
- const existing = state.uniforms[name];
15419
- if (existing && isUniformNode(existing)) {
15574
+ const registered = useScopedResource({
15575
+ store,
15576
+ kind: "uniforms",
15577
+ scope: void 0,
15578
+ isLeaf: isUniformNode,
15579
+ input: name,
15580
+ create: () => value === void 0 ? {} : { [name]: value },
15581
+ prepare: createNamedUniform,
15582
+ reconcile: (existing2) => {
15420
15583
  if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
15421
- existing.value = typeof value === "string" ? new webgpu.Color(value) : value;
15584
+ existing2.value = typeof value === "string" ? new webgpu.Color(value) : value;
15422
15585
  }
15423
- return existing;
15424
- }
15425
- if (value === void 0) {
15426
- throw new Error(
15427
- `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
15428
- );
15429
- }
15430
- if (isUniformNode(value)) {
15431
- const node2 = value;
15432
- if (typeof node2.setName === "function") {
15433
- node2.setName(name);
15434
- }
15435
- set((s) => ({
15436
- uniforms: { ...s.uniforms, [name]: node2 }
15437
- }));
15438
- return node2;
15439
15586
  }
15440
- let node;
15441
- if (isTSLNode$1(value)) {
15442
- node = tsl.uniform(value);
15443
- } else if (typeof value === "string") {
15444
- node = tsl.uniform(new webgpu.Color(value));
15445
- } else {
15446
- node = tsl.uniform(value);
15447
- }
15448
- if (typeof node.setName === "function") {
15449
- node.setName(name);
15450
- }
15451
- set((s) => ({
15452
- uniforms: {
15453
- ...s.uniforms,
15454
- [name]: node
15455
- }
15456
- }));
15457
- return node;
15458
- }, [store, name, hmrVersion]);
15587
+ });
15588
+ if (registered[name]) return registered[name];
15589
+ const existing = peekStaged(store, "uniforms", ROOT_SCOPE)?.[name] ?? store.getState().uniforms[name];
15590
+ if (existing && isUniformNode(existing)) return existing;
15591
+ throw new Error(
15592
+ `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
15593
+ );
15594
+ }
15595
+ function createNamedUniform(name, value) {
15596
+ let node;
15597
+ if (isUniformNode(value)) {
15598
+ node = value;
15599
+ } else if (isTSLNode$1(value)) {
15600
+ node = uniformOf(value);
15601
+ } else if (typeof value === "string") {
15602
+ node = tsl.uniform(new webgpu.Color(value));
15603
+ } else {
15604
+ node = uniformOf(value);
15605
+ }
15606
+ if (typeof node.setName === "function") {
15607
+ node.setName(name);
15608
+ }
15609
+ return node;
15459
15610
  }
15460
15611
 
15461
15612
  const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
15462
15613
  function useNodes(creatorOrScope, scope) {
15463
15614
  const store = usePrimaryStore();
15464
15615
  const removeNodes2 = React.useCallback(
15465
- (names, targetScope) => {
15466
- const nameArray = Array.isArray(names) ? names : [names];
15467
- store.setState((state) => {
15468
- if (targetScope) {
15469
- const currentScope = { ...state.nodes[targetScope] };
15470
- for (const name of nameArray) delete currentScope[name];
15471
- return { nodes: { ...state.nodes, [targetScope]: currentScope } };
15472
- }
15473
- const nodes2 = { ...state.nodes };
15474
- for (const name of nameArray) if (isTSLNode(nodes2[name])) delete nodes2[name];
15475
- return { nodes: nodes2 };
15476
- });
15477
- },
15616
+ (names, targetScope) => removeResourceEntries(store, "nodes", Array.isArray(names) ? names : [names], targetScope, isTSLNode),
15478
15617
  [store]
15479
15618
  );
15480
15619
  const clearNodes = React.useCallback(
15481
- (targetScope) => {
15482
- store.setState((state) => {
15483
- if (targetScope && targetScope !== "root") {
15484
- const { [targetScope]: _, ...rest } = state.nodes;
15485
- return { nodes: rest };
15486
- }
15487
- if (targetScope === "root") {
15488
- const nodes2 = {};
15489
- for (const [key, value] of Object.entries(state.nodes)) {
15490
- if (!isTSLNode(value)) nodes2[key] = value;
15491
- }
15492
- return { nodes: nodes2 };
15493
- }
15494
- return { nodes: {} };
15495
- });
15496
- },
15620
+ (targetScope) => clearResourceEntries(store, "nodes", targetScope, isTSLNode),
15497
15621
  [store]
15498
15622
  );
15499
15623
  const rebuildNodes = React.useCallback(
15500
- (targetScope) => {
15501
- store.setState((state) => {
15502
- let newNodes = state.nodes;
15503
- if (targetScope && targetScope !== "root") {
15504
- const { [targetScope]: _, ...rest } = state.nodes;
15505
- newNodes = rest;
15506
- } else if (targetScope === "root") {
15507
- newNodes = {};
15508
- for (const [key, value] of Object.entries(state.nodes)) {
15509
- if (!isTSLNode(value)) newNodes[key] = value;
15510
- }
15511
- } else {
15512
- newNodes = {};
15513
- }
15514
- return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
15515
- });
15516
- },
15624
+ (targetScope) => rebuildResource(store, "nodes", targetScope),
15517
15625
  [store]
15518
15626
  );
15519
15627
  const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15520
15628
  const storeNodes = usePrimaryThree((s) => s.nodes);
15521
- const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15522
- const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15523
- const readerDep = isReader ? storeNodes : null;
15524
- const creatorDep = isReader ? null : hmrVersion;
15525
- const nodes = React.useMemo(() => {
15526
- if (creatorOrScope === void 0) {
15527
- return storeNodes;
15528
- }
15529
- if (typeof creatorOrScope === "string") {
15530
- const scopeData = storeNodes[creatorOrScope];
15531
- if (scopeData && !isTSLNode(scopeData)) return scopeData;
15532
- return {};
15533
- }
15534
- const state = store.getState();
15535
- const set = store.setState;
15536
- const creator = creatorOrScope;
15537
- const wrappedState = createLazyCreatorState(state);
15538
- const created = creator(wrappedState);
15539
- const result = {};
15540
- let hasNewNodes = false;
15541
- if (scope) {
15542
- const currentScope = state.nodes[scope] ?? {};
15543
- for (const [name, node] of Object.entries(created)) {
15544
- if (currentScope[name]) {
15545
- result[name] = currentScope[name];
15546
- } else {
15547
- node.setName?.(`${scope}.${name}`);
15548
- result[name] = node;
15549
- hasNewNodes = true;
15550
- }
15551
- }
15552
- if (hasNewNodes) {
15553
- set((s) => ({
15554
- nodes: {
15555
- ...s.nodes,
15556
- [scope]: { ...s.nodes[scope], ...result }
15557
- }
15558
- }));
15559
- }
15560
- return result;
15561
- }
15562
- for (const [name, node] of Object.entries(created)) {
15563
- const existing = state.nodes[name];
15564
- if (existing && isTSLNode(existing)) {
15565
- result[name] = existing;
15566
- } else {
15567
- node.setName?.(name);
15568
- result[name] = node;
15569
- hasNewNodes = true;
15570
- }
15571
- }
15572
- if (hasNewNodes) {
15573
- set((s) => ({ nodes: { ...s.nodes, ...result } }));
15629
+ const created = useScopedResource({
15630
+ store,
15631
+ kind: "nodes",
15632
+ scope: isReader ? void 0 : scope,
15633
+ isLeaf: isTSLNode,
15634
+ create: () => {
15635
+ if (isReader) return {};
15636
+ return creatorOrScope(createLazyCreatorState(store.getState(), store));
15637
+ },
15638
+ prepare: (name, node) => {
15639
+ node.setName?.(scope ? `${scope}.${name}` : name);
15640
+ return node;
15574
15641
  }
15575
- return result;
15576
- }, [store, scopeDep, readerDep, creatorDep]);
15642
+ });
15643
+ let nodes = created;
15644
+ if (creatorOrScope === void 0) {
15645
+ nodes = storeNodes;
15646
+ } else if (typeof creatorOrScope === "string") {
15647
+ const scopeData = storeNodes[creatorOrScope];
15648
+ nodes = scopeData && !isTSLNode(scopeData) ? scopeData : {};
15649
+ }
15577
15650
  return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
15578
15651
  }
15579
15652
  function rebuildAllNodes(store, scope) {
15580
- store = store.getState().primaryStore ?? store;
15581
- store.setState((state) => {
15582
- let newNodes = state.nodes;
15583
- if (scope && scope !== "root") {
15584
- const { [scope]: _, ...rest } = state.nodes;
15585
- newNodes = rest;
15586
- } else if (scope === "root") {
15587
- newNodes = {};
15588
- for (const [key, value] of Object.entries(state.nodes)) {
15589
- if (!isTSLNode(value)) newNodes[key] = value;
15590
- }
15591
- } else {
15592
- newNodes = {};
15593
- }
15594
- return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
15595
- });
15653
+ rebuildResource(store, "nodes", scope);
15596
15654
  }
15597
15655
  function removeNodes(set, names, scope) {
15598
15656
  set((state) => {
@@ -15628,7 +15686,7 @@ function useLocalNodes(creator) {
15628
15686
  const textures = usePrimaryThree((s) => s.textures);
15629
15687
  const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15630
15688
  return React.useMemo(() => {
15631
- const wrappedState = createLazyCreatorState(store.getState());
15689
+ const wrappedState = createLazyCreatorState(store.getState(), store);
15632
15690
  return creator(wrappedState);
15633
15691
  }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15634
15692
  }
@@ -15649,58 +15707,15 @@ const disposeBuffer = (buffer) => {
15649
15707
  function useBuffers(creatorOrScope, scope) {
15650
15708
  const store = usePrimaryStore();
15651
15709
  const removeBuffers = React.useCallback(
15652
- (names, targetScope) => {
15653
- const nameArray = Array.isArray(names) ? names : [names];
15654
- store.setState((state) => {
15655
- if (targetScope) {
15656
- const currentScope = { ...state.buffers[targetScope] };
15657
- for (const name of nameArray) delete currentScope[name];
15658
- return { buffers: { ...state.buffers, [targetScope]: currentScope } };
15659
- }
15660
- const buffers2 = { ...state.buffers };
15661
- for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
15662
- return { buffers: buffers2 };
15663
- });
15664
- },
15710
+ (names, targetScope) => removeResourceEntries(store, "buffers", Array.isArray(names) ? names : [names], targetScope, isBufferLike),
15665
15711
  [store]
15666
15712
  );
15667
15713
  const clearBuffers = React.useCallback(
15668
- (targetScope) => {
15669
- store.setState((state) => {
15670
- if (targetScope && targetScope !== "root") {
15671
- const { [targetScope]: _, ...rest } = state.buffers;
15672
- return { buffers: rest };
15673
- }
15674
- if (targetScope === "root") {
15675
- const buffers2 = {};
15676
- for (const [key, value] of Object.entries(state.buffers)) {
15677
- if (!isBufferLike(value)) buffers2[key] = value;
15678
- }
15679
- return { buffers: buffers2 };
15680
- }
15681
- return { buffers: {} };
15682
- });
15683
- },
15714
+ (targetScope) => clearResourceEntries(store, "buffers", targetScope, isBufferLike),
15684
15715
  [store]
15685
15716
  );
15686
15717
  const rebuildBuffers = React.useCallback(
15687
- (targetScope) => {
15688
- store.setState((state) => {
15689
- let newBuffers = state.buffers;
15690
- if (targetScope && targetScope !== "root") {
15691
- const { [targetScope]: _, ...rest } = state.buffers;
15692
- newBuffers = rest;
15693
- } else if (targetScope === "root") {
15694
- newBuffers = {};
15695
- for (const [key, value] of Object.entries(state.buffers)) {
15696
- if (!isBufferLike(value)) newBuffers[key] = value;
15697
- }
15698
- } else {
15699
- newBuffers = {};
15700
- }
15701
- return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
15702
- });
15703
- },
15718
+ (targetScope) => rebuildResource(store, "buffers", targetScope),
15704
15719
  [store]
15705
15720
  );
15706
15721
  const disposeBuffers = React.useCallback(
@@ -15719,85 +15734,33 @@ function useBuffers(creatorOrScope, scope) {
15719
15734
  );
15720
15735
  const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15721
15736
  const storeBuffers = usePrimaryThree((s) => s.buffers);
15722
- const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15723
- const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15724
- const readerDep = isReader ? storeBuffers : null;
15725
- const creatorDep = isReader ? null : hmrVersion;
15726
- const buffers = React.useMemo(() => {
15727
- if (creatorOrScope === void 0) {
15728
- return storeBuffers;
15729
- }
15730
- if (typeof creatorOrScope === "string") {
15731
- const scopeData = storeBuffers[creatorOrScope];
15732
- if (scopeData && !isBufferLike(scopeData)) return scopeData;
15733
- return {};
15734
- }
15735
- const state = store.getState();
15736
- const set = store.setState;
15737
- const creator = creatorOrScope;
15738
- const wrappedState = createLazyCreatorState(state);
15739
- const created = creator(wrappedState);
15740
- const result = {};
15741
- let hasNewBuffers = false;
15742
- if (scope) {
15743
- const currentScope = state.buffers[scope] ?? {};
15744
- for (const [name, buffer] of Object.entries(created)) {
15745
- if (currentScope[name]) {
15746
- result[name] = currentScope[name];
15747
- } else {
15748
- if ("setName" in buffer && typeof buffer.setName === "function") {
15749
- buffer.setName(`${scope}.${name}`);
15750
- }
15751
- result[name] = buffer;
15752
- hasNewBuffers = true;
15753
- }
15754
- }
15755
- if (hasNewBuffers) {
15756
- set((s) => ({
15757
- buffers: {
15758
- ...s.buffers,
15759
- [scope]: { ...s.buffers[scope], ...result }
15760
- }
15761
- }));
15762
- }
15763
- return result;
15764
- }
15765
- for (const [name, buffer] of Object.entries(created)) {
15766
- const existing = state.buffers[name];
15767
- if (existing && isBufferLike(existing)) {
15768
- result[name] = existing;
15769
- } else {
15770
- if ("setName" in buffer && typeof buffer.setName === "function") {
15771
- buffer.setName(name);
15772
- }
15773
- result[name] = buffer;
15774
- hasNewBuffers = true;
15737
+ const created = useScopedResource({
15738
+ store,
15739
+ kind: "buffers",
15740
+ scope: isReader ? void 0 : scope,
15741
+ isLeaf: isBufferLike,
15742
+ create: () => {
15743
+ if (isReader) return {};
15744
+ return creatorOrScope(createLazyCreatorState(store.getState(), store));
15745
+ },
15746
+ prepare: (name, buffer) => {
15747
+ if ("setName" in buffer && typeof buffer.setName === "function") {
15748
+ buffer.setName(scope ? `${scope}.${name}` : name);
15775
15749
  }
15750
+ return buffer;
15776
15751
  }
15777
- if (hasNewBuffers) {
15778
- set((s) => ({ buffers: { ...s.buffers, ...result } }));
15779
- }
15780
- return result;
15781
- }, [store, scopeDep, readerDep, creatorDep]);
15752
+ });
15753
+ let buffers = created;
15754
+ if (creatorOrScope === void 0) {
15755
+ buffers = storeBuffers;
15756
+ } else if (typeof creatorOrScope === "string") {
15757
+ const scopeData = storeBuffers[creatorOrScope];
15758
+ buffers = scopeData && !isBufferLike(scopeData) ? scopeData : {};
15759
+ }
15782
15760
  return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
15783
15761
  }
15784
15762
  function rebuildAllBuffers(store, scope) {
15785
- store = store.getState().primaryStore ?? store;
15786
- store.setState((state) => {
15787
- let newBuffers = state.buffers;
15788
- if (scope && scope !== "root") {
15789
- const { [scope]: _, ...rest } = state.buffers;
15790
- newBuffers = rest;
15791
- } else if (scope === "root") {
15792
- newBuffers = {};
15793
- for (const [key, value] of Object.entries(state.buffers)) {
15794
- if (!isBufferLike(value)) newBuffers[key] = value;
15795
- }
15796
- } else {
15797
- newBuffers = {};
15798
- }
15799
- return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
15800
- });
15763
+ rebuildResource(store, "buffers", scope);
15801
15764
  }
15802
15765
 
15803
15766
  const isStorageLike = (value) => {
@@ -15816,58 +15779,15 @@ const disposeStorage = (storage) => {
15816
15779
  function useGPUStorage(creatorOrScope, scope) {
15817
15780
  const store = usePrimaryStore();
15818
15781
  const removeStorage = React.useCallback(
15819
- (names, targetScope) => {
15820
- const nameArray = Array.isArray(names) ? names : [names];
15821
- store.setState((state) => {
15822
- if (targetScope) {
15823
- const currentScope = { ...state.gpuStorage[targetScope] };
15824
- for (const name of nameArray) delete currentScope[name];
15825
- return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
15826
- }
15827
- const gpuStorage2 = { ...state.gpuStorage };
15828
- for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
15829
- return { gpuStorage: gpuStorage2 };
15830
- });
15831
- },
15782
+ (names, targetScope) => removeResourceEntries(store, "gpuStorage", Array.isArray(names) ? names : [names], targetScope, isStorageLike),
15832
15783
  [store]
15833
15784
  );
15834
15785
  const clearStorage = React.useCallback(
15835
- (targetScope) => {
15836
- store.setState((state) => {
15837
- if (targetScope && targetScope !== "root") {
15838
- const { [targetScope]: _, ...rest } = state.gpuStorage;
15839
- return { gpuStorage: rest };
15840
- }
15841
- if (targetScope === "root") {
15842
- const gpuStorage2 = {};
15843
- for (const [key, value] of Object.entries(state.gpuStorage)) {
15844
- if (!isStorageLike(value)) gpuStorage2[key] = value;
15845
- }
15846
- return { gpuStorage: gpuStorage2 };
15847
- }
15848
- return { gpuStorage: {} };
15849
- });
15850
- },
15786
+ (targetScope) => clearResourceEntries(store, "gpuStorage", targetScope, isStorageLike),
15851
15787
  [store]
15852
15788
  );
15853
15789
  const rebuildStorage = React.useCallback(
15854
- (targetScope) => {
15855
- store.setState((state) => {
15856
- let newStorage = state.gpuStorage;
15857
- if (targetScope && targetScope !== "root") {
15858
- const { [targetScope]: _, ...rest } = state.gpuStorage;
15859
- newStorage = rest;
15860
- } else if (targetScope === "root") {
15861
- newStorage = {};
15862
- for (const [key, value] of Object.entries(state.gpuStorage)) {
15863
- if (!isStorageLike(value)) newStorage[key] = value;
15864
- }
15865
- } else {
15866
- newStorage = {};
15867
- }
15868
- return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
15869
- });
15870
- },
15790
+ (targetScope) => rebuildResource(store, "gpuStorage", targetScope),
15871
15791
  [store]
15872
15792
  );
15873
15793
  const disposeStorageFn = React.useCallback(
@@ -15886,72 +15806,33 @@ function useGPUStorage(creatorOrScope, scope) {
15886
15806
  );
15887
15807
  const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15888
15808
  const storeStorage = usePrimaryThree((s) => s.gpuStorage);
15889
- const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15890
- const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15891
- const readerDep = isReader ? storeStorage : null;
15892
- const creatorDep = isReader ? null : hmrVersion;
15893
- const gpuStorage = React.useMemo(() => {
15894
- if (creatorOrScope === void 0) {
15895
- return storeStorage;
15896
- }
15897
- if (typeof creatorOrScope === "string") {
15898
- const scopeData = storeStorage[creatorOrScope];
15899
- if (scopeData && !isStorageLike(scopeData)) return scopeData;
15900
- return {};
15901
- }
15902
- const state = store.getState();
15903
- const set = store.setState;
15904
- const creator = creatorOrScope;
15905
- const wrappedState = createLazyCreatorState(state);
15906
- const created = creator(wrappedState);
15907
- const result = {};
15908
- let hasNewStorage = false;
15909
- if (scope) {
15910
- const currentScope = state.gpuStorage[scope] ?? {};
15911
- for (const [name, storage] of Object.entries(created)) {
15912
- if (currentScope[name]) {
15913
- result[name] = currentScope[name];
15914
- } else {
15915
- if ("setName" in storage && typeof storage.setName === "function") {
15916
- storage.setName(`${scope}.${name}`);
15917
- }
15918
- if ("name" in storage && typeof storage.name === "string") {
15919
- storage.name = `${scope}.${name}`;
15920
- }
15921
- result[name] = storage;
15922
- hasNewStorage = true;
15923
- }
15924
- }
15925
- if (hasNewStorage) {
15926
- set((s) => ({
15927
- gpuStorage: {
15928
- ...s.gpuStorage,
15929
- [scope]: { ...s.gpuStorage[scope], ...result }
15930
- }
15931
- }));
15809
+ const created = useScopedResource({
15810
+ store,
15811
+ kind: "gpuStorage",
15812
+ scope: isReader ? void 0 : scope,
15813
+ isLeaf: isStorageLike,
15814
+ create: () => {
15815
+ if (isReader) return {};
15816
+ return creatorOrScope(createLazyCreatorState(store.getState(), store));
15817
+ },
15818
+ prepare: (name, storage) => {
15819
+ const label = scope ? `${scope}.${name}` : name;
15820
+ if ("setName" in storage && typeof storage.setName === "function") {
15821
+ storage.setName(label);
15932
15822
  }
15933
- return result;
15934
- }
15935
- for (const [name, storage] of Object.entries(created)) {
15936
- const existing = state.gpuStorage[name];
15937
- if (existing && isStorageLike(existing)) {
15938
- result[name] = existing;
15939
- } else {
15940
- if ("setName" in storage && typeof storage.setName === "function") {
15941
- storage.setName(name);
15942
- }
15943
- if ("name" in storage && typeof storage.name === "string") {
15944
- storage.name = name;
15945
- }
15946
- result[name] = storage;
15947
- hasNewStorage = true;
15823
+ if ("name" in storage && typeof storage.name === "string") {
15824
+ storage.name = label;
15948
15825
  }
15826
+ return storage;
15949
15827
  }
15950
- if (hasNewStorage) {
15951
- set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
15952
- }
15953
- return result;
15954
- }, [store, scopeDep, readerDep, creatorDep]);
15828
+ });
15829
+ let gpuStorage = created;
15830
+ if (creatorOrScope === void 0) {
15831
+ gpuStorage = storeStorage;
15832
+ } else if (typeof creatorOrScope === "string") {
15833
+ const scopeData = storeStorage[creatorOrScope];
15834
+ gpuStorage = scopeData && !isStorageLike(scopeData) ? scopeData : {};
15835
+ }
15955
15836
  return {
15956
15837
  ...gpuStorage,
15957
15838
  removeStorage,
@@ -15961,22 +15842,7 @@ function useGPUStorage(creatorOrScope, scope) {
15961
15842
  };
15962
15843
  }
15963
15844
  function rebuildAllStorage(store, scope) {
15964
- store = store.getState().primaryStore ?? store;
15965
- store.setState((state) => {
15966
- let newStorage = state.gpuStorage;
15967
- if (scope && scope !== "root") {
15968
- const { [scope]: _, ...rest } = state.gpuStorage;
15969
- newStorage = rest;
15970
- } else if (scope === "root") {
15971
- newStorage = {};
15972
- for (const [key, value] of Object.entries(state.gpuStorage)) {
15973
- if (!isStorageLike(value)) newStorage[key] = value;
15974
- }
15975
- } else {
15976
- newStorage = {};
15977
- }
15978
- return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
15979
- });
15845
+ rebuildResource(store, "gpuStorage", scope);
15980
15846
  }
15981
15847
 
15982
15848
  function useRenderPipeline(mainCB, setupCB) {
@@ -16017,12 +15883,12 @@ function useRenderPipeline(mainCB, setupCB) {
16017
15883
  const state = store.getState();
16018
15884
  const set = store.setState;
16019
15885
  try {
16020
- let pp = state.renderPipeline;
15886
+ let pipeline = state.renderPipeline;
16021
15887
  let currentPasses = { ...state.passes };
16022
- let justCreatedPP = false;
16023
- if (!pp) {
16024
- pp = new webgpu.PostProcessing(renderer);
16025
- justCreatedPP = true;
15888
+ let justCreatedPipeline = false;
15889
+ if (!pipeline) {
15890
+ pipeline = new webgpu.RenderPipeline(renderer);
15891
+ justCreatedPipeline = true;
16026
15892
  }
16027
15893
  const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16028
15894
  let scenePass;
@@ -16033,9 +15899,9 @@ function useRenderPipeline(mainCB, setupCB) {
16033
15899
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16034
15900
  }
16035
15901
  currentPasses.scenePass = scenePass;
16036
- if (!pp.outputNode || justCreatedPP) pp.outputNode = scenePass;
16037
- set({ renderPipeline: pp, passes: currentPasses });
16038
- const shouldRunCallbacks = justCreatedPP || !callbacksRanRef.current || !cacheValid;
15902
+ if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
15903
+ set({ renderPipeline: pipeline, passes: currentPasses });
15904
+ const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16039
15905
  if (shouldRunCallbacks) {
16040
15906
  if (setupCBRef.current) {
16041
15907
  const freshState = store.getState();
@@ -16056,7 +15922,7 @@ function useRenderPipeline(mainCB, setupCB) {
16056
15922
  callbacksRanRef.current = true;
16057
15923
  }
16058
15924
  } catch (error) {
16059
- console.error("[useRenderPipeline] Setup error:", error);
15925
+ console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16060
15926
  }
16061
15927
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16062
15928
  const passes = useThree((s) => s.passes);