use super::decode_rgba_image_file;
use std::sync::Arc;
const SAMPLER_LINEAR_REPEAT: wgpu::SamplerDescriptor<'static> = wgpu::SamplerDescriptor {
label: Some("linear_repeat_sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest, lod_min_clamp: 0.0,
lod_max_clamp: 0.0,
compare: None,
anisotropy_clamp: 1,
border_color: None,
};
const SAMPLER_NEAREST_REPEAT: wgpu::SamplerDescriptor<'static> = wgpu::SamplerDescriptor {
label: Some("nearest_repeat_sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
lod_min_clamp: 0.0,
lod_max_clamp: 0.0,
compare: None,
anisotropy_clamp: 1,
border_color: None,
};
impl super::AssetManager {
fn resolve_texture_cache_key(&self, path_or_uuid: &str) -> Result<(String, String), String> {
let resolved = self.resolve_path_from_meta_source(path_or_uuid)?;
let cache_key = self
.get_uuid(&resolved)
.map(|id| id.to_string())
.unwrap_or_else(|| resolved.clone());
Ok((resolved, cache_key))
}
pub fn install_decoded_material_texture(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
cache_key: &str,
rgba: &[u8],
width: u32,
height: u32,
) -> Result<Arc<wgpu::BindGroup>, String> {
if width == 0 || height == 0 {
return Err(format!(
"Cannot create texture with zero dimension: {width}×{height} (key={cache_key})"
));
}
let expected = (width as usize)
.saturating_mul(height as usize)
.saturating_mul(4);
if rgba.len() != expected {
return Err(format!(
"RGBA size mismatch for '{cache_key}': got {} bytes, expected {expected} \
({width}×{height}×4)",
rgba.len()
));
}
let texture_size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
size: texture_size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: Some(cache_key),
view_formats: &[],
});
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
texture_size,
);
let bg = self.build_bind_group(device, &texture, layout, &SAMPLER_LINEAR_REPEAT, cache_key);
self.texture_cache.insert(cache_key.to_string(), bg.clone());
Ok(bg)
}
pub fn load_material_texture(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
path_or_uuid: &str,
) -> Result<Arc<wgpu::BindGroup>, String> {
let (resolved_path, cache_key) = self.resolve_texture_cache_key(path_or_uuid)?;
if let Some(cached) = self.texture_cache.get(&cache_key) {
return Ok(cached.clone());
}
let (rgba, w, h) = self.decode_texture_rgba(&resolved_path)?;
self.install_decoded_material_texture(device, queue, layout, &cache_key, &rgba, w, h)
}
pub fn reload_material_texture(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
path_or_uuid: &str,
) -> Result<Arc<wgpu::BindGroup>, String> {
let (_, cache_key) = self.resolve_texture_cache_key(path_or_uuid)?;
self.texture_cache.remove(&cache_key);
self.load_material_texture(device, queue, layout, path_or_uuid)
}
pub fn create_white_texture(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
) -> Arc<wgpu::BindGroup> {
const KEY: &str = "__white_fallback_texture__";
if let Some(cached) = self.texture_cache.get(KEY) {
return cached.clone();
}
let bg = self.upload_solid_1x1(device, queue, layout, [255, 255, 255, 255], KEY);
self.texture_cache.insert(KEY.to_string(), bg.clone());
bg
}
pub fn create_checkerboard_texture(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
) -> Arc<wgpu::BindGroup> {
const KEY: &str = "__checkerboard_texture__";
const SIZE: u32 = 256;
const CELL: u32 = 32;
if let Some(cached) = self.texture_cache.get(KEY) {
return cached.clone();
}
let mut pixels = vec![0u8; (SIZE * SIZE * 4) as usize];
for y in 0..SIZE {
for x in 0..SIZE {
let light = ((x / CELL) + (y / CELL)).is_multiple_of(2);
let luma = if light { 200u8 } else { 50u8 };
let base = ((y * SIZE + x) * 4) as usize;
pixels[base] = luma;
pixels[base + 1] = luma;
pixels[base + 2] = luma;
pixels[base + 3] = 255;
}
}
self.install_decoded_material_texture(device, queue, layout, KEY, &pixels, SIZE, SIZE)
.expect("checkerboard texture creation must not fail")
}
fn decode_texture_rgba(&self, resolved_path: &str) -> Result<(Vec<u8>, u32, u32), String> {
if let Some(data) = self.embedded_assets.get(resolved_path) {
let img = image::load_from_memory(data)
.map_err(|e| format!("Embedded texture decode failed ({resolved_path}): {e}"))?
.to_rgba8();
let (w, h) = img.dimensions();
return Ok((img.into_raw(), w, h));
}
decode_rgba_image_file(resolved_path)
}
fn upload_solid_1x1(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
pixel: [u8; 4],
label: &str,
) -> Arc<wgpu::BindGroup> {
let size = wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: Some(label),
view_formats: &[],
});
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&pixel,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4),
rows_per_image: Some(1),
},
size,
);
self.build_bind_group(device, &texture, layout, &SAMPLER_NEAREST_REPEAT, label)
}
fn build_bind_group(
&self,
device: &wgpu::Device,
texture: &wgpu::Texture,
layout: &wgpu::BindGroupLayout,
sampler_desc: &wgpu::SamplerDescriptor,
label: &str,
) -> Arc<wgpu::BindGroup> {
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(sampler_desc);
Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
}))
}
}