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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  const webgpu = require('three/webgpu');
4
4
  const three = require('three');
5
- const Inspector_js = require('three/addons/inspector/Inspector.js');
6
5
  const jsxRuntime = require('react/jsx-runtime');
7
6
  const React = require('react');
8
7
  const useMeasure = require('react-use-measure');
@@ -52,18 +51,22 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
52
51
 
53
52
  const R3F_BUILD_LEGACY = true;
54
53
  const R3F_BUILD_WEBGPU = true;
54
+ async function loadInspector() {
55
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
56
+ return Inspector;
57
+ }
55
58
 
56
59
  const THREE = /*#__PURE__*/_mergeNamespaces({
57
60
  __proto__: null,
58
61
  CubeRenderTarget: webgpu.CubeRenderTarget,
59
62
  CubeRenderTargetCompat: webgpu.CubeRenderTarget,
60
- Inspector: Inspector_js.Inspector,
61
63
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
62
64
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
63
65
  RenderTargetCompat: webgpu.RenderTarget,
64
66
  WebGLCubeRenderTarget: three.WebGLCubeRenderTarget,
65
67
  WebGLRenderTarget: three.WebGLRenderTarget,
66
- WebGLRenderer: three.WebGLRenderer
68
+ WebGLRenderer: three.WebGLRenderer,
69
+ loadInspector: loadInspector
67
70
  }, [webgpu__namespace]);
68
71
 
69
72
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -1061,17 +1064,10 @@ const createStore = (invalidate, advance) => {
1061
1064
  oldSize = size;
1062
1065
  oldDpr = viewport.dpr;
1063
1066
  updateCamera(camera, size);
1064
- if (internal.isSecondary && canvasTarget) {
1065
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1066
- canvasTarget.setSize(size.width, size.height, false);
1067
- } else {
1068
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1069
- actualRenderer.setSize(size.width, size.height, false);
1070
- if (canvasTarget) {
1071
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1072
- canvasTarget.setSize(size.width, size.height, false);
1073
- }
1074
- }
1067
+ const resizeTarget = canvasTarget ?? actualRenderer;
1068
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1069
+ resizeTarget.setSize(size.width, size.height, false);
1070
+ internal.canvasTargetSizeDirty = true;
1075
1071
  }
1076
1072
  if (camera !== oldCamera) {
1077
1073
  oldCamera = camera;
@@ -1133,7 +1129,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1133
1129
  const results = cacheKeys.map(
1134
1130
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1135
1131
  );
1136
- return Array.isArray(input) ? results : results[0];
1132
+ const stableRef = React.useRef(null);
1133
+ const previous = stableRef.current;
1134
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1135
+ stableRef.current = stable;
1136
+ return Array.isArray(input) ? stable : stable[0];
1137
1137
  }
1138
1138
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1139
1139
  const inputs = Array.isArray(input) ? input : [input];
@@ -1298,6 +1298,11 @@ function getUrls(input) {
1298
1298
  if (Array.isArray(input)) return input;
1299
1299
  return Object.values(input);
1300
1300
  }
1301
+ function inputSignature(input) {
1302
+ if (typeof input === "string") return input;
1303
+ if (Array.isArray(input)) return input.join("\0");
1304
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1305
+ }
1301
1306
  function allUrlsCached(urls, textureCache) {
1302
1307
  return urls.every((url) => textureCache.has(url));
1303
1308
  }
@@ -1323,18 +1328,19 @@ function useTexture(input, optionsOrOnLoad) {
1323
1328
  const onLoadRef = React.useRef(onLoad);
1324
1329
  onLoadRef.current = onLoad;
1325
1330
  const onLoadCalledForRef = React.useRef(null);
1326
- const urls = React.useMemo(() => getUrls(input), [input]);
1331
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1332
+ const stableInput = React.useMemo(() => input, [inputKey]);
1333
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1327
1334
  const cachedResult = React.useMemo(() => {
1328
1335
  if (!cache) return null;
1329
1336
  const textures = store.getState().textures;
1330
1337
  if (!allUrlsCached(urls, textures)) return null;
1331
- return buildFromCache(input, textures);
1332
- }, [cache, urls, input, store]);
1338
+ return buildFromCache(stableInput, textures);
1339
+ }, [cache, urls, stableInput, store]);
1333
1340
  const loadedTextures = useLoader(
1334
1341
  webgpu.TextureLoader,
1335
- IsObject(input) ? Object.values(input) : input
1342
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1336
1343
  );
1337
- const inputKey = urls.join("\0");
1338
1344
  React.useLayoutEffect(() => {
1339
1345
  if (cachedResult) return;
1340
1346
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1361,28 +1367,28 @@ function useTexture(input, optionsOrOnLoad) {
1361
1367
  }, [renderer, loadedTextures, cachedResult]);
1362
1368
  const mappedTextures = React.useMemo(() => {
1363
1369
  if (cachedResult) return cachedResult;
1364
- if (IsObject(input)) {
1370
+ if (IsObject(stableInput)) {
1365
1371
  const keyed = {};
1366
1372
  const textureArray = loadedTextures;
1367
1373
  let i = 0;
1368
- for (const key in input) keyed[key] = textureArray[i++];
1374
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1369
1375
  return keyed;
1370
1376
  } else {
1371
1377
  return loadedTextures;
1372
1378
  }
1373
- }, [input, loadedTextures, cachedResult]);
1379
+ }, [stableInput, loadedTextures, cachedResult]);
1374
1380
  React.useEffect(() => {
1375
1381
  if (!cache) return;
1376
1382
  const urlTextureMap = [];
1377
- if (typeof input === "string") {
1378
- urlTextureMap.push([input, mappedTextures]);
1379
- } else if (Array.isArray(input)) {
1383
+ if (typeof stableInput === "string") {
1384
+ urlTextureMap.push([stableInput, mappedTextures]);
1385
+ } else if (Array.isArray(stableInput)) {
1380
1386
  const textureArray = mappedTextures;
1381
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1382
- } else if (IsObject(input)) {
1387
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1388
+ } else if (IsObject(stableInput)) {
1383
1389
  const textureRecord = mappedTextures;
1384
- for (const key in input) {
1385
- const url = input[key];
1390
+ for (const key in stableInput) {
1391
+ const url = stableInput[key];
1386
1392
  urlTextureMap.push([url, textureRecord[key]]);
1387
1393
  }
1388
1394
  }
@@ -1411,7 +1417,7 @@ function useTexture(input, optionsOrOnLoad) {
1411
1417
  }
1412
1418
  return { _textureRefs: refs };
1413
1419
  });
1414
- }, [cache, input, mappedTextures, store]);
1420
+ }, [cache, stableInput, mappedTextures, store]);
1415
1421
  return mappedTextures;
1416
1422
  }
1417
1423
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -14445,6 +14451,10 @@ function createRoot(canvas) {
14445
14451
  if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14446
14452
  const renderer2 = state2.internal.actualRenderer;
14447
14453
  renderer2.setCanvasTarget(state2.internal.canvasTarget);
14454
+ if (state2.internal.canvasTargetSizeDirty) {
14455
+ state2.internal.canvasTargetSizeDirty = false;
14456
+ renderer2.backend?.updateSize?.();
14457
+ }
14448
14458
  }
14449
14459
  },
14450
14460
  {
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as three_examples_jsm_inspector_Inspector_js from 'three/examples/jsm/inspector/Inspector.js';
1
2
  import * as three_webgpu from 'three/webgpu';
2
3
  import { CubeRenderTarget, RenderTarget, WebGPURenderer, Node, StorageTexture, Data3DTexture, CanvasTarget, ShaderNodeObject, Euler as Euler$2, Color as Color$2, ColorRepresentation as ColorRepresentation$1, Layers as Layers$1, Raycaster, Intersection as Intersection$1, BufferGeometry, Matrix4 as Matrix4$1, Quaternion as Quaternion$1, Vector2 as Vector2$1, Vector3 as Vector3$1, Vector4 as Vector4$1, Matrix3 as Matrix3$1, Loader as Loader$1, ColorSpace, Texture as Texture$1, CubeTexture, Scene, Object3D, Frustum, OrthographicCamera } from 'three/webgpu';
3
4
  import * as THREE$1 from 'three';
@@ -47,6 +48,9 @@ function _mergeNamespaces(n, m) {
47
48
  declare const R3F_BUILD_LEGACY = true;
48
49
  declare const R3F_BUILD_WEBGPU = true;
49
50
 
51
+ /** Lazily load the three Inspector. Keeps it out of the eager module graph — see note above. */
52
+ declare function loadInspector(): Promise<typeof three_examples_jsm_inspector_Inspector_js.Inspector>;
53
+
50
54
  var THREE = /*#__PURE__*/_mergeNamespaces({
51
55
  __proto__: null,
52
56
  CubeRenderTarget: CubeRenderTarget,
@@ -58,7 +62,8 @@ var THREE = /*#__PURE__*/_mergeNamespaces({
58
62
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
59
63
  WebGLRenderTarget: WebGLRenderTarget,
60
64
  WebGLRenderer: WebGLRenderer,
61
- WebGLRendererParameters: WebGLRendererParameters
65
+ WebGLRendererParameters: WebGLRendererParameters,
66
+ loadInspector: loadInspector
62
67
  }, [three_webgpu]);
63
68
 
64
69
  //* Utility Types ==============================
@@ -496,6 +501,18 @@ interface InternalState {
496
501
  * @see https://threejs.org/docs/#api/en/renderers/common/CanvasTarget
497
502
  */
498
503
  canvasTarget?: CanvasTarget
504
+ /**
505
+ * Set when this root's canvas target has been resized and the backend's cached render pass
506
+ * descriptor (which holds a depth-stencil view built once per canvas) is therefore stale.
507
+ *
508
+ * Flushed by the canvas-target job in the `start` phase, which is the only place this root's
509
+ * target is guaranteed to be the renderer's active one — `backend.updateSize()` operates on
510
+ * whatever `getCanvasTarget()` returns, so calling it from the resize subscription would
511
+ * invalidate some other canvas's descriptor instead.
512
+ *
513
+ * @see https://github.com/pmndrs/react-three-fiber/issues/3847
514
+ */
515
+ canvasTargetSizeDirty?: boolean
499
516
  /**
500
517
  * Whether multi-canvas rendering is active.
501
518
  * True when any canvas uses `renderer={{ primaryCanvas: 'id' }}` to share a renderer.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as three_examples_jsm_inspector_Inspector_js from 'three/examples/jsm/inspector/Inspector.js';
1
2
  import * as three_webgpu from 'three/webgpu';
2
3
  import { CubeRenderTarget, RenderTarget, WebGPURenderer, Node, StorageTexture, Data3DTexture, CanvasTarget, ShaderNodeObject, Euler as Euler$2, Color as Color$2, ColorRepresentation as ColorRepresentation$1, Layers as Layers$1, Raycaster, Intersection as Intersection$1, BufferGeometry, Matrix4 as Matrix4$1, Quaternion as Quaternion$1, Vector2 as Vector2$1, Vector3 as Vector3$1, Vector4 as Vector4$1, Matrix3 as Matrix3$1, Loader as Loader$1, ColorSpace, Texture as Texture$1, CubeTexture, Scene, Object3D, Frustum, OrthographicCamera } from 'three/webgpu';
3
4
  import * as THREE$1 from 'three';
@@ -47,6 +48,9 @@ function _mergeNamespaces(n, m) {
47
48
  declare const R3F_BUILD_LEGACY = true;
48
49
  declare const R3F_BUILD_WEBGPU = true;
49
50
 
51
+ /** Lazily load the three Inspector. Keeps it out of the eager module graph — see note above. */
52
+ declare function loadInspector(): Promise<typeof three_examples_jsm_inspector_Inspector_js.Inspector>;
53
+
50
54
  var THREE = /*#__PURE__*/_mergeNamespaces({
51
55
  __proto__: null,
52
56
  CubeRenderTarget: CubeRenderTarget,
@@ -58,7 +62,8 @@ var THREE = /*#__PURE__*/_mergeNamespaces({
58
62
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
59
63
  WebGLRenderTarget: WebGLRenderTarget,
60
64
  WebGLRenderer: WebGLRenderer,
61
- WebGLRendererParameters: WebGLRendererParameters
65
+ WebGLRendererParameters: WebGLRendererParameters,
66
+ loadInspector: loadInspector
62
67
  }, [three_webgpu]);
63
68
 
64
69
  //* Utility Types ==============================
@@ -496,6 +501,18 @@ interface InternalState {
496
501
  * @see https://threejs.org/docs/#api/en/renderers/common/CanvasTarget
497
502
  */
498
503
  canvasTarget?: CanvasTarget
504
+ /**
505
+ * Set when this root's canvas target has been resized and the backend's cached render pass
506
+ * descriptor (which holds a depth-stencil view built once per canvas) is therefore stale.
507
+ *
508
+ * Flushed by the canvas-target job in the `start` phase, which is the only place this root's
509
+ * target is guaranteed to be the renderer's active one — `backend.updateSize()` operates on
510
+ * whatever `getCanvasTarget()` returns, so calling it from the resize subscription would
511
+ * invalidate some other canvas's descriptor instead.
512
+ *
513
+ * @see https://github.com/pmndrs/react-three-fiber/issues/3847
514
+ */
515
+ canvasTargetSizeDirty?: boolean
499
516
  /**
500
517
  * Whether multi-canvas rendering is active.
501
518
  * True when any canvas uses `renderer={{ primaryCanvas: 'id' }}` to share a renderer.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as three_examples_jsm_inspector_Inspector_js from 'three/examples/jsm/inspector/Inspector.js';
1
2
  import * as three_webgpu from 'three/webgpu';
2
3
  import { CubeRenderTarget, RenderTarget, WebGPURenderer, Node, StorageTexture, Data3DTexture, CanvasTarget, ShaderNodeObject, Euler as Euler$2, Color as Color$2, ColorRepresentation as ColorRepresentation$1, Layers as Layers$1, Raycaster, Intersection as Intersection$1, BufferGeometry, Matrix4 as Matrix4$1, Quaternion as Quaternion$1, Vector2 as Vector2$1, Vector3 as Vector3$1, Vector4 as Vector4$1, Matrix3 as Matrix3$1, Loader as Loader$1, ColorSpace, Texture as Texture$1, CubeTexture, Scene, Object3D, Frustum, OrthographicCamera } from 'three/webgpu';
3
4
  import * as THREE$1 from 'three';
@@ -47,6 +48,9 @@ function _mergeNamespaces(n, m) {
47
48
  declare const R3F_BUILD_LEGACY = true;
48
49
  declare const R3F_BUILD_WEBGPU = true;
49
50
 
51
+ /** Lazily load the three Inspector. Keeps it out of the eager module graph — see note above. */
52
+ declare function loadInspector(): Promise<typeof three_examples_jsm_inspector_Inspector_js.Inspector>;
53
+
50
54
  var THREE = /*#__PURE__*/_mergeNamespaces({
51
55
  __proto__: null,
52
56
  CubeRenderTarget: CubeRenderTarget,
@@ -58,7 +62,8 @@ var THREE = /*#__PURE__*/_mergeNamespaces({
58
62
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
59
63
  WebGLRenderTarget: WebGLRenderTarget,
60
64
  WebGLRenderer: WebGLRenderer,
61
- WebGLRendererParameters: WebGLRendererParameters
65
+ WebGLRendererParameters: WebGLRendererParameters,
66
+ loadInspector: loadInspector
62
67
  }, [three_webgpu]);
63
68
 
64
69
  //* Utility Types ==============================
@@ -496,6 +501,18 @@ interface InternalState {
496
501
  * @see https://threejs.org/docs/#api/en/renderers/common/CanvasTarget
497
502
  */
498
503
  canvasTarget?: CanvasTarget
504
+ /**
505
+ * Set when this root's canvas target has been resized and the backend's cached render pass
506
+ * descriptor (which holds a depth-stencil view built once per canvas) is therefore stale.
507
+ *
508
+ * Flushed by the canvas-target job in the `start` phase, which is the only place this root's
509
+ * target is guaranteed to be the renderer's active one — `backend.updateSize()` operates on
510
+ * whatever `getCanvasTarget()` returns, so calling it from the resize subscription would
511
+ * invalidate some other canvas's descriptor instead.
512
+ *
513
+ * @see https://github.com/pmndrs/react-three-fiber/issues/3847
514
+ */
515
+ canvasTargetSizeDirty?: boolean
499
516
  /**
500
517
  * Whether multi-canvas rendering is active.
501
518
  * True when any canvas uses `renderer={{ primaryCanvas: 'id' }}` to share a renderer.
package/dist/index.mjs CHANGED
@@ -1,7 +1,6 @@
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 } from 'three/webgpu';
3
3
  import { WebGLCubeRenderTarget, WebGLRenderTarget, WebGLRenderer } from 'three';
4
- import { Inspector } from 'three/addons/inspector/Inspector.js';
5
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
6
5
  import * as React from 'react';
7
6
  import React__default, { useRef, useMemo, useLayoutEffect, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
@@ -32,18 +31,22 @@ function _mergeNamespaces(n, m) {
32
31
 
33
32
  const R3F_BUILD_LEGACY = true;
34
33
  const R3F_BUILD_WEBGPU = true;
34
+ async function loadInspector() {
35
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
36
+ return Inspector;
37
+ }
35
38
 
36
39
  const THREE = /*#__PURE__*/_mergeNamespaces({
37
40
  __proto__: null,
38
41
  CubeRenderTarget: CubeRenderTarget,
39
42
  CubeRenderTargetCompat: CubeRenderTarget,
40
- Inspector: Inspector,
41
43
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
42
44
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
43
45
  RenderTargetCompat: RenderTarget,
44
46
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
45
47
  WebGLRenderTarget: WebGLRenderTarget,
46
- WebGLRenderer: WebGLRenderer
48
+ WebGLRenderer: WebGLRenderer,
49
+ loadInspector: loadInspector
47
50
  }, [webgpu]);
48
51
 
49
52
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -1041,17 +1044,10 @@ const createStore = (invalidate, advance) => {
1041
1044
  oldSize = size;
1042
1045
  oldDpr = viewport.dpr;
1043
1046
  updateCamera(camera, size);
1044
- if (internal.isSecondary && canvasTarget) {
1045
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1046
- canvasTarget.setSize(size.width, size.height, false);
1047
- } else {
1048
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1049
- actualRenderer.setSize(size.width, size.height, false);
1050
- if (canvasTarget) {
1051
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1052
- canvasTarget.setSize(size.width, size.height, false);
1053
- }
1054
- }
1047
+ const resizeTarget = canvasTarget ?? actualRenderer;
1048
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1049
+ resizeTarget.setSize(size.width, size.height, false);
1050
+ internal.canvasTargetSizeDirty = true;
1055
1051
  }
1056
1052
  if (camera !== oldCamera) {
1057
1053
  oldCamera = camera;
@@ -1113,7 +1109,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1113
1109
  const results = cacheKeys.map(
1114
1110
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1115
1111
  );
1116
- return Array.isArray(input) ? results : results[0];
1112
+ const stableRef = useRef(null);
1113
+ const previous = stableRef.current;
1114
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1115
+ stableRef.current = stable;
1116
+ return Array.isArray(input) ? stable : stable[0];
1117
1117
  }
1118
1118
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1119
1119
  const inputs = Array.isArray(input) ? input : [input];
@@ -1278,6 +1278,11 @@ function getUrls(input) {
1278
1278
  if (Array.isArray(input)) return input;
1279
1279
  return Object.values(input);
1280
1280
  }
1281
+ function inputSignature(input) {
1282
+ if (typeof input === "string") return input;
1283
+ if (Array.isArray(input)) return input.join("\0");
1284
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1285
+ }
1281
1286
  function allUrlsCached(urls, textureCache) {
1282
1287
  return urls.every((url) => textureCache.has(url));
1283
1288
  }
@@ -1303,18 +1308,19 @@ function useTexture(input, optionsOrOnLoad) {
1303
1308
  const onLoadRef = useRef(onLoad);
1304
1309
  onLoadRef.current = onLoad;
1305
1310
  const onLoadCalledForRef = useRef(null);
1306
- const urls = useMemo(() => getUrls(input), [input]);
1311
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1312
+ const stableInput = useMemo(() => input, [inputKey]);
1313
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1307
1314
  const cachedResult = useMemo(() => {
1308
1315
  if (!cache) return null;
1309
1316
  const textures = store.getState().textures;
1310
1317
  if (!allUrlsCached(urls, textures)) return null;
1311
- return buildFromCache(input, textures);
1312
- }, [cache, urls, input, store]);
1318
+ return buildFromCache(stableInput, textures);
1319
+ }, [cache, urls, stableInput, store]);
1313
1320
  const loadedTextures = useLoader(
1314
1321
  TextureLoader,
1315
- IsObject(input) ? Object.values(input) : input
1322
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1316
1323
  );
1317
- const inputKey = urls.join("\0");
1318
1324
  useLayoutEffect(() => {
1319
1325
  if (cachedResult) return;
1320
1326
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1341,28 +1347,28 @@ function useTexture(input, optionsOrOnLoad) {
1341
1347
  }, [renderer, loadedTextures, cachedResult]);
1342
1348
  const mappedTextures = useMemo(() => {
1343
1349
  if (cachedResult) return cachedResult;
1344
- if (IsObject(input)) {
1350
+ if (IsObject(stableInput)) {
1345
1351
  const keyed = {};
1346
1352
  const textureArray = loadedTextures;
1347
1353
  let i = 0;
1348
- for (const key in input) keyed[key] = textureArray[i++];
1354
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1349
1355
  return keyed;
1350
1356
  } else {
1351
1357
  return loadedTextures;
1352
1358
  }
1353
- }, [input, loadedTextures, cachedResult]);
1359
+ }, [stableInput, loadedTextures, cachedResult]);
1354
1360
  useEffect(() => {
1355
1361
  if (!cache) return;
1356
1362
  const urlTextureMap = [];
1357
- if (typeof input === "string") {
1358
- urlTextureMap.push([input, mappedTextures]);
1359
- } else if (Array.isArray(input)) {
1363
+ if (typeof stableInput === "string") {
1364
+ urlTextureMap.push([stableInput, mappedTextures]);
1365
+ } else if (Array.isArray(stableInput)) {
1360
1366
  const textureArray = mappedTextures;
1361
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1362
- } else if (IsObject(input)) {
1367
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1368
+ } else if (IsObject(stableInput)) {
1363
1369
  const textureRecord = mappedTextures;
1364
- for (const key in input) {
1365
- const url = input[key];
1370
+ for (const key in stableInput) {
1371
+ const url = stableInput[key];
1366
1372
  urlTextureMap.push([url, textureRecord[key]]);
1367
1373
  }
1368
1374
  }
@@ -1391,7 +1397,7 @@ function useTexture(input, optionsOrOnLoad) {
1391
1397
  }
1392
1398
  return { _textureRefs: refs };
1393
1399
  });
1394
- }, [cache, input, mappedTextures, store]);
1400
+ }, [cache, stableInput, mappedTextures, store]);
1395
1401
  return mappedTextures;
1396
1402
  }
1397
1403
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -14425,6 +14431,10 @@ function createRoot(canvas) {
14425
14431
  if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14426
14432
  const renderer2 = state2.internal.actualRenderer;
14427
14433
  renderer2.setCanvasTarget(state2.internal.canvasTarget);
14434
+ if (state2.internal.canvasTargetSizeDirty) {
14435
+ state2.internal.canvasTargetSizeDirty = false;
14436
+ renderer2.backend?.updateSize?.();
14437
+ }
14428
14438
  }
14429
14439
  },
14430
14440
  {
package/dist/legacy.cjs CHANGED
@@ -50,11 +50,9 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
50
50
 
51
51
  const R3F_BUILD_LEGACY = true;
52
52
  const R3F_BUILD_WEBGPU = false;
53
- const Inspector = class Inspector2 {
54
- constructor() {
55
- throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
56
- }
57
- };
53
+ async function loadInspector() {
54
+ throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
55
+ }
58
56
  const WebGPURenderer = class WebGPURenderer2 {
59
57
  constructor() {
60
58
  throw new Error("WebGPURenderer is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
@@ -67,12 +65,12 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
67
65
  __proto__: null,
68
66
  CubeRenderTarget: CubeRenderTarget,
69
67
  CubeRenderTargetCompat: three.WebGLCubeRenderTarget,
70
- Inspector: Inspector,
71
68
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
72
69
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
73
70
  RenderTarget: RenderTarget,
74
71
  RenderTargetCompat: three.WebGLRenderTarget,
75
- WebGPURenderer: WebGPURenderer
72
+ WebGPURenderer: WebGPURenderer,
73
+ loadInspector: loadInspector
76
74
  }, [three__namespace]);
77
75
 
78
76
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -1070,17 +1068,10 @@ const createStore = (invalidate, advance) => {
1070
1068
  oldSize = size;
1071
1069
  oldDpr = viewport.dpr;
1072
1070
  updateCamera(camera, size);
1073
- if (internal.isSecondary && canvasTarget) {
1074
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1075
- canvasTarget.setSize(size.width, size.height, false);
1076
- } else {
1077
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1078
- actualRenderer.setSize(size.width, size.height, false);
1079
- if (canvasTarget) {
1080
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1081
- canvasTarget.setSize(size.width, size.height, false);
1082
- }
1083
- }
1071
+ const resizeTarget = canvasTarget ?? actualRenderer;
1072
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1073
+ resizeTarget.setSize(size.width, size.height, false);
1074
+ internal.canvasTargetSizeDirty = true;
1084
1075
  }
1085
1076
  if (camera !== oldCamera) {
1086
1077
  oldCamera = camera;
@@ -1142,7 +1133,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1142
1133
  const results = cacheKeys.map(
1143
1134
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1144
1135
  );
1145
- return Array.isArray(input) ? results : results[0];
1136
+ const stableRef = React.useRef(null);
1137
+ const previous = stableRef.current;
1138
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1139
+ stableRef.current = stable;
1140
+ return Array.isArray(input) ? stable : stable[0];
1146
1141
  }
1147
1142
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1148
1143
  const inputs = Array.isArray(input) ? input : [input];
@@ -1307,6 +1302,11 @@ function getUrls(input) {
1307
1302
  if (Array.isArray(input)) return input;
1308
1303
  return Object.values(input);
1309
1304
  }
1305
+ function inputSignature(input) {
1306
+ if (typeof input === "string") return input;
1307
+ if (Array.isArray(input)) return input.join("\0");
1308
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1309
+ }
1310
1310
  function allUrlsCached(urls, textureCache) {
1311
1311
  return urls.every((url) => textureCache.has(url));
1312
1312
  }
@@ -1332,18 +1332,19 @@ function useTexture(input, optionsOrOnLoad) {
1332
1332
  const onLoadRef = React.useRef(onLoad);
1333
1333
  onLoadRef.current = onLoad;
1334
1334
  const onLoadCalledForRef = React.useRef(null);
1335
- const urls = React.useMemo(() => getUrls(input), [input]);
1335
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1336
+ const stableInput = React.useMemo(() => input, [inputKey]);
1337
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1336
1338
  const cachedResult = React.useMemo(() => {
1337
1339
  if (!cache) return null;
1338
1340
  const textures = store.getState().textures;
1339
1341
  if (!allUrlsCached(urls, textures)) return null;
1340
- return buildFromCache(input, textures);
1341
- }, [cache, urls, input, store]);
1342
+ return buildFromCache(stableInput, textures);
1343
+ }, [cache, urls, stableInput, store]);
1342
1344
  const loadedTextures = useLoader(
1343
1345
  three.TextureLoader,
1344
- IsObject(input) ? Object.values(input) : input
1346
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1345
1347
  );
1346
- const inputKey = urls.join("\0");
1347
1348
  React.useLayoutEffect(() => {
1348
1349
  if (cachedResult) return;
1349
1350
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1370,28 +1371,28 @@ function useTexture(input, optionsOrOnLoad) {
1370
1371
  }, [renderer, loadedTextures, cachedResult]);
1371
1372
  const mappedTextures = React.useMemo(() => {
1372
1373
  if (cachedResult) return cachedResult;
1373
- if (IsObject(input)) {
1374
+ if (IsObject(stableInput)) {
1374
1375
  const keyed = {};
1375
1376
  const textureArray = loadedTextures;
1376
1377
  let i = 0;
1377
- for (const key in input) keyed[key] = textureArray[i++];
1378
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1378
1379
  return keyed;
1379
1380
  } else {
1380
1381
  return loadedTextures;
1381
1382
  }
1382
- }, [input, loadedTextures, cachedResult]);
1383
+ }, [stableInput, loadedTextures, cachedResult]);
1383
1384
  React.useEffect(() => {
1384
1385
  if (!cache) return;
1385
1386
  const urlTextureMap = [];
1386
- if (typeof input === "string") {
1387
- urlTextureMap.push([input, mappedTextures]);
1388
- } else if (Array.isArray(input)) {
1387
+ if (typeof stableInput === "string") {
1388
+ urlTextureMap.push([stableInput, mappedTextures]);
1389
+ } else if (Array.isArray(stableInput)) {
1389
1390
  const textureArray = mappedTextures;
1390
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1391
- } else if (IsObject(input)) {
1391
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1392
+ } else if (IsObject(stableInput)) {
1392
1393
  const textureRecord = mappedTextures;
1393
- for (const key in input) {
1394
- const url = input[key];
1394
+ for (const key in stableInput) {
1395
+ const url = stableInput[key];
1395
1396
  urlTextureMap.push([url, textureRecord[key]]);
1396
1397
  }
1397
1398
  }
@@ -1420,7 +1421,7 @@ function useTexture(input, optionsOrOnLoad) {
1420
1421
  }
1421
1422
  return { _textureRefs: refs };
1422
1423
  });
1423
- }, [cache, input, mappedTextures, store]);
1424
+ }, [cache, stableInput, mappedTextures, store]);
1424
1425
  return mappedTextures;
1425
1426
  }
1426
1427
  useTexture.preload = (url) => useLoader.preload(three.TextureLoader, url);
@@ -14405,6 +14406,10 @@ function createRoot(canvas) {
14405
14406
  if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14406
14407
  const renderer2 = state2.internal.actualRenderer;
14407
14408
  renderer2.setCanvasTarget(state2.internal.canvasTarget);
14409
+ if (state2.internal.canvasTargetSizeDirty) {
14410
+ state2.internal.canvasTargetSizeDirty = false;
14411
+ renderer2.backend?.updateSize?.();
14412
+ }
14408
14413
  }
14409
14414
  },
14410
14415
  {
package/dist/legacy.d.cts CHANGED
@@ -38,9 +38,9 @@ function _mergeNamespaces(n, m) {
38
38
  declare const R3F_BUILD_LEGACY = true;
39
39
  declare const R3F_BUILD_WEBGPU = false;
40
40
 
41
- declare const Inspector: {
42
- new (): {};
43
- };
41
+ type Inspector = never;
42
+ /** Not available on the legacy (WebGL-only) entry. */
43
+ declare function loadInspector(): Promise<never>;
44
44
  declare const WebGPURenderer: {
45
45
  new (): {};
46
46
  };
@@ -58,7 +58,8 @@ var THREE = /*#__PURE__*/_mergeNamespaces({
58
58
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
59
59
  RenderTarget: RenderTarget,
60
60
  RenderTargetCompat: WebGLRenderTarget,
61
- WebGPURenderer: WebGPURenderer
61
+ WebGPURenderer: WebGPURenderer,
62
+ loadInspector: loadInspector
62
63
  }, [THREE$1]);
63
64
 
64
65
  //* Utility Types ==============================
@@ -496,6 +497,18 @@ interface InternalState {
496
497
  * @see https://threejs.org/docs/#api/en/renderers/common/CanvasTarget
497
498
  */
498
499
  canvasTarget?: CanvasTarget
500
+ /**
501
+ * Set when this root's canvas target has been resized and the backend's cached render pass
502
+ * descriptor (which holds a depth-stencil view built once per canvas) is therefore stale.
503
+ *
504
+ * Flushed by the canvas-target job in the `start` phase, which is the only place this root's
505
+ * target is guaranteed to be the renderer's active one — `backend.updateSize()` operates on
506
+ * whatever `getCanvasTarget()` returns, so calling it from the resize subscription would
507
+ * invalidate some other canvas's descriptor instead.
508
+ *
509
+ * @see https://github.com/pmndrs/react-three-fiber/issues/3847
510
+ */
511
+ canvasTargetSizeDirty?: boolean
499
512
  /**
500
513
  * Whether multi-canvas rendering is active.
501
514
  * True when any canvas uses `renderer={{ primaryCanvas: 'id' }}` to share a renderer.