@react-three/fiber 10.0.0-canary.8f5b4d3 → 10.0.0-canary.979dc2d

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.
@@ -1,6 +1,5 @@
1
1
  import * as webgpu from 'three/webgpu';
2
2
  import { CubeRenderTarget, RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, HalfFloatType, Color, Vector4, RenderPipeline } from 'three/webgpu';
3
- import { Inspector } from 'three/addons/inspector/Inspector.js';
4
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
4
  import * as React from 'react';
6
5
  import React__default, { useRef, useMemo, useLayoutEffect, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
@@ -34,6 +33,10 @@ function _mergeNamespaces(n, m) {
34
33
 
35
34
  const R3F_BUILD_LEGACY = false;
36
35
  const R3F_BUILD_WEBGPU = true;
36
+ async function loadInspector() {
37
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
38
+ return Inspector;
39
+ }
37
40
  const WebGLRenderer = class WebGLRenderer2 {
38
41
  constructor() {
39
42
  throw new Error(
@@ -47,13 +50,13 @@ const WebGLCubeRenderTarget = null;
47
50
  const THREE = /*#__PURE__*/_mergeNamespaces({
48
51
  __proto__: null,
49
52
  CubeRenderTargetCompat: CubeRenderTarget,
50
- Inspector: Inspector,
51
53
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
52
54
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
53
55
  RenderTargetCompat: RenderTarget,
54
56
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
55
57
  WebGLRenderTarget: WebGLRenderTarget,
56
- WebGLRenderer: WebGLRenderer
58
+ WebGLRenderer: WebGLRenderer,
59
+ loadInspector: loadInspector
57
60
  }, [webgpu]);
58
61
 
59
62
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -912,7 +915,7 @@ const createStore = (invalidate, advance) => {
912
915
  size: newSize,
913
916
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
914
917
  }));
915
- getScheduler().invalidate();
918
+ get().invalidate();
916
919
  }
917
920
  }
918
921
  return;
@@ -927,15 +930,16 @@ const createStore = (invalidate, advance) => {
927
930
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
928
931
  _sizeImperative: true
929
932
  }));
930
- getScheduler().invalidate();
933
+ get().invalidate();
931
934
  },
932
935
  setDpr: (dpr) => set((state2) => {
933
936
  const resolved = calculateDpr(dpr);
934
937
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
935
938
  }),
936
939
  setFrameloop: (frameloop = "always") => {
940
+ const rootId = get().internal.rootId;
941
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
937
942
  set(() => ({ frameloop }));
938
- getScheduler().frameloop = frameloop;
939
943
  },
940
944
  setError: (error) => set(() => ({ error })),
941
945
  error: null,
@@ -1051,17 +1055,11 @@ const createStore = (invalidate, advance) => {
1051
1055
  oldSize = size;
1052
1056
  oldDpr = viewport.dpr;
1053
1057
  updateCamera(camera, size);
1054
- if (internal.isSecondary && canvasTarget) {
1055
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1056
- canvasTarget.setSize(size.width, size.height, false);
1057
- } else {
1058
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1059
- actualRenderer.setSize(size.width, size.height, false);
1060
- if (canvasTarget) {
1061
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1062
- canvasTarget.setSize(size.width, size.height, false);
1063
- }
1064
- }
1058
+ const resizeTarget = canvasTarget ?? actualRenderer;
1059
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1060
+ resizeTarget.setSize(size.width, size.height, false);
1061
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1062
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1065
1063
  }
1066
1064
  if (camera !== oldCamera) {
1067
1065
  oldCamera = camera;
@@ -1123,7 +1121,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1123
1121
  const results = cacheKeys.map(
1124
1122
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1125
1123
  );
1126
- return Array.isArray(input) ? results : results[0];
1124
+ const stableRef = useRef(null);
1125
+ const previous = stableRef.current;
1126
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1127
+ stableRef.current = stable;
1128
+ return Array.isArray(input) ? stable : stable[0];
1127
1129
  }
1128
1130
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1129
1131
  const inputs = Array.isArray(input) ? input : [input];
@@ -1138,7 +1140,7 @@ useLoader.clear = function(loader, input, cacheKey) {
1138
1140
  };
1139
1141
  useLoader.loader = getLoader$1;
1140
1142
 
1141
- function useFrame(callback, priorityOrOptions) {
1143
+ function useFrame$1(callback, priorityOrOptions) {
1142
1144
  const store = React.useContext(context);
1143
1145
  const isInsideCanvas = store !== null;
1144
1146
  const scheduler = getScheduler();
@@ -1243,6 +1245,13 @@ function useFrame(callback, priorityOrOptions) {
1243
1245
  * Use for controlling the entire frame loop, adding phases, etc.
1244
1246
  */
1245
1247
  scheduler: scheduler2,
1248
+ /**
1249
+ * The root that currently owns this job. Resolve on access because ambient
1250
+ * jobs can be adopted by a Canvas after the controls object is created.
1251
+ */
1252
+ get rootId() {
1253
+ return getScheduler().getJobRootId(id);
1254
+ },
1246
1255
  /**
1247
1256
  * Manually step this job only.
1248
1257
  * Bypasses FPS limiting - always runs.
@@ -1259,6 +1268,14 @@ function useFrame(callback, priorityOrOptions) {
1259
1268
  stepAll: (timestamp) => {
1260
1269
  getScheduler().step(timestamp);
1261
1270
  },
1271
+ /**
1272
+ * Request frames for this job's owning root without waking sibling roots.
1273
+ */
1274
+ invalidate: (frames, stackFrames) => {
1275
+ const scheduler3 = getScheduler();
1276
+ const rootId = scheduler3.getJobRootId(id);
1277
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1278
+ },
1262
1279
  /**
1263
1280
  * Pause this job (set enabled=false).
1264
1281
  * Job remains registered but won't run.
@@ -1288,6 +1305,11 @@ function getUrls(input) {
1288
1305
  if (Array.isArray(input)) return input;
1289
1306
  return Object.values(input);
1290
1307
  }
1308
+ function inputSignature(input) {
1309
+ if (typeof input === "string") return input;
1310
+ if (Array.isArray(input)) return input.join("\0");
1311
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1312
+ }
1291
1313
  function allUrlsCached(urls, textureCache) {
1292
1314
  return urls.every((url) => textureCache.has(url));
1293
1315
  }
@@ -1306,25 +1328,26 @@ function buildFromCache(input, textureCache) {
1306
1328
  }
1307
1329
  }
1308
1330
  function useTexture(input, optionsOrOnLoad) {
1309
- const renderer = useThree((state) => state.internal.actualRenderer);
1331
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1310
1332
  const store = useStore();
1311
1333
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1312
1334
  const { onLoad, cache = true } = options;
1313
1335
  const onLoadRef = useRef(onLoad);
1314
1336
  onLoadRef.current = onLoad;
1315
1337
  const onLoadCalledForRef = useRef(null);
1316
- const urls = useMemo(() => getUrls(input), [input]);
1338
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1339
+ const stableInput = useMemo(() => input, [inputKey]);
1340
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1317
1341
  const cachedResult = useMemo(() => {
1318
1342
  if (!cache) return null;
1319
1343
  const textures = store.getState().textures;
1320
1344
  if (!allUrlsCached(urls, textures)) return null;
1321
- return buildFromCache(input, textures);
1322
- }, [cache, urls, input, store]);
1345
+ return buildFromCache(stableInput, textures);
1346
+ }, [cache, urls, stableInput, store]);
1323
1347
  const loadedTextures = useLoader(
1324
1348
  TextureLoader,
1325
- IsObject(input) ? Object.values(input) : input
1349
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1326
1350
  );
1327
- const inputKey = urls.join("\0");
1328
1351
  useLayoutEffect(() => {
1329
1352
  if (cachedResult) return;
1330
1353
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1351,28 +1374,28 @@ function useTexture(input, optionsOrOnLoad) {
1351
1374
  }, [renderer, loadedTextures, cachedResult]);
1352
1375
  const mappedTextures = useMemo(() => {
1353
1376
  if (cachedResult) return cachedResult;
1354
- if (IsObject(input)) {
1377
+ if (IsObject(stableInput)) {
1355
1378
  const keyed = {};
1356
1379
  const textureArray = loadedTextures;
1357
1380
  let i = 0;
1358
- for (const key in input) keyed[key] = textureArray[i++];
1381
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1359
1382
  return keyed;
1360
1383
  } else {
1361
1384
  return loadedTextures;
1362
1385
  }
1363
- }, [input, loadedTextures, cachedResult]);
1386
+ }, [stableInput, loadedTextures, cachedResult]);
1364
1387
  useEffect(() => {
1365
1388
  if (!cache) return;
1366
1389
  const urlTextureMap = [];
1367
- if (typeof input === "string") {
1368
- urlTextureMap.push([input, mappedTextures]);
1369
- } else if (Array.isArray(input)) {
1390
+ if (typeof stableInput === "string") {
1391
+ urlTextureMap.push([stableInput, mappedTextures]);
1392
+ } else if (Array.isArray(stableInput)) {
1370
1393
  const textureArray = mappedTextures;
1371
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1372
- } else if (IsObject(input)) {
1394
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1395
+ } else if (IsObject(stableInput)) {
1373
1396
  const textureRecord = mappedTextures;
1374
- for (const key in input) {
1375
- const url = input[key];
1397
+ for (const key in stableInput) {
1398
+ const url = stableInput[key];
1376
1399
  urlTextureMap.push([url, textureRecord[key]]);
1377
1400
  }
1378
1401
  }
@@ -1401,7 +1424,7 @@ function useTexture(input, optionsOrOnLoad) {
1401
1424
  }
1402
1425
  return { _textureRefs: refs };
1403
1426
  });
1404
- }, [cache, input, mappedTextures, store]);
1427
+ }, [cache, stableInput, mappedTextures, store]);
1405
1428
  return mappedTextures;
1406
1429
  }
1407
1430
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1472,13 +1495,13 @@ function useTextures(selector) {
1472
1495
  };
1473
1496
  }, [store]);
1474
1497
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1475
- const selected = useThree(subscribe);
1498
+ const selected = useThree$1(subscribe);
1476
1499
  return selector ? selected : registry;
1477
1500
  }
1478
1501
 
1479
1502
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1480
- const isLegacy = useThree((s) => s.isLegacy);
1481
- const size = useThree((s) => s.size);
1503
+ const isLegacy = useThree$1((s) => s.isLegacy);
1504
+ const size = useThree$1((s) => s.size);
1482
1505
  let width;
1483
1506
  let height;
1484
1507
  let opts;
@@ -1535,7 +1558,7 @@ function useEnvironment({
1535
1558
  const { extension, isCubemap } = getExtension(files);
1536
1559
  const loader = getLoader(extension);
1537
1560
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1538
- const renderer = useThree((state) => state.renderer);
1561
+ const renderer = useThree$1((state) => state.renderer);
1539
1562
  useLayoutEffect(() => {
1540
1563
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1541
1564
  function clearGainmapTexture() {
@@ -1628,7 +1651,7 @@ function useStore() {
1628
1651
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1629
1652
  return store;
1630
1653
  }
1631
- function useThree(selector = (state) => state, equalityFn) {
1654
+ function useThree$1(selector = (state) => state, equalityFn) {
1632
1655
  return useStore()(selector, equalityFn);
1633
1656
  }
1634
1657
  function useInstanceHandle(ref) {
@@ -1670,13 +1693,19 @@ function addTail(callback) {
1670
1693
  return getScheduler().onIdle(callback);
1671
1694
  }
1672
1695
  function invalidate(state, frames = 1, stackFrames = false) {
1673
- getScheduler().invalidate(frames, stackFrames);
1696
+ const rootId = state?.internal?.rootId;
1697
+ const scheduler = getScheduler();
1698
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1699
+ else scheduler.invalidate(frames, stackFrames);
1674
1700
  }
1675
- function advance(timestamp) {
1676
- getScheduler().step(timestamp);
1701
+ function advance(timestamp, runGlobalEffects, state) {
1702
+ const rootId = state?.internal?.rootId;
1703
+ const scheduler = getScheduler();
1704
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1705
+ else scheduler.step(timestamp);
1677
1706
  }
1678
1707
 
1679
- const version = "10.0.0-alpha.3";
1708
+ const version = "10.0.0-alpha.4";
1680
1709
  const packageData = {
1681
1710
  version: version};
1682
1711
 
@@ -12959,7 +12988,10 @@ function releaseInternalPointerCapture(internal, obj, pointerId) {
12959
12988
  const captureData = pointerState.captured.get(obj);
12960
12989
  if (captureData) {
12961
12990
  pointerState.captured.delete(obj);
12962
- captureData.target.releasePointerCapture(pointerId);
12991
+ try {
12992
+ captureData.target.releasePointerCapture?.(pointerId);
12993
+ } catch {
12994
+ }
12963
12995
  }
12964
12996
  }
12965
12997
  function swapInteractivity(store, object, newObject) {
@@ -13053,7 +13085,9 @@ function createEvents(store) {
13053
13085
  state2.events.compute?.(event, state2, state2.previousRoot?.getState());
13054
13086
  if (state2.raycaster.camera === void 0) state2.raycaster.camera = null;
13055
13087
  }
13056
- return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
13088
+ if (!state2.raycaster.camera) return [];
13089
+ obj.updateWorldMatrix(true, true);
13090
+ return state2.raycaster.intersectObject(obj, true);
13057
13091
  }
13058
13092
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
13059
13093
  const aInteractivePriority = a.object.userData?.interactivePriority;
@@ -14187,9 +14221,8 @@ function createRoot(canvas) {
14187
14221
  if (!renderer.hasInitialized?.()) {
14188
14222
  const size2 = computeInitialSize(canvas, propsSize);
14189
14223
  if (size2.width > 0 && size2.height > 0) {
14190
- const pixelRatio = calculateDpr(dpr);
14191
- canvas.width = size2.width * pixelRatio;
14192
- canvas.height = size2.height * pixelRatio;
14224
+ renderer.setPixelRatio(calculateDpr(dpr));
14225
+ renderer.setSize(size2.width, size2.height, false);
14193
14226
  }
14194
14227
  await renderer.init();
14195
14228
  }
@@ -14198,7 +14231,7 @@ function createRoot(canvas) {
14198
14231
  state.internal.actualRenderer = renderer;
14199
14232
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14200
14233
  if (canvasId && !state.internal.isSecondary) {
14201
- const canvasTarget = new CanvasTarget(canvas);
14234
+ const canvasTarget = renderer.getCanvasTarget?.();
14202
14235
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14203
14236
  state.set((prev) => ({
14204
14237
  internal: {
@@ -14312,7 +14345,7 @@ function createRoot(canvas) {
14312
14345
  const handleXRFrame = (timestamp, _frame) => {
14313
14346
  const state2 = store.getState();
14314
14347
  if (state2.frameloop === "never") return;
14315
- advance(timestamp);
14348
+ advance(timestamp, true, state2);
14316
14349
  };
14317
14350
  const actualRenderer = state.internal.actualRenderer;
14318
14351
  const handleSessionChange = () => {
@@ -14320,7 +14353,7 @@ function createRoot(canvas) {
14320
14353
  const renderer2 = state2.internal.actualRenderer;
14321
14354
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14322
14355
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14323
- if (!renderer2.xr.isPresenting) invalidate();
14356
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14324
14357
  };
14325
14358
  const xr = {
14326
14359
  connect() {
@@ -14402,14 +14435,23 @@ function createRoot(canvas) {
14402
14435
  const newRootId = canvasId || scheduler.generateRootId();
14403
14436
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14404
14437
  getState: () => store.getState(),
14405
- onError: (err) => store.getState().setError(err)
14438
+ onError: (err) => store.getState().setError(err),
14439
+ frameloop: store.getState().frameloop,
14440
+ before: schedulerConfig?.before,
14441
+ after: schedulerConfig?.after,
14442
+ order: schedulerConfig?.order
14406
14443
  });
14407
14444
  const unregisterCanvasTarget = scheduler.register(
14408
14445
  () => {
14409
14446
  const state2 = store.getState();
14410
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14447
+ const canvasTarget = state2.internal.canvasTarget;
14448
+ if (canvasTarget) {
14411
14449
  const renderer2 = state2.internal.actualRenderer;
14412
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14450
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14451
+ if (state2.internal.canvasTargetSizeDirty) {
14452
+ state2.internal.canvasTargetSizeDirty = false;
14453
+ renderer2.backend?.updateSize?.();
14454
+ }
14413
14455
  }
14414
14456
  },
14415
14457
  {
@@ -14478,8 +14520,7 @@ function createRoot(canvas) {
14478
14520
  phase: "render",
14479
14521
  system: true,
14480
14522
  // Internal flag: this is a system job, not user-controlled
14481
- // Apply scheduler config for render ordering and rate limiting
14482
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14523
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14483
14524
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14484
14525
  }
14485
14526
  );
@@ -14498,8 +14539,21 @@ function createRoot(canvas) {
14498
14539
  scheduler
14499
14540
  }
14500
14541
  }));
14542
+ } else {
14543
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14544
+ if (constraintsChanged) {
14545
+ scheduler.setRootConstraints(rootId, {
14546
+ before: schedulerConfig?.before,
14547
+ after: schedulerConfig?.after
14548
+ });
14549
+ }
14550
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14551
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14552
+ }
14501
14553
  }
14502
- scheduler.frameloop = frameloop;
14554
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14555
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14556
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14503
14557
  onCreated = onCreatedCallback;
14504
14558
  configured = true;
14505
14559
  resolve();
@@ -14541,18 +14595,23 @@ function unmountComponentAtNode(canvas, callback) {
14541
14595
  const fiber = root?.fiber;
14542
14596
  if (fiber) {
14543
14597
  const state = root?.store.getState();
14544
- if (state) state.internal.active = false;
14598
+ if (state) {
14599
+ state.internal.active = false;
14600
+ const unregisterRoot = state.internal.unregisterRoot;
14601
+ if (unregisterRoot) {
14602
+ unregisterRoot();
14603
+ state.internal.unregisterRoot = void 0;
14604
+ }
14605
+ }
14545
14606
  reconciler.updateContainer(null, fiber, null, () => {
14546
14607
  if (state) {
14547
14608
  setTimeout(() => {
14548
14609
  try {
14549
14610
  const renderer = state.internal.actualRenderer;
14550
- const unregisterRoot = state.internal.unregisterRoot;
14551
- if (unregisterRoot) unregisterRoot();
14552
14611
  const unregisterPrimary = state.internal.unregisterPrimary;
14553
14612
  if (unregisterPrimary) unregisterPrimary();
14554
14613
  const canvasTarget = state.internal.canvasTarget;
14555
- if (canvasTarget?.dispose) canvasTarget.dispose();
14614
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14556
14615
  state.events.disconnect?.();
14557
14616
  cleanupHelperGroup(root.store);
14558
14617
  if (state.isLegacy && renderer) {
@@ -14714,7 +14773,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14714
14773
  };
14715
14774
  }
14716
14775
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14717
- const defaultScene = useThree((state) => state.scene);
14776
+ const defaultScene = useThree$1((state) => state.scene);
14718
14777
  React.useLayoutEffect(() => {
14719
14778
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14720
14779
  });
@@ -14732,7 +14791,7 @@ function EnvironmentCube({
14732
14791
  ...rest
14733
14792
  }) {
14734
14793
  const texture = useEnvironment(rest);
14735
- const defaultScene = useThree((state) => state.scene);
14794
+ const defaultScene = useThree$1((state) => state.scene);
14736
14795
  React.useLayoutEffect(() => {
14737
14796
  return setEnvProps(background, scene, defaultScene, texture, {
14738
14797
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14769,9 +14828,9 @@ function EnvironmentPortal({
14769
14828
  preset = void 0,
14770
14829
  extensions
14771
14830
  }) {
14772
- const renderer = useThree((state) => state.renderer);
14773
- const isLegacy = useThree((state) => state.isLegacy);
14774
- const defaultScene = useThree((state) => state.scene);
14831
+ const renderer = useThree$1((state) => state.renderer);
14832
+ const isLegacy = useThree$1((state) => state.isLegacy);
14833
+ const defaultScene = useThree$1((state) => state.scene);
14775
14834
  const camera = React.useRef(null);
14776
14835
  const [virtualScene] = React.useState(() => new Scene());
14777
14836
  const fbo = React.useMemo(() => {
@@ -14815,7 +14874,7 @@ function EnvironmentPortal({
14815
14874
  environmentRotation
14816
14875
  ]);
14817
14876
  let count = 1;
14818
- useFrame(() => {
14877
+ useFrame$1(() => {
14819
14878
  if (frames === Infinity || count < frames) {
14820
14879
  const autoClear = renderer.autoClear;
14821
14880
  renderer.autoClear = true;
@@ -14855,7 +14914,7 @@ function EnvironmentGround(props) {
14855
14914
  ] });
14856
14915
  }
14857
14916
  function EnvironmentColor({ color, scene }) {
14858
- const defaultScene = useThree((state) => state.scene);
14917
+ const defaultScene = useThree$1((state) => state.scene);
14859
14918
  React.useLayoutEffect(() => {
14860
14919
  if (color === void 0) return;
14861
14920
  const target = resolveScene(scene || defaultScene);
@@ -15093,9 +15152,9 @@ function CanvasImpl({
15093
15152
  React.useMemo(() => extend(THREE), []);
15094
15153
  const Bridge = useBridge();
15095
15154
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15096
- const hasInitialSizeRef = React.useRef(false);
15155
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15097
15156
  const measureConfig = React.useMemo(() => {
15098
- if (!hasInitialSizeRef.current) {
15157
+ if (!hasInitialSize) {
15099
15158
  return {
15100
15159
  ...resize,
15101
15160
  scroll: resize?.scroll ?? true,
@@ -15107,7 +15166,7 @@ function CanvasImpl({
15107
15166
  debounce: { scroll: 50, resize: 0 },
15108
15167
  ...resize
15109
15168
  };
15110
- }, [resize, hasInitialSizeRef.current]);
15169
+ }, [hasInitialSize, resize]);
15111
15170
  const [containerRef, containerRect] = useMeasure(measureConfig);
15112
15171
  const effectiveSize = React.useMemo(() => {
15113
15172
  let w = width ?? containerRect.width;
@@ -15123,9 +15182,7 @@ function CanvasImpl({
15123
15182
  left: containerRect.left
15124
15183
  };
15125
15184
  }, [width, height, containerRect, forceEven]);
15126
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15127
- hasInitialSizeRef.current = true;
15128
- }
15185
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15129
15186
  const canvasRef = React.useRef(null);
15130
15187
  const divRef = React.useRef(null);
15131
15188
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15239,6 +15296,7 @@ function CanvasImpl({
15239
15296
  const canvas = canvasRef.current;
15240
15297
  if (canvas) {
15241
15298
  return () => {
15299
+ if (canvas.isConnected) return;
15242
15300
  unmountComponentAtNode(canvas);
15243
15301
  root.current = null;
15244
15302
  };
@@ -15449,6 +15507,11 @@ function createTextureOperations(set) {
15449
15507
  removeMultiple: (keys) => removeTextures(set, keys)
15450
15508
  };
15451
15509
  }
15510
+ function scopedNodeName(scope, name) {
15511
+ const raw = scope ? `${scope}_${name}` : name;
15512
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15513
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15514
+ }
15452
15515
  function extractTSLValue(value) {
15453
15516
  if (value === null || value === void 0) return value;
15454
15517
  if (typeof value !== "object") return value;
@@ -15647,8 +15710,7 @@ function createUniform(inName, node, scope) {
15647
15710
  const inValue = vectorize(node);
15648
15711
  const newUniform = uniform(inValue);
15649
15712
  if (typeof newUniform.setName === "function") {
15650
- const name = scope ? `${scope}_${inName}` : inName;
15651
- newUniform.setName(name);
15713
+ newUniform.setName(scopedNodeName(scope, inName));
15652
15714
  }
15653
15715
  return newUniform;
15654
15716
  }
@@ -15747,7 +15809,7 @@ function useNodes(creatorOrScope, scope) {
15747
15809
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15748
15810
  },
15749
15811
  prepare: (name, node) => {
15750
- node.setName?.(scope ? `${scope}.${name}` : name);
15812
+ node.setName?.(scopedNodeName(scope, name));
15751
15813
  return node;
15752
15814
  }
15753
15815
  });
@@ -15856,7 +15918,7 @@ function useBuffers(creatorOrScope, scope) {
15856
15918
  },
15857
15919
  prepare: (name, buffer) => {
15858
15920
  if ("setName" in buffer && typeof buffer.setName === "function") {
15859
- buffer.setName(scope ? `${scope}.${name}` : name);
15921
+ buffer.setName(scopedNodeName(scope, name));
15860
15922
  }
15861
15923
  return buffer;
15862
15924
  }
@@ -15927,7 +15989,7 @@ function useGPUStorage(creatorOrScope, scope) {
15927
15989
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15928
15990
  },
15929
15991
  prepare: (name, storage) => {
15930
- const label = scope ? `${scope}.${name}` : name;
15992
+ const label = scopedNodeName(scope, name);
15931
15993
  if ("setName" in storage && typeof storage.setName === "function") {
15932
15994
  storage.setName(label);
15933
15995
  }
@@ -15958,9 +16020,10 @@ function rebuildAllStorage(store, scope) {
15958
16020
 
15959
16021
  function useRenderPipeline(mainCB, setupCB) {
15960
16022
  const store = useStore();
15961
- const { scene, camera, renderer, isLegacy } = useThree();
16023
+ const { scene, camera, renderer, isLegacy } = useThree$1();
15962
16024
  const callbacksRanRef = useRef(false);
15963
16025
  const scenePassCacheRef = useRef(null);
16026
+ const forceScenePassRebuildRef = useRef(false);
15964
16027
  const mainCBRef = useRef(mainCB);
15965
16028
  const setupCBRef = useRef(setupCB);
15966
16029
  mainCBRef.current = mainCB;
@@ -15968,7 +16031,7 @@ function useRenderPipeline(mainCB, setupCB) {
15968
16031
  const [rebuildVersion, setRebuildVersion] = useState(0);
15969
16032
  useEffect(() => {
15970
16033
  callbacksRanRef.current = false;
15971
- scenePassCacheRef.current = null;
16034
+ forceScenePassRebuildRef.current = true;
15972
16035
  }, []);
15973
16036
  const clearPasses = useCallback(() => {
15974
16037
  store.setState({ passes: {} });
@@ -15978,12 +16041,14 @@ function useRenderPipeline(mainCB, setupCB) {
15978
16041
  renderPipeline: null,
15979
16042
  passes: {}
15980
16043
  });
16044
+ scenePassCacheRef.current?.scenePass.dispose();
15981
16045
  callbacksRanRef.current = false;
15982
16046
  scenePassCacheRef.current = null;
16047
+ forceScenePassRebuildRef.current = false;
15983
16048
  }, [store]);
15984
16049
  const rebuild = useCallback(() => {
15985
16050
  callbacksRanRef.current = false;
15986
- scenePassCacheRef.current = null;
16051
+ forceScenePassRebuildRef.current = true;
15987
16052
  setRebuildVersion((v) => v + 1);
15988
16053
  }, []);
15989
16054
  useLayoutEffect(() => {
@@ -15991,6 +16056,9 @@ function useRenderPipeline(mainCB, setupCB) {
15991
16056
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
15992
16057
  }
15993
16058
  if (!renderer || !scene || !camera) return;
16059
+ if (!("isWebGPURenderer" in renderer)) {
16060
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16061
+ }
15994
16062
  const state = store.getState();
15995
16063
  const set = store.setState;
15996
16064
  try {
@@ -16001,21 +16069,33 @@ function useRenderPipeline(mainCB, setupCB) {
16001
16069
  pipeline = new RenderPipeline(renderer);
16002
16070
  justCreatedPipeline = true;
16003
16071
  }
16004
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16072
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16073
+ let outgoingScenePass = null;
16005
16074
  let scenePass;
16006
16075
  if (cacheValid) {
16007
16076
  scenePass = scenePassCacheRef.current.scenePass;
16008
16077
  } else {
16078
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16009
16079
  scenePass = pass(scene, camera);
16010
16080
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16081
+ forceScenePassRebuildRef.current = false;
16011
16082
  }
16012
16083
  currentPasses.scenePass = scenePass;
16013
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16084
+ let outputNodeChanged = false;
16085
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16086
+ pipeline.outputNode = scenePass;
16087
+ outputNodeChanged = true;
16088
+ }
16014
16089
  set({ renderPipeline: pipeline, passes: currentPasses });
16015
16090
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16016
16091
  if (shouldRunCallbacks) {
16017
16092
  if (setupCBRef.current) {
16018
- const freshState = store.getState();
16093
+ const freshState = Object.assign({}, store.getState(), {
16094
+ renderPipeline: pipeline,
16095
+ passes: { ...currentPasses, scenePass },
16096
+ renderer,
16097
+ isLegacy: false
16098
+ });
16019
16099
  const setupResult = setupCBRef.current(freshState);
16020
16100
  if (setupResult && typeof setupResult === "object") {
16021
16101
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16023,32 +16103,36 @@ function useRenderPipeline(mainCB, setupCB) {
16023
16103
  }
16024
16104
  }
16025
16105
  if (mainCBRef.current) {
16026
- const freshState = store.getState();
16106
+ const freshState = Object.assign({}, store.getState(), {
16107
+ renderPipeline: pipeline,
16108
+ passes: { ...currentPasses, scenePass },
16109
+ renderer,
16110
+ isLegacy: false
16111
+ });
16027
16112
  const mainResult = mainCBRef.current(freshState);
16028
16113
  if (mainResult && typeof mainResult === "object") {
16029
16114
  currentPasses = { ...currentPasses, ...mainResult };
16030
16115
  set({ passes: currentPasses });
16031
16116
  }
16032
16117
  }
16118
+ outputNodeChanged = true;
16033
16119
  callbacksRanRef.current = true;
16034
16120
  }
16121
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16122
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16035
16123
  } catch (error) {
16036
16124
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16037
16125
  }
16038
16126
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16039
- const passes = useThree((s) => s.passes);
16040
- const renderPipeline = useThree((s) => s.renderPipeline);
16041
- return {
16042
- passes,
16043
- renderPipeline,
16044
- clearPasses,
16045
- reset,
16046
- rebuild,
16047
- // isReady indicates if RenderPipeline is configured and ready for rendering
16048
- isReady: renderPipeline !== null
16049
- };
16127
+ const passes = useThree$1((s) => s.passes);
16128
+ const renderPipeline = useThree$1((s) => s.renderPipeline);
16129
+ const actions = { passes, clearPasses, reset, rebuild };
16130
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16131
+ return { ...actions, isReady: false, renderPipeline: null };
16050
16132
  }
16051
16133
 
16052
16134
  extend(THREE);
16135
+ const useThree = useThree$1;
16136
+ const useFrame = useFrame$1;
16053
16137
 
16054
16138
  export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Texture, _roots, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createScopedStore, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef$1 as isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, reconciler, registerPrimary, removeInteractivity, removeNodes, removeUniforms, resolve, swapInteractivity, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useBuffers, useEnvironment, useFrame, useGPUStorage, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, useRenderPipeline, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms, waitForPrimary };