bevy_render/texture/
texture_cache.rs

1use crate::{
2    render_resource::{Texture, TextureView},
3    renderer::RenderDevice,
4};
5use bevy_ecs::{prelude::ResMut, system::Resource};
6use bevy_utils::{Entry, HashMap};
7use wgpu::{TextureDescriptor, TextureViewDescriptor};
8
9/// The internal representation of a [`CachedTexture`] used to track whether it was recently used
10/// and is currently taken.
11struct CachedTextureMeta {
12    texture: Texture,
13    default_view: TextureView,
14    taken: bool,
15    frames_since_last_use: usize,
16}
17
18/// A cached GPU [`Texture`] with corresponding [`TextureView`].
19/// This is useful for textures that are created repeatedly (each frame) in the rendering process
20/// to reduce the amount of GPU memory allocations.
21#[derive(Clone)]
22pub struct CachedTexture {
23    pub texture: Texture,
24    pub default_view: TextureView,
25}
26
27/// This resource caches textures that are created repeatedly in the rendering process and
28/// are only required for one frame.
29#[derive(Resource, Default)]
30pub struct TextureCache {
31    textures: HashMap<TextureDescriptor<'static>, Vec<CachedTextureMeta>>,
32}
33
34impl TextureCache {
35    /// Retrieves a texture that matches the `descriptor`. If no matching one is found a new
36    /// [`CachedTexture`] is created.
37    pub fn get(
38        &mut self,
39        render_device: &RenderDevice,
40        descriptor: TextureDescriptor<'static>,
41    ) -> CachedTexture {
42        match self.textures.entry(descriptor) {
43            Entry::Occupied(mut entry) => {
44                for texture in entry.get_mut().iter_mut() {
45                    if !texture.taken {
46                        texture.frames_since_last_use = 0;
47                        texture.taken = true;
48                        return CachedTexture {
49                            texture: texture.texture.clone(),
50                            default_view: texture.default_view.clone(),
51                        };
52                    }
53                }
54
55                let texture = render_device.create_texture(&entry.key().clone());
56                let default_view = texture.create_view(&TextureViewDescriptor::default());
57                entry.get_mut().push(CachedTextureMeta {
58                    texture: texture.clone(),
59                    default_view: default_view.clone(),
60                    frames_since_last_use: 0,
61                    taken: true,
62                });
63                CachedTexture {
64                    texture,
65                    default_view,
66                }
67            }
68            Entry::Vacant(entry) => {
69                let texture = render_device.create_texture(entry.key());
70                let default_view = texture.create_view(&TextureViewDescriptor::default());
71                entry.insert(vec![CachedTextureMeta {
72                    texture: texture.clone(),
73                    default_view: default_view.clone(),
74                    taken: true,
75                    frames_since_last_use: 0,
76                }]);
77                CachedTexture {
78                    texture,
79                    default_view,
80                }
81            }
82        }
83    }
84
85    /// Returns `true` if the texture cache contains no textures.
86    pub fn is_empty(&self) -> bool {
87        self.textures.is_empty()
88    }
89
90    /// Updates the cache and only retains recently used textures.
91    pub fn update(&mut self) {
92        self.textures.retain(|_, textures| {
93            for texture in textures.iter_mut() {
94                texture.frames_since_last_use += 1;
95                texture.taken = false;
96            }
97
98            textures.retain(|texture| texture.frames_since_last_use < 3);
99            !textures.is_empty()
100        });
101    }
102}
103
104/// Updates the [`TextureCache`] to only retains recently used textures.
105pub fn update_texture_cache_system(mut texture_cache: ResMut<TextureCache>) {
106    texture_cache.update();
107}