Skip to main content

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    asset::manager::ResourceManager,
23    core::err_once,
24    core::log::{Log, MessageKind},
25    graphics::{
26        error::FrameworkError,
27        gpu_texture::{GpuTexture, GpuTextureDescriptor, GpuTextureKind, PixelKind},
28        sampler::{
29            GpuSampler, GpuSamplerDescriptor, MagnificationFilter, MinificationFilter, WrapMode,
30        },
31        server::GraphicsServer,
32    },
33    renderer::cache::{TemporaryCache, TimeToLive},
34    resource::texture::{Texture, TextureResource},
35};
36use fyrox_texture::{
37    TextureKind, TextureMagnificationFilter, TextureMinificationFilter, TexturePixelKind,
38    TextureWrapMode,
39};
40use std::{borrow::Cow, time::Duration};
41use uuid::Uuid;
42
43#[derive(Clone)]
44pub struct TextureRenderData {
45    pub gpu_texture: GpuTexture,
46    pub gpu_sampler: GpuSampler,
47    modifications_counter: u64,
48    sampler_modifications_counter: u64,
49}
50
51#[derive(Default)]
52pub struct TextureCache {
53    cache: TemporaryCache<TextureRenderData>,
54}
55
56fn convert_texture_kind(v: TextureKind) -> GpuTextureKind {
57    match v {
58        TextureKind::Line { length } => GpuTextureKind::Line {
59            length: length as usize,
60        },
61        TextureKind::Rectangle { width, height } => GpuTextureKind::Rectangle {
62            width: width as usize,
63            height: height as usize,
64        },
65        TextureKind::Cube { size } => GpuTextureKind::Cube {
66            size: size as usize,
67        },
68        TextureKind::Volume {
69            width,
70            height,
71            depth,
72        } => GpuTextureKind::Volume {
73            width: width as usize,
74            height: height as usize,
75            depth: depth as usize,
76        },
77    }
78}
79
80pub fn convert_pixel_kind(texture_kind: TexturePixelKind) -> PixelKind {
81    match texture_kind {
82        TexturePixelKind::R8 => PixelKind::R8,
83        TexturePixelKind::RGB8 => PixelKind::RGB8,
84        TexturePixelKind::RGBA8 => PixelKind::RGBA8,
85        TexturePixelKind::RG8 => PixelKind::RG8,
86        TexturePixelKind::R16 => PixelKind::R16,
87        TexturePixelKind::RG16 => PixelKind::RG16,
88        TexturePixelKind::BGR8 => PixelKind::BGR8,
89        TexturePixelKind::BGRA8 => PixelKind::BGRA8,
90        TexturePixelKind::RGB16 => PixelKind::RGB16,
91        TexturePixelKind::RGBA16 => PixelKind::RGBA16,
92        TexturePixelKind::RGB16F => PixelKind::RGB16F,
93        TexturePixelKind::DXT1RGB => PixelKind::DXT1RGB,
94        TexturePixelKind::DXT1RGBA => PixelKind::DXT1RGBA,
95        TexturePixelKind::DXT3RGBA => PixelKind::DXT3RGBA,
96        TexturePixelKind::DXT5RGBA => PixelKind::DXT5RGBA,
97        TexturePixelKind::R8RGTC => PixelKind::R8RGTC,
98        TexturePixelKind::RG8RGTC => PixelKind::RG8RGTC,
99        TexturePixelKind::RGB32F => PixelKind::RGB32F,
100        TexturePixelKind::RGBA32F => PixelKind::RGBA32F,
101        TexturePixelKind::Luminance8 => PixelKind::L8,
102        TexturePixelKind::LuminanceAlpha8 => PixelKind::LA8,
103        TexturePixelKind::Luminance16 => PixelKind::L16,
104        TexturePixelKind::LuminanceAlpha16 => PixelKind::LA16,
105        TexturePixelKind::R32F => PixelKind::R32F,
106        TexturePixelKind::R16F => PixelKind::R16F,
107        TexturePixelKind::SRGBA8 => PixelKind::SRGBA8,
108        TexturePixelKind::SRGB8 => PixelKind::SRGB8,
109    }
110}
111
112pub fn convert_magnification_filter(v: TextureMagnificationFilter) -> MagnificationFilter {
113    match v {
114        TextureMagnificationFilter::Nearest => MagnificationFilter::Nearest,
115        TextureMagnificationFilter::Linear => MagnificationFilter::Linear,
116    }
117}
118
119pub fn convert_minification_filter(v: TextureMinificationFilter) -> MinificationFilter {
120    match v {
121        TextureMinificationFilter::Nearest => MinificationFilter::Nearest,
122        TextureMinificationFilter::NearestMipMapNearest => MinificationFilter::NearestMipMapNearest,
123        TextureMinificationFilter::NearestMipMapLinear => MinificationFilter::NearestMipMapLinear,
124        TextureMinificationFilter::Linear => MinificationFilter::Linear,
125        TextureMinificationFilter::LinearMipMapNearest => MinificationFilter::LinearMipMapNearest,
126        TextureMinificationFilter::LinearMipMapLinear => MinificationFilter::LinearMipMapLinear,
127    }
128}
129
130pub fn convert_wrap_mode(v: TextureWrapMode) -> WrapMode {
131    match v {
132        TextureWrapMode::Repeat => WrapMode::Repeat,
133        TextureWrapMode::ClampToEdge => WrapMode::ClampToEdge,
134        TextureWrapMode::ClampToBorder => WrapMode::ClampToBorder,
135        TextureWrapMode::MirroredRepeat => WrapMode::MirroredRepeat,
136        TextureWrapMode::MirrorClampToEdge => WrapMode::MirrorClampToEdge,
137    }
138}
139
140fn create_sampler(
141    server: &dyn GraphicsServer,
142    texture: &Texture,
143) -> Result<GpuSampler, FrameworkError> {
144    server.create_sampler(GpuSamplerDescriptor {
145        mag_filter: convert_magnification_filter(texture.magnification_filter()),
146        min_filter: convert_minification_filter(texture.minification_filter()),
147        s_wrap_mode: convert_wrap_mode(texture.s_wrap_mode()),
148        t_wrap_mode: convert_wrap_mode(texture.t_wrap_mode()),
149        r_wrap_mode: convert_wrap_mode(texture.r_wrap_mode()),
150        anisotropy: texture.anisotropy_level(),
151        min_lod: texture.min_lod(),
152        max_lod: texture.max_lod(),
153        lod_bias: texture.lod_bias(),
154    })
155}
156
157fn create_gpu_texture(
158    server: &dyn GraphicsServer,
159    resource_manager: &ResourceManager,
160    uuid: &Uuid,
161    texture: &Texture,
162) -> Result<TextureRenderData, FrameworkError> {
163    let path = resource_manager
164        .try_get_state(Duration::from_millis(1))
165        .and_then(|state| state.uuid_to_resource_path(*uuid));
166    let name = path
167        .as_ref()
168        .map(|path| path.to_string_lossy())
169        .unwrap_or_else(|| Cow::Borrowed(""));
170
171    let gpu_texture = server.create_texture(GpuTextureDescriptor {
172        name: &name,
173        kind: convert_texture_kind(texture.kind()),
174        pixel_kind: convert_pixel_kind(texture.pixel_kind()),
175        mip_count: texture.mip_count() as usize,
176        data: Some(texture.data()),
177        base_level: texture.base_level(),
178        max_level: texture.max_level(),
179    })?;
180
181    Ok(TextureRenderData {
182        gpu_texture,
183        gpu_sampler: create_sampler(server, texture)?,
184        modifications_counter: texture.modifications_count(),
185        sampler_modifications_counter: texture.sampler_modifications_count(),
186    })
187}
188
189impl TextureCache {
190    /// Unconditionally uploads requested texture into GPU memory, previous GPU texture will be automatically
191    /// destroyed.
192    pub fn upload(
193        &mut self,
194        server: &dyn GraphicsServer,
195        resource_manager: &ResourceManager,
196        texture: &TextureResource,
197    ) -> Result<(), FrameworkError> {
198        let uuid = texture.resource_uuid();
199        let texture = texture.state();
200        if let Some(texture) = texture.data_ref() {
201            self.cache.get_entry_mut_or_insert_with(
202                &texture.cache_index,
203                Default::default(),
204                || create_gpu_texture(server, resource_manager, &uuid, texture),
205            )?;
206            Ok(())
207        } else {
208            Err(FrameworkError::Custom(
209                "Texture is not loaded yet!".to_string(),
210            ))
211        }
212    }
213
214    pub fn get(
215        &mut self,
216        server: &dyn GraphicsServer,
217        resource_manager: &ResourceManager,
218        texture_resource: &TextureResource,
219    ) -> Option<&TextureRenderData> {
220        let uuid = texture_resource.resource_uuid();
221        let texture_data_guard = texture_resource.state();
222        if let Some(texture) = texture_data_guard.data_ref() {
223            match self.cache.get_mut_or_insert_with(
224                &texture.cache_index,
225                Default::default(),
226                || create_gpu_texture(server, resource_manager, &uuid, texture),
227            ) {
228                Ok(entry) => {
229                    // Check if some value has changed in resource.
230
231                    // Data might change from last frame, so we have to check it and upload new if so.
232                    let modifications_count = texture.modifications_count();
233                    if entry.modifications_counter != modifications_count {
234                        if let Err(e) = entry.gpu_texture.set_data(
235                            convert_texture_kind(texture.kind()),
236                            convert_pixel_kind(texture.pixel_kind()),
237                            texture.mip_count() as usize,
238                            Some(texture.data()),
239                        ) {
240                            Log::writeln(
241                                MessageKind::Error,
242                                format!("Unable to upload new texture data to GPU. Reason: {e:?}"),
243                            )
244                        } else {
245                            entry.modifications_counter = modifications_count;
246                        }
247                    }
248
249                    if entry.sampler_modifications_counter != texture.sampler_modifications_count()
250                    {
251                        entry.gpu_sampler = create_sampler(server, texture).unwrap();
252                    }
253
254                    return Some(entry);
255                }
256                Err(e) => {
257                    drop(texture_data_guard);
258                    err_once!(
259                        texture_resource.key() as usize,
260                        "Failed to create GPU texture from texture. Reason: {:?}",
261                        e,
262                    );
263                }
264            }
265        }
266        None
267    }
268
269    pub fn update(&mut self, dt: f32) {
270        self.cache.update(dt)
271    }
272
273    pub fn clear(&mut self) {
274        self.cache.clear();
275    }
276
277    pub fn unload(&mut self, texture: &TextureResource) {
278        if let Some(texture) = texture.state().data() {
279            self.cache.remove(&texture.cache_index);
280        }
281    }
282
283    pub fn alive_count(&self) -> usize {
284        self.cache.alive_count()
285    }
286
287    /// Tries to bind existing GPU texture with a texture resource. If there's no such binding, then
288    /// a new binding is created, otherwise - only the TTL is updated to keep the GPU texture alive
289    /// for a certain time period (see [`TimeToLive`]).
290    pub fn try_register(
291        &mut self,
292        server: &dyn GraphicsServer,
293        texture: &TextureResource,
294        gpu_texture: GpuTexture,
295    ) -> Result<(), FrameworkError> {
296        let data = texture.data_ref();
297        let index = data.cache_index.clone();
298        let entry = self.cache.get_mut(&index);
299        if entry.is_none() {
300            self.cache.spawn(
301                TextureRenderData {
302                    gpu_texture,
303                    gpu_sampler: create_sampler(server, &data)?,
304                    modifications_counter: data.modifications_count(),
305                    sampler_modifications_counter: data.sampler_modifications_count(),
306                },
307                index,
308                TimeToLive::default(),
309            );
310        }
311        Ok(())
312    }
313}