Skip to main content

engvis_renderer/
texture_cache.rs

1
2pub struct TextureCache {
3    pub textures: Vec<wgpu::Texture>,
4    pub views: Vec<wgpu::TextureView>,
5    pub default_white_view: wgpu::TextureView,
6    pub default_normal_view: wgpu::TextureView,
7    pub sampler: wgpu::Sampler,
8    _default_white: wgpu::Texture,
9    _default_normal: wgpu::Texture,
10}
11
12impl TextureCache {
13    pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
14        // Default white 1x1 texture (for albedo when no texture)
15        let default_white = device.create_texture(&wgpu::TextureDescriptor {
16            label: Some("Default White Texture"),
17            size: wgpu::Extent3d {
18                width: 1,
19                height: 1,
20                depth_or_array_layers: 1,
21            },
22            mip_level_count: 1,
23            sample_count: 1,
24            dimension: wgpu::TextureDimension::D2,
25            format: wgpu::TextureFormat::Rgba8UnormSrgb,
26            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
27            view_formats: &[],
28        });
29        queue.write_texture(
30            wgpu::TexelCopyTextureInfo {
31                texture: &default_white,
32                mip_level: 0,
33                origin: wgpu::Origin3d::ZERO,
34                aspect: wgpu::TextureAspect::All,
35            },
36            &[255, 255, 255, 255],
37            wgpu::TexelCopyBufferLayout {
38                offset: 0,
39                bytes_per_row: Some(4),
40                rows_per_image: Some(1),
41            },
42            wgpu::Extent3d {
43                width: 1,
44                height: 1,
45                depth_or_array_layers: 1,
46            },
47        );
48        let default_white_view = default_white.create_view(&wgpu::TextureViewDescriptor::default());
49
50        // Default normal 1x1 texture (flat normal pointing up: 128, 128, 255)
51        let default_normal = device.create_texture(&wgpu::TextureDescriptor {
52            label: Some("Default Normal Texture"),
53            size: wgpu::Extent3d {
54                width: 1,
55                height: 1,
56                depth_or_array_layers: 1,
57            },
58            mip_level_count: 1,
59            sample_count: 1,
60            dimension: wgpu::TextureDimension::D2,
61            format: wgpu::TextureFormat::Rgba8Unorm,
62            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
63            view_formats: &[],
64        });
65        queue.write_texture(
66            wgpu::TexelCopyTextureInfo {
67                texture: &default_normal,
68                mip_level: 0,
69                origin: wgpu::Origin3d::ZERO,
70                aspect: wgpu::TextureAspect::All,
71            },
72            &[128, 128, 255, 255],
73            wgpu::TexelCopyBufferLayout {
74                offset: 0,
75                bytes_per_row: Some(4),
76                rows_per_image: Some(1),
77            },
78            wgpu::Extent3d {
79                width: 1,
80                height: 1,
81                depth_or_array_layers: 1,
82            },
83        );
84        let default_normal_view =
85            default_normal.create_view(&wgpu::TextureViewDescriptor::default());
86
87        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
88            label: Some("Material Sampler"),
89            address_mode_u: wgpu::AddressMode::Repeat,
90            address_mode_v: wgpu::AddressMode::Repeat,
91            address_mode_w: wgpu::AddressMode::Repeat,
92            mag_filter: wgpu::FilterMode::Linear,
93            min_filter: wgpu::FilterMode::Linear,
94            mipmap_filter: wgpu::FilterMode::Linear,
95            lod_min_clamp: 0.0,
96            lod_max_clamp: 100.0,
97            compare: None,
98            anisotropy_clamp: 16,
99            border_color: None,
100        });
101
102        Self {
103            textures: Vec::new(),
104            views: Vec::new(),
105            default_white_view,
106            default_normal_view,
107            sampler,
108            _default_white: default_white,
109            _default_normal: default_normal,
110        }
111    }
112
113    /// Upload an image to the GPU, returns texture index
114    pub fn upload_image(
115        &mut self,
116        device: &wgpu::Device,
117        queue: &wgpu::Queue,
118        image: &image::RgbaImage,
119        label: &str,
120        srgb: bool,
121    ) -> usize {
122        let (width, height) = image.dimensions();
123        let format = if srgb {
124            wgpu::TextureFormat::Rgba8UnormSrgb
125        } else {
126            wgpu::TextureFormat::Rgba8Unorm
127        };
128
129        let texture = device.create_texture(&wgpu::TextureDescriptor {
130            label: Some(label),
131            size: wgpu::Extent3d {
132                width,
133                height,
134                depth_or_array_layers: 1,
135            },
136            mip_level_count: 1,
137            sample_count: 1,
138            dimension: wgpu::TextureDimension::D2,
139            format,
140            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
141            view_formats: &[],
142        });
143
144        queue.write_texture(
145            wgpu::TexelCopyTextureInfo {
146                texture: &texture,
147                mip_level: 0,
148                origin: wgpu::Origin3d::ZERO,
149                aspect: wgpu::TextureAspect::All,
150            },
151            image.as_raw(),
152            wgpu::TexelCopyBufferLayout {
153                offset: 0,
154                bytes_per_row: Some(width * 4),
155                rows_per_image: Some(height),
156            },
157            wgpu::Extent3d {
158                width,
159                height,
160                depth_or_array_layers: 1,
161            },
162        );
163
164        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
165        let index = self.textures.len();
166        self.textures.push(texture);
167        self.views.push(view);
168        index
169    }
170
171    /// Get texture view by index, or default white texture if index is None
172    pub fn get_view(&self, index: Option<usize>) -> &wgpu::TextureView {
173        match index {
174            Some(i) if i < self.views.len() => &self.views[i],
175            _ => &self.default_white_view,
176        }
177    }
178
179    /// Get normal texture view by index, or default normal if None
180    pub fn get_normal_view(&self, index: Option<usize>) -> &wgpu::TextureView {
181        match index {
182            Some(i) if i < self.views.len() => &self.views[i],
183            _ => &self.default_normal_view,
184        }
185    }
186}