@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/legacy.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as three from 'three';
2
- import { WebGLRenderTarget, 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, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGLRenderer } from 'three';
2
+ import { WebGLCubeRenderTarget, WebGLRenderTarget, 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, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGLRenderer } from 'three';
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
5
  import React__default, { useLayoutEffect, useRef, useMemo, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
@@ -42,9 +42,12 @@ const WebGPURenderer = class WebGPURenderer2 {
42
42
  }
43
43
  };
44
44
  const RenderTarget = null;
45
+ const CubeRenderTarget = null;
45
46
 
46
47
  const THREE = /*#__PURE__*/_mergeNamespaces({
47
48
  __proto__: null,
49
+ CubeRenderTarget: CubeRenderTarget,
50
+ CubeRenderTargetCompat: WebGLCubeRenderTarget,
48
51
  Inspector: Inspector,
49
52
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
50
53
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
@@ -322,15 +325,16 @@ function EnvironmentPortal({
322
325
  preset = void 0,
323
326
  extensions
324
327
  }) {
325
- const gl = useThree$1((state) => state.gl);
328
+ const renderer = useThree$1((state) => state.renderer);
329
+ const isLegacy = useThree$1((state) => state.isLegacy);
326
330
  const defaultScene = useThree$1((state) => state.scene);
327
331
  const camera = React.useRef(null);
328
332
  const [virtualScene] = React.useState(() => new Scene());
329
333
  const fbo = React.useMemo(() => {
330
- const fbo2 = new WebGLCubeRenderTarget(resolution);
334
+ const fbo2 = isLegacy ? new WebGLCubeRenderTarget(resolution) : new CubeRenderTarget(resolution);
331
335
  fbo2.texture.type = HalfFloatType;
332
336
  return fbo2;
333
- }, [resolution]);
337
+ }, [resolution, isLegacy]);
334
338
  React.useEffect(() => {
335
339
  return () => {
336
340
  fbo.dispose();
@@ -338,10 +342,10 @@ function EnvironmentPortal({
338
342
  }, [fbo]);
339
343
  React.useLayoutEffect(() => {
340
344
  if (frames === 1) {
341
- const autoClear = gl.autoClear;
342
- gl.autoClear = true;
343
- camera.current.update(gl, virtualScene);
344
- gl.autoClear = autoClear;
345
+ const autoClear = renderer.autoClear;
346
+ renderer.autoClear = true;
347
+ camera.current.update(renderer, virtualScene);
348
+ renderer.autoClear = autoClear;
345
349
  }
346
350
  return setEnvProps(background, scene, defaultScene, fbo.texture, {
347
351
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -358,7 +362,7 @@ function EnvironmentPortal({
358
362
  defaultScene,
359
363
  background,
360
364
  frames,
361
- gl,
365
+ renderer,
362
366
  blur,
363
367
  backgroundBlurriness,
364
368
  backgroundIntensity,
@@ -369,10 +373,10 @@ function EnvironmentPortal({
369
373
  let count = 1;
370
374
  useFrame$1(() => {
371
375
  if (frames === Infinity || count < frames) {
372
- const autoClear = gl.autoClear;
373
- gl.autoClear = true;
374
- camera.current.update(gl, virtualScene);
375
- gl.autoClear = autoClear;
376
+ const autoClear = renderer.autoClear;
377
+ renderer.autoClear = true;
378
+ camera.current.update(renderer, virtualScene);
379
+ renderer.autoClear = autoClear;
376
380
  count++;
377
381
  }
378
382
  });
@@ -615,8 +619,7 @@ function prepare(target, root, type, props) {
615
619
  object,
616
620
  eventCount: 0,
617
621
  handlers: {},
618
- isHidden: false,
619
- deferredRefs: []
622
+ isHidden: false
620
623
  };
621
624
  if (object) object.__r3f = instance;
622
625
  }
@@ -773,6 +776,17 @@ function unregisterVisibility(store, object) {
773
776
  internal.visibilityRegistry.delete(object.uuid);
774
777
  internal.occlusionCache.delete(object);
775
778
  }
779
+ function updateVisibilityHandlers(store, object, handlers) {
780
+ const { internal } = store.getState();
781
+ const entry = internal.visibilityRegistry.get(object.uuid);
782
+ if (!entry) return false;
783
+ entry.handlers = handlers;
784
+ if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
785
+ object.occlusionTest = true;
786
+ enableOcclusion(store);
787
+ }
788
+ return true;
789
+ }
776
790
  function checkVisibility(state) {
777
791
  const { internal, camera } = state;
778
792
  const registry = internal.visibilityRegistry;
@@ -960,10 +974,7 @@ function applyProps(object, props) {
960
974
  continue;
961
975
  }
962
976
  if (value === void 0) continue;
963
- if (isFromRef(value)) {
964
- instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
965
- continue;
966
- }
977
+ if (isFromRef(value)) continue;
967
978
  if (isOnce(value)) {
968
979
  if (instance?.appliedOnce?.has(prop)) continue;
969
980
  if (instance) {
@@ -999,6 +1010,19 @@ function applyProps(object, props) {
999
1010
  } else if (isVectorLike(target) && is.num(value)) {
1000
1011
  if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
1001
1012
  else target.set(value);
1013
+ } else if (root instanceof ShaderMaterial && key === "uniforms" && is.obj(value)) {
1014
+ if (!is.obj(root.uniforms)) root.uniforms = {};
1015
+ const uniforms = root.uniforms;
1016
+ const nextUniforms = value;
1017
+ for (const name in nextUniforms) {
1018
+ const uniform = nextUniforms[name];
1019
+ const targetUniform = uniforms[name];
1020
+ if (targetUniform) Object.assign(targetUniform, uniform);
1021
+ else {
1022
+ const nextUniform = uniform instanceof Uniform ? uniform.clone() : new Uniform(uniform.value);
1023
+ uniforms[name] = nextUniform;
1024
+ }
1025
+ }
1002
1026
  } else {
1003
1027
  root[key] = value;
1004
1028
  if (key.endsWith("Node") && root.isMaterial) {
@@ -1017,6 +1041,9 @@ function applyProps(object, props) {
1017
1041
  if (instance.eventCount && object2.raycast !== null) {
1018
1042
  rootState.internal.interaction.push(object2);
1019
1043
  }
1044
+ }
1045
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
1046
+ const object2 = instance.object;
1020
1047
  const root = findInitialRoot(instance);
1021
1048
  const visibilityHandlers = {
1022
1049
  onFramed: instance.handlers.onFramed,
@@ -1024,7 +1051,9 @@ function applyProps(object, props) {
1024
1051
  onVisible: instance.handlers.onVisible
1025
1052
  };
1026
1053
  if (hasVisibilityHandlers(visibilityHandlers)) {
1027
- registerVisibility(root, object2, visibilityHandlers);
1054
+ if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
1055
+ registerVisibility(root, object2, visibilityHandlers);
1056
+ }
1028
1057
  } else {
1029
1058
  unregisterVisibility(root, object2);
1030
1059
  }
@@ -1552,6 +1581,7 @@ function createPointerEvents(store) {
1552
1581
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1553
1582
  }
1554
1583
  },
1584
+ // PREVIEW (A8): the XR pointer registration API may change before stable.
1555
1585
  registerPointer: (config) => {
1556
1586
  const pointerId = nextXRPointerId++;
1557
1587
  xrPointers.set(pointerId, config);
@@ -1638,6 +1668,13 @@ function notifyAlpha({ message, link }) {
1638
1668
  }
1639
1669
  }
1640
1670
 
1671
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
1672
+ function isInternalRendererAccess(stack, callerDepth = 2) {
1673
+ if (!stack) return false;
1674
+ const caller = stack.split("\n")[callerDepth] ?? "";
1675
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
1676
+ }
1677
+
1641
1678
  const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1642
1679
  const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
1643
1680
  const createStore = (invalidate, advance) => {
@@ -1770,6 +1807,7 @@ const createStore = (invalidate, advance) => {
1770
1807
  }),
1771
1808
  setFrameloop: (frameloop = "always") => {
1772
1809
  set(() => ({ frameloop }));
1810
+ getScheduler().frameloop = frameloop;
1773
1811
  },
1774
1812
  setError: (error) => set(() => ({ error })),
1775
1813
  error: null,
@@ -1837,8 +1875,7 @@ const createStore = (invalidate, advance) => {
1837
1875
  const currentState = rootStore.getState();
1838
1876
  if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1839
1877
  const stack = new Error().stack || "";
1840
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1841
- if (!isInternalAccess) {
1878
+ if (!isInternalRendererAccess(stack)) {
1842
1879
  const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1843
1880
  notifyDepreciated({
1844
1881
  heading: "Accessing state.gl in WebGPU mode",
@@ -1951,19 +1988,25 @@ function loadingFn(extensions, onProgress) {
1951
1988
  );
1952
1989
  };
1953
1990
  }
1954
- function useLoader(loader, input, extensions, onProgress) {
1955
- const keys = Array.isArray(input) ? input : [input];
1991
+ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1992
+ const inputs = Array.isArray(input) ? input : [input];
1993
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1956
1994
  const fn = loadingFn(extensions, onProgress);
1957
- const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
1995
+ const results = cacheKeys.map(
1996
+ (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1997
+ );
1958
1998
  return Array.isArray(input) ? results : results[0];
1959
1999
  }
1960
- useLoader.preload = function(loader, input, extensions, onProgress) {
1961
- const keys = Array.isArray(input) ? input : [input];
1962
- keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
2000
+ useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
2001
+ const inputs = Array.isArray(input) ? input : [input];
2002
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2003
+ cacheKeys.forEach(
2004
+ (key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
2005
+ );
1963
2006
  };
1964
- useLoader.clear = function(loader, input) {
1965
- const keys = Array.isArray(input) ? input : [input];
1966
- keys.forEach((key) => clear([loader, key]));
2007
+ useLoader.clear = function(loader, input, cacheKey) {
2008
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2009
+ cacheKeys.forEach((key) => clear([loader, key]));
1967
2010
  };
1968
2011
  useLoader.loader = getLoader;
1969
2012
 
@@ -2383,7 +2426,7 @@ function advance(timestamp) {
2383
2426
  getScheduler().step(timestamp);
2384
2427
  }
2385
2428
 
2386
- const version = "10.0.0-alpha.2";
2429
+ const version = "10.0.0-alpha.3";
2387
2430
  const packageData = {
2388
2431
  version: version};
2389
2432
 
@@ -13822,6 +13865,7 @@ function swapInstances() {
13822
13865
  }
13823
13866
  }
13824
13867
  if (instance.isHidden) unhideInstance(instance);
13868
+ if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
13825
13869
  if (instance.object.__r3f) delete instance.object.__r3f;
13826
13870
  if (instance.type !== "primitive") disposeOnIdle(instance.object);
13827
13871
  }
@@ -13834,6 +13878,8 @@ function swapInstances() {
13834
13878
  instance.object.__r3f = instance;
13835
13879
  setFiberRef(fiber, instance.object);
13836
13880
  delete instance.appliedOnce;
13881
+ instance.eventCount = 0;
13882
+ instance.handlers = {};
13837
13883
  applyProps(instance.object, instance.props);
13838
13884
  if (instance.props.attach) {
13839
13885
  attach(parent, instance);
@@ -14049,11 +14095,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
14049
14095
  cloneMutableTextInstance(textInstance) {
14050
14096
  return textInstance;
14051
14097
  },
14098
+ // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14099
+ // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14100
+ // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14101
+ // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14102
+ // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14103
+ // Guarded by a test asserting a full render + unmount never reaches them.
14052
14104
  cloneRootViewTransitionContainer(_rootContainer) {
14053
- throw new Error("Not implemented.");
14054
14105
  },
14055
14106
  removeRootViewTransitionClone(_rootContainer, _clone) {
14056
- throw new Error("Not implemented.");
14057
14107
  },
14058
14108
  // https://github.com/facebook/react/pull/32465
14059
14109
  createFragmentInstance: (_fiber) => null,
@@ -14138,6 +14188,7 @@ function createRoot(canvas) {
14138
14188
  const lastConfiguredProps = {};
14139
14189
  let configured = false;
14140
14190
  let pending = null;
14191
+ let rendererSetup = null;
14141
14192
  return {
14142
14193
  async configure(props = {}) {
14143
14194
  let resolve;
@@ -14168,7 +14219,7 @@ function createRoot(canvas) {
14168
14219
  forceEven
14169
14220
  } = props;
14170
14221
  const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
14171
- const state = store.getState();
14222
+ let state = store.getState();
14172
14223
  const defaultGLProps = {
14173
14224
  canvas,
14174
14225
  powerPreference: "high-performance",
@@ -14196,9 +14247,21 @@ function createRoot(canvas) {
14196
14247
  );
14197
14248
  }
14198
14249
  if (!state.internal.actualRenderer) {
14199
- renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
14200
- state.internal.actualRenderer = renderer;
14201
- state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14250
+ if (!rendererSetup) {
14251
+ rendererSetup = (async () => {
14252
+ {
14253
+ renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
14254
+ state.internal.actualRenderer = renderer;
14255
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14256
+ }
14257
+ })().catch((err) => {
14258
+ rendererSetup = null;
14259
+ throw err;
14260
+ });
14261
+ }
14262
+ await rendererSetup;
14263
+ state = store.getState();
14264
+ renderer = state.internal.actualRenderer;
14202
14265
  }
14203
14266
  let raycaster = state.raycaster;
14204
14267
  if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
@@ -14548,7 +14611,8 @@ function unmountComponentAtNode(canvas, callback) {
14548
14611
  dispose(state.scene);
14549
14612
  _roots.delete(canvas);
14550
14613
  if (callback) callback(canvas);
14551
- } catch {
14614
+ } catch (error) {
14615
+ console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
14552
14616
  }
14553
14617
  }, 500);
14554
14618
  }
@@ -14611,6 +14675,7 @@ function PortalInner({ state = {}, children, container }) {
14611
14675
  viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
14612
14676
  if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14613
14677
  }
14678
+ const set = injectState.set;
14614
14679
  return {
14615
14680
  // The intersect consists of the previous root state
14616
14681
  ...rootState,
@@ -14629,7 +14694,7 @@ function PortalInner({ state = {}, children, container }) {
14629
14694
  size: resolvedSize,
14630
14695
  viewport: { ...rootState.viewport, ...viewport },
14631
14696
  // Layers are allowed to override events
14632
- setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14697
+ setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14633
14698
  // Container for child attachment - the portalScene (injected or container itself)
14634
14699
  internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
14635
14700
  };
@@ -14688,14 +14753,49 @@ function parseBackground(background) {
14688
14753
  return null;
14689
14754
  }
14690
14755
 
14756
+ function parseRendererConfig(rendererProp) {
14757
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14758
+ if (isRendererConfig) {
14759
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14760
+ return {
14761
+ primaryCanvas: pc,
14762
+ scheduler: sc,
14763
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14764
+ };
14765
+ }
14766
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14767
+ }
14768
+
14769
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14770
+ const registries = /* @__PURE__ */ new WeakMap();
14771
+ function getKindRegistry(store, kind) {
14772
+ let registry = registries.get(store);
14773
+ if (!registry) {
14774
+ registry = {
14775
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14776
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14777
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14778
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14779
+ };
14780
+ registries.set(store, registry);
14781
+ }
14782
+ return registry[kind];
14783
+ }
14784
+ function invalidateResource(store, kind, scopeKey) {
14785
+ const registry = getKindRegistry(store, kind);
14786
+ {
14787
+ registry.valid.clear();
14788
+ registry.staged.clear();
14789
+ }
14790
+ }
14791
+ function invalidateAllResources(store) {
14792
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14793
+ }
14794
+
14691
14795
  function clearHmrCaches(store) {
14692
- store.setState((state) => ({
14693
- nodes: {},
14694
- uniforms: {},
14695
- buffers: {},
14696
- gpuStorage: {},
14697
- _hmrVersion: state._hmrVersion + 1
14698
- }));
14796
+ const primary = store.getState().primaryStore ?? store;
14797
+ invalidateAllResources(primary);
14798
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14699
14799
  }
14700
14800
 
14701
14801
  function CanvasImpl({
@@ -14731,18 +14831,7 @@ function CanvasImpl({
14731
14831
  forceEven,
14732
14832
  ...props
14733
14833
  }) {
14734
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14735
- let primaryCanvas;
14736
- let scheduler;
14737
- let renderer;
14738
- if (isRendererConfig) {
14739
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14740
- primaryCanvas = pc;
14741
- scheduler = sc;
14742
- renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
14743
- } else {
14744
- renderer = rendererProp;
14745
- }
14834
+ const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
14746
14835
  React.useMemo(() => extend(THREE), []);
14747
14836
  const Bridge = useBridge();
14748
14837
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
@@ -14787,6 +14876,7 @@ function CanvasImpl({
14787
14876
  const handleDropMissed = useMutableCallback(onDropMissed);
14788
14877
  const [block, setBlock] = React.useState(false);
14789
14878
  const [error, setError] = React.useState(false);
14879
+ const [fallbackVisible, setFallbackVisible] = React.useState(false);
14790
14880
  if (block) throw block;
14791
14881
  if (error) throw error;
14792
14882
  const root = React.useRef(null);
@@ -14794,6 +14884,7 @@ function CanvasImpl({
14794
14884
  const unsubscribeErrorRef = React.useRef(null);
14795
14885
  useIsomorphicLayoutEffect(() => {
14796
14886
  effectActiveRef.current = true;
14887
+ if (fallbackVisible) return;
14797
14888
  const canvas = canvasRef.current;
14798
14889
  if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
14799
14890
  if (!root.current) {
@@ -14814,7 +14905,7 @@ function CanvasImpl({
14814
14905
  }
14815
14906
  async function run() {
14816
14907
  if (!effectActiveRef.current || !root.current) return;
14817
- await root.current.configure({
14908
+ const configured = await root.current.configure({
14818
14909
  id,
14819
14910
  primaryCanvas,
14820
14911
  scheduler,
@@ -14855,8 +14946,20 @@ function CanvasImpl({
14855
14946
  }
14856
14947
  onCreated?.(state);
14857
14948
  }
14858
- });
14859
- if (!effectActiveRef.current || !root.current) return;
14949
+ }).then(
14950
+ () => true,
14951
+ // Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
14952
+ // inside <canvas>, which browsers don't display, so surface it as visible DOM
14953
+ // instead; with no fallback, rethrow to an external error boundary. (#3757)
14954
+ (setupError) => {
14955
+ if (effectActiveRef.current) {
14956
+ if (fallback != null) setFallbackVisible(true);
14957
+ else setError(setupError);
14958
+ }
14959
+ return false;
14960
+ }
14961
+ );
14962
+ if (!configured || !effectActiveRef.current || !root.current) return;
14860
14963
  root.current.render(
14861
14964
  /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
14862
14965
  backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
@@ -14919,7 +15022,11 @@ function CanvasImpl({
14919
15022
  ...style
14920
15023
  },
14921
15024
  ...props,
14922
- children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
15025
+ children: fallbackVisible ? (
15026
+ // Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
15027
+ // it exists in the tree but browsers never paint it, which is the whole bug (#3757).
15028
+ fallback
15029
+ ) : /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
14923
15030
  "canvas",
14924
15031
  {
14925
15032
  ref: canvasRef,