1use super::decode_rgba_image_file;
2use std::sync::Arc;
3
4const SAMPLER_LINEAR_REPEAT: wgpu::SamplerDescriptor<'static> = wgpu::SamplerDescriptor {
12 label: Some("linear_repeat_sampler"),
13 address_mode_u: wgpu::AddressMode::Repeat,
14 address_mode_v: wgpu::AddressMode::Repeat,
15 address_mode_w: wgpu::AddressMode::Repeat,
16 mag_filter: wgpu::FilterMode::Linear,
17 min_filter: wgpu::FilterMode::Linear,
18 mipmap_filter: wgpu::FilterMode::Nearest, lod_min_clamp: 0.0,
20 lod_max_clamp: 0.0,
21 compare: None,
22 anisotropy_clamp: 1,
23 border_color: None,
24};
25
26const SAMPLER_NEAREST_REPEAT: wgpu::SamplerDescriptor<'static> = wgpu::SamplerDescriptor {
28 label: Some("nearest_repeat_sampler"),
29 address_mode_u: wgpu::AddressMode::Repeat,
30 address_mode_v: wgpu::AddressMode::Repeat,
31 address_mode_w: wgpu::AddressMode::Repeat,
32 mag_filter: wgpu::FilterMode::Nearest,
33 min_filter: wgpu::FilterMode::Nearest,
34 mipmap_filter: wgpu::FilterMode::Nearest,
35 lod_min_clamp: 0.0,
36 lod_max_clamp: 0.0,
37 compare: None,
38 anisotropy_clamp: 1,
39 border_color: None,
40};
41
42impl super::AssetManager {
47 fn resolve_texture_cache_key(&self, path_or_uuid: &str) -> Result<(String, String), String> {
56 let resolved = self.resolve_path_from_meta_source(path_or_uuid)?;
57 let cache_key = self
58 .get_uuid(&resolved)
59 .map(|id| id.to_string())
60 .unwrap_or_else(|| resolved.clone());
61 Ok((resolved, cache_key))
62 }
63
64 pub fn install_decoded_material_texture(
76 &mut self,
77 device: &wgpu::Device,
78 queue: &wgpu::Queue,
79 layout: &wgpu::BindGroupLayout,
80 cache_key: &str,
81 rgba: &[u8],
82 width: u32,
83 height: u32,
84 ) -> Result<Arc<wgpu::BindGroup>, String> {
85 if width == 0 || height == 0 {
87 return Err(format!(
88 "Cannot create texture with zero dimension: {width}×{height} (key={cache_key})"
89 ));
90 }
91
92 let expected = (width as usize)
93 .saturating_mul(height as usize)
94 .saturating_mul(4);
95
96 if rgba.len() != expected {
97 return Err(format!(
98 "RGBA size mismatch for '{cache_key}': got {} bytes, expected {expected} \
99 ({width}×{height}×4)",
100 rgba.len()
101 ));
102 }
103
104 let texture_size = wgpu::Extent3d {
105 width,
106 height,
107 depth_or_array_layers: 1,
108 };
109
110 let texture = device.create_texture(&wgpu::TextureDescriptor {
111 size: texture_size,
112 mip_level_count: 1,
113 sample_count: 1,
114 dimension: wgpu::TextureDimension::D2,
115 format: wgpu::TextureFormat::Rgba8UnormSrgb,
116 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
117 label: Some(cache_key),
118 view_formats: &[],
119 });
120
121 queue.write_texture(
122 wgpu::ImageCopyTexture {
123 texture: &texture,
124 mip_level: 0,
125 origin: wgpu::Origin3d::ZERO,
126 aspect: wgpu::TextureAspect::All,
127 },
128 rgba,
129 wgpu::ImageDataLayout {
130 offset: 0,
131 bytes_per_row: Some(4 * width),
132 rows_per_image: Some(height),
133 },
134 texture_size,
135 );
136
137 let bg = self.build_bind_group(device, &texture, layout, &SAMPLER_LINEAR_REPEAT, cache_key);
138 self.texture_cache.insert(cache_key.to_string(), bg.clone());
139 Ok(bg)
140 }
141
142 pub fn load_material_texture(
151 &mut self,
152 device: &wgpu::Device,
153 queue: &wgpu::Queue,
154 layout: &wgpu::BindGroupLayout,
155 path_or_uuid: &str,
156 ) -> Result<Arc<wgpu::BindGroup>, String> {
157 let (resolved_path, cache_key) = self.resolve_texture_cache_key(path_or_uuid)?;
158
159 if let Some(cached) = self.texture_cache.get(&cache_key) {
160 return Ok(cached.clone());
161 }
162
163 let (rgba, w, h) = self.decode_texture_rgba(&resolved_path)?;
164 self.install_decoded_material_texture(device, queue, layout, &cache_key, &rgba, w, h)
165 }
166
167 pub fn reload_material_texture(
171 &mut self,
172 device: &wgpu::Device,
173 queue: &wgpu::Queue,
174 layout: &wgpu::BindGroupLayout,
175 path_or_uuid: &str,
176 ) -> Result<Arc<wgpu::BindGroup>, String> {
177 let (_, cache_key) = self.resolve_texture_cache_key(path_or_uuid)?;
179 self.texture_cache.remove(&cache_key);
180 self.load_material_texture(device, queue, layout, path_or_uuid)
181 }
182
183 pub fn create_white_texture(
189 &mut self,
190 device: &wgpu::Device,
191 queue: &wgpu::Queue,
192 layout: &wgpu::BindGroupLayout,
193 ) -> Arc<wgpu::BindGroup> {
194 const KEY: &str = "__white_fallback_texture__";
195
196 if let Some(cached) = self.texture_cache.get(KEY) {
197 return cached.clone();
198 }
199
200 let bg = self.upload_solid_1x1(device, queue, layout, [255, 255, 255, 255], KEY);
201 self.texture_cache.insert(KEY.to_string(), bg.clone());
202 bg
203 }
204
205 pub fn create_checkerboard_texture(
210 &mut self,
211 device: &wgpu::Device,
212 queue: &wgpu::Queue,
213 layout: &wgpu::BindGroupLayout,
214 ) -> Arc<wgpu::BindGroup> {
215 const KEY: &str = "__checkerboard_texture__";
216 const SIZE: u32 = 256;
217 const CELL: u32 = 32; if let Some(cached) = self.texture_cache.get(KEY) {
220 return cached.clone();
221 }
222
223 let mut pixels = vec![0u8; (SIZE * SIZE * 4) as usize];
224 for y in 0..SIZE {
225 for x in 0..SIZE {
226 let light = ((x / CELL) + (y / CELL)).is_multiple_of(2);
227 let luma = if light { 200u8 } else { 50u8 };
228 let base = ((y * SIZE + x) * 4) as usize;
229 pixels[base] = luma;
230 pixels[base + 1] = luma;
231 pixels[base + 2] = luma;
232 pixels[base + 3] = 255;
233 }
234 }
235
236 self.install_decoded_material_texture(device, queue, layout, KEY, &pixels, SIZE, SIZE)
239 .expect("checkerboard texture creation must not fail")
240 }
241
242 pub fn create_uv_debug_texture(
243 &mut self,
244 device: &wgpu::Device,
245 queue: &wgpu::Queue,
246 layout: &wgpu::BindGroupLayout,
247 ) -> Arc<wgpu::BindGroup> {
248 const KEY: &str = "__uv_debug_texture__";
249 const TEXTURE_SIZE: usize = 8;
250
251 if let Some(cached) = self.texture_cache.get(KEY) {
252 return cached.clone();
253 }
254
255 let mut palette: [u8; 32] = [
256 255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
257 198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
258 ];
259
260 let mut texture_data = [0u8; TEXTURE_SIZE * TEXTURE_SIZE * 4];
261 for y in 0..TEXTURE_SIZE {
262 let offset = TEXTURE_SIZE * y * 4;
263 texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
264 palette.rotate_right(4);
265 }
266
267 self.install_decoded_material_texture(
268 device,
269 queue,
270 layout,
271 KEY,
272 &texture_data,
273 TEXTURE_SIZE as u32,
274 TEXTURE_SIZE as u32,
275 )
276 .expect("uv debug texture creation must not fail")
277 }
278
279 fn decode_texture_rgba(&self, resolved_path: &str) -> Result<(Vec<u8>, u32, u32), String> {
283 if let Some(data) = self.embedded_assets.get(resolved_path) {
284 let img = image::load_from_memory(data)
285 .map_err(|e| format!("Embedded texture decode failed ({resolved_path}): {e}"))?
286 .to_rgba8();
287 let (w, h) = img.dimensions();
288 return Ok((img.into_raw(), w, h));
289 }
290
291 decode_rgba_image_file(resolved_path)
292 }
293
294 fn upload_solid_1x1(
299 &self,
300 device: &wgpu::Device,
301 queue: &wgpu::Queue,
302 layout: &wgpu::BindGroupLayout,
303 pixel: [u8; 4],
304 label: &str,
305 ) -> Arc<wgpu::BindGroup> {
306 let size = wgpu::Extent3d {
307 width: 1,
308 height: 1,
309 depth_or_array_layers: 1,
310 };
311
312 let texture = device.create_texture(&wgpu::TextureDescriptor {
313 size,
314 mip_level_count: 1,
315 sample_count: 1,
316 dimension: wgpu::TextureDimension::D2,
317 format: wgpu::TextureFormat::Rgba8UnormSrgb,
318 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
319 label: Some(label),
320 view_formats: &[],
321 });
322
323 queue.write_texture(
324 wgpu::ImageCopyTexture {
325 texture: &texture,
326 mip_level: 0,
327 origin: wgpu::Origin3d::ZERO,
328 aspect: wgpu::TextureAspect::All,
329 },
330 &pixel,
331 wgpu::ImageDataLayout {
332 offset: 0,
333 bytes_per_row: Some(4),
334 rows_per_image: Some(1),
335 },
336 size,
337 );
338
339 self.build_bind_group(device, &texture, layout, &SAMPLER_NEAREST_REPEAT, label)
340 }
341
342 fn build_bind_group(
347 &self,
348 device: &wgpu::Device,
349 texture: &wgpu::Texture,
350 layout: &wgpu::BindGroupLayout,
351 sampler_desc: &wgpu::SamplerDescriptor,
352 label: &str,
353 ) -> Arc<wgpu::BindGroup> {
354 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
355 let sampler = device.create_sampler(sampler_desc);
356
357 Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
358 label: Some(label),
359 layout,
360 entries: &[
361 wgpu::BindGroupEntry {
362 binding: 0,
363 resource: wgpu::BindingResource::TextureView(&view),
364 },
365 wgpu::BindGroupEntry {
366 binding: 1,
367 resource: wgpu::BindingResource::Sampler(&sampler),
368 },
369 ],
370 }))
371 }
372}