use crate::render::prepare::{PreparedMaterialSlot, compute_material_batch_plan};
use super::material_batched::{MaterialBatchedResources, create_batched_material_resources};
use super::material_bindings::{
MaterialTextureBindingMode, create_material_texture_layout_entries,
};
use super::material_mips::{downsample_rgba8_mip, mip_level_extents};
use super::material_uniform::{
MATERIAL_UNIFORM_BYTE_LEN, MATERIAL_UNIFORM_ENTRY_STRIDE, MaterialUniformUpload,
};
pub(super) use super::material_upload::{
MaterialTextureUpload, address_mode, filter_mode, mipmap_filter_mode,
};
mod bind_group;
pub(super) use bind_group::create_material_bind_group;
#[derive(Debug)]
pub(super) enum MaterialResources {
PerMaterial(Vec<MaterialTextureResources>),
Batched(MaterialBatchedResources),
}
#[derive(Debug)]
pub(super) struct MaterialTextureResources {
#[allow(dead_code)]
pub(super) texture_bindings: Vec<MaterialTextureBindingResources>,
#[allow(dead_code)]
pub(super) uniform: wgpu::Buffer,
pub(super) bind_group: wgpu::BindGroup,
pub(super) texture_byte_len: u64,
}
#[derive(Debug)]
pub(super) struct MaterialTextureBindingResources {
#[allow(dead_code)]
texture: wgpu::Texture,
#[allow(dead_code)]
view: wgpu::TextureView,
#[allow(dead_code)]
sampler: wgpu::Sampler,
byte_len: u64,
}
impl MaterialTextureBindingResources {
pub(super) fn from_parts(
texture: wgpu::Texture,
view: wgpu::TextureView,
sampler: wgpu::Sampler,
byte_len: u64,
) -> Self {
Self {
texture,
view,
sampler,
byte_len,
}
}
pub(super) fn byte_len(&self) -> u64 {
self.byte_len
}
pub(super) fn view(&self) -> &wgpu::TextureView {
&self.view
}
pub(super) fn sampler(&self) -> &wgpu::Sampler {
&self.sampler
}
}
pub(super) fn create_material_bind_group_layout(
device: &wgpu::Device,
texture_binding_mode: MaterialTextureBindingMode,
) -> wgpu::BindGroupLayout {
let mut entries = create_material_texture_layout_entries(texture_binding_mode);
entries.push(wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: std::num::NonZeroU64::new(MATERIAL_UNIFORM_BYTE_LEN),
},
count: None,
});
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("scena.material.bind_group_layout"),
entries: &entries,
})
}
pub(super) fn create_material_resources(
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
material_slots: &[PreparedMaterialSlot],
texture_binding_mode: MaterialTextureBindingMode,
) -> MaterialResources {
if texture_binding_mode.supports_batching() {
let plan = compute_material_batch_plan(material_slots);
if plan.batchable && plan.layer_count >= 2 {
return MaterialResources::Batched(create_batched_material_resources(
device,
queue,
layout,
material_slots,
));
}
}
let mut resources = Vec::with_capacity(material_slots.len() + 1);
resources.push(create_material_resource(
device,
queue,
layout,
None,
texture_binding_mode,
));
resources.extend(material_slots.iter().map(|slot| {
create_material_resource(device, queue, layout, Some(slot), texture_binding_mode)
}));
MaterialResources::PerMaterial(resources)
}
pub(super) fn material_texture_byte_len(resources: &MaterialResources) -> u64 {
match resources {
MaterialResources::PerMaterial(slots) => {
slots.iter().map(|slot| slot.texture_byte_len).sum()
}
MaterialResources::Batched(batched) => batched.texture_byte_len,
}
}
pub(super) fn material_texture_count(resources: &MaterialResources) -> u64 {
match resources {
MaterialResources::PerMaterial(slots) => slots.len() as u64,
MaterialResources::Batched(batched) => u64::from(batched.layer_count),
}
}
pub(super) fn material_bind_group_count(resources: &MaterialResources) -> u32 {
match resources {
MaterialResources::PerMaterial(slots) => slots.len() as u32,
MaterialResources::Batched(_) => 1,
}
}
fn create_material_resource(
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
slot: Option<&PreparedMaterialSlot>,
texture_binding_mode: MaterialTextureBindingMode,
) -> MaterialTextureResources {
let material_uniform = MaterialUniformUpload::from_material(
slot.map(|slot| &slot.material),
slot.and_then(|slot| slot.base_color.as_ref())
.and_then(|texture| texture.transform),
)
.with_layer_index(0);
let base_color = create_texture_binding_resource(
device,
queue,
"base_color",
MaterialTextureUpload::from_base_color_texture(
slot.and_then(|slot| slot.base_color.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let normal = create_texture_binding_resource(
device,
queue,
"normal",
MaterialTextureUpload::from_normal_texture(
slot.and_then(|slot| slot.normal.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let metallic_roughness = create_texture_binding_resource(
device,
queue,
"metallic_roughness",
MaterialTextureUpload::from_metallic_roughness_texture(
slot.and_then(|slot| slot.metallic_roughness.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let occlusion = create_texture_binding_resource(
device,
queue,
"occlusion",
MaterialTextureUpload::from_occlusion_texture(
slot.and_then(|slot| slot.occlusion.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let emissive = create_texture_binding_resource(
device,
queue,
"emissive",
MaterialTextureUpload::from_emissive_texture(
slot.and_then(|slot| slot.emissive.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let clearcoat = create_texture_binding_resource(
device,
queue,
"clearcoat",
MaterialTextureUpload::from_clearcoat_texture(
slot.and_then(|slot| slot.clearcoat.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let clearcoat_roughness = create_texture_binding_resource(
device,
queue,
"clearcoat_roughness",
MaterialTextureUpload::from_clearcoat_roughness_texture(
slot.and_then(|slot| slot.clearcoat_roughness.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let clearcoat_normal = create_texture_binding_resource(
device,
queue,
"clearcoat_normal",
MaterialTextureUpload::from_clearcoat_normal_texture(
slot.and_then(|slot| slot.clearcoat_normal.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let sheen_color = create_texture_binding_resource(
device,
queue,
"sheen_color",
MaterialTextureUpload::from_sheen_color_texture(
slot.and_then(|slot| slot.sheen_color.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let sheen_roughness = create_texture_binding_resource(
device,
queue,
"sheen_roughness",
MaterialTextureUpload::from_sheen_roughness_texture(
slot.and_then(|slot| slot.sheen_roughness.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let anisotropy = create_texture_binding_resource(
device,
queue,
"anisotropy",
MaterialTextureUpload::from_anisotropy_texture(
slot.and_then(|slot| slot.anisotropy.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let iridescence = create_texture_binding_resource(
device,
queue,
"iridescence",
MaterialTextureUpload::from_iridescence_texture(
slot.and_then(|slot| slot.iridescence.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let iridescence_thickness = create_texture_binding_resource(
device,
queue,
"iridescence_thickness",
MaterialTextureUpload::from_iridescence_thickness_texture(
slot.and_then(|slot| slot.iridescence_thickness.as_ref())
.map(|texture| &texture.desc),
),
texture_binding_mode,
);
let texture_bindings = vec![
base_color,
normal,
metallic_roughness,
occlusion,
emissive,
clearcoat,
clearcoat_roughness,
clearcoat_normal,
sheen_color,
sheen_roughness,
anisotropy,
iridescence,
iridescence_thickness,
];
let texture_byte_len = texture_bindings
.iter()
.map(|binding| binding.byte_len)
.sum();
let uniform = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("scena.material.uniform"),
size: MATERIAL_UNIFORM_ENTRY_STRIDE,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&uniform, 0, &material_uniform.encode());
let bind_group = create_material_bind_group(device, layout, &texture_bindings, &uniform);
MaterialTextureResources {
texture_bindings,
uniform,
bind_group,
texture_byte_len,
}
}
fn create_texture_binding_resource(
device: &wgpu::Device,
queue: &wgpu::Queue,
label: &'static str,
upload: MaterialTextureUpload<'_>,
texture_binding_mode: MaterialTextureBindingMode,
) -> MaterialTextureBindingResources {
let mip_extents = {
#[cfg(target_arch = "wasm32")]
if upload.browser_image.is_some() {
vec![(upload.width.max(1), upload.height.max(1))]
} else {
mip_level_extents(upload.width, upload.height, upload.sampler.min_filter())
}
#[cfg(not(target_arch = "wasm32"))]
{
mip_level_extents(upload.width, upload.height, upload.sampler.min_filter())
}
};
#[cfg(target_arch = "wasm32")]
let texture_usage = if upload.browser_image.is_some() {
wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT
} else {
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST
};
#[cfg(not(target_arch = "wasm32"))]
let texture_usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some(if upload.uses_decoded_texture {
match label {
"base_color" => "scena.material.base_color",
"normal" => "scena.material.normal",
"metallic_roughness" => "scena.material.metallic_roughness",
"occlusion" => "scena.material.occlusion",
"emissive" => "scena.material.emissive",
"clearcoat" => "scena.material.clearcoat",
"clearcoat_roughness" => "scena.material.clearcoat_roughness",
"clearcoat_normal" => "scena.material.clearcoat_normal",
"sheen_color" => "scena.material.sheen_color",
"sheen_roughness" => "scena.material.sheen_roughness",
"anisotropy" => "scena.material.anisotropy",
"iridescence" => "scena.material.iridescence",
"iridescence_thickness" => "scena.material.iridescence_thickness",
_ => "scena.material.texture",
}
} else {
match label {
"base_color" => "scena.material.fallback_base_color",
"normal" => "scena.material.fallback_normal",
"metallic_roughness" => "scena.material.fallback_metallic_roughness",
"occlusion" => "scena.material.fallback_occlusion",
"emissive" => "scena.material.fallback_emissive",
"clearcoat" => "scena.material.fallback_clearcoat",
"clearcoat_roughness" => "scena.material.fallback_clearcoat_roughness",
"clearcoat_normal" => "scena.material.fallback_clearcoat_normal",
"sheen_color" => "scena.material.fallback_sheen_color",
"sheen_roughness" => "scena.material.fallback_sheen_roughness",
"anisotropy" => "scena.material.fallback_anisotropy",
"iridescence" => "scena.material.fallback_iridescence",
"iridescence_thickness" => "scena.material.fallback_iridescence_thickness",
_ => "scena.material.fallback_texture",
}
}),
size: wgpu::Extent3d {
width: upload.width,
height: upload.height,
depth_or_array_layers: 1,
},
mip_level_count: mip_extents.len() as u32,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: upload.format,
usage: texture_usage,
view_formats: &[],
});
write_material_texture_layer_mips(queue, &texture, upload, &mip_extents, 0);
let view = texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(texture_binding_mode.view_dimension()),
..wgpu::TextureViewDescriptor::default()
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some(if upload.uses_decoded_texture {
"scena.material.sampler"
} else {
"scena.material.fallback_sampler"
}),
address_mode_u: address_mode(upload.sampler.wrap_s()),
address_mode_v: address_mode(upload.sampler.wrap_t()),
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: filter_mode(upload.sampler.mag_filter()),
min_filter: filter_mode(upload.sampler.min_filter()),
mipmap_filter: mipmap_filter_mode(upload.sampler.min_filter()),
..wgpu::SamplerDescriptor::default()
});
MaterialTextureBindingResources::from_parts(texture, view, sampler, upload.byte_len())
}
pub(super) fn write_material_texture_layer_mips(
queue: &wgpu::Queue,
texture: &wgpu::Texture,
upload: MaterialTextureUpload<'_>,
mip_extents: &[(u32, u32)],
layer_index: u32,
) {
#[cfg(target_arch = "wasm32")]
if let Some(image) = upload.browser_image {
queue.copy_external_image_to_texture(
&wgpu::CopyExternalImageSourceInfo {
source: wgpu::ExternalImageSource::ImageBitmap(image.clone()),
origin: wgpu::Origin2d::ZERO,
flip_y: false,
},
wgpu::CopyExternalImageDestInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: layer_index,
},
aspect: wgpu::TextureAspect::All,
color_space: wgpu::PredefinedColorSpace::Srgb,
premultiplied_alpha: false,
},
wgpu::Extent3d {
width: upload.width.max(1),
height: upload.height.max(1),
depth_or_array_layers: 1,
},
);
return;
}
let mut previous = upload.rgba8.to_vec();
for (mip_level, (width, height)) in mip_extents.iter().copied().enumerate() {
let pixels = if mip_level == 0 {
upload.rgba8
} else {
previous = downsample_rgba8_mip(
&previous,
mip_extents[mip_level - 1].0,
mip_extents[mip_level - 1].1,
width,
height,
);
previous.as_slice()
};
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: mip_level as u32,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: layer_index,
},
aspect: wgpu::TextureAspect::All,
},
pixels,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width.saturating_mul(4)),
rows_per_image: None,
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
}
}
#[cfg(test)]
mod tests;