bottomless_pit/
texture.rs

1//! Cointains the interface into the texture cache and by
2//! extension accsss the texture interface
3
4use crate::context::WgpuClump;
5use crate::engine_handle::Engine;
6use crate::resource::{self, InProgressResource, LoadingOp, ResourceId, ResourceType};
7use crate::vectors::Vec2;
8use crate::{layouts, ERROR_TEXTURE_DATA};
9use image::{GenericImageView, ImageError};
10use std::fmt::Display;
11use std::io::Error;
12use std::path::Path;
13
14/// Contains all the information need to render an image/texture to the screen.
15/// In order to be used it must be put inside a [Material](crate::material::Material)
16pub struct Texture {
17    pub(crate) _view: wgpu::TextureView,
18    pub(crate) bind_group: wgpu::BindGroup,
19    pub(crate) size: Vec2<f32>,
20}
21
22impl Texture {
23    /// Attempts to both read a file at the specified path and turn it into an image.
24    pub fn new<P>(engine: &mut Engine, path: P, loading_op: LoadingOp) -> ResourceId<Texture>
25    where
26        P: AsRef<Path>,
27    {
28        let typed_id = resource::generate_id::<Texture>();
29        let id = typed_id.get_id();
30        let path = path.as_ref();
31        let ip_resource = InProgressResource::new(
32            path,
33            id,
34            ResourceType::Image(
35                SamplerType::LinearInterpolation,
36                SamplerType::NearestNeighbor,
37            ),
38            loading_op,
39        );
40
41        engine.loader.load(ip_resource, engine.get_proxy());
42
43        typed_id
44    }
45
46    /// Attempts to both read a file at the specified path and turn it into an image.
47    pub fn new_with_sampler<P>(
48        engine: &mut Engine,
49        path: P,
50        sampler: SamplerType,
51        loading_op: LoadingOp,
52    ) -> ResourceId<Texture>
53    where
54        P: AsRef<Path>,
55    {
56        let typed_id = resource::generate_id::<Texture>();
57        let id = typed_id.get_id();
58        let path = path.as_ref();
59        let ip_resource =
60            InProgressResource::new(path, id, ResourceType::Image(sampler, sampler), loading_op);
61
62        engine.loader.blocking_load(ip_resource, engine.get_proxy());
63
64        typed_id
65    }
66
67    /// Attempts to load the file at the path and then turn it into a texture. This also allows you to select what sampling type to use
68    /// for both the `mag_sampler`, when the texture is being drawn larger than the orignal resolution and `min_sampler`, when the texture
69    /// is being drawn smaller than the original resolution.
70    pub fn new_with_mag_min_sampler<P>(
71        engine: &mut Engine,
72        path: P,
73        mag_sampler: SamplerType,
74        min_sampler: SamplerType,
75        loading_op: LoadingOp,
76    ) -> ResourceId<Texture>
77    where
78        P: AsRef<Path>,
79    {
80        let typed_id = resource::generate_id::<Texture>();
81        let id = typed_id.get_id();
82        let path = path.as_ref();
83        let ip_resource = InProgressResource::new(
84            path,
85            id,
86            ResourceType::Image(mag_sampler, min_sampler),
87            loading_op,
88        );
89
90        engine.loader.blocking_load(ip_resource, engine.get_proxy());
91
92        typed_id
93    }
94
95    pub(crate) fn from_resource_data(
96        engine: &Engine,
97        label: Option<&str>,
98        data: Vec<u8>,
99        mag_sampler: SamplerType,
100        min_sampler: SamplerType,
101    ) -> Result<Self, TextureError> {
102        let img = image::load_from_memory(&data)?;
103        Ok(Self::from_image(
104            engine,
105            img,
106            label,
107            mag_sampler,
108            min_sampler,
109        ))
110    }
111
112    pub(crate) fn new_direct(
113        view: wgpu::TextureView,
114        bind_group: wgpu::BindGroup,
115        size: Vec2<f32>,
116    ) -> Self {
117        Self {
118            _view: view,
119            bind_group,
120            size,
121        }
122    }
123
124    pub(crate) fn default(engine: &Engine) -> Self {
125        let image = image::load_from_memory(ERROR_TEXTURE_DATA).unwrap();
126        Self::from_image(
127            engine,
128            image,
129            Some("Error Texture"),
130            SamplerType::LinearInterpolation,
131            SamplerType::NearestNeighbor,
132        )
133    }
134
135    fn from_image(
136        engine: &Engine,
137        img: image::DynamicImage,
138        label: Option<&str>,
139        mag_filter: SamplerType,
140        min_filter: SamplerType,
141    ) -> Self {
142        let wgpu = &engine.context.as_ref().expect("need graphic context").wgpu;
143        let diffuse_rgba = img.to_rgba8();
144        let (width, height) = img.dimensions();
145
146        let texture_size = wgpu::Extent3d {
147            width,
148            height,
149            depth_or_array_layers: 1,
150        };
151
152        let texture = wgpu.device.create_texture(&wgpu::TextureDescriptor {
153            size: texture_size,
154            mip_level_count: 1,
155            sample_count: 1,
156            dimension: wgpu::TextureDimension::D2,
157            format: wgpu::TextureFormat::Rgba8UnormSrgb,
158            view_formats: &[],
159            // TEXTURE_BINDING tells wgpu that we want to use this texture in shaders
160            // COPY_DST means that we want to copy data to this texture
161            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
162            label,
163        });
164
165        wgpu.queue.write_texture(
166            wgpu::ImageCopyTextureBase {
167                texture: &texture,
168                mip_level: 0,
169                origin: wgpu::Origin3d::ZERO,
170                aspect: wgpu::TextureAspect::All,
171            },
172            &diffuse_rgba,
173            wgpu::ImageDataLayout {
174                offset: 0,
175                bytes_per_row: Some(4 * width),
176                rows_per_image: Some(height),
177            },
178            texture_size,
179        );
180
181        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
182        let bind_group_layout = layouts::create_texture_layout(&wgpu.device);
183
184        let texture_sampler = wgpu.device.create_sampler(&wgpu::SamplerDescriptor {
185            // what to do when given cordinates outside the textures height/width
186            address_mode_u: wgpu::AddressMode::Repeat,
187            address_mode_v: wgpu::AddressMode::Repeat,
188            address_mode_w: wgpu::AddressMode::ClampToEdge,
189            // what do when give less or more than 1 pixel to sample
190            // linear interprelates between all of them nearest gives the closet colour
191            mag_filter: mag_filter.into(),
192            min_filter: min_filter.into(),
193            mipmap_filter: wgpu::FilterMode::Nearest,
194            ..Default::default()
195        });
196
197        let bind_group = wgpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
198            layout: &bind_group_layout,
199            entries: &[
200                wgpu::BindGroupEntry {
201                    binding: 0,
202                    resource: wgpu::BindingResource::TextureView(&view),
203                },
204                wgpu::BindGroupEntry {
205                    binding: 1,
206                    resource: wgpu::BindingResource::Sampler(&texture_sampler),
207                },
208            ],
209            label: Some("diffuse_bind_group"),
210        });
211
212        let size = Vec2 {
213            x: width as f32,
214            y: height as f32,
215        };
216
217        Self {
218            _view: view,
219            bind_group,
220            size,
221        }
222    }
223}
224
225/// Loading a texture can fail in two senarios. Either the file cant be opened, or the
226/// file loaded is not a supported image file type.
227#[derive(Debug)]
228pub(crate) enum TextureError {
229    IoError(Error),
230    ImageError(ImageError),
231}
232
233impl From<Error> for TextureError {
234    fn from(value: Error) -> TextureError {
235        Self::IoError(value)
236    }
237}
238
239impl From<ImageError> for TextureError {
240    fn from(value: ImageError) -> Self {
241        Self::ImageError(value)
242    }
243}
244
245/// A UniformTexture is a special type of texture that you can renderer to
246/// and also render itself to the screen. Uniform Textures is usefull for things like lightmaps or
247/// graphics techniques that reuqire multiple rendering passes.
248pub struct UniformTexture {
249    inner_texture: Option<InnerTexture>,
250    size: Vec2<u32>,
251    mag_sampler: SamplerType,
252    min_sampler: SamplerType,
253    // marks wether or not the view needs to updatred
254    // starts at true as we have to set the view later.
255    needs_update: bool,
256}
257
258impl UniformTexture {
259    /// creates a `UniformTexture` of a specified size
260    /// this can be reszied at anytime with
261    /// [Material::resize_uniform_texture](crate::material::Material::resize_uniform_texture)
262    pub fn new(engine: &Engine, size: Vec2<u32>) -> Self {
263        let inner_texture = engine.context.as_ref().map(|c| {
264            InnerTexture::from_wgpu(
265                size,
266                SamplerType::LinearInterpolation,
267                SamplerType::NearestNeighbor,
268                c.get_texture_format(),
269                &c.wgpu,
270            )
271        });
272
273        Self {
274            inner_texture,
275            size,
276            mag_sampler: SamplerType::LinearInterpolation,
277            min_sampler: SamplerType::NearestNeighbor,
278            needs_update: true,
279        }
280    }
281
282    /// This creates a UniformTexture with samplers which allows you to select what sampling type to use for both the `mag_sampler`,
283    /// when the texture is being drawn larger than the orignal resolution and `min_sampler`, when the texture is being drawn
284    /// smaller than the original resolution.
285    pub fn new_with_sampler(
286        engine: &Engine,
287        size: Vec2<u32>,
288        mag_sampler: SamplerType,
289        min_sampler: SamplerType,
290    ) -> Self {
291        let inner_texture = engine.context.as_ref().map(|c| {
292            InnerTexture::from_wgpu(
293                size,
294                mag_sampler,
295                min_sampler,
296                c.get_texture_format(),
297                &c.wgpu,
298            )
299        });
300
301        Self {
302            inner_texture,
303            size,
304            mag_sampler,
305            min_sampler,
306            needs_update: true,
307        }
308    }
309
310    pub(crate) fn resize(
311        &mut self,
312        new_size: Vec2<u32>,
313        wgpu: &WgpuClump,
314        format: wgpu::TextureFormat,
315    ) {
316        if self.inner_texture.is_none() {
317            self.inner_texture = Some(InnerTexture::from_wgpu(
318                new_size,
319                self.mag_sampler,
320                self.min_sampler,
321                format,
322                wgpu,
323            ));
324            self.size = new_size;
325            return;
326        }
327
328        self.inner_texture
329            .as_mut()
330            .unwrap()
331            .resize(new_size, wgpu, format);
332        self.needs_update = true;
333    }
334
335    /// Gets the current size of the texture
336    pub fn get_size(&self) -> Vec2<u32> {
337        self.size
338    }
339
340    pub(crate) fn get_sampler(&self) -> &wgpu::Sampler {
341        &self.inner_texture.as_ref().unwrap().sampler
342    }
343
344    pub(crate) fn get_sampler_info(&self) -> (SamplerType, SamplerType) {
345        (self.mag_sampler, self.min_sampler)
346    }
347
348    pub(crate) fn make_render_view<'a>(
349        &'a mut self,
350        wgpu: &WgpuClump,
351        format: wgpu::TextureFormat,
352    ) -> &'a wgpu::TextureView {
353        if self.inner_texture.is_none() {
354            self.inner_texture = Some(InnerTexture::from_wgpu(
355                self.size,
356                self.mag_sampler,
357                self.min_sampler,
358                format,
359                wgpu,
360            ));
361        }
362
363        self.inner_texture.as_mut().unwrap().make_render_view()
364    }
365
366    pub(crate) fn make_view(
367        &mut self,
368        wgpu: &WgpuClump,
369        format: wgpu::TextureFormat,
370    ) -> wgpu::TextureView {
371        if self.inner_texture.is_none() {
372            self.inner_texture = Some(InnerTexture::from_wgpu(
373                self.size,
374                self.mag_sampler,
375                self.min_sampler,
376                format,
377                wgpu,
378            ));
379        }
380
381        self.inner_texture.as_ref().unwrap().make_view()
382    }
383
384    pub(crate) fn updated(&mut self) {
385        self.needs_update = false;
386    }
387
388    pub(crate) fn needs_update(&self) -> bool {
389        self.needs_update
390    }
391}
392
393struct InnerTexture {
394    inner_texture: wgpu::Texture,
395    view: wgpu::TextureView,
396    sampler: wgpu::Sampler,
397}
398
399impl InnerTexture {
400    fn from_wgpu(
401        size: Vec2<u32>,
402        mag_sampler: SamplerType,
403        min_sampler: SamplerType,
404        format: wgpu::TextureFormat,
405        wgpu: &WgpuClump,
406    ) -> Self {
407        let sampler = wgpu.device.create_sampler(&wgpu::SamplerDescriptor {
408            label: Some("Uniform Texture Sampler"),
409            address_mode_u: wgpu::AddressMode::Repeat,
410            address_mode_v: wgpu::AddressMode::Repeat,
411            address_mode_w: wgpu::AddressMode::ClampToEdge,
412            mag_filter: mag_sampler.into(),
413            min_filter: min_sampler.into(),
414            mipmap_filter: wgpu::FilterMode::Nearest,
415            ..Default::default()
416        });
417
418        let inner_texture = wgpu.device.create_texture(&wgpu::TextureDescriptor {
419            label: Some("Uniform Texture"),
420            size: wgpu::Extent3d {
421                width: size.x,
422                height: size.y,
423                depth_or_array_layers: 1,
424            },
425            dimension: wgpu::TextureDimension::D2,
426            mip_level_count: 1,
427            sample_count: 1,
428            format,
429            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
430            view_formats: &[],
431        });
432
433        let view = inner_texture.create_view(&wgpu::TextureViewDescriptor::default());
434
435        Self {
436            inner_texture,
437            view,
438            sampler,
439        }
440    }
441
442    fn resize(&mut self, new_size: Vec2<u32>, wgpu: &WgpuClump, format: wgpu::TextureFormat) {
443        let inner_texture = wgpu.device.create_texture(&wgpu::TextureDescriptor {
444            label: Some("Uniform Texture"),
445            size: wgpu::Extent3d {
446                width: new_size.x,
447                height: new_size.y,
448                depth_or_array_layers: 1,
449            },
450            dimension: wgpu::TextureDimension::D2,
451            mip_level_count: 1,
452            sample_count: 1,
453            format,
454            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
455            view_formats: &[],
456        });
457
458        let new_view = inner_texture.create_view(&wgpu::TextureViewDescriptor {
459            label: Some("Uniform Texture View"),
460            ..Default::default()
461        });
462
463        self.inner_texture = inner_texture;
464        self.view = new_view;
465    }
466
467    pub(crate) fn make_view(&self) -> wgpu::TextureView {
468        self.inner_texture
469            .create_view(&wgpu::TextureViewDescriptor {
470                label: Some("Uniform Texture View"),
471                ..Default::default()
472            })
473    }
474
475    pub(crate) fn make_render_view(&mut self) -> &wgpu::TextureView {
476        self.view = self
477            .inner_texture
478            .create_view(&wgpu::TextureViewDescriptor {
479                label: Some("Uniform Texture View"),
480                ..Default::default()
481            });
482
483        &self.view
484    }
485}
486
487impl std::error::Error for TextureError {}
488
489impl Display for TextureError {
490    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491        match self {
492            Self::IoError(e) => write!(f, "{}", e),
493            Self::ImageError(e) => write!(f, "{}", e),
494        }
495    }
496}
497
498/// The diffrent types of sampling modes
499#[derive(Clone, Copy, Debug, PartialEq, Eq)]
500pub enum SamplerType {
501    /// Nearest Neighbor sampling
502    ///
503    /// This creates a pixelated look when used in upscaling best
504    /// for pixel art games
505    NearestNeighbor,
506    /// Linear Interpolation sampling
507    ///
508    /// Creates a smoother blury look when used in upscaling
509    LinearInterpolation,
510}
511
512impl From<SamplerType> for wgpu::FilterMode {
513    fn from(value: SamplerType) -> Self {
514        match value {
515            SamplerType::LinearInterpolation => wgpu::FilterMode::Linear,
516            SamplerType::NearestNeighbor => wgpu::FilterMode::Nearest,
517        }
518    }
519}