@react-three/fiber 10.0.0-canary.0d86c23 → 10.0.0-canary.0ea97c0

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();
@@ -609,7 +612,12 @@ function diffProps(instance, newProps) {
609
612
  const changedProps = {};
610
613
  for (const prop in newProps) {
611
614
  if (RESERVED_PROPS.includes(prop)) continue;
612
- if (is.equ(newProps[prop], instance.props[prop])) continue;
615
+ if (is.equ(newProps[prop], instance.props[prop])) {
616
+ const value = newProps[prop];
617
+ if (!isCopyable(value)) continue;
618
+ const { target } = resolve(instance.object, prop);
619
+ if (!isCopyable(target) || target.constructor !== value.constructor) continue;
620
+ }
613
621
  changedProps[prop] = newProps[prop];
614
622
  for (const other in newProps) {
615
623
  if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
@@ -912,7 +920,7 @@ const createStore = (invalidate, advance) => {
912
920
  size: newSize,
913
921
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
914
922
  }));
915
- getScheduler().invalidate();
923
+ get().invalidate();
916
924
  }
917
925
  }
918
926
  return;
@@ -927,15 +935,16 @@ const createStore = (invalidate, advance) => {
927
935
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
928
936
  _sizeImperative: true
929
937
  }));
930
- getScheduler().invalidate();
938
+ get().invalidate();
931
939
  },
932
940
  setDpr: (dpr) => set((state2) => {
933
941
  const resolved = calculateDpr(dpr);
934
942
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
935
943
  }),
936
944
  setFrameloop: (frameloop = "always") => {
945
+ const rootId = get().internal.rootId;
946
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
937
947
  set(() => ({ frameloop }));
938
- getScheduler().frameloop = frameloop;
939
948
  },
940
949
  setError: (error) => set(() => ({ error })),
941
950
  error: null,
@@ -1051,17 +1060,11 @@ const createStore = (invalidate, advance) => {
1051
1060
  oldSize = size;
1052
1061
  oldDpr = viewport.dpr;
1053
1062
  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
- }
1063
+ const resizeTarget = canvasTarget ?? actualRenderer;
1064
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1065
+ resizeTarget.setSize(size.width, size.height, false);
1066
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1067
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1065
1068
  }
1066
1069
  if (camera !== oldCamera) {
1067
1070
  oldCamera = camera;
@@ -1123,7 +1126,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1123
1126
  const results = cacheKeys.map(
1124
1127
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1125
1128
  );
1126
- return Array.isArray(input) ? results : results[0];
1129
+ const stableRef = useRef(null);
1130
+ const previous = stableRef.current;
1131
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1132
+ stableRef.current = stable;
1133
+ return Array.isArray(input) ? stable : stable[0];
1127
1134
  }
1128
1135
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1129
1136
  const inputs = Array.isArray(input) ? input : [input];
@@ -1138,7 +1145,7 @@ useLoader.clear = function(loader, input, cacheKey) {
1138
1145
  };
1139
1146
  useLoader.loader = getLoader$1;
1140
1147
 
1141
- function useFrame(callback, priorityOrOptions) {
1148
+ function useFrame$1(callback, priorityOrOptions) {
1142
1149
  const store = React.useContext(context);
1143
1150
  const isInsideCanvas = store !== null;
1144
1151
  const scheduler = getScheduler();
@@ -1243,6 +1250,13 @@ function useFrame(callback, priorityOrOptions) {
1243
1250
  * Use for controlling the entire frame loop, adding phases, etc.
1244
1251
  */
1245
1252
  scheduler: scheduler2,
1253
+ /**
1254
+ * The root that currently owns this job. Resolve on access because ambient
1255
+ * jobs can be adopted by a Canvas after the controls object is created.
1256
+ */
1257
+ get rootId() {
1258
+ return getScheduler().getJobRootId(id);
1259
+ },
1246
1260
  /**
1247
1261
  * Manually step this job only.
1248
1262
  * Bypasses FPS limiting - always runs.
@@ -1259,6 +1273,14 @@ function useFrame(callback, priorityOrOptions) {
1259
1273
  stepAll: (timestamp) => {
1260
1274
  getScheduler().step(timestamp);
1261
1275
  },
1276
+ /**
1277
+ * Request frames for this job's owning root without waking sibling roots.
1278
+ */
1279
+ invalidate: (frames, stackFrames) => {
1280
+ const scheduler3 = getScheduler();
1281
+ const rootId = scheduler3.getJobRootId(id);
1282
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1283
+ },
1262
1284
  /**
1263
1285
  * Pause this job (set enabled=false).
1264
1286
  * Job remains registered but won't run.
@@ -1288,6 +1310,11 @@ function getUrls(input) {
1288
1310
  if (Array.isArray(input)) return input;
1289
1311
  return Object.values(input);
1290
1312
  }
1313
+ function inputSignature(input) {
1314
+ if (typeof input === "string") return input;
1315
+ if (Array.isArray(input)) return input.join("\0");
1316
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1317
+ }
1291
1318
  function allUrlsCached(urls, textureCache) {
1292
1319
  return urls.every((url) => textureCache.has(url));
1293
1320
  }
@@ -1306,25 +1333,26 @@ function buildFromCache(input, textureCache) {
1306
1333
  }
1307
1334
  }
1308
1335
  function useTexture(input, optionsOrOnLoad) {
1309
- const renderer = useThree((state) => state.internal.actualRenderer);
1336
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1310
1337
  const store = useStore();
1311
1338
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1312
1339
  const { onLoad, cache = true } = options;
1313
1340
  const onLoadRef = useRef(onLoad);
1314
1341
  onLoadRef.current = onLoad;
1315
1342
  const onLoadCalledForRef = useRef(null);
1316
- const urls = useMemo(() => getUrls(input), [input]);
1343
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1344
+ const stableInput = useMemo(() => input, [inputKey]);
1345
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1317
1346
  const cachedResult = useMemo(() => {
1318
1347
  if (!cache) return null;
1319
1348
  const textures = store.getState().textures;
1320
1349
  if (!allUrlsCached(urls, textures)) return null;
1321
- return buildFromCache(input, textures);
1322
- }, [cache, urls, input, store]);
1350
+ return buildFromCache(stableInput, textures);
1351
+ }, [cache, urls, stableInput, store]);
1323
1352
  const loadedTextures = useLoader(
1324
1353
  TextureLoader,
1325
- IsObject(input) ? Object.values(input) : input
1354
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1326
1355
  );
1327
- const inputKey = urls.join("\0");
1328
1356
  useLayoutEffect(() => {
1329
1357
  if (cachedResult) return;
1330
1358
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1351,28 +1379,28 @@ function useTexture(input, optionsOrOnLoad) {
1351
1379
  }, [renderer, loadedTextures, cachedResult]);
1352
1380
  const mappedTextures = useMemo(() => {
1353
1381
  if (cachedResult) return cachedResult;
1354
- if (IsObject(input)) {
1382
+ if (IsObject(stableInput)) {
1355
1383
  const keyed = {};
1356
1384
  const textureArray = loadedTextures;
1357
1385
  let i = 0;
1358
- for (const key in input) keyed[key] = textureArray[i++];
1386
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1359
1387
  return keyed;
1360
1388
  } else {
1361
1389
  return loadedTextures;
1362
1390
  }
1363
- }, [input, loadedTextures, cachedResult]);
1391
+ }, [stableInput, loadedTextures, cachedResult]);
1364
1392
  useEffect(() => {
1365
1393
  if (!cache) return;
1366
1394
  const urlTextureMap = [];
1367
- if (typeof input === "string") {
1368
- urlTextureMap.push([input, mappedTextures]);
1369
- } else if (Array.isArray(input)) {
1395
+ if (typeof stableInput === "string") {
1396
+ urlTextureMap.push([stableInput, mappedTextures]);
1397
+ } else if (Array.isArray(stableInput)) {
1370
1398
  const textureArray = mappedTextures;
1371
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1372
- } else if (IsObject(input)) {
1399
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1400
+ } else if (IsObject(stableInput)) {
1373
1401
  const textureRecord = mappedTextures;
1374
- for (const key in input) {
1375
- const url = input[key];
1402
+ for (const key in stableInput) {
1403
+ const url = stableInput[key];
1376
1404
  urlTextureMap.push([url, textureRecord[key]]);
1377
1405
  }
1378
1406
  }
@@ -1401,7 +1429,7 @@ function useTexture(input, optionsOrOnLoad) {
1401
1429
  }
1402
1430
  return { _textureRefs: refs };
1403
1431
  });
1404
- }, [cache, input, mappedTextures, store]);
1432
+ }, [cache, stableInput, mappedTextures, store]);
1405
1433
  return mappedTextures;
1406
1434
  }
1407
1435
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1472,13 +1500,13 @@ function useTextures(selector) {
1472
1500
  };
1473
1501
  }, [store]);
1474
1502
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1475
- const selected = useThree(subscribe);
1503
+ const selected = useThree$1(subscribe);
1476
1504
  return selector ? selected : registry;
1477
1505
  }
1478
1506
 
1479
1507
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1480
- const isLegacy = useThree((s) => s.isLegacy);
1481
- const size = useThree((s) => s.size);
1508
+ const isLegacy = useThree$1((s) => s.isLegacy);
1509
+ const size = useThree$1((s) => s.size);
1482
1510
  let width;
1483
1511
  let height;
1484
1512
  let opts;
@@ -1535,7 +1563,7 @@ function useEnvironment({
1535
1563
  const { extension, isCubemap } = getExtension(files);
1536
1564
  const loader = getLoader(extension);
1537
1565
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1538
- const renderer = useThree((state) => state.renderer);
1566
+ const renderer = useThree$1((state) => state.renderer);
1539
1567
  useLayoutEffect(() => {
1540
1568
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1541
1569
  function clearGainmapTexture() {
@@ -1628,7 +1656,7 @@ function useStore() {
1628
1656
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1629
1657
  return store;
1630
1658
  }
1631
- function useThree(selector = (state) => state, equalityFn) {
1659
+ function useThree$1(selector = (state) => state, equalityFn) {
1632
1660
  return useStore()(selector, equalityFn);
1633
1661
  }
1634
1662
  function useInstanceHandle(ref) {
@@ -1670,13 +1698,19 @@ function addTail(callback) {
1670
1698
  return getScheduler().onIdle(callback);
1671
1699
  }
1672
1700
  function invalidate(state, frames = 1, stackFrames = false) {
1673
- getScheduler().invalidate(frames, stackFrames);
1701
+ const rootId = state?.internal?.rootId;
1702
+ const scheduler = getScheduler();
1703
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1704
+ else scheduler.invalidate(frames, stackFrames);
1674
1705
  }
1675
- function advance(timestamp) {
1676
- getScheduler().step(timestamp);
1706
+ function advance(timestamp, runGlobalEffects, state) {
1707
+ const rootId = state?.internal?.rootId;
1708
+ const scheduler = getScheduler();
1709
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1710
+ else scheduler.step(timestamp);
1677
1711
  }
1678
1712
 
1679
- const version = "10.0.0-alpha.3";
1713
+ const version = "10.0.0-alpha.4";
1680
1714
  const packageData = {
1681
1715
  version: version};
1682
1716
 
@@ -14192,9 +14226,8 @@ function createRoot(canvas) {
14192
14226
  if (!renderer.hasInitialized?.()) {
14193
14227
  const size2 = computeInitialSize(canvas, propsSize);
14194
14228
  if (size2.width > 0 && size2.height > 0) {
14195
- const pixelRatio = calculateDpr(dpr);
14196
- canvas.width = size2.width * pixelRatio;
14197
- canvas.height = size2.height * pixelRatio;
14229
+ renderer.setPixelRatio(calculateDpr(dpr));
14230
+ renderer.setSize(size2.width, size2.height, false);
14198
14231
  }
14199
14232
  await renderer.init();
14200
14233
  }
@@ -14203,7 +14236,7 @@ function createRoot(canvas) {
14203
14236
  state.internal.actualRenderer = renderer;
14204
14237
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14205
14238
  if (canvasId && !state.internal.isSecondary) {
14206
- const canvasTarget = new CanvasTarget(canvas);
14239
+ const canvasTarget = renderer.getCanvasTarget?.();
14207
14240
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14208
14241
  state.set((prev) => ({
14209
14242
  internal: {
@@ -14317,7 +14350,7 @@ function createRoot(canvas) {
14317
14350
  const handleXRFrame = (timestamp, _frame) => {
14318
14351
  const state2 = store.getState();
14319
14352
  if (state2.frameloop === "never") return;
14320
- advance(timestamp);
14353
+ advance(timestamp, true, state2);
14321
14354
  };
14322
14355
  const actualRenderer = state.internal.actualRenderer;
14323
14356
  const handleSessionChange = () => {
@@ -14325,7 +14358,7 @@ function createRoot(canvas) {
14325
14358
  const renderer2 = state2.internal.actualRenderer;
14326
14359
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14327
14360
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14328
- if (!renderer2.xr.isPresenting) invalidate();
14361
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14329
14362
  };
14330
14363
  const xr = {
14331
14364
  connect() {
@@ -14407,14 +14440,23 @@ function createRoot(canvas) {
14407
14440
  const newRootId = canvasId || scheduler.generateRootId();
14408
14441
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14409
14442
  getState: () => store.getState(),
14410
- onError: (err) => store.getState().setError(err)
14443
+ onError: (err) => store.getState().setError(err),
14444
+ frameloop: store.getState().frameloop,
14445
+ before: schedulerConfig?.before,
14446
+ after: schedulerConfig?.after,
14447
+ order: schedulerConfig?.order
14411
14448
  });
14412
14449
  const unregisterCanvasTarget = scheduler.register(
14413
14450
  () => {
14414
14451
  const state2 = store.getState();
14415
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14452
+ const canvasTarget = state2.internal.canvasTarget;
14453
+ if (canvasTarget) {
14416
14454
  const renderer2 = state2.internal.actualRenderer;
14417
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14455
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14456
+ if (state2.internal.canvasTargetSizeDirty) {
14457
+ state2.internal.canvasTargetSizeDirty = false;
14458
+ renderer2.backend?.updateSize?.();
14459
+ }
14418
14460
  }
14419
14461
  },
14420
14462
  {
@@ -14483,8 +14525,7 @@ function createRoot(canvas) {
14483
14525
  phase: "render",
14484
14526
  system: true,
14485
14527
  // Internal flag: this is a system job, not user-controlled
14486
- // Apply scheduler config for render ordering and rate limiting
14487
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14528
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14488
14529
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14489
14530
  }
14490
14531
  );
@@ -14503,8 +14544,21 @@ function createRoot(canvas) {
14503
14544
  scheduler
14504
14545
  }
14505
14546
  }));
14547
+ } else {
14548
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14549
+ if (constraintsChanged) {
14550
+ scheduler.setRootConstraints(rootId, {
14551
+ before: schedulerConfig?.before,
14552
+ after: schedulerConfig?.after
14553
+ });
14554
+ }
14555
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14556
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14557
+ }
14506
14558
  }
14507
- scheduler.frameloop = frameloop;
14559
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14560
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14561
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14508
14562
  onCreated = onCreatedCallback;
14509
14563
  configured = true;
14510
14564
  resolve();
@@ -14546,18 +14600,23 @@ function unmountComponentAtNode(canvas, callback) {
14546
14600
  const fiber = root?.fiber;
14547
14601
  if (fiber) {
14548
14602
  const state = root?.store.getState();
14549
- if (state) state.internal.active = false;
14603
+ if (state) {
14604
+ state.internal.active = false;
14605
+ const unregisterRoot = state.internal.unregisterRoot;
14606
+ if (unregisterRoot) {
14607
+ unregisterRoot();
14608
+ state.internal.unregisterRoot = void 0;
14609
+ }
14610
+ }
14550
14611
  reconciler.updateContainer(null, fiber, null, () => {
14551
14612
  if (state) {
14552
14613
  setTimeout(() => {
14553
14614
  try {
14554
14615
  const renderer = state.internal.actualRenderer;
14555
- const unregisterRoot = state.internal.unregisterRoot;
14556
- if (unregisterRoot) unregisterRoot();
14557
14616
  const unregisterPrimary = state.internal.unregisterPrimary;
14558
14617
  if (unregisterPrimary) unregisterPrimary();
14559
14618
  const canvasTarget = state.internal.canvasTarget;
14560
- if (canvasTarget?.dispose) canvasTarget.dispose();
14619
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14561
14620
  state.events.disconnect?.();
14562
14621
  cleanupHelperGroup(root.store);
14563
14622
  if (state.isLegacy && renderer) {
@@ -14719,7 +14778,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14719
14778
  };
14720
14779
  }
14721
14780
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14722
- const defaultScene = useThree((state) => state.scene);
14781
+ const defaultScene = useThree$1((state) => state.scene);
14723
14782
  React.useLayoutEffect(() => {
14724
14783
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14725
14784
  });
@@ -14737,7 +14796,7 @@ function EnvironmentCube({
14737
14796
  ...rest
14738
14797
  }) {
14739
14798
  const texture = useEnvironment(rest);
14740
- const defaultScene = useThree((state) => state.scene);
14799
+ const defaultScene = useThree$1((state) => state.scene);
14741
14800
  React.useLayoutEffect(() => {
14742
14801
  return setEnvProps(background, scene, defaultScene, texture, {
14743
14802
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14774,9 +14833,9 @@ function EnvironmentPortal({
14774
14833
  preset = void 0,
14775
14834
  extensions
14776
14835
  }) {
14777
- const renderer = useThree((state) => state.renderer);
14778
- const isLegacy = useThree((state) => state.isLegacy);
14779
- const defaultScene = useThree((state) => state.scene);
14836
+ const renderer = useThree$1((state) => state.renderer);
14837
+ const isLegacy = useThree$1((state) => state.isLegacy);
14838
+ const defaultScene = useThree$1((state) => state.scene);
14780
14839
  const camera = React.useRef(null);
14781
14840
  const [virtualScene] = React.useState(() => new Scene());
14782
14841
  const fbo = React.useMemo(() => {
@@ -14820,7 +14879,7 @@ function EnvironmentPortal({
14820
14879
  environmentRotation
14821
14880
  ]);
14822
14881
  let count = 1;
14823
- useFrame(() => {
14882
+ useFrame$1(() => {
14824
14883
  if (frames === Infinity || count < frames) {
14825
14884
  const autoClear = renderer.autoClear;
14826
14885
  renderer.autoClear = true;
@@ -14860,7 +14919,7 @@ function EnvironmentGround(props) {
14860
14919
  ] });
14861
14920
  }
14862
14921
  function EnvironmentColor({ color, scene }) {
14863
- const defaultScene = useThree((state) => state.scene);
14922
+ const defaultScene = useThree$1((state) => state.scene);
14864
14923
  React.useLayoutEffect(() => {
14865
14924
  if (color === void 0) return;
14866
14925
  const target = resolveScene(scene || defaultScene);
@@ -15098,9 +15157,9 @@ function CanvasImpl({
15098
15157
  React.useMemo(() => extend(THREE), []);
15099
15158
  const Bridge = useBridge();
15100
15159
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15101
- const hasInitialSizeRef = React.useRef(false);
15160
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15102
15161
  const measureConfig = React.useMemo(() => {
15103
- if (!hasInitialSizeRef.current) {
15162
+ if (!hasInitialSize) {
15104
15163
  return {
15105
15164
  ...resize,
15106
15165
  scroll: resize?.scroll ?? true,
@@ -15112,7 +15171,7 @@ function CanvasImpl({
15112
15171
  debounce: { scroll: 50, resize: 0 },
15113
15172
  ...resize
15114
15173
  };
15115
- }, [resize, hasInitialSizeRef.current]);
15174
+ }, [hasInitialSize, resize]);
15116
15175
  const [containerRef, containerRect] = useMeasure(measureConfig);
15117
15176
  const effectiveSize = React.useMemo(() => {
15118
15177
  let w = width ?? containerRect.width;
@@ -15128,9 +15187,7 @@ function CanvasImpl({
15128
15187
  left: containerRect.left
15129
15188
  };
15130
15189
  }, [width, height, containerRect, forceEven]);
15131
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15132
- hasInitialSizeRef.current = true;
15133
- }
15190
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15134
15191
  const canvasRef = React.useRef(null);
15135
15192
  const divRef = React.useRef(null);
15136
15193
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15244,6 +15301,7 @@ function CanvasImpl({
15244
15301
  const canvas = canvasRef.current;
15245
15302
  if (canvas) {
15246
15303
  return () => {
15304
+ if (canvas.isConnected) return;
15247
15305
  unmountComponentAtNode(canvas);
15248
15306
  root.current = null;
15249
15307
  };
@@ -15454,6 +15512,11 @@ function createTextureOperations(set) {
15454
15512
  removeMultiple: (keys) => removeTextures(set, keys)
15455
15513
  };
15456
15514
  }
15515
+ function scopedNodeName(scope, name) {
15516
+ const raw = scope ? `${scope}_${name}` : name;
15517
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15518
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15519
+ }
15457
15520
  function extractTSLValue(value) {
15458
15521
  if (value === null || value === void 0) return value;
15459
15522
  if (typeof value !== "object") return value;
@@ -15652,8 +15715,7 @@ function createUniform(inName, node, scope) {
15652
15715
  const inValue = vectorize(node);
15653
15716
  const newUniform = uniform(inValue);
15654
15717
  if (typeof newUniform.setName === "function") {
15655
- const name = scope ? `${scope}_${inName}` : inName;
15656
- newUniform.setName(name);
15718
+ newUniform.setName(scopedNodeName(scope, inName));
15657
15719
  }
15658
15720
  return newUniform;
15659
15721
  }
@@ -15752,7 +15814,7 @@ function useNodes(creatorOrScope, scope) {
15752
15814
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15753
15815
  },
15754
15816
  prepare: (name, node) => {
15755
- node.setName?.(scope ? `${scope}.${name}` : name);
15817
+ node.setName?.(scopedNodeName(scope, name));
15756
15818
  return node;
15757
15819
  }
15758
15820
  });
@@ -15861,7 +15923,7 @@ function useBuffers(creatorOrScope, scope) {
15861
15923
  },
15862
15924
  prepare: (name, buffer) => {
15863
15925
  if ("setName" in buffer && typeof buffer.setName === "function") {
15864
- buffer.setName(scope ? `${scope}.${name}` : name);
15926
+ buffer.setName(scopedNodeName(scope, name));
15865
15927
  }
15866
15928
  return buffer;
15867
15929
  }
@@ -15932,7 +15994,7 @@ function useGPUStorage(creatorOrScope, scope) {
15932
15994
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15933
15995
  },
15934
15996
  prepare: (name, storage) => {
15935
- const label = scope ? `${scope}.${name}` : name;
15997
+ const label = scopedNodeName(scope, name);
15936
15998
  if ("setName" in storage && typeof storage.setName === "function") {
15937
15999
  storage.setName(label);
15938
16000
  }
@@ -15963,9 +16025,10 @@ function rebuildAllStorage(store, scope) {
15963
16025
 
15964
16026
  function useRenderPipeline(mainCB, setupCB) {
15965
16027
  const store = useStore();
15966
- const { scene, camera, renderer, isLegacy } = useThree();
16028
+ const { scene, camera, renderer, isLegacy } = useThree$1();
15967
16029
  const callbacksRanRef = useRef(false);
15968
16030
  const scenePassCacheRef = useRef(null);
16031
+ const forceScenePassRebuildRef = useRef(false);
15969
16032
  const mainCBRef = useRef(mainCB);
15970
16033
  const setupCBRef = useRef(setupCB);
15971
16034
  mainCBRef.current = mainCB;
@@ -15973,7 +16036,7 @@ function useRenderPipeline(mainCB, setupCB) {
15973
16036
  const [rebuildVersion, setRebuildVersion] = useState(0);
15974
16037
  useEffect(() => {
15975
16038
  callbacksRanRef.current = false;
15976
- scenePassCacheRef.current = null;
16039
+ forceScenePassRebuildRef.current = true;
15977
16040
  }, []);
15978
16041
  const clearPasses = useCallback(() => {
15979
16042
  store.setState({ passes: {} });
@@ -15983,12 +16046,14 @@ function useRenderPipeline(mainCB, setupCB) {
15983
16046
  renderPipeline: null,
15984
16047
  passes: {}
15985
16048
  });
16049
+ scenePassCacheRef.current?.scenePass.dispose();
15986
16050
  callbacksRanRef.current = false;
15987
16051
  scenePassCacheRef.current = null;
16052
+ forceScenePassRebuildRef.current = false;
15988
16053
  }, [store]);
15989
16054
  const rebuild = useCallback(() => {
15990
16055
  callbacksRanRef.current = false;
15991
- scenePassCacheRef.current = null;
16056
+ forceScenePassRebuildRef.current = true;
15992
16057
  setRebuildVersion((v) => v + 1);
15993
16058
  }, []);
15994
16059
  useLayoutEffect(() => {
@@ -15996,6 +16061,9 @@ function useRenderPipeline(mainCB, setupCB) {
15996
16061
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
15997
16062
  }
15998
16063
  if (!renderer || !scene || !camera) return;
16064
+ if (!("isWebGPURenderer" in renderer)) {
16065
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16066
+ }
15999
16067
  const state = store.getState();
16000
16068
  const set = store.setState;
16001
16069
  try {
@@ -16006,21 +16074,33 @@ function useRenderPipeline(mainCB, setupCB) {
16006
16074
  pipeline = new RenderPipeline(renderer);
16007
16075
  justCreatedPipeline = true;
16008
16076
  }
16009
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16077
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16078
+ let outgoingScenePass = null;
16010
16079
  let scenePass;
16011
16080
  if (cacheValid) {
16012
16081
  scenePass = scenePassCacheRef.current.scenePass;
16013
16082
  } else {
16083
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16014
16084
  scenePass = pass(scene, camera);
16015
16085
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16086
+ forceScenePassRebuildRef.current = false;
16016
16087
  }
16017
16088
  currentPasses.scenePass = scenePass;
16018
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16089
+ let outputNodeChanged = false;
16090
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16091
+ pipeline.outputNode = scenePass;
16092
+ outputNodeChanged = true;
16093
+ }
16019
16094
  set({ renderPipeline: pipeline, passes: currentPasses });
16020
16095
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16021
16096
  if (shouldRunCallbacks) {
16022
16097
  if (setupCBRef.current) {
16023
- const freshState = store.getState();
16098
+ const freshState = Object.assign({}, store.getState(), {
16099
+ renderPipeline: pipeline,
16100
+ passes: { ...currentPasses, scenePass },
16101
+ renderer,
16102
+ isLegacy: false
16103
+ });
16024
16104
  const setupResult = setupCBRef.current(freshState);
16025
16105
  if (setupResult && typeof setupResult === "object") {
16026
16106
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16028,32 +16108,36 @@ function useRenderPipeline(mainCB, setupCB) {
16028
16108
  }
16029
16109
  }
16030
16110
  if (mainCBRef.current) {
16031
- const freshState = store.getState();
16111
+ const freshState = Object.assign({}, store.getState(), {
16112
+ renderPipeline: pipeline,
16113
+ passes: { ...currentPasses, scenePass },
16114
+ renderer,
16115
+ isLegacy: false
16116
+ });
16032
16117
  const mainResult = mainCBRef.current(freshState);
16033
16118
  if (mainResult && typeof mainResult === "object") {
16034
16119
  currentPasses = { ...currentPasses, ...mainResult };
16035
16120
  set({ passes: currentPasses });
16036
16121
  }
16037
16122
  }
16123
+ outputNodeChanged = true;
16038
16124
  callbacksRanRef.current = true;
16039
16125
  }
16126
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16127
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16040
16128
  } catch (error) {
16041
16129
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16042
16130
  }
16043
16131
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16044
- const passes = useThree((s) => s.passes);
16045
- const renderPipeline = useThree((s) => s.renderPipeline);
16046
- return {
16047
- passes,
16048
- renderPipeline,
16049
- clearPasses,
16050
- reset,
16051
- rebuild,
16052
- // isReady indicates if RenderPipeline is configured and ready for rendering
16053
- isReady: renderPipeline !== null
16054
- };
16132
+ const passes = useThree$1((s) => s.passes);
16133
+ const renderPipeline = useThree$1((s) => s.renderPipeline);
16134
+ const actions = { passes, clearPasses, reset, rebuild };
16135
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16136
+ return { ...actions, isReady: false, renderPipeline: null };
16055
16137
  }
16056
16138
 
16057
16139
  extend(THREE);
16140
+ const useThree = useThree$1;
16141
+ const useFrame = useFrame$1;
16058
16142
 
16059
16143
  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 };