@react-three/fiber 10.0.0-canary.c3fa45d → 10.0.0-canary.d6fdf72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/legacy.mjs CHANGED
@@ -2809,6 +2809,7 @@ const createStore = (invalidate, advance) => {
2809
2809
  buffers: {},
2810
2810
  gpuStorage: {},
2811
2811
  textures: /* @__PURE__ */ new Map(),
2812
+ _textureRefs: /* @__PURE__ */ new Map(),
2812
2813
  renderPipeline: null,
2813
2814
  passes: {},
2814
2815
  _hmrVersion: 0,
@@ -3172,18 +3173,18 @@ function buildFromCache(input, textureCache) {
3172
3173
  function useTexture(input, optionsOrOnLoad) {
3173
3174
  const renderer = useThree((state) => state.internal.actualRenderer);
3174
3175
  const store = useStore();
3175
- const textureCache = useThree((state) => state.textures);
3176
3176
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
3177
- const { onLoad, cache = false } = options;
3177
+ const { onLoad, cache = true } = options;
3178
3178
  const onLoadRef = useRef(onLoad);
3179
3179
  onLoadRef.current = onLoad;
3180
3180
  const onLoadCalledForRef = useRef(null);
3181
3181
  const urls = useMemo(() => getUrls(input), [input]);
3182
3182
  const cachedResult = useMemo(() => {
3183
3183
  if (!cache) return null;
3184
- if (!allUrlsCached(urls, textureCache)) return null;
3185
- return buildFromCache(input, textureCache);
3186
- }, [cache, urls, textureCache, input]);
3184
+ const textures = store.getState().textures;
3185
+ if (!allUrlsCached(urls, textures)) return null;
3186
+ return buildFromCache(input, textures);
3187
+ }, [cache, urls, input, store]);
3187
3188
  const loadedTextures = useLoader(
3188
3189
  TextureLoader,
3189
3190
  IsObject(input) ? Object.values(input) : input
@@ -3227,8 +3228,6 @@ function useTexture(input, optionsOrOnLoad) {
3227
3228
  }, [input, loadedTextures, cachedResult]);
3228
3229
  useEffect(() => {
3229
3230
  if (!cache) return;
3230
- if (cachedResult) return;
3231
- const set = store.setState;
3232
3231
  const urlTextureMap = [];
3233
3232
  if (typeof input === "string") {
3234
3233
  urlTextureMap.push([input, mappedTextures]);
@@ -3242,18 +3241,32 @@ function useTexture(input, optionsOrOnLoad) {
3242
3241
  urlTextureMap.push([url, textureRecord[key]]);
3243
3242
  }
3244
3243
  }
3245
- set((state) => {
3246
- const newMap = new Map(state.textures);
3247
- let changed = false;
3244
+ store.setState((state) => {
3245
+ const refs = new Map(state._textureRefs);
3246
+ let textures = state.textures;
3247
+ let added = false;
3248
3248
  for (const [url, texture] of urlTextureMap) {
3249
- if (!newMap.has(url)) {
3250
- newMap.set(url, texture);
3251
- changed = true;
3249
+ if (!textures.has(url)) {
3250
+ if (!added) {
3251
+ textures = new Map(textures);
3252
+ added = true;
3253
+ }
3254
+ textures.set(url, texture);
3252
3255
  }
3256
+ refs.set(url, (refs.get(url) ?? 0) + 1);
3253
3257
  }
3254
- return changed ? { textures: newMap } : state;
3258
+ return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
3255
3259
  });
3256
- }, [cache, input, mappedTextures, store, cachedResult]);
3260
+ return () => store.setState((state) => {
3261
+ const refs = new Map(state._textureRefs);
3262
+ for (const [url] of urlTextureMap) {
3263
+ const next = (refs.get(url) ?? 0) - 1;
3264
+ if (next <= 0) refs.delete(url);
3265
+ else refs.set(url, next);
3266
+ }
3267
+ return { _textureRefs: refs };
3268
+ });
3269
+ }, [cache, input, mappedTextures, store]);
3257
3270
  return mappedTextures;
3258
3271
  }
3259
3272
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -3269,96 +3282,63 @@ const Texture = ({
3269
3282
  return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
3270
3283
  };
3271
3284
 
3272
- function getTextureValue(entry) {
3273
- if (entry instanceof Texture$1) return entry;
3274
- if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof Texture$1) {
3275
- return entry.value;
3276
- }
3277
- return null;
3278
- }
3279
- function useTextures() {
3285
+ function useTextures(selector) {
3280
3286
  const store = useStore();
3281
- return useMemo(() => {
3282
- const set = store.setState;
3287
+ const registry = useMemo(() => {
3283
3288
  const getState = store.getState;
3284
- const add = (key, value) => {
3285
- set((state) => {
3286
- const newMap = new Map(state.textures);
3287
- newMap.set(key, value);
3288
- return { textures: newMap };
3289
- });
3290
- };
3291
- const addMultiple = (items) => {
3292
- set((state) => {
3293
- const newMap = new Map(state.textures);
3294
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
3295
- for (const [key, value] of entries) {
3296
- newMap.set(key, value);
3297
- }
3298
- return { textures: newMap };
3299
- });
3300
- };
3301
- const remove = (key) => {
3302
- set((state) => {
3303
- const newMap = new Map(state.textures);
3304
- newMap.delete(key);
3305
- return { textures: newMap };
3306
- });
3307
- };
3308
- const removeMultiple = (keys) => {
3309
- set((state) => {
3310
- const newMap = new Map(state.textures);
3311
- for (const key of keys) newMap.delete(key);
3312
- return { textures: newMap };
3313
- });
3314
- };
3315
- const dispose = (key) => {
3316
- const entry = getState().textures.get(key);
3317
- if (entry) {
3318
- const tex = getTextureValue(entry);
3319
- tex?.dispose();
3320
- }
3321
- remove(key);
3322
- };
3323
- const disposeMultiple = (keys) => {
3324
- const textures = getState().textures;
3325
- for (const key of keys) {
3326
- const entry = textures.get(key);
3327
- if (entry) {
3328
- const tex = getTextureValue(entry);
3329
- tex?.dispose();
3330
- }
3331
- }
3332
- removeMultiple(keys);
3333
- };
3334
- const disposeAll = () => {
3335
- const textures = getState().textures;
3336
- for (const entry of textures.values()) {
3337
- const tex = getTextureValue(entry);
3338
- tex?.dispose();
3339
- }
3340
- set({ textures: /* @__PURE__ */ new Map() });
3341
- };
3289
+ const setState = store.setState;
3290
+ const getOne = (key) => getState().textures.get(key);
3342
3291
  return {
3343
- // Getter for the textures Map (reactive via getState)
3344
- get textures() {
3292
+ get all() {
3345
3293
  return getState().textures;
3346
3294
  },
3347
- // Read
3348
- get: (key) => getState().textures.get(key),
3295
+ get(input) {
3296
+ if (typeof input === "string") return getOne(input);
3297
+ if (Array.isArray(input)) return input.map(getOne);
3298
+ const out = {};
3299
+ for (const name in input) out[name] = getOne(input[name]);
3300
+ return out;
3301
+ },
3349
3302
  has: (key) => getState().textures.has(key),
3350
- // Write
3351
- add,
3352
- addMultiple,
3353
- // Remove (cache only)
3354
- remove,
3355
- removeMultiple,
3356
- // Dispose (GPU + cache)
3357
- dispose,
3358
- disposeMultiple,
3359
- disposeAll
3303
+ add(keyOrRecord, texture) {
3304
+ setState((state) => {
3305
+ const textures = new Map(state.textures);
3306
+ if (typeof keyOrRecord === "string") {
3307
+ textures.set(keyOrRecord, texture);
3308
+ } else {
3309
+ for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
3310
+ }
3311
+ return { textures };
3312
+ });
3313
+ },
3314
+ dispose(key, options) {
3315
+ const state = getState();
3316
+ const refs = state._textureRefs.get(key) ?? 0;
3317
+ if (refs > 0 && !options?.force) {
3318
+ console.warn(
3319
+ `[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
3320
+ );
3321
+ return false;
3322
+ }
3323
+ state.textures.get(key)?.dispose();
3324
+ setState((s) => {
3325
+ const textures = new Map(s.textures);
3326
+ textures.delete(key);
3327
+ const nextRefs = new Map(s._textureRefs);
3328
+ nextRefs.delete(key);
3329
+ return { textures, _textureRefs: nextRefs };
3330
+ });
3331
+ return true;
3332
+ },
3333
+ disposeAll() {
3334
+ for (const texture of getState().textures.values()) texture.dispose();
3335
+ setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
3336
+ }
3360
3337
  };
3361
3338
  }, [store]);
3339
+ const subscribe = selector ? () => selector(registry) : (state) => state.textures;
3340
+ const selected = useThree(subscribe);
3341
+ return selector ? selected : registry;
3362
3342
  }
3363
3343
 
3364
3344
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
@@ -2831,6 +2831,7 @@ const createStore = (invalidate, advance) => {
2831
2831
  buffers: {},
2832
2832
  gpuStorage: {},
2833
2833
  textures: /* @__PURE__ */ new Map(),
2834
+ _textureRefs: /* @__PURE__ */ new Map(),
2834
2835
  renderPipeline: null,
2835
2836
  passes: {},
2836
2837
  _hmrVersion: 0,
@@ -3194,18 +3195,18 @@ function buildFromCache(input, textureCache) {
3194
3195
  function useTexture(input, optionsOrOnLoad) {
3195
3196
  const renderer = useThree((state) => state.internal.actualRenderer);
3196
3197
  const store = useStore();
3197
- const textureCache = useThree((state) => state.textures);
3198
3198
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
3199
- const { onLoad, cache = false } = options;
3199
+ const { onLoad, cache = true } = options;
3200
3200
  const onLoadRef = React.useRef(onLoad);
3201
3201
  onLoadRef.current = onLoad;
3202
3202
  const onLoadCalledForRef = React.useRef(null);
3203
3203
  const urls = React.useMemo(() => getUrls(input), [input]);
3204
3204
  const cachedResult = React.useMemo(() => {
3205
3205
  if (!cache) return null;
3206
- if (!allUrlsCached(urls, textureCache)) return null;
3207
- return buildFromCache(input, textureCache);
3208
- }, [cache, urls, textureCache, input]);
3206
+ const textures = store.getState().textures;
3207
+ if (!allUrlsCached(urls, textures)) return null;
3208
+ return buildFromCache(input, textures);
3209
+ }, [cache, urls, input, store]);
3209
3210
  const loadedTextures = useLoader(
3210
3211
  webgpu.TextureLoader,
3211
3212
  IsObject(input) ? Object.values(input) : input
@@ -3249,8 +3250,6 @@ function useTexture(input, optionsOrOnLoad) {
3249
3250
  }, [input, loadedTextures, cachedResult]);
3250
3251
  React.useEffect(() => {
3251
3252
  if (!cache) return;
3252
- if (cachedResult) return;
3253
- const set = store.setState;
3254
3253
  const urlTextureMap = [];
3255
3254
  if (typeof input === "string") {
3256
3255
  urlTextureMap.push([input, mappedTextures]);
@@ -3264,18 +3263,32 @@ function useTexture(input, optionsOrOnLoad) {
3264
3263
  urlTextureMap.push([url, textureRecord[key]]);
3265
3264
  }
3266
3265
  }
3267
- set((state) => {
3268
- const newMap = new Map(state.textures);
3269
- let changed = false;
3266
+ store.setState((state) => {
3267
+ const refs = new Map(state._textureRefs);
3268
+ let textures = state.textures;
3269
+ let added = false;
3270
3270
  for (const [url, texture] of urlTextureMap) {
3271
- if (!newMap.has(url)) {
3272
- newMap.set(url, texture);
3273
- changed = true;
3271
+ if (!textures.has(url)) {
3272
+ if (!added) {
3273
+ textures = new Map(textures);
3274
+ added = true;
3275
+ }
3276
+ textures.set(url, texture);
3274
3277
  }
3278
+ refs.set(url, (refs.get(url) ?? 0) + 1);
3275
3279
  }
3276
- return changed ? { textures: newMap } : state;
3280
+ return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
3277
3281
  });
3278
- }, [cache, input, mappedTextures, store, cachedResult]);
3282
+ return () => store.setState((state) => {
3283
+ const refs = new Map(state._textureRefs);
3284
+ for (const [url] of urlTextureMap) {
3285
+ const next = (refs.get(url) ?? 0) - 1;
3286
+ if (next <= 0) refs.delete(url);
3287
+ else refs.set(url, next);
3288
+ }
3289
+ return { _textureRefs: refs };
3290
+ });
3291
+ }, [cache, input, mappedTextures, store]);
3279
3292
  return mappedTextures;
3280
3293
  }
3281
3294
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -3291,96 +3304,63 @@ const Texture = ({
3291
3304
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
3292
3305
  };
3293
3306
 
3294
- function getTextureValue(entry) {
3295
- if (entry instanceof webgpu.Texture) return entry;
3296
- if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
3297
- return entry.value;
3298
- }
3299
- return null;
3300
- }
3301
- function useTextures() {
3307
+ function useTextures(selector) {
3302
3308
  const store = useStore();
3303
- return React.useMemo(() => {
3304
- const set = store.setState;
3309
+ const registry = React.useMemo(() => {
3305
3310
  const getState = store.getState;
3306
- const add = (key, value) => {
3307
- set((state) => {
3308
- const newMap = new Map(state.textures);
3309
- newMap.set(key, value);
3310
- return { textures: newMap };
3311
- });
3312
- };
3313
- const addMultiple = (items) => {
3314
- set((state) => {
3315
- const newMap = new Map(state.textures);
3316
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
3317
- for (const [key, value] of entries) {
3318
- newMap.set(key, value);
3319
- }
3320
- return { textures: newMap };
3321
- });
3322
- };
3323
- const remove = (key) => {
3324
- set((state) => {
3325
- const newMap = new Map(state.textures);
3326
- newMap.delete(key);
3327
- return { textures: newMap };
3328
- });
3329
- };
3330
- const removeMultiple = (keys) => {
3331
- set((state) => {
3332
- const newMap = new Map(state.textures);
3333
- for (const key of keys) newMap.delete(key);
3334
- return { textures: newMap };
3335
- });
3336
- };
3337
- const dispose = (key) => {
3338
- const entry = getState().textures.get(key);
3339
- if (entry) {
3340
- const tex = getTextureValue(entry);
3341
- tex?.dispose();
3342
- }
3343
- remove(key);
3344
- };
3345
- const disposeMultiple = (keys) => {
3346
- const textures = getState().textures;
3347
- for (const key of keys) {
3348
- const entry = textures.get(key);
3349
- if (entry) {
3350
- const tex = getTextureValue(entry);
3351
- tex?.dispose();
3352
- }
3353
- }
3354
- removeMultiple(keys);
3355
- };
3356
- const disposeAll = () => {
3357
- const textures = getState().textures;
3358
- for (const entry of textures.values()) {
3359
- const tex = getTextureValue(entry);
3360
- tex?.dispose();
3361
- }
3362
- set({ textures: /* @__PURE__ */ new Map() });
3363
- };
3311
+ const setState = store.setState;
3312
+ const getOne = (key) => getState().textures.get(key);
3364
3313
  return {
3365
- // Getter for the textures Map (reactive via getState)
3366
- get textures() {
3314
+ get all() {
3367
3315
  return getState().textures;
3368
3316
  },
3369
- // Read
3370
- get: (key) => getState().textures.get(key),
3317
+ get(input) {
3318
+ if (typeof input === "string") return getOne(input);
3319
+ if (Array.isArray(input)) return input.map(getOne);
3320
+ const out = {};
3321
+ for (const name in input) out[name] = getOne(input[name]);
3322
+ return out;
3323
+ },
3371
3324
  has: (key) => getState().textures.has(key),
3372
- // Write
3373
- add,
3374
- addMultiple,
3375
- // Remove (cache only)
3376
- remove,
3377
- removeMultiple,
3378
- // Dispose (GPU + cache)
3379
- dispose,
3380
- disposeMultiple,
3381
- disposeAll
3325
+ add(keyOrRecord, texture) {
3326
+ setState((state) => {
3327
+ const textures = new Map(state.textures);
3328
+ if (typeof keyOrRecord === "string") {
3329
+ textures.set(keyOrRecord, texture);
3330
+ } else {
3331
+ for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
3332
+ }
3333
+ return { textures };
3334
+ });
3335
+ },
3336
+ dispose(key, options) {
3337
+ const state = getState();
3338
+ const refs = state._textureRefs.get(key) ?? 0;
3339
+ if (refs > 0 && !options?.force) {
3340
+ console.warn(
3341
+ `[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
3342
+ );
3343
+ return false;
3344
+ }
3345
+ state.textures.get(key)?.dispose();
3346
+ setState((s) => {
3347
+ const textures = new Map(s.textures);
3348
+ textures.delete(key);
3349
+ const nextRefs = new Map(s._textureRefs);
3350
+ nextRefs.delete(key);
3351
+ return { textures, _textureRefs: nextRefs };
3352
+ });
3353
+ return true;
3354
+ },
3355
+ disposeAll() {
3356
+ for (const texture of getState().textures.values()) texture.dispose();
3357
+ setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
3358
+ }
3382
3359
  };
3383
3360
  }, [store]);
3361
+ const subscribe = selector ? () => selector(registry) : (state) => state.textures;
3362
+ const selected = useThree(subscribe);
3363
+ return selector ? selected : registry;
3384
3364
  }
3385
3365
 
3386
3366
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
@@ -803,8 +803,10 @@ interface RootState {
803
803
  buffers: BufferStore
804
804
  /** Global GPU storage (textures, etc.) - root-level storage + scoped sub-objects. Use useGPUStorage() hook */
805
805
  gpuStorage: StorageStore
806
- /** Global TSL texture nodes - use useTextures() hook for operations */
807
- textures: Map<string, any>
806
+ /** Global Texture registry (key → Texture, usually keyed by URL) - use useTextures() hook for access + lifecycle */
807
+ textures: Map<string, THREE$1.Texture>
808
+ /** Internal: refcount per texture key, driven by mounted useTexture consumers (registry enrollment is on by default) */
809
+ _textureRefs: Map<string, number>
808
810
  /** WebGPU RenderPipeline instance - use useRenderPipeline() hook */
809
811
  renderPipeline: any | null // THREE.PostProcessing (will be THREE.RenderPipeline in future Three.js release)
810
812
  /** Global TSL pass nodes for render pipeline - use useRenderPipeline() hook */
@@ -2575,11 +2577,16 @@ type UseTextureOptions<Url extends string[] | string | Record<string, string>> =
2575
2577
  /** Callback when texture(s) finish loading */
2576
2578
  onLoad?: (texture: MappedTextureType<Url>) => void;
2577
2579
  /**
2578
- * Cache the texture in R3F's global state for access via useTextures().
2579
- * When true:
2580
+ * Register the texture(s) in R3F's global texture registry for access via useTextures().
2581
+ * When true (the default):
2580
2582
  * - Textures persist until explicitly disposed
2581
- * - Returns existing cached textures if available (preserving modifications like colorSpace)
2582
- * @default false
2583
+ * - On mount, returns existing registered textures if available (preserving modifications like colorSpace)
2584
+ *
2585
+ * Reads are taken once at mount — a mounted `useTexture` does not re-render when the registry
2586
+ * changes. To react to registry swaps, use `useTextures(s => s.get(key))` instead.
2587
+ *
2588
+ * Pass `false` to opt out of registry enrollment entirely.
2589
+ * @default true
2583
2590
  */
2584
2591
  cache?: boolean;
2585
2592
  };
@@ -2603,15 +2610,18 @@ type UseTextureOptions<Url extends string[] | string | Record<string, string>> =
2603
2610
  * normal: '/normal.png'
2604
2611
  * })
2605
2612
  *
2606
- * // With caching - returns same texture object across components
2607
- * // Modifications (colorSpace, wrapS, etc.) are preserved
2608
- * const diffuse = useTexture('/diffuse.png', { cache: true })
2613
+ * // Textures are registered in the global registry by default — the same texture
2614
+ * // object is shared across components and modifications (colorSpace, wrapS, etc.) are preserved
2615
+ * const diffuse = useTexture('/diffuse.png')
2609
2616
  * diffuse.colorSpace = THREE.SRGBColorSpace
2610
2617
  *
2611
2618
  * // Another component gets the SAME texture with colorSpace already set
2612
- * const sameDiffuse = useTexture('/diffuse.png', { cache: true })
2619
+ * const sameDiffuse = useTexture('/diffuse.png')
2613
2620
  *
2614
- * // Access cache directly
2621
+ * // Opt out of registry enrollment for a one-off texture
2622
+ * const scratch = useTexture('/scratch.png', { cache: false })
2623
+ *
2624
+ * // Access the registry directly
2615
2625
  * const { get } = useTextures()
2616
2626
  * const cached = get('/diffuse.png')
2617
2627
  * ```
@@ -2628,63 +2638,80 @@ declare const Texture: ({ children, input, onLoad, cache, }: {
2628
2638
  cache?: boolean;
2629
2639
  }) => react_jsx_runtime.JSX.Element;
2630
2640
 
2631
- type TextureEntry = Texture$1 | {
2632
- value: Texture$1;
2633
- [key: string]: any;
2641
+ /** Accepted shapes for a registry read: a single key, a list of keys, or a name→key map. */
2642
+ type TextureInput = string | string[] | Record<string, string>;
2643
+ /** Result of a `get()` read, mirroring the input shape. */
2644
+ type TextureResult<Input extends TextureInput> = Input extends string ? Texture$1 | undefined : Input extends string[] ? (Texture$1 | undefined)[] : {
2645
+ [K in keyof Input]: Texture$1 | undefined;
2634
2646
  };
2647
+ /** Options for `dispose()`. */
2648
+ interface DisposeOptions {
2649
+ /** Dispose even if the texture still has active references (default false). */
2650
+ force?: boolean;
2651
+ }
2635
2652
  interface UseTexturesReturn {
2636
- /** Map of all textures currently in cache */
2637
- textures: Map<string, TextureEntry>;
2638
- /** Get a specific texture by key (usually URL) */
2639
- get: (key: string) => TextureEntry | undefined;
2640
- /** Check if a texture exists in cache */
2641
- has: (key: string) => boolean;
2642
- /** Add a texture to the cache */
2643
- add: (key: string, value: TextureEntry) => void;
2644
- /** Add multiple textures to the cache */
2645
- addMultiple: (items: Map<string, TextureEntry> | Record<string, TextureEntry>) => void;
2646
- /** Remove a texture from cache (does NOT dispose GPU resources) */
2647
- remove: (key: string) => void;
2648
- /** Remove multiple textures from cache */
2649
- removeMultiple: (keys: string[]) => void;
2650
- /** Dispose a texture's GPU resources and remove from cache */
2651
- dispose: (key: string) => void;
2652
- /** Dispose multiple textures */
2653
- disposeMultiple: (keys: string[]) => void;
2654
- /** Dispose ALL cached textures - use with caution */
2655
- disposeAll: () => void;
2653
+ /** The live registry Map (key Texture). Treat as read-only. */
2654
+ readonly all: Map<string, Texture$1>;
2655
+ /** Read one texture, an array of textures, or a name→texture record (mirrors the input shape). */
2656
+ get<Input extends TextureInput>(input: Input): TextureResult<Input>;
2657
+ /** Check whether a key exists in the registry. */
2658
+ has(key: string): boolean;
2659
+ /**
2660
+ * Register a texture (or a record of textures) that has no URL — e.g. a render
2661
+ * target or procedural texture. URL-loaded textures are registered automatically
2662
+ * by `useTexture` (registry enrollment is on by default).
2663
+ */
2664
+ add(key: string, texture: Texture$1): void;
2665
+ add(record: Record<string, Texture$1>): void;
2666
+ /**
2667
+ * Dispose a texture's GPU resources and remove it from the registry.
2668
+ * Refcount-aware: if the key is still in use by a mounted `useTexture` consumer it is
2669
+ * skipped (with a warning) unless `{ force: true }` is passed.
2670
+ * @returns true if disposed, false if skipped.
2671
+ */
2672
+ dispose(key: string, options?: DisposeOptions): boolean;
2673
+ /** Dispose every texture in the registry and clear it. Use on scene teardown. */
2674
+ disposeAll(): void;
2656
2675
  }
2657
2676
  /**
2658
- * Hook for managing the global texture cache in R3F state.
2677
+ * Reactive Texture registry load once with `useTexture`, reach the textures from anywhere.
2678
+ *
2679
+ * This is a registry of plain `Texture` objects (not TSL nodes), so the same texture can feed
2680
+ * a material map, a heightmap sampler, or anything else. It does **not** load — pair it with
2681
+ * `useTexture` for loading (registry enrollment is on by default); `useTextures` is for access and lifecycle.
2659
2682
  *
2660
- * Textures are stored in a Map with URL/path keys for efficient lookup.
2661
- * Useful for sharing texture references across materials and components.
2683
+ * The returned handle is **reactive**: the calling component re-renders when the registry
2684
+ * changes. Pass a selector to scope the subscription to a single read.
2662
2685
  *
2663
2686
  * @example
2664
2687
  * ```tsx
2665
- * const { textures, add, get, remove, has, dispose } = useTextures()
2688
+ * // Load + register once (anywhere in the tree) registered by default
2689
+ * useTexture({ map: '/diffuse.jpg', normalMap: '/normal.jpg' })
2666
2690
  *
2667
- * // Check if texture is already cached
2668
- * if (!has('/textures/diffuse.png')) {
2669
- * // Load with useTexture and cache: true, or manually add
2670
- * const tex = useTexture('/textures/diffuse.png', { cache: true })
2671
- * }
2672
- *
2673
- * // Access cached texture from anywhere
2674
- * const diffuse = get('/textures/diffuse.png')
2675
- * if (diffuse) material.map = diffuse
2691
+ * // Reach them from another component — record form lands straight into a material
2692
+ * const { map, normalMap } = useTextures().get({ map: '/diffuse.jpg', normalMap: '/normal.jpg' })
2693
+ * return <meshStandardMaterial map={map} normalMap={normalMap} />
2676
2694
  *
2677
- * // Remove from cache only (texture still in GPU memory)
2678
- * remove('/textures/old.png')
2695
+ * // A set of heightmaps at once
2696
+ * const [a, b, c] = useTextures().get(['/h0.png', '/h1.png', '/h2.png'])
2679
2697
  *
2680
- * // Fully dispose (frees GPU memory + removes from cache)
2681
- * dispose('/textures/unused.png')
2698
+ * // Register a non-URL texture (render target / procedural)
2699
+ * useTextures().add('rt-main', renderTarget.texture)
2682
2700
  *
2683
- * // Nuclear option - dispose everything
2684
- * disposeAll()
2701
+ * // Deliberate GPU cleanup (refcount-aware; force to override)
2702
+ * useTextures().dispose('/diffuse.jpg')
2703
+ * useTextures().disposeAll() // scene teardown
2685
2704
  * ```
2705
+ *
2706
+ * @remarks
2707
+ * **Future expansion:** an imperative async loader (e.g. `await useTextures().load('/x.jpg')`) is
2708
+ * intentionally not included yet. It would let non-React code (loading screens, dynamic streaming)
2709
+ * populate the registry without Suspense, at the cost of putting loading back into this hook. It
2710
+ * will be added only if a concrete consumer needs imperative loading; for now, loading lives in
2711
+ * `useTexture`.
2686
2712
  */
2687
2713
  declare function useTextures(): UseTexturesReturn;
2714
+ declare function useTextures<T>(selector: (registry: UseTexturesReturn) => T): T;
2688
2715
 
2689
2716
  /**
2690
2717
  * Creates a render target compatible with the current renderer.
@@ -4766,4 +4793,4 @@ interface WebGPURootState extends Omit<RootState, 'renderer' | 'gl' | 'internal'
4766
4793
  }
4767
4794
 
4768
4795
  export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, three_d as ReactThreeFiber, Scheduler, Texture, _roots, act, 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, getScheduler, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, reconciler, registerPrimary, removeInteractivity, removeNodes, removeUniforms, resolve, 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 };
4769
- export type { Act, AddPhaseOptions, Args, ArgsProp, AttachFnType, AttachType, BackgroundConfig, BackgroundProp, BaseRendererProps, Bridge, BufferCreator, BufferLike, BufferRecord, BufferStore, BuffersWithUtils, Camera, CameraProps, CanvasProps, CanvasSchedulerConfig, Catalogue, ClearBuffersFn, ClearNodesFn, ClearStorageFn, ClearUniformsFn, Color, ColorManagementConfig, ComputeFunction, ConstructorRepresentation, CreatorState, DefaultGLProps, DefaultRendererProps, Disposable, DisposeBuffersFn, DisposeStorageFn, DomEvent, Dpr, ElementProps, EnvironmentLoaderProps, EnvironmentProps, EquConfig, Euler, EventHandlers, EventManager, EventProps, Events, Extensions, FiberRoot, FilterFunction, FrameCallback, FrameControls, FrameNextCallback, FrameNextControls, FrameNextState, FrameState, FrameTimingState, Frameloop, GLProps, GLTFLike, GeometryProps, GeometryTransformProps, GlobalEffectType, GlobalRenderCallback, HostConfig, InferLoadResult, InjectState, InputLike, Instance, InstanceProps, WebGPUInternalState as InternalState, Intersection, IntersectionEvent, IsAllOptional, IsOptional, Layers, LegacyInternalState, LegacyRenderer, LegacyRootState, LoaderInstance, LoaderLike, LoaderResult, LocalNodeCreator, MappedTextureType, MathProps, MathRepresentation, MathType, MathTypes, Matrix3, Matrix4, Mutable, MutableOrReadonlyParameters, NodeCreator, NodeProps, NodeRecord, NodesWithUtils, NonFunctionKeys, ObjectMap, OffscreenCanvas$1 as OffscreenCanvas, Overwrite, Performance, PointerCaptureTarget, PointerState, PresetsType, PrimaryCanvasEntry, Properties, Quaternion, WebGPUR3FRenderer as R3FRenderer, RaycastableRepresentation, ReactProps, RebuildBuffersFn, RebuildNodesFn, RebuildStorageFn, RebuildUniformsFn, ReconcilerRoot, RemoveBuffersFn, RemoveNodesFn, RemoveStorageFn, RemoveUniformsFn, RenderCallback, RenderProps, RenderTargetOptions, Renderer, RendererConfigExtended, RendererFactory, RendererProps, Root, RootOptions, WebGPURootState as RootState, RootStore, SchedulerApi, ScopedStoreType, SetBlock, Size, StorageCreator, StorageLike, StorageRecord, StorageStore, StorageWithUtils, Subscription, TSLNode, TSLNodeInput, TextureEntry, TextureOperations, ThreeCamera, ThreeElement, ThreeElements, ThreeElementsImpl, ThreeEvent, ThreeExports, ThreeToJSXElements, UnblockProps, UniformCreator, UniformValue, UniformsWithUtils, UseFrameNextOptions, UseFrameOptions, UseTextureOptions, UseTexturesReturn, Vector2, Vector3, Vector4, VectorRepresentation, Viewport, VisibilityEntry, WebGLDefaultProps, WebGLProps, WebGLShadowConfig, WebGPUDefaultProps, WebGPUProps, WebGPUShadowConfig, XRManager, XRPointerConfig };
4796
+ export type { Act, AddPhaseOptions, Args, ArgsProp, AttachFnType, AttachType, BackgroundConfig, BackgroundProp, BaseRendererProps, Bridge, BufferCreator, BufferLike, BufferRecord, BufferStore, BuffersWithUtils, Camera, CameraProps, CanvasProps, CanvasSchedulerConfig, Catalogue, ClearBuffersFn, ClearNodesFn, ClearStorageFn, ClearUniformsFn, Color, ColorManagementConfig, ComputeFunction, ConstructorRepresentation, CreatorState, DefaultGLProps, DefaultRendererProps, Disposable, DisposeBuffersFn, DisposeOptions, DisposeStorageFn, DomEvent, Dpr, ElementProps, EnvironmentLoaderProps, EnvironmentProps, EquConfig, Euler, EventHandlers, EventManager, EventProps, Events, Extensions, FiberRoot, FilterFunction, FrameCallback, FrameControls, FrameNextCallback, FrameNextControls, FrameNextState, FrameState, FrameTimingState, Frameloop, GLProps, GLTFLike, GeometryProps, GeometryTransformProps, GlobalEffectType, GlobalRenderCallback, HostConfig, InferLoadResult, InjectState, InputLike, Instance, InstanceProps, WebGPUInternalState as InternalState, Intersection, IntersectionEvent, IsAllOptional, IsOptional, Layers, LegacyInternalState, LegacyRenderer, LegacyRootState, LoaderInstance, LoaderLike, LoaderResult, LocalNodeCreator, MappedTextureType, MathProps, MathRepresentation, MathType, MathTypes, Matrix3, Matrix4, Mutable, MutableOrReadonlyParameters, NodeCreator, NodeProps, NodeRecord, NodesWithUtils, NonFunctionKeys, ObjectMap, OffscreenCanvas$1 as OffscreenCanvas, Overwrite, Performance, PointerCaptureTarget, PointerState, PresetsType, PrimaryCanvasEntry, Properties, Quaternion, WebGPUR3FRenderer as R3FRenderer, RaycastableRepresentation, ReactProps, RebuildBuffersFn, RebuildNodesFn, RebuildStorageFn, RebuildUniformsFn, ReconcilerRoot, RemoveBuffersFn, RemoveNodesFn, RemoveStorageFn, RemoveUniformsFn, RenderCallback, RenderProps, RenderTargetOptions, Renderer, RendererConfigExtended, RendererFactory, RendererProps, Root, RootOptions, WebGPURootState as RootState, RootStore, SchedulerApi, ScopedStoreType, SetBlock, Size, StorageCreator, StorageLike, StorageRecord, StorageStore, StorageWithUtils, Subscription, TSLNode, TSLNodeInput, TextureInput, TextureOperations, TextureResult, ThreeCamera, ThreeElement, ThreeElements, ThreeElementsImpl, ThreeEvent, ThreeExports, ThreeToJSXElements, UnblockProps, UniformCreator, UniformValue, UniformsWithUtils, UseFrameNextOptions, UseFrameOptions, UseTextureOptions, UseTexturesReturn, Vector2, Vector3, Vector4, VectorRepresentation, Viewport, VisibilityEntry, WebGLDefaultProps, WebGLProps, WebGLShadowConfig, WebGPUDefaultProps, WebGPUProps, WebGPUShadowConfig, XRManager, XRPointerConfig };