use super::decode_rgba_image_file;
use super::error::AssetError;
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::MipmapFilterMode::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), AssetError> {
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>, AssetError> {
if width == 0 || height == 0 {
return Err(AssetError::ZeroDimensionTexture {
cache_key: cache_key.to_string(),
width,
height,
});
}
let expected = (width as usize)
.saturating_mul(height as usize)
.saturating_mul(4);
if rgba.len() != expected {
return Err(AssetError::RgbaSizeMismatch {
cache_key: cache_key.to_string(),
got: rgba.len(),
expected,
width,
height,
});
}
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::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
texture_size,
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&SAMPLER_LINEAR_REPEAT);
let bg = self
.assemble_single_texture_bind_group(device, queue, layout, &view, &sampler, 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>, AssetError> {
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>, AssetError> {
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();
}
self.ensure_material_defaults(device, queue);
let d = self
.material_defaults()
.expect("material defaults ensured above");
let bg = Self::assemble_material_bind_group(
device,
layout,
&d.white_view,
&d.sampler,
&d.flat_normal_view,
&d.white_view,
&d.white_view,
&d.white_view,
&d.params_buffer,
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")
}
pub fn create_uv_debug_texture(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
) -> Arc<wgpu::BindGroup> {
const KEY: &str = "__uv_debug_texture__";
const TEXTURE_SIZE: usize = 8;
if let Some(cached) = self.texture_cache.get(KEY) {
return cached.clone();
}
let mut palette: [u8; 32] = [
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
];
let mut texture_data = [0u8; TEXTURE_SIZE * TEXTURE_SIZE * 4];
for y in 0..TEXTURE_SIZE {
let offset = TEXTURE_SIZE * y * 4;
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
palette.rotate_right(4);
}
self.install_decoded_material_texture(
device,
queue,
layout,
KEY,
&texture_data,
TEXTURE_SIZE as u32,
TEXTURE_SIZE as u32,
)
.expect("uv debug texture creation must not fail")
}
fn decode_texture_rgba(&self, resolved_path: &str) -> Result<(Vec<u8>, u32, u32), AssetError> {
if let Some(data) = self.embedded_assets.get(resolved_path) {
let img = image::load_from_memory(data)
.map_err(|source| AssetError::ImageDecode {
path: std::path::PathBuf::from(resolved_path),
source,
})?
.to_rgba8();
let (w, h) = img.dimensions();
return Ok((img.into_raw(), w, h));
}
decode_rgba_image_file(resolved_path)
}
}