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