Skip to main content

care_game/graphics/
texture.rs

1use std::{fmt::Debug, io::Cursor, path::Path, sync::Arc};
2
3use image::{DynamicImage, EncodableLayout, ImageFormat, ImageReader, RgbaImage};
4
5use crate::math::{Vec2, Vec4};
6
7use super::GRAPHICS_STATE;
8
9#[derive(Debug, Clone)]
10/// A high-level object to wrap textures
11pub struct Texture(pub(crate) Arc<TextureHandle>);
12
13impl PartialEq for Texture {
14    fn eq(&self, other: &Self) -> bool {
15        Arc::ptr_eq(&self.0, &other.0)
16    }
17}
18
19impl Texture {
20    /// Create a new texture by loading an image from the filesystem
21    pub fn new(filename: impl AsRef<Path>) -> Self {
22        Self::new_from_image(ImageReader::open(filename).unwrap().decode().unwrap())
23    }
24    /// Creates a new texture by loading an image from encoded image data of an optionally specified format.
25    pub fn new_from_file_format(file_data: &[u8], format_hint: Option<ImageFormat>) -> Self {
26        let mut image = ImageReader::new(Cursor::new(file_data))
27            .with_guessed_format()
28            .unwrap();
29        if let Some(fmt) = format_hint {
30            image.set_format(fmt);
31        }
32        Self::new_from_image(image.decode().unwrap())
33    }
34    /// Create a new texture by filling it up in a single colour
35    pub fn new_fill(width: u32, height: u32, colour: impl Into<Vec4>) -> Self {
36        let c = colour.into() * 255.9;
37        Self::new_from_data(
38            width,
39            height,
40            (0..width * height)
41                .flat_map(|_| [c.x() as u8, c.y() as u8, c.z() as u8, c.w() as u8])
42                .collect::<Vec<_>>()
43                .as_slice(),
44        )
45    }
46    /// Create a new texture out of an image from the image crate
47    pub fn new_from_image(img: DynamicImage) -> Self {
48        Self::new_from_data(img.width(), img.height(), img.to_rgba8().as_bytes())
49    }
50    /// Create a new texture out of a size and raw data
51    pub fn new_from_data(width: u32, height: u32, data: &[u8]) -> Self {
52        let size = wgpu::Extent3d {
53            width,
54            height,
55            depth_or_array_layers: 1,
56        };
57        let texture = GRAPHICS_STATE.device.create_texture(&wgpu::TextureDescriptor {
58            label: None,
59            size,
60            mip_level_count: 1,
61            sample_count: 1,
62            dimension: wgpu::TextureDimension::D2,
63            format: wgpu::TextureFormat::Rgba8Unorm,
64            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
65            view_formats: &[],
66        });
67        GRAPHICS_STATE.queue.write_texture(
68            wgpu::ImageCopyTexture {
69                texture: &texture,
70                mip_level: 0,
71                origin: wgpu::Origin3d::ZERO,
72                aspect: wgpu::TextureAspect::All,
73            },
74            data,
75            wgpu::ImageDataLayout {
76                offset: 0,
77                bytes_per_row: Some(4 * width),
78                rows_per_image: Some(height),
79            },
80            size,
81        );
82        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
83        let sampler = GRAPHICS_STATE.device.create_sampler(&wgpu::SamplerDescriptor {
84            address_mode_u: wgpu::AddressMode::ClampToEdge,
85            address_mode_v: wgpu::AddressMode::ClampToEdge,
86            address_mode_w: wgpu::AddressMode::ClampToEdge,
87            mag_filter: wgpu::FilterMode::Nearest,
88            min_filter: wgpu::FilterMode::Nearest,
89            mipmap_filter: wgpu::FilterMode::Nearest,
90            ..Default::default()
91        });
92        Texture(Arc::new(TextureHandle {
93            size: Vec2::new(width, height),
94            texture: Arc::new(texture),
95            view,
96            sampler,
97        }))
98    }
99    pub(crate) fn new_from_wgpu(texture: Arc<wgpu::Texture>) -> Self {
100        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
101        let sampler = GRAPHICS_STATE.device.create_sampler(&wgpu::SamplerDescriptor {
102            address_mode_u: wgpu::AddressMode::ClampToEdge,
103            address_mode_v: wgpu::AddressMode::ClampToEdge,
104            address_mode_w: wgpu::AddressMode::ClampToEdge,
105            mag_filter: wgpu::FilterMode::Nearest,
106            min_filter: wgpu::FilterMode::Nearest,
107            mipmap_filter: wgpu::FilterMode::Nearest,
108            ..Default::default()
109        });
110        Texture(Arc::new(TextureHandle {
111            size: Vec2::new(texture.width(), texture.height()),
112            texture,
113            view,
114            sampler,
115        }))
116    }
117    /// Upload data to a specific region of the texture
118    pub fn upload_region(&self, data: &[u8], x: u32, y: u32, width: u32, height: u32) {
119        GRAPHICS_STATE.queue.write_texture(
120            wgpu::ImageCopyTexture {
121                texture: &self.0.texture,
122                mip_level: 0,
123                origin: wgpu::Origin3d { x, y, z: 0 },
124                aspect: wgpu::TextureAspect::All,
125            },
126            data,
127            wgpu::ImageDataLayout {
128                offset: 0,
129                bytes_per_row: Some(4 * width),
130                rows_per_image: Some(height),
131            },
132            wgpu::Extent3d {
133                width,
134                height,
135                depth_or_array_layers: 1,
136            },
137        );
138    }
139    /// Upload an image to a specific region of the image
140    pub fn upload_image_region(&self, image: RgbaImage, x: u32, y: u32) {
141        let (width, height) = (image.width(), image.height());
142        self.upload_region(image.as_bytes(), x, y, width, height);
143    }
144    /// Get the size of the texture
145    pub fn size(&self) -> Vec2 {
146        self.0.size
147    }
148}
149
150#[derive(Debug)]
151pub(crate) struct TextureHandle {
152    pub(crate) size: Vec2,
153    pub(crate) texture: Arc<wgpu::Texture>,
154    pub(crate) view: wgpu::TextureView,
155    pub(crate) sampler: wgpu::Sampler,
156}
157
158impl TextureHandle {
159    pub(crate) fn bind_group_entries(&self, i: u32) -> [wgpu::BindGroupEntry<'_>; 2] {
160        [
161            wgpu::BindGroupEntry {
162                binding: i * 2,
163                resource: wgpu::BindingResource::TextureView(&self.view),
164            },
165            wgpu::BindGroupEntry {
166                binding: i * 2 + 1,
167                resource: wgpu::BindingResource::Sampler(&self.sampler),
168            },
169        ]
170    }
171}