@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.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as webgpu from 'three/webgpu';
2
- import { RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, WebGLCubeRenderTarget, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer } from 'three/webgpu';
3
- import { WebGLRenderTarget, WebGLRenderer } from 'three';
2
+ import { CubeRenderTarget, RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer } from 'three/webgpu';
3
+ import { WebGLCubeRenderTarget, WebGLRenderTarget, WebGLRenderer } from 'three';
4
4
  import { Inspector } from 'three/addons/inspector/Inspector.js';
5
5
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
6
6
  import * as React from 'react';
@@ -36,10 +36,13 @@ const R3F_BUILD_WEBGPU = true;
36
36
 
37
37
  const THREE = /*#__PURE__*/_mergeNamespaces({
38
38
  __proto__: null,
39
+ CubeRenderTarget: CubeRenderTarget,
40
+ CubeRenderTargetCompat: CubeRenderTarget,
39
41
  Inspector: Inspector,
40
42
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
41
43
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
42
44
  RenderTargetCompat: RenderTarget,
45
+ WebGLCubeRenderTarget: WebGLCubeRenderTarget,
43
46
  WebGLRenderTarget: WebGLRenderTarget,
44
47
  WebGLRenderer: WebGLRenderer
45
48
  }, [webgpu]);
@@ -313,15 +316,16 @@ function EnvironmentPortal({
313
316
  preset = void 0,
314
317
  extensions
315
318
  }) {
316
- const gl = useThree$1((state) => state.gl);
319
+ const renderer = useThree$1((state) => state.renderer);
320
+ const isLegacy = useThree$1((state) => state.isLegacy);
317
321
  const defaultScene = useThree$1((state) => state.scene);
318
322
  const camera = React.useRef(null);
319
323
  const [virtualScene] = React.useState(() => new Scene());
320
324
  const fbo = React.useMemo(() => {
321
- const fbo2 = new WebGLCubeRenderTarget(resolution);
325
+ const fbo2 = isLegacy ? new WebGLCubeRenderTarget(resolution) : new CubeRenderTarget(resolution);
322
326
  fbo2.texture.type = HalfFloatType;
323
327
  return fbo2;
324
- }, [resolution]);
328
+ }, [resolution, isLegacy]);
325
329
  React.useEffect(() => {
326
330
  return () => {
327
331
  fbo.dispose();
@@ -329,10 +333,10 @@ function EnvironmentPortal({
329
333
  }, [fbo]);
330
334
  React.useLayoutEffect(() => {
331
335
  if (frames === 1) {
332
- const autoClear = gl.autoClear;
333
- gl.autoClear = true;
334
- camera.current.update(gl, virtualScene);
335
- gl.autoClear = autoClear;
336
+ const autoClear = renderer.autoClear;
337
+ renderer.autoClear = true;
338
+ camera.current.update(renderer, virtualScene);
339
+ renderer.autoClear = autoClear;
336
340
  }
337
341
  return setEnvProps(background, scene, defaultScene, fbo.texture, {
338
342
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -349,7 +353,7 @@ function EnvironmentPortal({
349
353
  defaultScene,
350
354
  background,
351
355
  frames,
352
- gl,
356
+ renderer,
353
357
  blur,
354
358
  backgroundBlurriness,
355
359
  backgroundIntensity,
@@ -360,10 +364,10 @@ function EnvironmentPortal({
360
364
  let count = 1;
361
365
  useFrame$1(() => {
362
366
  if (frames === Infinity || count < frames) {
363
- const autoClear = gl.autoClear;
364
- gl.autoClear = true;
365
- camera.current.update(gl, virtualScene);
366
- gl.autoClear = autoClear;
367
+ const autoClear = renderer.autoClear;
368
+ renderer.autoClear = true;
369
+ camera.current.update(renderer, virtualScene);
370
+ renderer.autoClear = autoClear;
367
371
  count++;
368
372
  }
369
373
  });
@@ -606,8 +610,7 @@ function prepare(target, root, type, props) {
606
610
  object,
607
611
  eventCount: 0,
608
612
  handlers: {},
609
- isHidden: false,
610
- deferredRefs: []
613
+ isHidden: false
611
614
  };
612
615
  if (object) object.__r3f = instance;
613
616
  }
@@ -764,6 +767,17 @@ function unregisterVisibility(store, object) {
764
767
  internal.visibilityRegistry.delete(object.uuid);
765
768
  internal.occlusionCache.delete(object);
766
769
  }
770
+ function updateVisibilityHandlers(store, object, handlers) {
771
+ const { internal } = store.getState();
772
+ const entry = internal.visibilityRegistry.get(object.uuid);
773
+ if (!entry) return false;
774
+ entry.handlers = handlers;
775
+ if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
776
+ object.occlusionTest = true;
777
+ enableOcclusion(store);
778
+ }
779
+ return true;
780
+ }
767
781
  function checkVisibility(state) {
768
782
  const { internal, camera } = state;
769
783
  const registry = internal.visibilityRegistry;
@@ -951,10 +965,7 @@ function applyProps(object, props) {
951
965
  continue;
952
966
  }
953
967
  if (value === void 0) continue;
954
- if (isFromRef(value)) {
955
- instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
956
- continue;
957
- }
968
+ if (isFromRef(value)) continue;
958
969
  if (isOnce(value)) {
959
970
  if (instance?.appliedOnce?.has(prop)) continue;
960
971
  if (instance) {
@@ -990,6 +1001,19 @@ function applyProps(object, props) {
990
1001
  } else if (isVectorLike(target) && is.num(value)) {
991
1002
  if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
992
1003
  else target.set(value);
1004
+ } else if (root instanceof ShaderMaterial && key === "uniforms" && is.obj(value)) {
1005
+ if (!is.obj(root.uniforms)) root.uniforms = {};
1006
+ const uniforms = root.uniforms;
1007
+ const nextUniforms = value;
1008
+ for (const name in nextUniforms) {
1009
+ const uniform = nextUniforms[name];
1010
+ const targetUniform = uniforms[name];
1011
+ if (targetUniform) Object.assign(targetUniform, uniform);
1012
+ else {
1013
+ const nextUniform = uniform instanceof Uniform ? uniform.clone() : new Uniform(uniform.value);
1014
+ uniforms[name] = nextUniform;
1015
+ }
1016
+ }
993
1017
  } else {
994
1018
  root[key] = value;
995
1019
  if (key.endsWith("Node") && root.isMaterial) {
@@ -1008,6 +1032,9 @@ function applyProps(object, props) {
1008
1032
  if (instance.eventCount && object2.raycast !== null) {
1009
1033
  rootState.internal.interaction.push(object2);
1010
1034
  }
1035
+ }
1036
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
1037
+ const object2 = instance.object;
1011
1038
  const root = findInitialRoot(instance);
1012
1039
  const visibilityHandlers = {
1013
1040
  onFramed: instance.handlers.onFramed,
@@ -1015,7 +1042,9 @@ function applyProps(object, props) {
1015
1042
  onVisible: instance.handlers.onVisible
1016
1043
  };
1017
1044
  if (hasVisibilityHandlers(visibilityHandlers)) {
1018
- registerVisibility(root, object2, visibilityHandlers);
1045
+ if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
1046
+ registerVisibility(root, object2, visibilityHandlers);
1047
+ }
1019
1048
  } else {
1020
1049
  unregisterVisibility(root, object2);
1021
1050
  }
@@ -1543,6 +1572,7 @@ function createPointerEvents(store) {
1543
1572
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1544
1573
  }
1545
1574
  },
1575
+ // PREVIEW (A8): the XR pointer registration API may change before stable.
1546
1576
  registerPointer: (config) => {
1547
1577
  const pointerId = nextXRPointerId++;
1548
1578
  xrPointers.set(pointerId, config);
@@ -1629,6 +1659,13 @@ function notifyAlpha({ message, link }) {
1629
1659
  }
1630
1660
  }
1631
1661
 
1662
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
1663
+ function isInternalRendererAccess(stack, callerDepth = 2) {
1664
+ if (!stack) return false;
1665
+ const caller = stack.split("\n")[callerDepth] ?? "";
1666
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
1667
+ }
1668
+
1632
1669
  const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1633
1670
  const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
1634
1671
  const createStore = (invalidate, advance) => {
@@ -1761,6 +1798,7 @@ const createStore = (invalidate, advance) => {
1761
1798
  }),
1762
1799
  setFrameloop: (frameloop = "always") => {
1763
1800
  set(() => ({ frameloop }));
1801
+ getScheduler().frameloop = frameloop;
1764
1802
  },
1765
1803
  setError: (error) => set(() => ({ error })),
1766
1804
  error: null,
@@ -1828,8 +1866,7 @@ const createStore = (invalidate, advance) => {
1828
1866
  const currentState = rootStore.getState();
1829
1867
  if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1830
1868
  const stack = new Error().stack || "";
1831
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1832
- if (!isInternalAccess) {
1869
+ if (!isInternalRendererAccess(stack)) {
1833
1870
  const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1834
1871
  notifyDepreciated({
1835
1872
  heading: "Accessing state.gl in WebGPU mode",
@@ -1942,19 +1979,25 @@ function loadingFn(extensions, onProgress) {
1942
1979
  );
1943
1980
  };
1944
1981
  }
1945
- function useLoader(loader, input, extensions, onProgress) {
1946
- const keys = Array.isArray(input) ? input : [input];
1982
+ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1983
+ const inputs = Array.isArray(input) ? input : [input];
1984
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1947
1985
  const fn = loadingFn(extensions, onProgress);
1948
- const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
1986
+ const results = cacheKeys.map(
1987
+ (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1988
+ );
1949
1989
  return Array.isArray(input) ? results : results[0];
1950
1990
  }
1951
- useLoader.preload = function(loader, input, extensions, onProgress) {
1952
- const keys = Array.isArray(input) ? input : [input];
1953
- keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
1991
+ useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1992
+ const inputs = Array.isArray(input) ? input : [input];
1993
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1994
+ cacheKeys.forEach(
1995
+ (key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
1996
+ );
1954
1997
  };
1955
- useLoader.clear = function(loader, input) {
1956
- const keys = Array.isArray(input) ? input : [input];
1957
- keys.forEach((key) => clear([loader, key]));
1998
+ useLoader.clear = function(loader, input, cacheKey) {
1999
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2000
+ cacheKeys.forEach((key) => clear([loader, key]));
1958
2001
  };
1959
2002
  useLoader.loader = getLoader;
1960
2003
 
@@ -2376,7 +2419,7 @@ function advance(timestamp) {
2376
2419
  getScheduler().step(timestamp);
2377
2420
  }
2378
2421
 
2379
- const version = "10.0.0-alpha.2";
2422
+ const version = "10.0.0-alpha.3";
2380
2423
  const packageData = {
2381
2424
  version: version};
2382
2425
 
@@ -13815,6 +13858,7 @@ function swapInstances() {
13815
13858
  }
13816
13859
  }
13817
13860
  if (instance.isHidden) unhideInstance(instance);
13861
+ if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
13818
13862
  if (instance.object.__r3f) delete instance.object.__r3f;
13819
13863
  if (instance.type !== "primitive") disposeOnIdle(instance.object);
13820
13864
  }
@@ -13827,6 +13871,8 @@ function swapInstances() {
13827
13871
  instance.object.__r3f = instance;
13828
13872
  setFiberRef(fiber, instance.object);
13829
13873
  delete instance.appliedOnce;
13874
+ instance.eventCount = 0;
13875
+ instance.handlers = {};
13830
13876
  applyProps(instance.object, instance.props);
13831
13877
  if (instance.props.attach) {
13832
13878
  attach(parent, instance);
@@ -14042,11 +14088,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
14042
14088
  cloneMutableTextInstance(textInstance) {
14043
14089
  return textInstance;
14044
14090
  },
14091
+ // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14092
+ // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14093
+ // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14094
+ // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14095
+ // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14096
+ // Guarded by a test asserting a full render + unmount never reaches them.
14045
14097
  cloneRootViewTransitionContainer(_rootContainer) {
14046
- throw new Error("Not implemented.");
14047
14098
  },
14048
14099
  removeRootViewTransitionClone(_rootContainer, _clone) {
14049
- throw new Error("Not implemented.");
14050
14100
  },
14051
14101
  // https://github.com/facebook/react/pull/32465
14052
14102
  createFragmentInstance: (_fiber) => null,
@@ -14131,6 +14181,7 @@ function createRoot(canvas) {
14131
14181
  const lastConfiguredProps = {};
14132
14182
  let configured = false;
14133
14183
  let pending = null;
14184
+ let rendererSetup = null;
14134
14185
  return {
14135
14186
  async configure(props = {}) {
14136
14187
  let resolve;
@@ -14161,7 +14212,7 @@ function createRoot(canvas) {
14161
14212
  forceEven
14162
14213
  } = props;
14163
14214
  const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
14164
- const state = store.getState();
14215
+ let state = store.getState();
14165
14216
  const defaultGLProps = {
14166
14217
  canvas,
14167
14218
  powerPreference: "high-performance",
@@ -14189,56 +14240,68 @@ function createRoot(canvas) {
14189
14240
  "The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
14190
14241
  );
14191
14242
  }
14192
- if (wantsGL && !state.internal.actualRenderer) {
14193
- renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
14194
- state.internal.actualRenderer = renderer;
14195
- state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14196
- } else if (!wantsGL && primaryCanvas && !state.internal.actualRenderer) {
14197
- const primary = await waitForPrimary(primaryCanvas);
14198
- renderer = primary.renderer;
14199
- state.internal.actualRenderer = renderer;
14200
- const canvasTarget = new CanvasTarget(canvas);
14201
- primary.store.setState((prev) => ({
14202
- internal: { ...prev.internal, isMultiCanvas: true }
14203
- }));
14204
- state.set((prev) => ({
14205
- webGPUSupported: primary.store.getState().webGPUSupported,
14206
- renderer,
14207
- primaryStore: primary.store,
14208
- internal: {
14209
- ...prev.internal,
14210
- canvasTarget,
14211
- isMultiCanvas: true,
14212
- isSecondary: true,
14213
- targetId: primaryCanvas
14214
- }
14215
- }));
14216
- } else if (!wantsGL && !state.internal.actualRenderer) {
14217
- renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
14218
- if (!renderer.hasInitialized?.()) {
14219
- const size2 = computeInitialSize(canvas, propsSize);
14220
- if (size2.width > 0 && size2.height > 0) {
14221
- const pixelRatio = calculateDpr(dpr);
14222
- canvas.width = size2.width * pixelRatio;
14223
- canvas.height = size2.height * pixelRatio;
14224
- }
14225
- await renderer.init();
14226
- }
14227
- const backend = renderer.backend;
14228
- const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14229
- state.internal.actualRenderer = renderer;
14230
- state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14231
- if (canvasId && !state.internal.isSecondary) {
14232
- const canvasTarget = new CanvasTarget(canvas);
14233
- const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14234
- state.set((prev) => ({
14235
- internal: {
14236
- ...prev.internal,
14237
- canvasTarget,
14238
- unregisterPrimary
14243
+ if (!state.internal.actualRenderer) {
14244
+ if (!rendererSetup) {
14245
+ rendererSetup = (async () => {
14246
+ if (wantsGL) {
14247
+ renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
14248
+ state.internal.actualRenderer = renderer;
14249
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14250
+ } else if (!wantsGL && primaryCanvas) {
14251
+ const primary = await waitForPrimary(primaryCanvas);
14252
+ renderer = primary.renderer;
14253
+ state.internal.actualRenderer = renderer;
14254
+ const canvasTarget = new CanvasTarget(canvas);
14255
+ primary.store.setState((prev) => ({
14256
+ internal: { ...prev.internal, isMultiCanvas: true }
14257
+ }));
14258
+ state.set((prev) => ({
14259
+ webGPUSupported: primary.store.getState().webGPUSupported,
14260
+ renderer,
14261
+ primaryStore: primary.store,
14262
+ internal: {
14263
+ ...prev.internal,
14264
+ canvasTarget,
14265
+ isMultiCanvas: true,
14266
+ isSecondary: true,
14267
+ targetId: primaryCanvas
14268
+ }
14269
+ }));
14270
+ } else if (!wantsGL) {
14271
+ renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
14272
+ if (!renderer.hasInitialized?.()) {
14273
+ const size2 = computeInitialSize(canvas, propsSize);
14274
+ if (size2.width > 0 && size2.height > 0) {
14275
+ const pixelRatio = calculateDpr(dpr);
14276
+ canvas.width = size2.width * pixelRatio;
14277
+ canvas.height = size2.height * pixelRatio;
14278
+ }
14279
+ await renderer.init();
14280
+ }
14281
+ const backend = renderer.backend;
14282
+ const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14283
+ state.internal.actualRenderer = renderer;
14284
+ state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14285
+ if (canvasId && !state.internal.isSecondary) {
14286
+ const canvasTarget = new CanvasTarget(canvas);
14287
+ const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14288
+ state.set((prev) => ({
14289
+ internal: {
14290
+ ...prev.internal,
14291
+ canvasTarget,
14292
+ unregisterPrimary
14293
+ }
14294
+ }));
14295
+ }
14239
14296
  }
14240
- }));
14297
+ })().catch((err) => {
14298
+ rendererSetup = null;
14299
+ throw err;
14300
+ });
14241
14301
  }
14302
+ await rendererSetup;
14303
+ state = store.getState();
14304
+ renderer = state.internal.actualRenderer;
14242
14305
  }
14243
14306
  let raycaster = state.raycaster;
14244
14307
  if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
@@ -14588,7 +14651,8 @@ function unmountComponentAtNode(canvas, callback) {
14588
14651
  dispose(state.scene);
14589
14652
  _roots.delete(canvas);
14590
14653
  if (callback) callback(canvas);
14591
- } catch {
14654
+ } catch (error) {
14655
+ console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
14592
14656
  }
14593
14657
  }, 500);
14594
14658
  }
@@ -14651,6 +14715,7 @@ function PortalInner({ state = {}, children, container }) {
14651
14715
  viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
14652
14716
  if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14653
14717
  }
14718
+ const set = injectState.set;
14654
14719
  return {
14655
14720
  // The intersect consists of the previous root state
14656
14721
  ...rootState,
@@ -14669,7 +14734,7 @@ function PortalInner({ state = {}, children, container }) {
14669
14734
  size: resolvedSize,
14670
14735
  viewport: { ...rootState.viewport, ...viewport },
14671
14736
  // Layers are allowed to override events
14672
- setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14737
+ setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14673
14738
  // Container for child attachment - the portalScene (injected or container itself)
14674
14739
  internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
14675
14740
  };
@@ -14728,14 +14793,49 @@ function parseBackground(background) {
14728
14793
  return null;
14729
14794
  }
14730
14795
 
14796
+ function parseRendererConfig(rendererProp) {
14797
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14798
+ if (isRendererConfig) {
14799
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14800
+ return {
14801
+ primaryCanvas: pc,
14802
+ scheduler: sc,
14803
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14804
+ };
14805
+ }
14806
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14807
+ }
14808
+
14809
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14810
+ const registries = /* @__PURE__ */ new WeakMap();
14811
+ function getKindRegistry(store, kind) {
14812
+ let registry = registries.get(store);
14813
+ if (!registry) {
14814
+ registry = {
14815
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14816
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14817
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14818
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14819
+ };
14820
+ registries.set(store, registry);
14821
+ }
14822
+ return registry[kind];
14823
+ }
14824
+ function invalidateResource(store, kind, scopeKey) {
14825
+ const registry = getKindRegistry(store, kind);
14826
+ {
14827
+ registry.valid.clear();
14828
+ registry.staged.clear();
14829
+ }
14830
+ }
14831
+ function invalidateAllResources(store) {
14832
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14833
+ }
14834
+
14731
14835
  function clearHmrCaches(store) {
14732
- store.setState((state) => ({
14733
- nodes: {},
14734
- uniforms: {},
14735
- buffers: {},
14736
- gpuStorage: {},
14737
- _hmrVersion: state._hmrVersion + 1
14738
- }));
14836
+ const primary = store.getState().primaryStore ?? store;
14837
+ invalidateAllResources(primary);
14838
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14739
14839
  }
14740
14840
 
14741
14841
  function CanvasImpl({
@@ -14771,18 +14871,7 @@ function CanvasImpl({
14771
14871
  forceEven,
14772
14872
  ...props
14773
14873
  }) {
14774
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14775
- let primaryCanvas;
14776
- let scheduler;
14777
- let renderer;
14778
- if (isRendererConfig) {
14779
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14780
- primaryCanvas = pc;
14781
- scheduler = sc;
14782
- renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
14783
- } else {
14784
- renderer = rendererProp;
14785
- }
14874
+ const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
14786
14875
  React.useMemo(() => extend(THREE), []);
14787
14876
  const Bridge = useBridge();
14788
14877
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
@@ -14827,6 +14916,7 @@ function CanvasImpl({
14827
14916
  const handleDropMissed = useMutableCallback(onDropMissed);
14828
14917
  const [block, setBlock] = React.useState(false);
14829
14918
  const [error, setError] = React.useState(false);
14919
+ const [fallbackVisible, setFallbackVisible] = React.useState(false);
14830
14920
  if (block) throw block;
14831
14921
  if (error) throw error;
14832
14922
  const root = React.useRef(null);
@@ -14834,6 +14924,7 @@ function CanvasImpl({
14834
14924
  const unsubscribeErrorRef = React.useRef(null);
14835
14925
  useIsomorphicLayoutEffect(() => {
14836
14926
  effectActiveRef.current = true;
14927
+ if (fallbackVisible) return;
14837
14928
  const canvas = canvasRef.current;
14838
14929
  if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
14839
14930
  if (!root.current) {
@@ -14854,7 +14945,7 @@ function CanvasImpl({
14854
14945
  }
14855
14946
  async function run() {
14856
14947
  if (!effectActiveRef.current || !root.current) return;
14857
- await root.current.configure({
14948
+ const configured = await root.current.configure({
14858
14949
  id,
14859
14950
  primaryCanvas,
14860
14951
  scheduler,
@@ -14895,8 +14986,20 @@ function CanvasImpl({
14895
14986
  }
14896
14987
  onCreated?.(state);
14897
14988
  }
14898
- });
14899
- if (!effectActiveRef.current || !root.current) return;
14989
+ }).then(
14990
+ () => true,
14991
+ // Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
14992
+ // inside <canvas>, which browsers don't display, so surface it as visible DOM
14993
+ // instead; with no fallback, rethrow to an external error boundary. (#3757)
14994
+ (setupError) => {
14995
+ if (effectActiveRef.current) {
14996
+ if (fallback != null) setFallbackVisible(true);
14997
+ else setError(setupError);
14998
+ }
14999
+ return false;
15000
+ }
15001
+ );
15002
+ if (!configured || !effectActiveRef.current || !root.current) return;
14900
15003
  root.current.render(
14901
15004
  /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
14902
15005
  backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
@@ -14959,7 +15062,11 @@ function CanvasImpl({
14959
15062
  ...style
14960
15063
  },
14961
15064
  ...props,
14962
- children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
15065
+ children: fallbackVisible ? (
15066
+ // Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
15067
+ // it exists in the tree but browsers never paint it, which is the whole bug (#3757).
15068
+ fallback
15069
+ ) : /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
14963
15070
  "canvas",
14964
15071
  {
14965
15072
  ref: canvasRef,