use crate::render::wgpu::texture_cache::{SamplerWrap, TextureUsage};
use std::collections::HashMap;
const DEFAULT_LAYER_SIZE: u32 = 1024;
const DEFAULT_MAX_LAYERS: u32 = 256;
pub const NO_TEXTURE_LAYER: u32 = 0xFFFFu32;
fn wrap_code(wrap: SamplerWrap) -> u32 {
match wrap {
SamplerWrap::Repeat => 0,
SamplerWrap::MirroredRepeat => 1,
SamplerWrap::ClampToEdge => 2,
}
}
pub fn pack_layer(layer: u32, wrap_u: SamplerWrap, wrap_v: SamplerWrap) -> u32 {
(layer & 0xFFFFu32) | (wrap_code(wrap_u) << 16) | (wrap_code(wrap_v) << 18)
}
#[derive(Copy, Clone, Debug)]
pub struct MaterialTextureLayer {
pub usage: TextureUsage,
pub layer: u32,
pub wrap_u: SamplerWrap,
pub wrap_v: SamplerWrap,
}
impl MaterialTextureLayer {
pub fn packed(&self) -> u32 {
pack_layer(self.layer, self.wrap_u, self.wrap_v)
}
}
pub struct MaterialTextureUpload<'a> {
pub name: String,
pub rgba_data: &'a [u8],
pub width: u32,
pub height: u32,
pub usage: TextureUsage,
pub wrap_u: SamplerWrap,
pub wrap_v: SamplerWrap,
}
pub const DEFAULT_ANISOTROPY: u16 = 16;
pub struct MaterialTextureArrays {
pub layer_size: u32,
pub max_layers: u32,
pub mip_level_count: u32,
pub srgb_texture: wgpu::Texture,
pub linear_texture: wgpu::Texture,
pub srgb_view: wgpu::TextureView,
pub linear_view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
pub anisotropy_clamp: u16,
pub srgb_next_layer: u32,
pub linear_next_layer: u32,
pub layer_map: HashMap<String, MaterialTextureLayer>,
pub srgb_free_layers: Vec<u32>,
pub linear_free_layers: Vec<u32>,
}
fn build_material_sampler(device: &wgpu::Device, anisotropy_clamp: u16) -> wgpu::Sampler {
let clamped = anisotropy_clamp.clamp(1, 16);
device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Material Texture Array Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
anisotropy_clamp: clamped,
..Default::default()
})
}
impl MaterialTextureArrays {
pub fn new(device: &wgpu::Device) -> Self {
Self::with_size(device, DEFAULT_LAYER_SIZE, DEFAULT_MAX_LAYERS)
}
pub fn with_size(device: &wgpu::Device, layer_size: u32, max_layers: u32) -> Self {
let largest = layer_size.max(1);
let mip_level_count = (largest as f32).log2().floor() as u32 + 1;
let extent = wgpu::Extent3d {
width: layer_size,
height: layer_size,
depth_or_array_layers: max_layers,
};
let srgb_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Material Texture Array (sRGB)"),
size: extent,
mip_level_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let linear_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Material Texture Array (Linear)"),
size: extent,
mip_level_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let srgb_view = srgb_texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2Array),
..Default::default()
});
let linear_view = linear_texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2Array),
..Default::default()
});
let anisotropy_clamp = DEFAULT_ANISOTROPY;
let sampler = build_material_sampler(device, anisotropy_clamp);
Self {
layer_size,
max_layers,
mip_level_count,
srgb_texture,
linear_texture,
srgb_view,
linear_view,
sampler,
anisotropy_clamp,
srgb_next_layer: 0,
linear_next_layer: 0,
layer_map: HashMap::new(),
srgb_free_layers: Vec::new(),
linear_free_layers: Vec::new(),
}
}
pub fn release(&mut self, name: &str) -> Option<MaterialTextureLayer> {
let layer_info = self.layer_map.remove(name)?;
match layer_info.usage {
TextureUsage::Color => self.srgb_free_layers.push(layer_info.layer),
TextureUsage::Linear => self.linear_free_layers.push(layer_info.layer),
}
Some(layer_info)
}
pub fn upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mip_generator: &super::mip_generator::MipGenerator,
request: MaterialTextureUpload<'_>,
) -> Option<u32> {
let MaterialTextureUpload {
name,
rgba_data,
width,
height,
usage,
wrap_u,
wrap_v,
} = request;
if let Some(existing) = self.layer_map.get(&name) {
return Some(existing.layer);
}
let layer = match usage {
TextureUsage::Color => {
if let Some(reused) = self.srgb_free_layers.pop() {
reused
} else if self.srgb_next_layer >= self.max_layers {
tracing::error!("Material sRGB texture array exhausted");
return None;
} else {
let layer = self.srgb_next_layer;
self.srgb_next_layer += 1;
layer
}
}
TextureUsage::Linear => {
if let Some(reused) = self.linear_free_layers.pop() {
reused
} else if self.linear_next_layer >= self.max_layers {
tracing::error!("Material linear texture array exhausted");
return None;
} else {
let layer = self.linear_next_layer;
self.linear_next_layer += 1;
layer
}
}
};
let resampled = resample_to_size(rgba_data, width, height, self.layer_size);
let texture = match usage {
TextureUsage::Color => &self.srgb_texture,
TextureUsage::Linear => &self.linear_texture,
};
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: layer,
},
aspect: wgpu::TextureAspect::All,
},
&resampled,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * self.layer_size),
rows_per_image: Some(self.layer_size),
},
wgpu::Extent3d {
width: self.layer_size,
height: self.layer_size,
depth_or_array_layers: 1,
},
);
if self.mip_level_count > 1 {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Material Texture Array Mip Gen"),
});
mip_generator.generate_mips(device, &mut encoder, texture, layer);
queue.submit(std::iter::once(encoder.finish()));
}
self.layer_map.insert(
name,
MaterialTextureLayer {
usage,
layer,
wrap_u,
wrap_v,
},
);
Some(layer)
}
pub fn get_layer(&self, name: &str) -> Option<MaterialTextureLayer> {
self.layer_map.get(name).copied()
}
pub fn srgb_view(&self) -> &wgpu::TextureView {
&self.srgb_view
}
pub fn linear_view(&self) -> &wgpu::TextureView {
&self.linear_view
}
pub fn sampler(&self) -> &wgpu::Sampler {
&self.sampler
}
pub fn set_anisotropy(&mut self, device: &wgpu::Device, anisotropy_clamp: u16) -> bool {
let clamped = anisotropy_clamp.clamp(1, 16);
if clamped == self.anisotropy_clamp {
return false;
}
self.sampler = build_material_sampler(device, clamped);
self.anisotropy_clamp = clamped;
true
}
}
fn resample_to_size(rgba_data: &[u8], width: u32, height: u32, target: u32) -> Vec<u8> {
if width == target && height == target {
return rgba_data.to_vec();
}
let mut out = vec![0u8; (target * target * 4) as usize];
for y in 0..target {
let sy = ((y as u64) * (height as u64) / (target as u64)) as u32;
for x in 0..target {
let sx = ((x as u64) * (width as u64) / (target as u64)) as u32;
let src_offset = ((sy * width + sx) * 4) as usize;
let dst_offset = ((y * target + x) * 4) as usize;
out[dst_offset..dst_offset + 4].copy_from_slice(&rgba_data[src_offset..src_offset + 4]);
}
}
out
}