use std::sync::LazyLock;
use awsm_renderer_core::{
bind_groups::{BindGroupDescriptor, BindGroupEntry, BindGroupResource},
buffers::BufferBinding,
compare::CompareFunction,
error::AwsmCoreError,
pipeline::{
depth_stencil::DepthStencilState,
multisample::MultisampleState,
primitive::{CullMode, FrontFace, PrimitiveState, PrimitiveTopology},
},
renderer::AwsmRendererWebGpu,
texture::{TextureFormat, TextureViewDescriptor, TextureViewDimension},
};
use glam::Mat4;
use std::borrow::Cow;
use crate::{
bind_group_layout::{BindGroupLayoutKey, BindGroupLayouts},
pipeline_layouts::PipelineLayoutKey,
pipelines::render_pipeline::RenderPipelineCacheKey,
render_passes::geometry::pipeline::{VERTEX_BUFFER_LAYOUT, VERTEX_BUFFER_LAYOUT_INSTANCING},
shadows::{
consts::{
MAX_SHADOW_DESCRIPTORS, SHADOW_DESCRIPTOR_BYTES, SHADOW_VIEW_BYTES, SHADOW_VIEW_STRIDE,
},
error::AwsmShadowError,
evsm,
light_shadow::LightShadowHardness,
},
};
pub(super) static SHADOW_DESCRIPTOR_UNIFORM_BYTES: LazyLock<usize> =
LazyLock::new(|| MAX_SHADOW_DESCRIPTORS as usize * SHADOW_DESCRIPTOR_BYTES);
#[allow(clippy::too_many_arguments)]
pub(super) fn write_shadow_descriptor(
dest: &mut [u8],
view_projection: &Mat4,
rect: [u32; 4],
atlas_size: u32,
depth_bias: f32,
normal_bias: f32,
hardness: LightShadowHardness,
pcss_scale: f32,
cascade_y_param: f32,
cascade_count: u32,
split_far: f32,
) {
debug_assert!(dest.len() >= SHADOW_DESCRIPTOR_BYTES);
let cols = view_projection.to_cols_array();
let mat_bytes: &[u8] = unsafe { std::slice::from_raw_parts(cols.as_ptr() as *const u8, 64) };
dest[0..64].copy_from_slice(mat_bytes);
let inv = if atlas_size == 0 {
1.0
} else {
1.0 / atlas_size as f32
};
let x = rect[0] as f32 * inv;
let y = rect[1] as f32 * inv;
let w = rect[2] as f32 * inv;
let h = rect[3] as f32 * inv;
dest[64..68].copy_from_slice(&x.to_ne_bytes());
dest[68..72].copy_from_slice(&y.to_ne_bytes());
dest[72..76].copy_from_slice(&w.to_ne_bytes());
dest[76..80].copy_from_slice(&h.to_ne_bytes());
dest[80..84].copy_from_slice(&depth_bias.to_ne_bytes());
dest[84..88].copy_from_slice(&normal_bias.to_ne_bytes());
let hardness_f = match hardness {
LightShadowHardness::Hard => 0.0_f32,
LightShadowHardness::Soft => 1.0_f32,
LightShadowHardness::Pcss => 2.0_f32,
};
dest[88..92].copy_from_slice(&hardness_f.to_ne_bytes());
dest[92..96].copy_from_slice(&pcss_scale.to_ne_bytes());
dest[96..100].copy_from_slice(&split_far.to_ne_bytes());
dest[100..104].copy_from_slice(&cascade_y_param.to_ne_bytes());
dest[104..108].copy_from_slice(&(cascade_count as f32).to_ne_bytes());
dest[108..112].copy_from_slice(&0.0_f32.to_ne_bytes());
}
#[allow(clippy::too_many_arguments)]
pub(super) fn write_shadow_cascade_array_descriptor(
dest: &mut [u8],
view_projection: &Mat4,
cascade_layer: u32,
used_res: u32,
layer_resolution: u32,
depth_bias: f32,
normal_bias: f32,
hardness: LightShadowHardness,
pcss_scale: f32,
world_per_texel: f32,
cascade_count: u32,
split_far: f32,
) {
debug_assert!(dest.len() >= SHADOW_DESCRIPTOR_BYTES);
let cols = view_projection.to_cols_array();
let mat_bytes: &[u8] = unsafe { std::slice::from_raw_parts(cols.as_ptr() as *const u8, 64) };
dest[0..64].copy_from_slice(mat_bytes);
let inv = if layer_resolution == 0 {
1.0
} else {
1.0 / layer_resolution as f32
};
let layer_f = cascade_layer as f32;
let w = used_res as f32 * inv;
let h = used_res as f32 * inv;
dest[64..68].copy_from_slice(&layer_f.to_ne_bytes());
dest[68..72].copy_from_slice(&0.0_f32.to_ne_bytes());
dest[72..76].copy_from_slice(&w.to_ne_bytes());
dest[76..80].copy_from_slice(&h.to_ne_bytes());
dest[80..84].copy_from_slice(&depth_bias.to_ne_bytes());
dest[84..88].copy_from_slice(&normal_bias.to_ne_bytes());
let hardness_f = match hardness {
LightShadowHardness::Hard => 0.0_f32,
LightShadowHardness::Soft => 1.0_f32,
LightShadowHardness::Pcss => 2.0_f32,
};
dest[88..92].copy_from_slice(&hardness_f.to_ne_bytes());
dest[92..96].copy_from_slice(&pcss_scale.to_ne_bytes());
dest[96..100].copy_from_slice(&split_far.to_ne_bytes());
dest[100..104].copy_from_slice(&world_per_texel.to_ne_bytes());
dest[104..108].copy_from_slice(&(cascade_count as f32).to_ne_bytes());
dest[108..112].copy_from_slice(&3.0_f32.to_ne_bytes());
}
pub(super) fn build_evsm_moment_write_bind_group(
gpu: &AwsmRendererWebGpu,
bind_group_layouts: &BindGroupLayouts,
layout_key: BindGroupLayoutKey,
cascade_array_view: &web_sys::GpuTextureView,
evsm_atlas_view: &web_sys::GpuTextureView,
params_buffer: &web_sys::GpuBuffer,
) -> Result<web_sys::GpuBindGroup, AwsmShadowError> {
let entries = vec![
BindGroupEntry::new(
0,
BindGroupResource::TextureView(Cow::Borrowed(cascade_array_view)),
),
BindGroupEntry::new(
1,
BindGroupResource::TextureView(Cow::Borrowed(evsm_atlas_view)),
),
BindGroupEntry::new(
2,
BindGroupResource::Buffer(
BufferBinding::new(params_buffer).with_size(evsm::EVSM_PARAMS_STRIDE),
),
),
];
let descriptor = BindGroupDescriptor::new(
bind_group_layouts.get(layout_key)?,
Some("Shadow EVSM Moment Write Bind Group"),
entries,
);
Ok(gpu.create_bind_group(&descriptor.into()))
}
pub(super) fn build_evsm_blur_bind_group(
gpu: &AwsmRendererWebGpu,
bind_group_layouts: &BindGroupLayouts,
layout_key: BindGroupLayoutKey,
src_view: &web_sys::GpuTextureView,
dst_view: &web_sys::GpuTextureView,
params_buffer: &web_sys::GpuBuffer,
label: &str,
) -> Result<web_sys::GpuBindGroup, AwsmShadowError> {
let entries = vec![
BindGroupEntry::new(0, BindGroupResource::TextureView(Cow::Borrowed(src_view))),
BindGroupEntry::new(1, BindGroupResource::TextureView(Cow::Borrowed(dst_view))),
BindGroupEntry::new(
2,
BindGroupResource::Buffer(
BufferBinding::new(params_buffer).with_size(evsm::EVSM_PARAMS_STRIDE),
),
),
];
let descriptor =
BindGroupDescriptor::new(bind_group_layouts.get(layout_key)?, Some(label), entries);
Ok(gpu.create_bind_group(&descriptor.into()))
}
pub(crate) fn shadow_pipeline_cache_key(
shader_key: crate::shaders::ShaderKey,
pipeline_layout_key: PipelineLayoutKey,
instancing: bool,
cube_face: bool,
double_sided: bool,
) -> RenderPipelineCacheKey {
let mut vertex_buffer_layouts = vec![VERTEX_BUFFER_LAYOUT.clone()];
if instancing {
vertex_buffer_layouts.push(VERTEX_BUFFER_LAYOUT_INSTANCING.clone());
}
let front_face = if cube_face {
FrontFace::Cw
} else {
FrontFace::Ccw
};
let cull_mode = if double_sided {
CullMode::None
} else {
CullMode::Front
};
let primitive = PrimitiveState::new()
.with_topology(PrimitiveTopology::TriangleList)
.with_front_face(front_face)
.with_cull_mode(cull_mode);
let depth_stencil = DepthStencilState::new(TextureFormat::Depth32float)
.with_depth_write_enabled(true)
.with_depth_compare(CompareFunction::LessEqual)
.with_depth_bias(1)
.with_depth_bias_slope_scale(1.5);
let multisample = MultisampleState::new().with_count(1);
let mut pipeline_cache_key = RenderPipelineCacheKey::new(shader_key, pipeline_layout_key)
.with_primitive(primitive)
.with_depth_stencil(depth_stencil)
.with_multisample(multisample);
for layout in vertex_buffer_layouts {
pipeline_cache_key = pipeline_cache_key.with_push_vertex_buffer_layout(layout);
}
pipeline_cache_key
}
pub(super) fn write_shadow_view_slot(
dest: &mut [u8],
view_slot: usize,
view_projection: &Mat4,
depth_bias: f32,
normal_bias: f32,
) {
let off = view_slot * SHADOW_VIEW_STRIDE;
debug_assert!(off + SHADOW_VIEW_BYTES <= dest.len());
let cols = view_projection.to_cols_array();
let mat_bytes: &[u8] = unsafe { std::slice::from_raw_parts(cols.as_ptr() as *const u8, 64) };
dest[off..off + 64].copy_from_slice(mat_bytes);
dest[off + 64..off + 68].copy_from_slice(&depth_bias.to_ne_bytes());
dest[off + 68..off + 72].copy_from_slice(&normal_bias.to_ne_bytes());
dest[off + 72..off + 80].copy_from_slice(&[0u8; 8]);
}
pub(super) fn view_projection_drift(prev: &Mat4, current: &Mat4) -> f32 {
let a = prev.to_cols_array();
let b = current.to_cols_array();
let mut acc = 0.0_f32;
for i in 0..16 {
acc += (a[i] - b[i]).abs();
}
acc
}
pub(super) fn extract_near_far(projection: &Mat4) -> (f32, f32) {
let m22 = projection.z_axis.z;
let m23 = projection.w_axis.z;
if m22.abs() > 1e-4 && (m22 + 1.0).abs() > 1e-4 {
let near = m23 / m22;
let far = m23 / (m22 + 1.0);
if near > 0.0 && far > near {
return (near, far);
}
}
(0.1, 100.0)
}
pub(super) fn create_cascade_array_view(
texture: &web_sys::GpuTexture,
) -> Result<web_sys::GpuTextureView, AwsmShadowError> {
let descriptor: web_sys::GpuTextureViewDescriptor =
TextureViewDescriptor::new(Some("Shadow Cascade Array"))
.with_dimension(TextureViewDimension::N2dArray)
.into();
texture
.create_view_with_descriptor(&descriptor)
.map_err(AwsmCoreError::create_texture_view)
.map_err(Into::into)
}
pub(super) fn build_cascade_layer_views(
texture: &web_sys::GpuTexture,
layer_count: u32,
) -> Result<Vec<web_sys::GpuTextureView>, AwsmShadowError> {
let mut views = Vec::with_capacity(layer_count as usize);
for layer in 0..layer_count {
let descriptor: web_sys::GpuTextureViewDescriptor =
TextureViewDescriptor::new(Some("Shadow Cascade Layer"))
.with_dimension(TextureViewDimension::N2d)
.with_base_array_layer(layer)
.with_array_layer_count(1)
.into();
let view = texture
.create_view_with_descriptor(&descriptor)
.map_err(AwsmCoreError::create_texture_view)?;
views.push(view);
}
Ok(views)
}
pub(super) fn create_cube_array_view(
texture: &web_sys::GpuTexture,
) -> Result<web_sys::GpuTextureView, AwsmShadowError> {
let descriptor: web_sys::GpuTextureViewDescriptor =
TextureViewDescriptor::new(Some("Shadow Cube Array"))
.with_dimension(TextureViewDimension::CubeArray)
.into();
texture
.create_view_with_descriptor(&descriptor)
.map_err(AwsmCoreError::create_texture_view)
.map_err(Into::into)
}
pub(super) fn create_cube_2d_array_view(
texture: &web_sys::GpuTexture,
) -> Result<web_sys::GpuTextureView, AwsmShadowError> {
let descriptor: web_sys::GpuTextureViewDescriptor =
TextureViewDescriptor::new(Some("Shadow Cube 2D-Array"))
.with_dimension(TextureViewDimension::N2dArray)
.into();
texture
.create_view_with_descriptor(&descriptor)
.map_err(AwsmCoreError::create_texture_view)
.map_err(Into::into)
}
pub(super) fn build_cube_face_views(
texture: &web_sys::GpuTexture,
total_layers: u32,
) -> Result<Vec<web_sys::GpuTextureView>, AwsmShadowError> {
let mut views = Vec::with_capacity(total_layers as usize);
for layer in 0..total_layers {
let descriptor: web_sys::GpuTextureViewDescriptor =
TextureViewDescriptor::new(Some("Shadow Cube Face"))
.with_dimension(TextureViewDimension::N2d)
.with_base_array_layer(layer)
.with_array_layer_count(1)
.into();
let view = texture
.create_view_with_descriptor(&descriptor)
.map_err(AwsmCoreError::create_texture_view)?;
views.push(view);
}
Ok(views)
}