fyrox_impl/renderer/cache/
texture.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{
22    core::log::{Log, MessageKind},
23    renderer::{
24        cache::{TemporaryCache, TimeToLive},
25        framework::{
26            error::FrameworkError,
27            gpu_texture::{Coordinate, GpuTexture, PixelKind},
28            server::GraphicsServer,
29        },
30    },
31    resource::texture::{Texture, TextureResource},
32};
33use fyrox_graphics::gpu_texture::{
34    GpuTextureDescriptor, GpuTextureKind, MagnificationFilter, MinificationFilter, WrapMode,
35};
36use fyrox_texture::{
37    TextureKind, TextureMagnificationFilter, TextureMinificationFilter, TexturePixelKind,
38    TextureWrapMode,
39};
40use std::{cell::RefCell, rc::Rc};
41
42pub(crate) struct TextureRenderData {
43    pub gpu_texture: Rc<RefCell<dyn GpuTexture>>,
44    pub modifications_counter: u64,
45}
46
47#[derive(Default)]
48pub struct TextureCache {
49    cache: TemporaryCache<TextureRenderData>,
50}
51
52fn convert_texture_kind(v: TextureKind) -> GpuTextureKind {
53    match v {
54        TextureKind::Line { length } => GpuTextureKind::Line {
55            length: length as usize,
56        },
57        TextureKind::Rectangle { width, height } => GpuTextureKind::Rectangle {
58            width: width as usize,
59            height: height as usize,
60        },
61        TextureKind::Cube { width, height } => GpuTextureKind::Cube {
62            width: width as usize,
63            height: height as usize,
64        },
65        TextureKind::Volume {
66            width,
67            height,
68            depth,
69        } => GpuTextureKind::Volume {
70            width: width as usize,
71            height: height as usize,
72            depth: depth as usize,
73        },
74    }
75}
76
77fn convert_pixel_kind(texture_kind: TexturePixelKind) -> PixelKind {
78    match texture_kind {
79        TexturePixelKind::R8 => PixelKind::R8,
80        TexturePixelKind::RGB8 => PixelKind::RGB8,
81        TexturePixelKind::RGBA8 => PixelKind::RGBA8,
82        TexturePixelKind::RG8 => PixelKind::RG8,
83        TexturePixelKind::R16 => PixelKind::R16,
84        TexturePixelKind::RG16 => PixelKind::RG16,
85        TexturePixelKind::BGR8 => PixelKind::BGR8,
86        TexturePixelKind::BGRA8 => PixelKind::BGRA8,
87        TexturePixelKind::RGB16 => PixelKind::RGB16,
88        TexturePixelKind::RGBA16 => PixelKind::RGBA16,
89        TexturePixelKind::RGB16F => PixelKind::RGB16F,
90        TexturePixelKind::DXT1RGB => PixelKind::DXT1RGB,
91        TexturePixelKind::DXT1RGBA => PixelKind::DXT1RGBA,
92        TexturePixelKind::DXT3RGBA => PixelKind::DXT3RGBA,
93        TexturePixelKind::DXT5RGBA => PixelKind::DXT5RGBA,
94        TexturePixelKind::R8RGTC => PixelKind::R8RGTC,
95        TexturePixelKind::RG8RGTC => PixelKind::RG8RGTC,
96        TexturePixelKind::RGB32F => PixelKind::RGB32F,
97        TexturePixelKind::RGBA32F => PixelKind::RGBA32F,
98        TexturePixelKind::Luminance8 => PixelKind::L8,
99        TexturePixelKind::LuminanceAlpha8 => PixelKind::LA8,
100        TexturePixelKind::Luminance16 => PixelKind::L16,
101        TexturePixelKind::LuminanceAlpha16 => PixelKind::LA16,
102        TexturePixelKind::R32F => PixelKind::R32F,
103        TexturePixelKind::R16F => PixelKind::R16F,
104    }
105}
106
107fn convert_magnification_filter(v: TextureMagnificationFilter) -> MagnificationFilter {
108    match v {
109        TextureMagnificationFilter::Nearest => MagnificationFilter::Nearest,
110        TextureMagnificationFilter::Linear => MagnificationFilter::Linear,
111    }
112}
113
114fn convert_minification_filter(v: TextureMinificationFilter) -> MinificationFilter {
115    match v {
116        TextureMinificationFilter::Nearest => MinificationFilter::Nearest,
117        TextureMinificationFilter::NearestMipMapNearest => MinificationFilter::NearestMipMapNearest,
118        TextureMinificationFilter::NearestMipMapLinear => MinificationFilter::NearestMipMapLinear,
119        TextureMinificationFilter::Linear => MinificationFilter::Linear,
120        TextureMinificationFilter::LinearMipMapNearest => MinificationFilter::LinearMipMapNearest,
121        TextureMinificationFilter::LinearMipMapLinear => MinificationFilter::LinearMipMapLinear,
122    }
123}
124
125fn convert_wrap_mode(v: TextureWrapMode) -> WrapMode {
126    match v {
127        TextureWrapMode::Repeat => WrapMode::Repeat,
128        TextureWrapMode::ClampToEdge => WrapMode::ClampToEdge,
129        TextureWrapMode::ClampToBorder => WrapMode::ClampToBorder,
130        TextureWrapMode::MirroredRepeat => WrapMode::MirroredRepeat,
131        TextureWrapMode::MirrorClampToEdge => WrapMode::MirrorClampToEdge,
132    }
133}
134
135fn create_gpu_texture(
136    server: &dyn GraphicsServer,
137    texture: &Texture,
138) -> Result<TextureRenderData, FrameworkError> {
139    server
140        .create_texture(GpuTextureDescriptor {
141            kind: convert_texture_kind(texture.kind()),
142            pixel_kind: convert_pixel_kind(texture.pixel_kind()),
143            mag_filter: convert_magnification_filter(texture.magnification_filter()),
144            min_filter: convert_minification_filter(texture.minification_filter()),
145            mip_count: texture.mip_count() as usize,
146            s_wrap_mode: convert_wrap_mode(texture.s_wrap_mode()),
147            t_wrap_mode: convert_wrap_mode(texture.t_wrap_mode()),
148            r_wrap_mode: convert_wrap_mode(texture.r_wrap_mode()),
149            anisotropy: texture.anisotropy_level(),
150            data: Some(texture.data()),
151            base_level: texture.base_level(),
152            max_level: texture.max_level(),
153            min_lod: texture.min_lod(),
154            max_lod: texture.max_lod(),
155            lod_bias: texture.lod_bias(),
156        })
157        .map(|gpu_texture| TextureRenderData {
158            gpu_texture,
159            modifications_counter: texture.modifications_count(),
160        })
161}
162
163impl TextureCache {
164    /// Unconditionally uploads requested texture into GPU memory, previous GPU texture will be automatically
165    /// destroyed.
166    pub fn upload(
167        &mut self,
168        server: &dyn GraphicsServer,
169        texture: &TextureResource,
170    ) -> Result<(), FrameworkError> {
171        let mut texture = texture.state();
172        if let Some(texture) = texture.data() {
173            self.cache.get_entry_mut_or_insert_with(
174                &texture.cache_index,
175                Default::default(),
176                || create_gpu_texture(server, texture),
177            )?;
178            Ok(())
179        } else {
180            Err(FrameworkError::Custom(
181                "Texture is not loaded yet!".to_string(),
182            ))
183        }
184    }
185
186    pub fn get(
187        &mut self,
188        server: &dyn GraphicsServer,
189        texture_resource: &TextureResource,
190    ) -> Option<&Rc<RefCell<dyn GpuTexture>>> {
191        let mut texture_data_guard = texture_resource.state();
192
193        if let Some(texture) = texture_data_guard.data() {
194            match self.cache.get_mut_or_insert_with(
195                &texture.cache_index,
196                Default::default(),
197                || create_gpu_texture(server, texture),
198            ) {
199                Ok(entry) => {
200                    // Check if some value has changed in resource.
201
202                    // Data might change from last frame, so we have to check it and upload new if so.
203                    let modifications_count = texture.modifications_count();
204                    if entry.modifications_counter != modifications_count {
205                        let mut gpu_texture = entry.gpu_texture.borrow_mut();
206                        if let Err(e) = gpu_texture.set_data(
207                            convert_texture_kind(texture.kind()),
208                            convert_pixel_kind(texture.pixel_kind()),
209                            texture.mip_count() as usize,
210                            Some(texture.data()),
211                        ) {
212                            Log::writeln(
213                                MessageKind::Error,
214                                format!("Unable to upload new texture data to GPU. Reason: {e:?}"),
215                            )
216                        } else {
217                            entry.modifications_counter = modifications_count;
218                        }
219                    }
220
221                    let mut gpu_texture = entry.gpu_texture.borrow_mut();
222
223                    let new_mag_filter =
224                        convert_magnification_filter(texture.magnification_filter());
225                    if gpu_texture.magnification_filter() != new_mag_filter {
226                        gpu_texture.set_magnification_filter(new_mag_filter);
227                    }
228
229                    let new_min_filter = convert_minification_filter(texture.minification_filter());
230                    if gpu_texture.minification_filter() != new_min_filter {
231                        gpu_texture.set_minification_filter(new_min_filter);
232                    }
233
234                    if gpu_texture.anisotropy().ne(&texture.anisotropy_level()) {
235                        gpu_texture.set_anisotropy(texture.anisotropy_level());
236                    }
237
238                    let new_s_wrap_mode = convert_wrap_mode(texture.s_wrap_mode());
239                    if gpu_texture.wrap_mode(Coordinate::S) != new_s_wrap_mode {
240                        gpu_texture.set_wrap(Coordinate::S, new_s_wrap_mode);
241                    }
242
243                    let new_t_wrap_mode = convert_wrap_mode(texture.t_wrap_mode());
244                    if gpu_texture.wrap_mode(Coordinate::T) != new_t_wrap_mode {
245                        gpu_texture.set_wrap(Coordinate::T, new_t_wrap_mode);
246                    }
247
248                    let new_r_wrap_mode = convert_wrap_mode(texture.r_wrap_mode());
249                    if gpu_texture.wrap_mode(Coordinate::R) != new_r_wrap_mode {
250                        gpu_texture.set_wrap(Coordinate::R, new_r_wrap_mode);
251                    }
252
253                    return Some(&entry.gpu_texture);
254                }
255                Err(e) => {
256                    drop(texture_data_guard);
257                    Log::writeln(
258                        MessageKind::Error,
259                        format!(
260                            "Failed to create GPU texture from {} texture. Reason: {:?}",
261                            texture_resource.kind(),
262                            e
263                        ),
264                    );
265                }
266            }
267        }
268        None
269    }
270
271    pub fn update(&mut self, dt: f32) {
272        self.cache.update(dt)
273    }
274
275    pub fn clear(&mut self) {
276        self.cache.clear();
277    }
278
279    pub fn unload(&mut self, texture: TextureResource) {
280        if let Some(texture) = texture.state().data() {
281            self.cache.remove(&texture.cache_index);
282        }
283    }
284
285    pub fn alive_count(&self) -> usize {
286        self.cache.alive_count()
287    }
288
289    /// Tries to bind existing GPU texture with a texture resource. If there's no such binding, then
290    /// a new binding is created, otherwise - only the TTL is updated to keep the GPU texture alive
291    /// for a certain time period (see [`TimeToLive`]).
292    pub fn try_register(
293        &mut self,
294        texture: &TextureResource,
295        gpu_texture: Rc<RefCell<dyn GpuTexture>>,
296    ) {
297        let data = texture.data_ref();
298        let index = data.cache_index.clone();
299        let entry = self.cache.get_mut(&index);
300        if entry.is_none() {
301            self.cache.spawn(
302                TextureRenderData {
303                    gpu_texture,
304                    modifications_counter: data.modifications_count(),
305                },
306                index,
307                TimeToLive::default(),
308            );
309        }
310    }
311}