use crate::ecs::generational_registry::registry_entry_by_name;
use crate::ecs::mesh::components::SkinnedVertex;
use crate::ecs::prefab::resources::mesh_cache_iter;
use crate::ecs::skin::systems::{GpuSkinData, SkinningCache};
use std::collections::{HashMap, HashSet};
use super::types::*;
use super::world_state::{SkinnedWorldGpuBuffers, SkinnedWorldRenderState};
pub struct SkinnedMeshPass {
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) bone_transforms_buffer: wgpu::Buffer,
pub(super) inverse_bind_matrices_buffer: wgpu::Buffer,
pub(super) joint_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) render_pipeline: wgpu::RenderPipeline,
pub(super) oit_render_pipeline: wgpu::RenderPipeline,
pub(super) oit_composite_pipeline: wgpu::RenderPipeline,
pub(super) oit_composite_bind_group_layout: wgpu::BindGroupLayout,
pub(super) uniform_buffer: wgpu::Buffer,
pub(super) uniform_bind_group: wgpu::BindGroup,
pub(super) instance_bind_group_layout: wgpu::BindGroupLayout,
pub(super) texture_bind_group_layout: wgpu::BindGroupLayout,
pub(super) shadow_bind_group: wgpu::BindGroup,
pub(super) shadow_bind_group_layout: wgpu::BindGroupLayout,
pub(super) shadow_sampler: wgpu::Sampler,
pub(super) spotlight_shadow_buffer: wgpu::Buffer,
pub(super) point_shadow_buffer: wgpu::Buffer,
pub(super) point_shadow_sampler: wgpu::Sampler,
pub(super) point_shadow_cubemap_view: wgpu::TextureView,
pub(super) dummy_white_view: wgpu::TextureView,
pub(super) dummy_black_view: wgpu::TextureView,
pub(super) dummy_normal_view: wgpu::TextureView,
pub(super) dummy_cube_view: wgpu::TextureView,
pub(super) dummy_sampler: wgpu::Sampler,
pub(super) brdf_lut_view: Option<wgpu::TextureView>,
pub(super) irradiance_map_view: Option<wgpu::TextureView>,
pub(super) prefiltered_env_view: Option<wgpu::TextureView>,
pub(super) irradiance_b_view: Option<wgpu::TextureView>,
pub(super) prefiltered_b_view: Option<wgpu::TextureView>,
pub(super) ibl_sampler: wgpu::Sampler,
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>,
pub(super) skinned_mesh_data: Vec<SkinnedMeshData>,
pub(super) current_skinned_vertex_offset: u32,
pub(super) current_skinned_index_offset: u32,
pub(super) morph_displacement_buffer: wgpu::Buffer,
pub(super) morph_displacement_buffer_size: usize,
pub(super) current_morph_displacement_offset: u32,
pub(super) total_joints_to_dispatch: u32,
pub(super) material_bind_groups: HashMap<u32, wgpu::BindGroup>,
pub(super) material_bind_group_cache_key: HashMap<u32, MaterialTextures>,
pub(super) registered_textures: HashMap<String, (wgpu::TextureView, wgpu::Sampler)>,
pub(super) skinning_cache: SkinningCache,
pub(super) oit_accum_texture: wgpu::Texture,
pub(super) oit_accum_view: wgpu::TextureView,
pub(super) oit_reveal_texture: wgpu::Texture,
pub(super) oit_reveal_view: wgpu::TextureView,
pub(super) oit_sampler: wgpu::Sampler,
pub(super) oit_texture_size: (u32, u32),
pub(super) _cluster_bounds_buffer: wgpu::Buffer,
pub(super) _view_matrix_buffer: wgpu::Buffer,
pub(super) _cluster_bounds_pipeline: wgpu::ComputePipeline,
pub(super) _cluster_assign_pipeline: wgpu::ComputePipeline,
pub(super) _cluster_bounds_bind_group: wgpu::BindGroup,
pub(super) _cluster_assign_bind_group_layout: wgpu::BindGroupLayout,
pub(super) _cluster_assign_bind_group: Option<wgpu::BindGroup>,
pub(super) _last_camera_hash: u64,
pub(super) _camera_changed: bool,
pub(super) _num_directional_lights: u32,
pub(super) current_world_id: u64,
pub(super) world_states: HashMap<u64, SkinnedWorldRenderState>,
pub(super) frame_counter: u64,
pub(crate) dirty_skinned_mesh_names: HashSet<String>,
}
fn create_skinned_shader_module(
device: &wgpu::Device,
label: &str,
source: &str,
) -> wgpu::ShaderModule {
#[cfg(target_arch = "wasm32")]
let source = strip_specular_color_texture_skinned(source);
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(label),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::from(source)),
})
}
#[cfg(target_arch = "wasm32")]
fn strip_specular_color_texture_skinned(source: &str) -> String {
let mut result = String::with_capacity(source.len());
let mut lines = source.lines().peekable();
while let Some(line) = lines.next() {
let trimmed = line.trim();
if trimmed == "@group(2) @binding(16)" || trimmed == "@group(2) @binding(17)" {
lines.next();
continue;
}
if trimmed.starts_with("if material.has_specular_color_texture") {
for inner in lines.by_ref() {
if inner.trim() == "}" {
break;
}
}
continue;
}
result.push_str(line);
result.push('\n');
}
result
}
fn skinned_texture_bind_group_layout_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
let texture_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
};
let sampler_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
};
#[allow(unused_mut)]
let mut entries = vec![
texture_entry(0),
sampler_entry(1),
texture_entry(2),
sampler_entry(3),
texture_entry(4),
sampler_entry(5),
texture_entry(6),
sampler_entry(7),
texture_entry(8),
sampler_entry(9),
texture_entry(10),
sampler_entry(11),
texture_entry(12),
sampler_entry(13),
texture_entry(14),
sampler_entry(15),
];
#[cfg(not(target_arch = "wasm32"))]
{
entries.push(texture_entry(16));
entries.push(sampler_entry(17));
}
entries
}
impl SkinnedMeshPass {
pub fn new(
device: &wgpu::Device,
_queue: &wgpu::Queue,
color_format: wgpu::TextureFormat,
depth_format: wgpu::TextureFormat,
) -> Self {
let skinning_compute_shader = device.create_shader_module(wgpu::include_wgsl!(
"../../../shaders/skinning_compute.wgsl"
));
let skinned_mesh_shader = create_skinned_shader_module(
device,
"skinned_mesh.wgsl",
include_str!("../../../shaders/skinned_mesh.wgsl"),
);
let bone_transforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("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("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("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("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_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("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 skinning_compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinning Compute Bind Group"),
layout: &skinning_compute_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: bone_transforms_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: inverse_bind_matrices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: skin_data_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: joint_matrices_buffer.as_entire_binding(),
},
],
});
let skinning_compute_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Skinning Compute Pipeline Layout"),
bind_group_layouts: &[&skinning_compute_bind_group_layout],
push_constant_ranges: &[],
});
let skinning_compute_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Skinning Compute Pipeline"),
layout: Some(&skinning_compute_pipeline_layout),
module: &skinning_compute_shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Uniform Buffer"),
size: std::mem::size_of::<SkinnedMeshUniforms>() 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("Skinned Mesh 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: false,
min_binding_size: None,
},
count: None,
}],
});
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinned Mesh Uniform Bind Group"),
layout: &uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
let morph_displacement_buffer_size = INITIAL_MORPH_DISPLACEMENT_COUNT;
let morph_displacement_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Morph Displacement Buffer"),
size: (std::mem::size_of::<MorphDisplacement>() * morph_displacement_buffer_size)
as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let cluster_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Cluster Uniforms Buffer"),
size: std::mem::size_of::<ClusterUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let cluster_bounds_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Cluster Bounds Buffer"),
size: (std::mem::size_of::<ClusterBounds>() * TOTAL_CLUSTERS as usize) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let view_matrix_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh View Matrix Buffer"),
size: std::mem::size_of::<[[f32; 4]; 4]>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let instance_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Skinned Mesh Instance Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
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::FRAGMENT,
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::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: 4,
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: 5,
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: 7,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 8,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 9,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Skinned Mesh Texture Bind Group Layout"),
entries: &skinned_texture_bind_group_layout_entries(),
});
let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Skinned Mesh Shadow Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let spotlight_shadow_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Spotlight Shadow Buffer"),
size: (std::mem::size_of::<crate::render::wgpu::passes::SpotlightShadowData>()
* MAX_SPOTLIGHT_SHADOWS) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let shadow_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Skinned Mesh Shadow Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 6,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 7,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 8,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 9,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 10,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 11,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::CubeArray,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 12,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 13,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 14,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 15,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
],
});
let dummy_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh Dummy Depth Texture"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let dummy_depth_view = dummy_depth_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("Skinned Mesh Dummy Depth View"),
..Default::default()
});
let dummy_brdf_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh Dummy BRDF LUT"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let dummy_brdf_view =
dummy_brdf_texture.create_view(&wgpu::TextureViewDescriptor::default());
let dummy_ibl_cube = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh Dummy IBL Cube"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 6,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let dummy_ibl_cube_view = dummy_ibl_cube.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let ibl_sampler_init = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Skinned Mesh IBL Init 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::FilterMode::Linear,
..Default::default()
});
let point_shadow_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Point Light Shadow Buffer"),
size: (std::mem::size_of::<PointLightShadowData>() * MAX_POINT_LIGHT_SHADOWS) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let point_shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Skinned Mesh Point Shadow Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let dummy_point_shadow_cubemap = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh Dummy Point Shadow Cubemap Array"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 6 * MAX_POINT_LIGHT_SHADOWS as u32,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let dummy_point_shadow_view =
dummy_point_shadow_cubemap.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::CubeArray),
..Default::default()
});
let shadow_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinned Mesh Shadow Bind Group"),
layout: &shadow_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&dummy_depth_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&dummy_depth_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: spotlight_shadow_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(&dummy_brdf_view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::Sampler(&ibl_sampler_init),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(&dummy_ibl_cube_view),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::Sampler(&ibl_sampler_init),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::TextureView(&dummy_ibl_cube_view),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::Sampler(&ibl_sampler_init),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::TextureView(&dummy_point_shadow_view),
},
wgpu::BindGroupEntry {
binding: 12,
resource: wgpu::BindingResource::Sampler(&point_shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 13,
resource: point_shadow_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 14,
resource: wgpu::BindingResource::TextureView(&dummy_ibl_cube_view),
},
wgpu::BindGroupEntry {
binding: 15,
resource: wgpu::BindingResource::TextureView(&dummy_ibl_cube_view),
},
],
});
let dummy_white_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh Dummy White Texture"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let dummy_white_view = dummy_white_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("Skinned Mesh Dummy White View"),
..Default::default()
});
let dummy_black_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh Dummy Black Texture"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let dummy_black_view = dummy_black_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("Skinned Mesh Dummy Black View"),
..Default::default()
});
let dummy_normal_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh Dummy Normal Texture"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let dummy_normal_view = dummy_normal_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("Skinned Mesh Dummy Normal View"),
..Default::default()
});
let dummy_cube_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh Dummy Cube Texture"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 6,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let dummy_cube_view = dummy_cube_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("Skinned Mesh Dummy Cube View"),
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Skinned Mesh Dummy 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::FilterMode::Nearest,
..Default::default()
});
let ibl_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Skinned Mesh IBL 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::FilterMode::Linear,
..Default::default()
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Skinned Mesh Pipeline Layout"),
bind_group_layouts: &[
&uniform_bind_group_layout,
&instance_bind_group_layout,
&texture_bind_group_layout,
&shadow_bind_group_layout,
],
push_constant_ranges: &[],
});
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 render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Skinned Mesh Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &skinned_mesh_shader,
entry_point: Some("vs_main"),
buffers: std::slice::from_ref(&skinned_vertex_layout),
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &skinned_mesh_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
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: depth_format,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::GreaterEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let skinned_mesh_oit_shader = create_skinned_shader_module(
device,
"skinned_mesh_oit.wgsl",
include_str!("../../../shaders/skinned_mesh_oit.wgsl"),
);
let oit_render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Skinned Mesh OIT Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &skinned_mesh_oit_shader,
entry_point: Some("vs_main"),
buffers: &[skinned_vertex_layout],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &skinned_mesh_oit_shader,
entry_point: Some("fs_main"),
targets: &[
Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba16Float,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
}),
Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::R16Float,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::OneMinusSrc,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
}),
],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: depth_format,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::GreaterEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let oit_composite_shader =
device.create_shader_module(wgpu::include_wgsl!("../../../shaders/oit_composite.wgsl"));
let oit_composite_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Skinned Mesh OIT Composite Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let oit_composite_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Skinned Mesh OIT Composite Pipeline Layout"),
bind_group_layouts: &[&oit_composite_bind_group_layout],
push_constant_ranges: &[],
});
let oit_composite_pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Skinned Mesh OIT Composite Pipeline"),
layout: Some(&oit_composite_pipeline_layout),
vertex: wgpu::VertexState {
module: &oit_composite_shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &oit_composite_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent::OVER,
}),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let cluster_bounds_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Skinned Mesh Cluster Bounds Shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
"../../../shaders/cluster_bounds.wgsl"
))),
});
let cluster_assign_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Skinned Mesh Cluster Light Assign Shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
"../../../shaders/cluster_light_assign.wgsl"
))),
});
let cluster_bounds_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Skinned Mesh Cluster Bounds Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
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: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let cluster_bounds_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinned Mesh Cluster Bounds Bind Group"),
layout: &cluster_bounds_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: cluster_uniforms_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: cluster_bounds_buffer.as_entire_binding(),
},
],
});
let cluster_bounds_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Skinned Mesh Cluster Bounds Pipeline Layout"),
bind_group_layouts: &[&cluster_bounds_bind_group_layout],
push_constant_ranges: &[],
});
let cluster_bounds_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Skinned Mesh Cluster Bounds Pipeline"),
layout: Some(&cluster_bounds_pipeline_layout),
module: &cluster_bounds_shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let cluster_assign_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Skinned Mesh Cluster Light Assign Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
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: false },
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,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
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: 5,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let cluster_assign_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Skinned Mesh Cluster Light Assign Pipeline Layout"),
bind_group_layouts: &[&cluster_assign_bind_group_layout],
push_constant_ranges: &[],
});
let cluster_assign_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Skinned Mesh Cluster Light Assign Pipeline"),
layout: Some(&cluster_assign_pipeline_layout),
module: &cluster_assign_shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let oit_accum_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh OIT Accum Texture"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let oit_accum_view = oit_accum_texture.create_view(&wgpu::TextureViewDescriptor::default());
let oit_reveal_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh OIT Reveal Texture"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R16Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let oit_reveal_view =
oit_reveal_texture.create_view(&wgpu::TextureViewDescriptor::default());
let oit_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Skinned Mesh OIT 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::FilterMode::Nearest,
..Default::default()
});
let skinned_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("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("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,
});
Self {
skinning_compute_pipeline,
skinning_compute_bind_group,
_skinning_compute_bind_group_layout: skinning_compute_bind_group_layout,
bone_transforms_buffer,
inverse_bind_matrices_buffer,
joint_matrices_buffer,
skin_data_buffer,
bone_transforms_buffer_size: INITIAL_MAX_TOTAL_BONES,
inverse_bind_matrices_buffer_size: MAX_JOINTS_PER_SKIN * INITIAL_MAX_SKINS,
joint_matrices_buffer_size: MAX_JOINTS_PER_SKIN * INITIAL_MAX_SKINS,
skin_data_buffer_size: INITIAL_MAX_SKINS,
render_pipeline,
oit_render_pipeline,
oit_composite_pipeline,
oit_composite_bind_group_layout,
uniform_buffer,
uniform_bind_group,
instance_bind_group_layout,
texture_bind_group_layout,
shadow_bind_group,
shadow_bind_group_layout,
shadow_sampler,
spotlight_shadow_buffer,
point_shadow_buffer,
point_shadow_sampler,
point_shadow_cubemap_view: dummy_point_shadow_view,
dummy_white_view,
dummy_black_view,
dummy_normal_view,
dummy_cube_view,
dummy_sampler,
brdf_lut_view: None,
irradiance_map_view: None,
prefiltered_env_view: None,
irradiance_b_view: None,
prefiltered_b_view: None,
ibl_sampler,
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(),
skinned_mesh_data: Vec::new(),
current_skinned_vertex_offset: 0,
current_skinned_index_offset: 0,
morph_displacement_buffer,
morph_displacement_buffer_size,
current_morph_displacement_offset: 0,
total_joints_to_dispatch: 0,
material_bind_groups: HashMap::new(),
material_bind_group_cache_key: HashMap::new(),
registered_textures: HashMap::new(),
skinning_cache: SkinningCache::default(),
oit_accum_texture,
oit_accum_view,
oit_reveal_texture,
oit_reveal_view,
oit_sampler,
oit_texture_size: (1, 1),
_cluster_bounds_buffer: cluster_bounds_buffer,
_view_matrix_buffer: view_matrix_buffer,
_cluster_bounds_pipeline: cluster_bounds_pipeline,
_cluster_assign_pipeline: cluster_assign_pipeline,
_cluster_bounds_bind_group: cluster_bounds_bind_group,
_cluster_assign_bind_group_layout: cluster_assign_bind_group_layout,
_cluster_assign_bind_group: None,
_last_camera_hash: 0,
_camera_changed: true,
_num_directional_lights: 0,
current_world_id: 0,
world_states: HashMap::new(),
frame_counter: 0,
dirty_skinned_mesh_names: HashSet::new(),
}
}
pub fn set_current_world(&mut self, world_id: u64) {
self.current_world_id = world_id;
self.world_states.entry(world_id).or_default();
}
pub(super) fn state(&self) -> &SkinnedWorldRenderState {
self.world_states.get(&self.current_world_id).unwrap()
}
pub(super) fn state_mut(&mut self) -> &mut SkinnedWorldRenderState {
self.world_states.get_mut(&self.current_world_id).unwrap()
}
pub(super) fn gpu(&self) -> &SkinnedWorldGpuBuffers {
self.state().gpu_buffers.as_ref().unwrap()
}
pub(super) fn ensure_world_gpu_buffers(&mut self, device: &wgpu::Device) {
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
if world_state.gpu_buffers.is_none() {
world_state.gpu_buffers = Some(SkinnedWorldGpuBuffers::new(
device,
&self.instance_bind_group_layout,
&self.joint_matrices_buffer,
&self.morph_displacement_buffer,
));
}
}
pub(super) fn rebuild_instance_bind_group(&mut self, device: &wgpu::Device) {
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
if let Some(gpu) = world_state.gpu_buffers.as_mut() {
gpu.instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinned Mesh Instance Bind Group (Per-World, Rebuilt)"),
layout: &self.instance_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: gpu.materials_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: gpu.object_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: gpu.lights_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: self.joint_matrices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: gpu.custom_data_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: self.morph_displacement_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 7,
resource: gpu.light_grid_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 8,
resource: gpu.light_indices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 9,
resource: gpu.cluster_uniforms_buffer.as_entire_binding(),
},
],
});
}
}
pub fn update_ibl_textures_for_world(
&mut self,
world_id: u64,
brdf_lut_view: wgpu::TextureView,
irradiance_view: wgpu::TextureView,
prefiltered_view: wgpu::TextureView,
) {
if let Some(world_state) = self.world_states.get_mut(&world_id) {
world_state.ibl_brdf_lut_view = Some(brdf_lut_view);
world_state.ibl_irradiance_view = Some(irradiance_view);
world_state.ibl_prefiltered_view = Some(prefiltered_view);
}
}
pub fn update_ibl_textures_blended_for_world(&mut self, world_id: u64, views: BlendedIblViews) {
if let Some(world_state) = self.world_states.get_mut(&world_id) {
world_state.ibl_brdf_lut_view = Some(views.brdf_lut);
world_state.ibl_irradiance_view = Some(views.irradiance_a);
world_state.ibl_prefiltered_view = Some(views.prefiltered_a);
world_state.ibl_irradiance_b_view = Some(views.irradiance_b);
world_state.ibl_prefiltered_b_view = Some(views.prefiltered_b);
world_state.ibl_blend_factor = views.blend_factor;
}
}
pub fn cleanup_stale_world_states(&mut self) {
const STALE_THRESHOLD_FRAMES: u64 = 300;
let current_frame = self.frame_counter;
self.world_states.retain(|world_id, state| {
let is_current = *world_id == self.current_world_id;
let is_recent =
current_frame.saturating_sub(state.last_used_frame) < STALE_THRESHOLD_FRAMES;
is_current || is_recent
});
}
pub fn resize_oit_textures(&mut self, device: &wgpu::Device, width: u32, height: u32) {
if self.oit_texture_size == (width, height) || width == 0 || height == 0 {
return;
}
self.oit_accum_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh OIT Accum Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
self.oit_accum_view = self
.oit_accum_texture
.create_view(&wgpu::TextureViewDescriptor::default());
self.oit_reveal_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Skinned Mesh OIT Reveal Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R16Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
self.oit_reveal_view = self
.oit_reveal_texture
.create_view(&wgpu::TextureViewDescriptor::default());
self.oit_texture_size = (width, height);
}
pub(super) fn sync_skinned_meshes(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mesh_cache: &crate::ecs::prefab::resources::MeshCache,
) {
for dirty_name in std::mem::take(&mut self.dirty_skinned_mesh_names) {
if let Some(&mesh_id) = self.skinned_meshes.get(&dirty_name)
&& let Some(mesh) = registry_entry_by_name(&mesh_cache.registry, &dirty_name)
&& let Some(skin_data) = &mesh.skin_data
{
let mesh_data = &self.skinned_mesh_data[mesh_id as usize];
if skin_data.skinned_vertices.len() as u32 == mesh_data.vertex_count {
let vertex_data = bytemuck::cast_slice(&skin_data.skinned_vertices);
let vertex_offset_bytes = (mesh_data.vertex_offset as usize
* std::mem::size_of::<SkinnedVertex>())
as u64;
queue.write_buffer(
&self.skinned_vertex_buffer,
vertex_offset_bytes,
vertex_data,
);
}
}
}
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 > self.skinned_vertex_buffer_size {
let new_size = (required_vertex_size * 2).max(self.skinned_vertex_buffer_size * 2);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("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("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);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("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("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),
);
let (morph_displacement_offset, morph_target_count) = if let Some(morph_targets) =
&mesh.morph_targets
{
let target_count = morph_targets.targets.len().min(8);
let vertex_count = skinned_vertices.len();
let total_displacements = target_count * vertex_count;
let required_size =
self.current_morph_displacement_offset as usize + total_displacements;
if required_size > self.morph_displacement_buffer_size {
let new_size = (required_size as f32 * 2.0).ceil() as usize;
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Morph Displacement Buffer (Resized)"),
size: (std::mem::size_of::<MorphDisplacement>() * new_size) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Skinned Mesh Morph Displacement Buffer Copy"),
});
encoder.copy_buffer_to_buffer(
&self.morph_displacement_buffer,
0,
&new_buffer,
0,
(std::mem::size_of::<MorphDisplacement>()
* self.morph_displacement_buffer_size) as u64,
);
queue.submit(Some(encoder.finish()));
self.morph_displacement_buffer = new_buffer;
self.morph_displacement_buffer_size = new_size;
self.rebuild_instance_bind_group(device);
}
let mut displacements = Vec::with_capacity(total_displacements);
for target_index in 0..target_count {
let target = &morph_targets.targets[target_index];
for vertex_index in 0..vertex_count {
let position = target
.position_displacements
.get(vertex_index)
.copied()
.unwrap_or([0.0, 0.0, 0.0]);
let normal = target
.normal_displacements
.as_ref()
.and_then(|n| n.get(vertex_index))
.copied()
.unwrap_or([0.0, 0.0, 0.0]);
let tangent = target
.tangent_displacements
.as_ref()
.and_then(|t| t.get(vertex_index))
.copied()
.unwrap_or([0.0, 0.0, 0.0]);
displacements.push(MorphDisplacement {
position,
_pad0: 0.0,
normal,
_pad1: 0.0,
tangent,
_pad2: 0.0,
});
}
}
if !displacements.is_empty() {
queue.write_buffer(
&self.morph_displacement_buffer,
(self.current_morph_displacement_offset as usize
* std::mem::size_of::<MorphDisplacement>())
as u64,
bytemuck::cast_slice(&displacements),
);
}
let offset = self.current_morph_displacement_offset;
self.current_morph_displacement_offset += total_displacements as u32;
(offset, target_count as u32)
} else {
(0, 0)
};
let mesh_id = self.skinned_mesh_data.len() as u32;
self.skinned_mesh_data.push(SkinnedMeshData {
vertex_offset: self.current_skinned_vertex_offset,
vertex_count: skinned_vertices.len() as u32,
index_offset: self.current_skinned_index_offset,
index_count: mesh.indices.len() as u32,
_skin_index: skin_data.skin_index.unwrap_or(0) as u32,
morph_displacement_offset,
morph_target_count,
});
self.skinned_meshes.insert(name.clone(), mesh_id);
self.current_skinned_vertex_offset += skinned_vertices.len() as u32;
self.current_skinned_index_offset += mesh.indices.len() as u32;
}
}
pub(super) fn rebuild_skinning_compute_bind_group(&mut self, device: &wgpu::Device) {
self.skinning_compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinning Compute Bind Group"),
layout: &self._skinning_compute_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.bone_transforms_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: self.inverse_bind_matrices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.skin_data_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: self.joint_matrices_buffer.as_entire_binding(),
},
],
});
}
pub fn update_shadow_bind_group(
&mut self,
device: &wgpu::Device,
shadow_view: &wgpu::TextureView,
spotlight_shadow_view: &wgpu::TextureView,
) {
let brdf_view = self
.brdf_lut_view
.as_ref()
.unwrap_or(&self.dummy_white_view);
let irradiance_view = self
.irradiance_map_view
.as_ref()
.unwrap_or(&self.dummy_cube_view);
let prefiltered_view = self
.prefiltered_env_view
.as_ref()
.unwrap_or(&self.dummy_cube_view);
self.shadow_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinned Mesh Shadow Bind Group"),
layout: &self.shadow_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(shadow_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(spotlight_shadow_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: self.spotlight_shadow_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(brdf_view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(irradiance_view),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::TextureView(prefiltered_view),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::TextureView(&self.point_shadow_cubemap_view),
},
wgpu::BindGroupEntry {
binding: 12,
resource: wgpu::BindingResource::Sampler(&self.point_shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 13,
resource: self.point_shadow_buffer.as_entire_binding(),
},
],
});
}
pub fn update_point_shadow_cubemap(&mut self, view: wgpu::TextureView) {
self.point_shadow_cubemap_view = view;
}
pub fn register_texture(
&mut self,
name: String,
view: wgpu::TextureView,
sampler: wgpu::Sampler,
) {
self.registered_textures.insert(name, (view, sampler));
}
pub fn set_ibl_textures(
&mut self,
brdf_lut_view: wgpu::TextureView,
irradiance_map_view: wgpu::TextureView,
prefiltered_env_view: wgpu::TextureView,
) {
self.brdf_lut_view = Some(brdf_lut_view);
self.irradiance_map_view = Some(irradiance_map_view);
self.prefiltered_env_view = Some(prefiltered_env_view);
}
pub fn sync_textures(
&mut self,
texture_cache: &crate::render::wgpu::texture_cache::TextureCache,
) {
if texture_cache.registry.name_to_index.is_empty() {
return;
}
self.registered_textures
.retain(|name, _| texture_cache.registry.name_to_index.contains_key(name));
}
}