use crate::ecs::mesh::components::{SkinnedVertex, Vertex};
use crate::ecs::prefab::resources::mesh_cache_iter;
use crate::ecs::skin::systems::GpuSkinData;
use std::collections::HashMap;
use wgpu::util::DeviceExt;
const MAX_BUFFER_SIZE: u64 = 256 * 1024 * 1024;
const COMPACTION_CHECK_INTERVAL_FRAMES: u64 = 300;
const COMPACTION_UTILIZATION_THRESHOLD: f32 = 0.5;
const COMPACTION_MIN_RECLAIM_BYTES: u64 = 8 * 1024 * 1024;
pub(super) const SKINNED_CULL_MAX_VIEWS: usize =
super::types::NUM_SHADOW_CASCADES + super::types::MAX_SPOTLIGHT_SHADOWS;
use super::types::{
CASCADE_SPLIT_DISTANCES, CascadeData, INITIAL_MAX_SKINS, INITIAL_MAX_TOTAL_BONES,
MAX_JOINTS_PER_SKIN, MAX_POINT_LIGHT_SHADOWS, MAX_SPOTLIGHT_SHADOWS, NUM_SHADOW_CASCADES,
POINT_SHADOW_FACE_SIZE, POINT_SHADOW_NUM_FACES, PointLightShadowData, PointLightShadowSlot,
PointShadowUniforms, ShadowSlotCache, ShadowUniforms, SkinnedShadowObjectData,
SpotlightShadowData, SpotlightShadowSlot,
};
pub struct ShadowDepthPass {
pub(super) uniform_buffer: wgpu::Buffer,
pub(super) uniform_bind_group_layout: wgpu::BindGroupLayout,
pub(super) transform_buffer: wgpu::Buffer,
pub(super) transform_bind_group: wgpu::BindGroup,
pub(super) transform_bind_group_layout: wgpu::BindGroupLayout,
pub(super) transform_buffer_size: usize,
pub(super) vertex_buffer: wgpu::Buffer,
pub(super) index_buffer: wgpu::Buffer,
pub(super) vertex_buffer_size: u64,
pub(super) index_buffer_size: u64,
pub(super) meshes: HashMap<String, (u32, u32, u32, u32)>,
pub(super) current_vertex_offset: u32,
pub(super) current_index_offset: u32,
pub(super) compaction_counter: u64,
pub(super) shadow_caster_entities: Vec<crate::ecs::world::Entity>,
pub(super) occluders: Vec<super::types::ShadowOccluder>,
pub(super) occluder_transforms: Vec<[[f32; 4]; 4]>,
pub(super) occluder_cache: Vec<super::node::OccluderCacheEntry>,
pub(super) all_caster_entities: Vec<crate::ecs::world::Entity>,
pub(super) last_visible_signature: u64,
pub(super) occluders_initialized: bool,
pub shadow_scene_dirty: bool,
pub(super) skinned_pipeline: wgpu::RenderPipeline,
pub(super) skinned_object_buffer: wgpu::Buffer,
pub(super) skinned_object_buffer_size: usize,
pub(super) skinned_indirect_buffer: wgpu::Buffer,
pub(super) skinned_indirect_buffer_size: usize,
pub(super) skinned_group_count: usize,
pub(super) skinned_cull_view_count: usize,
pub(super) skinned_occluder_count: usize,
pub(super) skinned_cull_objects_buffer: wgpu::Buffer,
pub(super) skinned_cull_objects_buffer_size: usize,
pub(super) skinned_visible_indices_buffer: wgpu::Buffer,
pub(super) skinned_visible_indices_buffer_size: usize,
pub(super) skinned_cull_pipeline: wgpu::ComputePipeline,
pub(super) skinned_cull_bind_group_layout: wgpu::BindGroupLayout,
pub(super) skinned_cull_uniform_buffers: Vec<wgpu::Buffer>,
pub(super) skinned_cull_bind_groups: Vec<wgpu::BindGroup>,
pub(super) skinned_bind_group: wgpu::BindGroup,
pub(super) skinned_bind_group_layout: wgpu::BindGroupLayout,
pub(super) joint_matrices_buffer: wgpu::Buffer,
pub(super) bone_transforms_buffer: wgpu::Buffer,
pub(super) inverse_bind_matrices_buffer: wgpu::Buffer,
pub(super) skin_data_buffer: wgpu::Buffer,
pub(super) bone_transforms_buffer_size: usize,
pub(super) inverse_bind_matrices_buffer_size: usize,
pub(super) joint_matrices_buffer_size: usize,
pub(super) skin_data_buffer_size: usize,
pub(super) skinning_compute_pipeline: wgpu::ComputePipeline,
pub(super) skinning_compute_bind_group: wgpu::BindGroup,
pub(super) skinning_compute_bind_group_layout: wgpu::BindGroupLayout,
pub(super) skinned_vertex_buffer: wgpu::Buffer,
pub(super) skinned_index_buffer: wgpu::Buffer,
pub(super) skinned_vertex_buffer_size: u64,
pub(super) skinned_index_buffer_size: u64,
pub(super) skinned_meshes: HashMap<String, (u32, u32, u32, u32)>,
pub(super) current_skinned_vertex_offset: u32,
pub(super) current_skinned_index_offset: u32,
pub(super) skinned_compaction_counter: u64,
pub(super) skinned_shadow_caster_entities: Vec<crate::ecs::world::Entity>,
pub(super) total_joints_to_dispatch: u32,
pub(super) spotlight_shadow_slots: Vec<SpotlightShadowSlot>,
pub(super) spotlight_shadow_data: Vec<SpotlightShadowData>,
pub(super) spotlight_atlas_slot_size: u32,
pub(super) spotlight_slot_cache: [ShadowSlotCache; MAX_SPOTLIGHT_SHADOWS],
pub(super) point_slot_cache: [ShadowSlotCache; MAX_POINT_LIGHT_SHADOWS],
pub(super) was_enabled_last_frame: bool,
pub(super) shadow_view_dirty: Vec<bool>,
pub(super) depth_clear_pipeline: wgpu::RenderPipeline,
pub point_light_cubemap: wgpu::Texture,
pub(super) point_light_cubemap_view: wgpu::TextureView,
pub(super) point_light_face_views: Vec<wgpu::TextureView>,
pub point_light_depth_texture: wgpu::Texture,
pub(super) point_light_depth_view: wgpu::TextureView,
pub(super) point_light_uniform_buffer: wgpu::Buffer,
pub(super) point_light_uniform_bind_group: wgpu::BindGroup,
pub(super) point_light_shadow_slots: Vec<PointLightShadowSlot>,
pub(super) point_light_shadow_data: Vec<PointLightShadowData>,
pub(super) cascade_data: CascadeData,
pub(super) cascade_uniform_buffers: [wgpu::Buffer; NUM_SHADOW_CASCADES],
pub(super) cascade_uniform_bind_groups: [wgpu::BindGroup; NUM_SHADOW_CASCADES],
pub(super) culling: super::culling::ShadowCulling,
}
fn buffer_bind_group(
device: &wgpu::Device,
label: &str,
layout: &wgpu::BindGroupLayout,
buffers: &[&wgpu::Buffer],
) -> wgpu::BindGroup {
let entries: Vec<wgpu::BindGroupEntry> = buffers
.iter()
.enumerate()
.map(|(index, buffer)| wgpu::BindGroupEntry {
binding: index as u32,
resource: buffer.as_entire_binding(),
})
.collect();
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout,
entries: &entries,
})
}
impl ShadowDepthPass {
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Uniform Buffer"),
size: std::mem::size_of::<ShadowUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Shadow Uniform Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let initial_cascade_uniforms = ShadowUniforms {
light_view_projection: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
_padding: [[0.0; 4]; 4],
_padding2: [[0.0; 4]; 4],
_padding3: [[0.0; 4]; 4],
_padding4: [0.0; 4],
};
let cascade_uniform_buffers: [wgpu::Buffer; NUM_SHADOW_CASCADES] =
std::array::from_fn(|_index| {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Cascade Uniform Buffer"),
contents: bytemuck::cast_slice(&[initial_cascade_uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})
});
let cascade_uniform_bind_groups: [wgpu::BindGroup; NUM_SHADOW_CASCADES] =
std::array::from_fn(|index| {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("Cascade {} Uniform Bind Group", index)),
layout: &uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: cascade_uniform_buffers[index].as_entire_binding(),
}],
})
});
let initial_transform_count = 1000;
let transform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Transform Buffer"),
size: (std::mem::size_of::<[[f32; 4]; 4]>() * initial_transform_count) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let transform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Shadow Transform Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let transform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Shadow Transform Bind Group"),
layout: &transform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: transform_buffer.as_entire_binding(),
}],
});
let initial_vertex_count = 10000;
let initial_index_count = 30000;
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Vertex Buffer"),
size: (std::mem::size_of::<Vertex>() * initial_vertex_count) as u64,
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Index Buffer"),
size: (std::mem::size_of::<u32>() * initial_index_count) as u64,
usage: wgpu::BufferUsages::INDEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let bone_transforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Bone Transforms Buffer"),
size: (std::mem::size_of::<[[f32; 4]; 4]>() * INITIAL_MAX_TOTAL_BONES) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let inverse_bind_matrices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Inverse Bind Matrices Buffer"),
size: (std::mem::size_of::<[[f32; 4]; 4]>() * MAX_JOINTS_PER_SKIN * INITIAL_MAX_SKINS)
as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let joint_matrices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Joint Matrices Buffer"),
size: (std::mem::size_of::<[[f32; 4]; 4]>() * MAX_JOINTS_PER_SKIN * INITIAL_MAX_SKINS)
as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let skin_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skin Data Buffer"),
size: (std::mem::size_of::<GpuSkinData>() * INITIAL_MAX_SKINS) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let skinning_compute_shader = crate::render::wgpu::shader_compose::compile_wgsl(
device,
"skinning_compute.wgsl",
include_str!("../../shaders/skinning_compute.wgsl"),
);
let skinning_compute_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Shadow Skinning Compute Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let initial_joint_matrices_size = MAX_JOINTS_PER_SKIN * INITIAL_MAX_SKINS;
let skinning_compute_bind_group = buffer_bind_group(
device,
"Shadow Skinning Compute Bind Group",
&skinning_compute_bind_group_layout,
&[
&bone_transforms_buffer,
&inverse_bind_matrices_buffer,
&skin_data_buffer,
&joint_matrices_buffer,
],
);
let skinning_compute_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Shadow Skinning Compute Pipeline Layout"),
bind_group_layouts: &[Some(&skinning_compute_bind_group_layout)],
immediate_size: 0,
});
let skinning_compute_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Shadow Skinning Compute Pipeline"),
layout: Some(&skinning_compute_pipeline_layout),
module: &skinning_compute_shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let initial_skinned_object_count = 100;
let skinned_object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Object Buffer"),
size: (std::mem::size_of::<SkinnedShadowObjectData>() * initial_skinned_object_count)
as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let initial_skinned_indirect_count = 32;
let skinned_indirect_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Indirect Buffer"),
size: (std::mem::size_of::<super::types::ShadowDrawIndexedIndirect>()
* initial_skinned_indirect_count) as u64,
usage: wgpu::BufferUsages::INDIRECT
| wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let initial_skinned_cull_objects = 100;
let skinned_cull_objects_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Cull Objects Buffer"),
size: (std::mem::size_of::<super::types::SkinnedShadowCullObject>()
* initial_skinned_cull_objects) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let initial_skinned_visible = 100;
let skinned_visible_indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Visible Indices Buffer"),
size: (std::mem::size_of::<u32>() * initial_skinned_visible) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let skinned_cull_storage_entry =
|binding: u32, read_only: bool| wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
};
let skinned_cull_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Shadow Skinned Cull Bind Group Layout"),
entries: &[
skinned_cull_storage_entry(0, true),
skinned_cull_storage_entry(1, false),
skinned_cull_storage_entry(2, false),
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let skinned_cull_shader = crate::render::wgpu::shader_compose::compile_wgsl(
device,
"skinned_shadow_cull.wgsl",
include_str!("../../shaders/skinned_shadow_cull.wgsl"),
);
let skinned_cull_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Shadow Skinned Cull Pipeline Layout"),
bind_group_layouts: &[Some(&skinned_cull_bind_group_layout)],
immediate_size: 0,
});
let skinned_cull_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Shadow Skinned Cull Pipeline"),
layout: Some(&skinned_cull_pipeline_layout),
module: &skinned_cull_shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let skinned_cull_uniform_buffers: Vec<wgpu::Buffer> = (0..SKINNED_CULL_MAX_VIEWS)
.map(|view| {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some(&format!("Shadow Skinned Cull Uniform Buffer {view}")),
size: std::mem::size_of::<super::types::ShadowCullUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
})
.collect();
let skinned_cull_bind_groups: Vec<wgpu::BindGroup> = skinned_cull_uniform_buffers
.iter()
.map(|uniform_buffer| {
buffer_bind_group(
device,
"Shadow Skinned Cull Bind Group",
&skinned_cull_bind_group_layout,
&[
&skinned_cull_objects_buffer,
&skinned_indirect_buffer,
&skinned_visible_indices_buffer,
uniform_buffer,
],
)
})
.collect();
let skinned_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Shadow Skinned Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let skinned_bind_group = buffer_bind_group(
device,
"Shadow Skinned Bind Group",
&skinned_bind_group_layout,
&[
&skinned_object_buffer,
&joint_matrices_buffer,
&skinned_visible_indices_buffer,
],
);
let skinned_shadow_shader = crate::render::wgpu::shader_compose::compile_wgsl(
device,
"skinned_shadow_depth.wgsl",
include_str!("../../shaders/skinned_shadow_depth.wgsl"),
);
let skinned_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Skinned Shadow Pipeline Layout"),
bind_group_layouts: &[
Some(&uniform_bind_group_layout),
Some(&skinned_bind_group_layout),
],
immediate_size: 0,
});
let skinned_vertex_layout = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<SkinnedVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: 12,
shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: 24,
shader_location: 2,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 32,
shader_location: 3,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 40,
shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: 56,
shader_location: 5,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: 72,
shader_location: 6,
format: wgpu::VertexFormat::Uint32x4,
},
wgpu::VertexAttribute {
offset: 88,
shader_location: 7,
format: wgpu::VertexFormat::Float32x4,
},
],
};
let skinned_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Skinned Shadow Depth Pipeline"),
layout: Some(&skinned_pipeline_layout),
vertex: wgpu::VertexState {
module: &skinned_shadow_shader,
entry_point: Some("vs_main"),
buffers: &[skinned_vertex_layout],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &skinned_shadow_shader,
entry_point: Some("fs_main"),
targets: &[],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: Some(true),
depth_compare: Some(wgpu::CompareFunction::GreaterEqual),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let initial_skinned_vertex_count = 10000;
let initial_skinned_index_count = 30000;
let skinned_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Vertex Buffer"),
size: (std::mem::size_of::<SkinnedVertex>() * initial_skinned_vertex_count) as u64,
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let skinned_index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Index Buffer"),
size: (std::mem::size_of::<u32>() * initial_skinned_index_count) as u64,
usage: wgpu::BufferUsages::INDEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let point_light_cubemap = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Point Light Shadow Cubemap Array"),
size: wgpu::Extent3d {
width: POINT_SHADOW_FACE_SIZE,
height: POINT_SHADOW_FACE_SIZE,
depth_or_array_layers: (POINT_SHADOW_NUM_FACES * MAX_POINT_LIGHT_SHADOWS) as u32,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let point_light_cubemap_view =
point_light_cubemap.create_view(&wgpu::TextureViewDescriptor {
label: Some("Point Light Shadow Cubemap Array View"),
format: Some(wgpu::TextureFormat::R32Float),
dimension: Some(wgpu::TextureViewDimension::CubeArray),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: Some(1),
base_array_layer: 0,
array_layer_count: Some((POINT_SHADOW_NUM_FACES * MAX_POINT_LIGHT_SHADOWS) as u32),
usage: Some(wgpu::TextureUsages::TEXTURE_BINDING),
});
let mut point_light_face_views =
Vec::with_capacity(POINT_SHADOW_NUM_FACES * MAX_POINT_LIGHT_SHADOWS);
for light_index in 0..MAX_POINT_LIGHT_SHADOWS {
for face_index in 0..POINT_SHADOW_NUM_FACES {
let layer = (light_index * POINT_SHADOW_NUM_FACES + face_index) as u32;
let view = point_light_cubemap.create_view(&wgpu::TextureViewDescriptor {
label: Some(&format!(
"Point Light {} Face {} View",
light_index, face_index
)),
format: Some(wgpu::TextureFormat::R32Float),
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: Some(1),
base_array_layer: layer,
array_layer_count: Some(1),
usage: Some(wgpu::TextureUsages::RENDER_ATTACHMENT),
});
point_light_face_views.push(view);
}
}
let point_light_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Point Light Shadow Depth Texture"),
size: wgpu::Extent3d {
width: POINT_SHADOW_FACE_SIZE,
height: POINT_SHADOW_FACE_SIZE,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let point_light_depth_view =
point_light_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
let point_uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Point Light Uniform Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: wgpu::BufferSize::new(std::mem::size_of::<
PointShadowUniforms,
>() as u64),
},
count: None,
}],
});
let total_point_uniforms = MAX_POINT_LIGHT_SHADOWS * POINT_SHADOW_NUM_FACES;
let point_light_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Point Light Shadow Uniform Buffer"),
size: (std::mem::size_of::<PointShadowUniforms>() * total_point_uniforms) as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let point_light_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Point Light Shadow Uniform Bind Group"),
layout: &point_uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &point_light_uniform_buffer,
offset: 0,
size: wgpu::BufferSize::new(std::mem::size_of::<PointShadowUniforms>() as u64),
}),
}],
});
let depth_clear_shader = crate::render::wgpu::shader_compose::compile_wgsl(
device,
"depth_clear.wgsl",
include_str!("../../shaders/depth_clear.wgsl"),
);
let depth_clear_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Shadow Depth Clear Pipeline Layout"),
bind_group_layouts: &[],
immediate_size: 0,
});
let depth_clear_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Spotlight Shadow Depth Clear Pipeline"),
layout: Some(&depth_clear_pipeline_layout),
vertex: wgpu::VertexState {
module: &depth_clear_shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: None,
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: Some(true),
depth_compare: Some(wgpu::CompareFunction::Always),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let culling = super::culling::ShadowCulling::new(
device,
&uniform_bind_group_layout,
&point_uniform_bind_group_layout,
);
let mut pass = Self {
uniform_buffer,
uniform_bind_group_layout,
transform_buffer,
transform_bind_group,
transform_bind_group_layout,
transform_buffer_size: initial_transform_count,
vertex_buffer,
index_buffer,
vertex_buffer_size: (std::mem::size_of::<Vertex>() * initial_vertex_count) as u64,
index_buffer_size: (std::mem::size_of::<u32>() * initial_index_count) as u64,
meshes: HashMap::new(),
current_vertex_offset: 0,
current_index_offset: 0,
compaction_counter: 0,
shadow_caster_entities: Vec::new(),
occluders: Vec::new(),
occluder_transforms: Vec::new(),
occluder_cache: Vec::new(),
all_caster_entities: Vec::new(),
last_visible_signature: 0,
occluders_initialized: false,
shadow_scene_dirty: false,
skinned_pipeline,
skinned_object_buffer,
skinned_object_buffer_size: initial_skinned_object_count,
skinned_indirect_buffer,
skinned_indirect_buffer_size: initial_skinned_indirect_count,
skinned_group_count: 0,
skinned_cull_view_count: 0,
skinned_occluder_count: 0,
skinned_cull_objects_buffer,
skinned_cull_objects_buffer_size: initial_skinned_cull_objects,
skinned_visible_indices_buffer,
skinned_visible_indices_buffer_size: initial_skinned_visible,
skinned_cull_pipeline,
skinned_cull_bind_group_layout,
skinned_cull_uniform_buffers,
skinned_cull_bind_groups,
skinned_bind_group,
skinned_bind_group_layout,
joint_matrices_buffer,
bone_transforms_buffer,
inverse_bind_matrices_buffer,
skin_data_buffer,
bone_transforms_buffer_size: INITIAL_MAX_TOTAL_BONES,
inverse_bind_matrices_buffer_size: initial_joint_matrices_size,
joint_matrices_buffer_size: initial_joint_matrices_size,
skin_data_buffer_size: INITIAL_MAX_SKINS,
skinning_compute_pipeline,
skinning_compute_bind_group,
skinning_compute_bind_group_layout,
skinned_vertex_buffer,
skinned_index_buffer,
skinned_vertex_buffer_size: (std::mem::size_of::<SkinnedVertex>()
* initial_skinned_vertex_count) as u64,
skinned_index_buffer_size: (std::mem::size_of::<u32>() * initial_skinned_index_count)
as u64,
skinned_meshes: HashMap::new(),
current_skinned_vertex_offset: 0,
current_skinned_index_offset: 0,
skinned_compaction_counter: 0,
skinned_shadow_caster_entities: Vec::new(),
total_joints_to_dispatch: 0,
spotlight_shadow_slots: Vec::new(),
spotlight_shadow_data: Vec::new(),
spotlight_atlas_slot_size: 0,
spotlight_slot_cache: [ShadowSlotCache::default(); MAX_SPOTLIGHT_SHADOWS],
point_slot_cache: [ShadowSlotCache::default(); MAX_POINT_LIGHT_SHADOWS],
was_enabled_last_frame: false,
shadow_view_dirty: Vec::new(),
depth_clear_pipeline,
point_light_cubemap,
point_light_cubemap_view,
point_light_face_views,
point_light_depth_texture,
point_light_depth_view,
point_light_uniform_buffer,
point_light_uniform_bind_group,
point_light_shadow_slots: Vec::new(),
point_light_shadow_data: Vec::new(),
cascade_data: CascadeData {
view_projections: [[[0.0; 4]; 4]; NUM_SHADOW_CASCADES],
split_distances: CASCADE_SPLIT_DISTANCES,
atlas_offsets: [[0.0, 0.0], [0.5, 0.0], [0.0, 0.5], [0.5, 0.5]],
atlas_scale: [0.5, 0.5],
_padding: [0.0, 0.0],
},
cascade_uniform_buffers,
cascade_uniform_bind_groups,
culling,
};
pass.add_builtin_meshes(device, queue);
pass
}
pub fn add_mesh(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
name: String,
vertices: &[Vertex],
indices: &[u32],
) {
let vertex_size = std::mem::size_of_val(vertices) as u64;
let index_size = std::mem::size_of_val(indices) as u64;
let required_vertex_size = (self.current_vertex_offset as u64
* std::mem::size_of::<Vertex>() as u64)
+ vertex_size;
let required_index_size =
(self.current_index_offset as u64 * std::mem::size_of::<u32>() as u64) + index_size;
if required_vertex_size > MAX_BUFFER_SIZE || required_index_size > MAX_BUFFER_SIZE {
tracing::warn!(
"Shadow mesh {} exceeds maximum buffer size, skipping shadow rendering",
name
);
return;
}
if required_vertex_size > self.vertex_buffer_size {
let new_size = (required_vertex_size * 2)
.max(self.vertex_buffer_size * 2)
.min(MAX_BUFFER_SIZE);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Vertex Buffer (Resized)"),
size: new_size,
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Vertex Buffer Copy"),
});
encoder.copy_buffer_to_buffer(
&self.vertex_buffer,
0,
&new_buffer,
0,
self.vertex_buffer_size,
);
queue.submit(Some(encoder.finish()));
self.vertex_buffer = new_buffer;
self.vertex_buffer_size = new_size;
}
if required_index_size > self.index_buffer_size {
let new_size = (required_index_size * 2)
.max(self.index_buffer_size * 2)
.min(MAX_BUFFER_SIZE);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Index Buffer (Resized)"),
size: new_size,
usage: wgpu::BufferUsages::INDEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Index Buffer Copy"),
});
encoder.copy_buffer_to_buffer(
&self.index_buffer,
0,
&new_buffer,
0,
self.index_buffer_size,
);
queue.submit(Some(encoder.finish()));
self.index_buffer = new_buffer;
self.index_buffer_size = new_size;
}
queue.write_buffer(
&self.vertex_buffer,
self.current_vertex_offset as u64 * std::mem::size_of::<Vertex>() as u64,
bytemuck::cast_slice(vertices),
);
queue.write_buffer(
&self.index_buffer,
self.current_index_offset as u64 * std::mem::size_of::<u32>() as u64,
bytemuck::cast_slice(indices),
);
self.culling.register_mesh(
&name,
indices.len() as u32,
self.current_index_offset,
self.current_vertex_offset as i32,
vertices,
);
self.meshes.insert(
name,
(
self.current_vertex_offset,
vertices.len() as u32,
self.current_index_offset,
indices.len() as u32,
),
);
self.current_vertex_offset += vertices.len() as u32;
self.current_index_offset += indices.len() as u32;
}
fn add_builtin_meshes(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
use crate::ecs::mesh::components::{
create_cone_mesh, create_cube_mesh, create_cylinder_mesh, create_plane_mesh,
create_sphere_mesh, create_subdivided_plane_mesh, create_torus_mesh,
};
let builtins = [
("Cube".to_string(), create_cube_mesh()),
("Sphere".to_string(), create_sphere_mesh(1.0, 16)),
("Torus".to_string(), create_torus_mesh(1.0, 0.3, 16, 16)),
("Plane".to_string(), create_plane_mesh(2.0)),
(
"SubdividedPlane".to_string(),
create_subdivided_plane_mesh(2.0, 20),
),
("Cylinder".to_string(), create_cylinder_mesh(0.5, 1.0, 16)),
("Cone".to_string(), create_cone_mesh(0.5, 1.0, 16)),
];
for (name, mesh) in builtins {
self.add_mesh(device, queue, name, &mesh.vertices, &mesh.indices);
}
}
pub fn sync_meshes(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mesh_cache: &crate::ecs::prefab::resources::MeshCache,
) {
let compacted = self.compact_shadow_meshes_if_warranted(device, queue, mesh_cache);
for (name, mesh) in mesh_cache_iter(mesh_cache) {
if mesh.skin_data.is_some() {
continue;
}
if !self.meshes.contains_key(name.as_str()) {
let vertex_size = (mesh.vertices.len() * std::mem::size_of::<Vertex>()) as u64;
let index_size = (mesh.indices.len() * std::mem::size_of::<u32>()) as u64;
let required_vertex_size = (self.current_vertex_offset as u64
* std::mem::size_of::<Vertex>() as u64)
+ vertex_size;
let required_index_size = (self.current_index_offset as u64
* std::mem::size_of::<u32>() as u64)
+ index_size;
if required_vertex_size > MAX_BUFFER_SIZE || required_index_size > MAX_BUFFER_SIZE {
tracing::warn!(
"Shadow mesh {} exceeds maximum buffer size, skipping shadow rendering",
name
);
continue;
}
if required_vertex_size > self.vertex_buffer_size {
let new_size = (required_vertex_size * 2)
.max(self.vertex_buffer_size * 2)
.min(MAX_BUFFER_SIZE);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Vertex Buffer (Resized)"),
size: new_size,
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Vertex Buffer Copy"),
});
encoder.copy_buffer_to_buffer(
&self.vertex_buffer,
0,
&new_buffer,
0,
self.vertex_buffer_size,
);
queue.submit(Some(encoder.finish()));
self.vertex_buffer = new_buffer;
self.vertex_buffer_size = new_size;
}
if required_index_size > self.index_buffer_size {
let new_size = (required_index_size * 2)
.max(self.index_buffer_size * 2)
.min(MAX_BUFFER_SIZE);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Index Buffer (Resized)"),
size: new_size,
usage: wgpu::BufferUsages::INDEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Index Buffer Copy"),
});
encoder.copy_buffer_to_buffer(
&self.index_buffer,
0,
&new_buffer,
0,
self.index_buffer_size,
);
queue.submit(Some(encoder.finish()));
self.index_buffer = new_buffer;
self.index_buffer_size = new_size;
}
queue.write_buffer(
&self.vertex_buffer,
self.current_vertex_offset as u64 * std::mem::size_of::<Vertex>() as u64,
bytemuck::cast_slice(&mesh.vertices),
);
queue.write_buffer(
&self.index_buffer,
self.current_index_offset as u64 * std::mem::size_of::<u32>() as u64,
bytemuck::cast_slice(&mesh.indices),
);
self.meshes.insert(
name.clone(),
(
self.current_vertex_offset,
mesh.vertices.len() as u32,
self.current_index_offset,
mesh.indices.len() as u32,
),
);
self.culling.register_mesh(
name.as_str(),
mesh.indices.len() as u32,
self.current_index_offset,
self.current_vertex_offset as i32,
&mesh.vertices,
);
self.current_vertex_offset += mesh.vertices.len() as u32;
self.current_index_offset += mesh.indices.len() as u32;
}
}
if compacted {
self.shrink_shadow_buffers(device, queue);
}
}
fn compact_shadow_meshes_if_warranted(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mesh_cache: &crate::ecs::prefab::resources::MeshCache,
) -> bool {
let used_vertex_bytes =
self.current_vertex_offset as u64 * std::mem::size_of::<Vertex>() as u64;
let used_index_bytes = self.current_index_offset as u64 * std::mem::size_of::<u32>() as u64;
self.compaction_counter += 1;
let timer_elapsed = self.compaction_counter >= COMPACTION_CHECK_INTERVAL_FRAMES;
let near_cap =
used_vertex_bytes * 2 > MAX_BUFFER_SIZE || used_index_bytes * 2 > MAX_BUFFER_SIZE;
if !timer_elapsed && !near_cap {
return false;
}
if timer_elapsed {
self.compaction_counter = 0;
}
let mut live_vertex_bytes = 0u64;
let mut live_index_bytes = 0u64;
for (_, mesh) in mesh_cache_iter(mesh_cache) {
if mesh.skin_data.is_some() {
continue;
}
live_vertex_bytes += (mesh.vertices.len() * std::mem::size_of::<Vertex>()) as u64;
live_index_bytes += (mesh.indices.len() * std::mem::size_of::<u32>()) as u64;
}
let reclaimable = used_vertex_bytes.saturating_sub(live_vertex_bytes)
+ used_index_bytes.saturating_sub(live_index_bytes);
let utilization = if used_vertex_bytes > 0 {
live_vertex_bytes as f32 / used_vertex_bytes as f32
} else {
1.0
};
if reclaimable < COMPACTION_MIN_RECLAIM_BYTES
|| utilization >= COMPACTION_UTILIZATION_THRESHOLD
{
return false;
}
self.meshes.clear();
self.current_vertex_offset = 0;
self.current_index_offset = 0;
self.culling.clear_mesh_table();
self.add_builtin_meshes(device, queue);
true
}
fn shrink_shadow_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
let used_vertex_bytes =
self.current_vertex_offset as u64 * std::mem::size_of::<Vertex>() as u64;
let used_index_bytes = self.current_index_offset as u64 * std::mem::size_of::<u32>() as u64;
let new_vertex_size = (used_vertex_bytes * 2).min(MAX_BUFFER_SIZE);
if used_vertex_bytes > 0 && new_vertex_size < self.vertex_buffer_size {
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Vertex Buffer (Compacted)"),
size: new_vertex_size,
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Vertex Buffer Compaction"),
});
encoder.copy_buffer_to_buffer(
&self.vertex_buffer,
0,
&new_buffer,
0,
used_vertex_bytes,
);
queue.submit(Some(encoder.finish()));
self.vertex_buffer = new_buffer;
self.vertex_buffer_size = new_vertex_size;
}
let new_index_size = (used_index_bytes * 2).min(MAX_BUFFER_SIZE);
if used_index_bytes > 0 && new_index_size < self.index_buffer_size {
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Index Buffer (Compacted)"),
size: new_index_size,
usage: wgpu::BufferUsages::INDEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Index Buffer Compaction"),
});
encoder.copy_buffer_to_buffer(&self.index_buffer, 0, &new_buffer, 0, used_index_bytes);
queue.submit(Some(encoder.finish()));
self.index_buffer = new_buffer;
self.index_buffer_size = new_index_size;
}
}
pub fn sync_skinned_meshes(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mesh_cache: &crate::ecs::prefab::resources::MeshCache,
) {
let compacted = self.compact_skinned_shadow_meshes_if_warranted(mesh_cache);
for (name, mesh) in mesh_cache_iter(mesh_cache) {
if mesh.skin_data.is_none() {
continue;
}
if self.skinned_meshes.contains_key(name.as_str()) {
continue;
}
let skin_data = mesh.skin_data.as_ref().unwrap();
let skinned_vertices = &skin_data.skinned_vertices;
let vertex_size =
(skinned_vertices.len() * std::mem::size_of::<SkinnedVertex>()) as u64;
let index_size = (mesh.indices.len() * std::mem::size_of::<u32>()) as u64;
let required_vertex_size = (self.current_skinned_vertex_offset as u64
* std::mem::size_of::<SkinnedVertex>() as u64)
+ vertex_size;
let required_index_size = (self.current_skinned_index_offset as u64
* std::mem::size_of::<u32>() as u64)
+ index_size;
if required_vertex_size > MAX_BUFFER_SIZE || required_index_size > MAX_BUFFER_SIZE {
tracing::warn!(
"Shadow skinned mesh {} exceeds maximum buffer size, skipping shadow rendering",
name
);
continue;
}
if required_vertex_size > self.skinned_vertex_buffer_size {
let new_size = (required_vertex_size * 2)
.max(self.skinned_vertex_buffer_size * 2)
.min(MAX_BUFFER_SIZE);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Vertex Buffer (Resized)"),
size: new_size,
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Skinned Vertex Buffer Copy"),
});
encoder.copy_buffer_to_buffer(
&self.skinned_vertex_buffer,
0,
&new_buffer,
0,
self.skinned_vertex_buffer_size,
);
queue.submit(Some(encoder.finish()));
self.skinned_vertex_buffer = new_buffer;
self.skinned_vertex_buffer_size = new_size;
}
if required_index_size > self.skinned_index_buffer_size {
let new_size = (required_index_size * 2)
.max(self.skinned_index_buffer_size * 2)
.min(MAX_BUFFER_SIZE);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Index Buffer (Resized)"),
size: new_size,
usage: wgpu::BufferUsages::INDEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Skinned Index Buffer Copy"),
});
encoder.copy_buffer_to_buffer(
&self.skinned_index_buffer,
0,
&new_buffer,
0,
self.skinned_index_buffer_size,
);
queue.submit(Some(encoder.finish()));
self.skinned_index_buffer = new_buffer;
self.skinned_index_buffer_size = new_size;
}
queue.write_buffer(
&self.skinned_vertex_buffer,
self.current_skinned_vertex_offset as u64
* std::mem::size_of::<SkinnedVertex>() as u64,
bytemuck::cast_slice(skinned_vertices),
);
queue.write_buffer(
&self.skinned_index_buffer,
self.current_skinned_index_offset as u64 * std::mem::size_of::<u32>() as u64,
bytemuck::cast_slice(&mesh.indices),
);
self.skinned_meshes.insert(
name.clone(),
(
self.current_skinned_vertex_offset,
skinned_vertices.len() as u32,
self.current_skinned_index_offset,
mesh.indices.len() as u32,
),
);
self.current_skinned_vertex_offset += skinned_vertices.len() as u32;
self.current_skinned_index_offset += mesh.indices.len() as u32;
}
if compacted {
self.shrink_skinned_shadow_buffers(device, queue);
}
}
fn compact_skinned_shadow_meshes_if_warranted(
&mut self,
mesh_cache: &crate::ecs::prefab::resources::MeshCache,
) -> bool {
let used_vertex_bytes =
self.current_skinned_vertex_offset as u64 * std::mem::size_of::<SkinnedVertex>() as u64;
let used_index_bytes =
self.current_skinned_index_offset as u64 * std::mem::size_of::<u32>() as u64;
self.skinned_compaction_counter += 1;
let timer_elapsed = self.skinned_compaction_counter >= COMPACTION_CHECK_INTERVAL_FRAMES;
let near_cap =
used_vertex_bytes * 2 > MAX_BUFFER_SIZE || used_index_bytes * 2 > MAX_BUFFER_SIZE;
if !timer_elapsed && !near_cap {
return false;
}
if timer_elapsed {
self.skinned_compaction_counter = 0;
}
let mut live_vertex_bytes = 0u64;
let mut live_index_bytes = 0u64;
for (_, mesh) in mesh_cache_iter(mesh_cache) {
let Some(skin_data) = mesh.skin_data.as_ref() else {
continue;
};
live_vertex_bytes +=
(skin_data.skinned_vertices.len() * std::mem::size_of::<SkinnedVertex>()) as u64;
live_index_bytes += (mesh.indices.len() * std::mem::size_of::<u32>()) as u64;
}
let reclaimable = used_vertex_bytes.saturating_sub(live_vertex_bytes)
+ used_index_bytes.saturating_sub(live_index_bytes);
let utilization = if used_vertex_bytes > 0 {
live_vertex_bytes as f32 / used_vertex_bytes as f32
} else {
1.0
};
if reclaimable < COMPACTION_MIN_RECLAIM_BYTES
|| utilization >= COMPACTION_UTILIZATION_THRESHOLD
{
return false;
}
self.skinned_meshes.clear();
self.current_skinned_vertex_offset = 0;
self.current_skinned_index_offset = 0;
true
}
fn shrink_skinned_shadow_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
let used_vertex_bytes =
self.current_skinned_vertex_offset as u64 * std::mem::size_of::<SkinnedVertex>() as u64;
let used_index_bytes =
self.current_skinned_index_offset as u64 * std::mem::size_of::<u32>() as u64;
let new_vertex_size = (used_vertex_bytes * 2).min(MAX_BUFFER_SIZE);
if used_vertex_bytes > 0 && new_vertex_size < self.skinned_vertex_buffer_size {
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Vertex Buffer (Compacted)"),
size: new_vertex_size,
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Skinned Vertex Buffer Compaction"),
});
encoder.copy_buffer_to_buffer(
&self.skinned_vertex_buffer,
0,
&new_buffer,
0,
used_vertex_bytes,
);
queue.submit(Some(encoder.finish()));
self.skinned_vertex_buffer = new_buffer;
self.skinned_vertex_buffer_size = new_vertex_size;
}
let new_index_size = (used_index_bytes * 2).min(MAX_BUFFER_SIZE);
if used_index_bytes > 0 && new_index_size < self.skinned_index_buffer_size {
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Index Buffer (Compacted)"),
size: new_index_size,
usage: wgpu::BufferUsages::INDEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Skinned Index Buffer Compaction"),
});
encoder.copy_buffer_to_buffer(
&self.skinned_index_buffer,
0,
&new_buffer,
0,
used_index_bytes,
);
queue.submit(Some(encoder.finish()));
self.skinned_index_buffer = new_buffer;
self.skinned_index_buffer_size = new_index_size;
}
}
pub fn rebuild_skinned_bind_group(&mut self, device: &wgpu::Device) {
self.skinned_bind_group = buffer_bind_group(
device,
"Shadow Skinned Bind Group",
&self.skinned_bind_group_layout,
&[
&self.skinned_object_buffer,
&self.joint_matrices_buffer,
&self.skinned_visible_indices_buffer,
],
);
}
pub fn rebuild_skinned_cull_bind_groups(&mut self, device: &wgpu::Device) {
self.skinned_cull_bind_groups = self
.skinned_cull_uniform_buffers
.iter()
.map(|uniform_buffer| {
buffer_bind_group(
device,
"Shadow Skinned Cull Bind Group",
&self.skinned_cull_bind_group_layout,
&[
&self.skinned_cull_objects_buffer,
&self.skinned_indirect_buffer,
&self.skinned_visible_indices_buffer,
uniform_buffer,
],
)
})
.collect();
}
pub fn rebuild_skinning_compute_bind_group(&mut self, device: &wgpu::Device) {
self.skinning_compute_bind_group = buffer_bind_group(
device,
"Shadow Skinning Compute Bind Group",
&self.skinning_compute_bind_group_layout,
&[
&self.bone_transforms_buffer,
&self.inverse_bind_matrices_buffer,
&self.skin_data_buffer,
&self.joint_matrices_buffer,
],
);
}
pub fn spotlight_shadow_slots(&self) -> &[SpotlightShadowSlot] {
&self.spotlight_shadow_slots
}
pub fn spotlight_shadow_data(&self) -> &[SpotlightShadowData] {
&self.spotlight_shadow_data
}
pub fn cascade_data(&self) -> &CascadeData {
&self.cascade_data
}
pub fn point_light_shadow_slots(&self) -> &[PointLightShadowSlot] {
&self.point_light_shadow_slots
}
pub fn point_light_shadow_data(&self) -> &[PointLightShadowData] {
&self.point_light_shadow_data
}
pub fn point_light_cubemap_view(&self) -> &wgpu::TextureView {
&self.point_light_cubemap_view
}
}