use crate::ecs::generational_registry::{registry_entry, registry_entry_by_name};
use crate::ecs::skin::systems::GpuSkinData;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use std::collections::HashMap;
use super::super::projection::*;
use super::pass::SkinnedMeshPass;
use super::types::*;
impl PassNode<crate::ecs::world::World> for SkinnedMeshPass {
fn name(&self) -> &str {
"skinned_mesh_pass"
}
fn reads(&self) -> Vec<&str> {
vec!["shadow_depth", "spotlight_shadow_atlas"]
}
fn writes(&self) -> Vec<&str> {
vec![]
}
fn reads_writes(&self) -> Vec<&str> {
vec!["color", "depth"]
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
world: &crate::ecs::world::World,
) {
self.set_current_world(world.resources.world_id);
self.ensure_world_gpu_buffers(device);
self.frame_counter += 1;
self.state_mut().last_used_frame = self.frame_counter;
self.sync_skinned_meshes(device, queue, &world.resources.mesh_cache);
self.state_mut().skinned_entities.clear();
self.state_mut().opaque_skinned_entities.clear();
self.state_mut().transparent_skinned_entities.clear();
let mut objects = Vec::new();
let mut materials = Vec::new();
let mut custom_data = Vec::new();
let mut material_texture_map: HashMap<u32, MaterialTextures> = HashMap::new();
let mut entity_is_transparent: Vec<bool> = Vec::new();
let mut buffers_resized = false;
let max_buffer_size = device.limits().max_buffer_size as usize;
let matrix_size = std::mem::size_of::<[[f32; 4]; 4]>();
if self.skinning_cache.needs_rebuild(world) {
self.skinning_cache.rebuild_static_data(world);
if !self.skinning_cache.inverse_bind_matrices.is_empty() {
let required_size = self.skinning_cache.inverse_bind_matrices.len();
if required_size > self.inverse_bind_matrices_buffer_size {
let new_size = (required_size as f32 * 2.0).ceil() as usize;
let buffer_bytes = matrix_size * new_size;
if buffer_bytes > max_buffer_size {
tracing::error!(
"Inverse bind matrices buffer would exceed GPU limit: {} > {} bytes",
buffer_bytes,
max_buffer_size
);
} else {
self.inverse_bind_matrices_buffer =
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Inverse Bind Matrices Buffer (Resized)"),
size: buffer_bytes as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.inverse_bind_matrices_buffer_size = new_size;
buffers_resized = true;
}
}
if self.skinning_cache.inverse_bind_matrices.len()
<= self.inverse_bind_matrices_buffer_size
{
queue.write_buffer(
&self.inverse_bind_matrices_buffer,
0,
bytemuck::cast_slice(&self.skinning_cache.inverse_bind_matrices),
);
}
}
if !self.skinning_cache.skin_data.is_empty() {
let required_size = self.skinning_cache.skin_data.len();
if required_size > self.skin_data_buffer_size {
let new_size = (required_size as f32 * 2.0).ceil() as usize;
let buffer_bytes = std::mem::size_of::<GpuSkinData>() * new_size;
if buffer_bytes > max_buffer_size {
tracing::error!(
"Skin data buffer would exceed GPU limit: {} > {} bytes",
buffer_bytes,
max_buffer_size
);
} else {
self.skin_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skin Data Buffer (Resized)"),
size: buffer_bytes as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.skin_data_buffer_size = new_size;
buffers_resized = true;
}
}
if self.skinning_cache.skin_data.len() <= self.skin_data_buffer_size {
queue.write_buffer(
&self.skin_data_buffer,
0,
bytemuck::cast_slice(&self.skinning_cache.skin_data),
);
}
}
}
let bone_transforms = self.skinning_cache.collect_bone_transforms(world);
if !bone_transforms.is_empty() {
let required_size = bone_transforms.len();
if required_size > self.bone_transforms_buffer_size {
let new_size = (required_size as f32 * 2.0).ceil() as usize;
let buffer_bytes = matrix_size * new_size;
if buffer_bytes > max_buffer_size {
tracing::error!(
"Bone transforms buffer would exceed GPU limit: {} > {} bytes",
buffer_bytes,
max_buffer_size
);
} else {
self.bone_transforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Bone Transforms Buffer (Resized)"),
size: buffer_bytes as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.bone_transforms_buffer_size = new_size;
buffers_resized = true;
}
}
if bone_transforms.len() <= self.bone_transforms_buffer_size {
queue.write_buffer(
&self.bone_transforms_buffer,
0,
bytemuck::cast_slice(&bone_transforms),
);
}
}
let required_joints = self.skinning_cache.total_joints as usize;
if required_joints > self.joint_matrices_buffer_size {
let new_size = (required_joints as f32 * 2.0).ceil() as usize;
let buffer_bytes = matrix_size * new_size;
if buffer_bytes > max_buffer_size {
tracing::error!(
"Joint matrices buffer would exceed GPU limit: {} > {} bytes",
buffer_bytes,
max_buffer_size
);
} else {
self.joint_matrices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Joint Matrices Buffer (Resized)"),
size: buffer_bytes as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.joint_matrices_buffer_size = new_size;
buffers_resized = true;
self.rebuild_instance_bind_group(device);
}
}
if buffers_resized {
self.rebuild_skinning_compute_bind_group(device);
}
self.total_joints_to_dispatch = self.skinning_cache.total_joints;
for entity in world.query_entities(
crate::ecs::world::SKIN
| crate::ecs::world::RENDER_MESH
| crate::ecs::world::GLOBAL_TRANSFORM,
) {
if world.entity_has_components(entity, crate::ecs::world::WATER) {
continue;
}
if let Some(visibility) = world.get_visibility(entity)
&& !visibility.visible
{
continue;
}
let render_mesh = match world.get_render_mesh(entity) {
Some(rm) => rm,
None => continue,
};
let mesh_id = match self.skinned_meshes.get(&render_mesh.name) {
Some(&id) => id,
None => continue,
};
let skin_index = self
.skinning_cache
.entity_skin_indices
.get(&entity)
.copied()
.unwrap_or(0);
let joint_offset = self.skinning_cache.get_joint_offset(skin_index);
let material_id = materials.len() as u32;
let material_data = if let Some(material_ref) = world.get_material_ref(entity) {
let material_opt = if let Some(id) = material_ref.id {
registry_entry(
&world.resources.material_registry.registry,
id.index,
id.generation,
)
} else {
registry_entry_by_name(
&world.resources.material_registry.registry,
&material_ref.name,
)
};
if let Some(material) = material_opt {
let alpha_mode = match material.alpha_mode {
crate::ecs::material::components::AlphaMode::Opaque => 0,
crate::ecs::material::components::AlphaMode::Mask => 1,
crate::ecs::material::components::AlphaMode::Blend => 2,
};
let mut flags = 0u32;
if material.normal_map_flip_y {
flags |= NORMAL_MAP_FLIP_Y;
}
if material.normal_map_two_component {
flags |= NORMAL_MAP_TWO_COMPONENT;
}
material_texture_map.insert(
material_id,
MaterialTextures {
base_texture: material.base_texture.clone(),
emissive_texture: material.emissive_texture.clone(),
normal_texture: material.normal_texture.clone(),
metallic_roughness_texture: material.metallic_roughness_texture.clone(),
occlusion_texture: material.occlusion_texture.clone(),
transmission_texture: material.transmission_texture.clone(),
thickness_texture: material.thickness_texture.clone(),
specular_texture: material.specular_texture.clone(),
specular_color_texture: material.specular_color_texture.clone(),
},
);
MaterialData {
base_color: material.base_color,
emissive_factor: material.emissive_factor,
alpha_mode,
alpha_cutoff: material.alpha_cutoff,
has_base_texture: material.base_texture.is_some() as u32,
has_emissive_texture: material.emissive_texture.is_some() as u32,
has_normal_texture: material.normal_texture.is_some() as u32,
has_metallic_roughness_texture: material
.metallic_roughness_texture
.is_some()
as u32,
has_occlusion_texture: material.occlusion_texture.is_some() as u32,
normal_scale: material.normal_scale,
occlusion_strength: material.occlusion_strength,
roughness: material.roughness,
metallic: material.metallic,
unlit: material.unlit as u32,
normal_map_flags: flags,
uv_scale: material.uv_scale,
transmission_factor: material.transmission_factor,
has_transmission_texture: material.transmission_texture.is_some() as u32,
thickness: material.thickness,
has_thickness_texture: material.thickness_texture.is_some() as u32,
_align_attenuation: [0, 0],
attenuation_color: material.attenuation_color,
attenuation_distance: material.attenuation_distance,
ior: material.ior,
specular_factor: material.specular_factor,
_align_specular: [0, 0],
specular_color_factor: material.specular_color_factor,
has_specular_texture: material.specular_texture.is_some() as u32,
has_specular_color_texture: material.specular_color_texture.is_some()
as u32,
emissive_strength: material.emissive_strength,
uv_set_indices: pack_uv_set_indices(material),
_padding: 0,
}
} else {
MaterialData {
base_color: [1.0, 1.0, 1.0, 1.0],
emissive_factor: [0.0, 0.0, 0.0],
alpha_mode: 0,
alpha_cutoff: 0.5,
has_base_texture: 0,
has_emissive_texture: 0,
has_normal_texture: 0,
has_metallic_roughness_texture: 0,
has_occlusion_texture: 0,
normal_scale: 1.0,
occlusion_strength: 1.0,
roughness: 0.5,
metallic: 0.0,
unlit: 0,
normal_map_flags: 0,
uv_scale: [1.0, 1.0],
transmission_factor: 0.0,
has_transmission_texture: 0,
thickness: 0.0,
has_thickness_texture: 0,
_align_attenuation: [0, 0],
attenuation_color: [1.0, 1.0, 1.0],
attenuation_distance: f32::INFINITY,
ior: 1.5,
specular_factor: 1.0,
_align_specular: [0, 0],
specular_color_factor: [1.0, 1.0, 1.0],
has_specular_texture: 0,
has_specular_color_texture: 0,
emissive_strength: 1.0,
uv_set_indices: 0,
_padding: 0,
}
}
} else {
MaterialData {
base_color: [1.0, 1.0, 1.0, 1.0],
emissive_factor: [0.0, 0.0, 0.0],
alpha_mode: 0,
alpha_cutoff: 0.5,
has_base_texture: 0,
has_emissive_texture: 0,
has_normal_texture: 0,
has_metallic_roughness_texture: 0,
has_occlusion_texture: 0,
normal_scale: 1.0,
occlusion_strength: 1.0,
roughness: 0.5,
metallic: 0.0,
unlit: 0,
normal_map_flags: 0,
uv_scale: [1.0, 1.0],
transmission_factor: 0.0,
has_transmission_texture: 0,
thickness: 0.0,
has_thickness_texture: 0,
_align_attenuation: [0, 0],
attenuation_color: [1.0, 1.0, 1.0],
attenuation_distance: f32::INFINITY,
ior: 1.5,
specular_factor: 1.0,
_align_specular: [0, 0],
specular_color_factor: [1.0, 1.0, 1.0],
has_specular_texture: 0,
has_specular_color_texture: 0,
emissive_strength: 1.0,
uv_set_indices: 0,
_padding: 0,
}
};
let is_transparent =
material_data.alpha_mode == 2 || material_data.transmission_factor > 0.0;
entity_is_transparent.push(is_transparent);
materials.push(material_data);
custom_data.push([1.0f32, 1.0, 1.0, 1.0]);
let mesh_data = &self.skinned_mesh_data[mesh_id as usize];
let mut morph_weights = [0.0f32; 8];
if let Some(morph_component) = world.get_morph_weights(entity) {
for (index, weight) in morph_component.weights.iter().take(8).enumerate() {
morph_weights[index] = *weight;
}
}
objects.push(SkinnedObjectData {
transform_index: 0,
mesh_id,
material_id,
joint_offset,
morph_weights,
morph_target_count: mesh_data.morph_target_count,
morph_displacement_offset: mesh_data.morph_displacement_offset,
mesh_vertex_offset: mesh_data.vertex_offset,
mesh_vertex_count: mesh_data.vertex_count,
});
self.state_mut().skinned_entities.push(entity);
}
{
let skinned_entities: Vec<_> = self.state().skinned_entities.clone();
for (index, &entity) in skinned_entities.iter().enumerate() {
if entity_is_transparent[index] {
self.state_mut().transparent_skinned_entities.push(entity);
} else {
self.state_mut().opaque_skinned_entities.push(entity);
}
}
}
if objects.is_empty() {
return;
}
{
let gpu_object_buffer_size = self.gpu().object_buffer_size;
if objects.len() > gpu_object_buffer_size {
let new_size = (objects.len() as f32 * 2.0).ceil() as usize;
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
let gpu = world_state.gpu_buffers.as_mut().unwrap();
gpu.object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Object Buffer (Per-World, Resized)"),
size: (std::mem::size_of::<SkinnedObjectData>() * new_size) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu.object_buffer_size = new_size;
}
self.rebuild_instance_bind_group(device);
}
{
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
let gpu = world_state.gpu_buffers.as_mut().unwrap();
if materials.len() > gpu.materials_buffer_size {
let new_size = (materials.len() as f32 * 2.0).ceil() as usize;
gpu.materials_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Materials Buffer (Per-World, Resized)"),
size: (std::mem::size_of::<MaterialData>() * new_size) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu.materials_buffer_size = new_size;
}
}
{
let gpu_custom_data_buffer_size = self.gpu().custom_data_buffer_size;
if custom_data.len() > gpu_custom_data_buffer_size {
let new_size = (custom_data.len() as f32 * 2.0).ceil() as usize;
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
let gpu = world_state.gpu_buffers.as_mut().unwrap();
gpu.custom_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Custom Data Buffer (Per-World, Resized)"),
size: (std::mem::size_of::<[f32; 4]>() * new_size) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu.custom_data_buffer_size = new_size;
}
self.rebuild_instance_bind_group(device);
}
queue.write_buffer(&self.gpu().object_buffer, 0, bytemuck::cast_slice(&objects));
queue.write_buffer(
&self.gpu().materials_buffer,
0,
bytemuck::cast_slice(&materials),
);
queue.write_buffer(
&self.gpu().custom_data_buffer,
0,
bytemuck::cast_slice(&custom_data),
);
self.sync_textures(&world.resources.texture_cache);
for material_id in 0..materials.len() as u32 {
let cache_key = material_texture_map
.get(&material_id)
.cloned()
.unwrap_or_default();
if self.material_bind_groups.contains_key(&material_id)
&& self.material_bind_group_cache_key.get(&material_id) == Some(&cache_key)
{
continue;
}
let (base_texture_view, base_sampler) = if let Some(ref name) = cache_key.base_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
} else {
(&self.dummy_white_view, &self.dummy_sampler)
};
let (emissive_texture_view, emissive_sampler) =
if let Some(ref name) = cache_key.emissive_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_black_view, &self.dummy_sampler))
} else {
(&self.dummy_black_view, &self.dummy_sampler)
};
let (normal_texture_view, normal_sampler) =
if let Some(ref name) = cache_key.normal_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_normal_view, &self.dummy_sampler))
} else {
(&self.dummy_normal_view, &self.dummy_sampler)
};
let (metallic_roughness_view, metallic_roughness_sampler) =
if let Some(ref name) = cache_key.metallic_roughness_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
} else {
(&self.dummy_white_view, &self.dummy_sampler)
};
let (occlusion_texture_view, occlusion_sampler) =
if let Some(ref name) = cache_key.occlusion_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
} else {
(&self.dummy_white_view, &self.dummy_sampler)
};
let (transmission_texture_view, transmission_sampler) =
if let Some(ref name) = cache_key.transmission_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
} else {
(&self.dummy_white_view, &self.dummy_sampler)
};
let (thickness_texture_view, thickness_sampler) =
if let Some(ref name) = cache_key.thickness_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
} else {
(&self.dummy_white_view, &self.dummy_sampler)
};
let (specular_texture_view, specular_sampler) =
if let Some(ref name) = cache_key.specular_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
} else {
(&self.dummy_white_view, &self.dummy_sampler)
};
#[cfg(not(target_arch = "wasm32"))]
let (specular_color_texture_view, specular_color_sampler) =
if let Some(ref name) = cache_key.specular_color_texture {
self.registered_textures
.get(name)
.map(|(v, s)| (v, s))
.unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
} else {
(&self.dummy_white_view, &self.dummy_sampler)
};
#[allow(unused_mut)]
let mut bind_group_entries = vec![
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(base_texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(base_sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(emissive_texture_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(emissive_sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(normal_texture_view),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::Sampler(normal_sampler),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::TextureView(metallic_roughness_view),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::Sampler(metallic_roughness_sampler),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::TextureView(occlusion_texture_view),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::Sampler(occlusion_sampler),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::TextureView(transmission_texture_view),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::Sampler(transmission_sampler),
},
wgpu::BindGroupEntry {
binding: 12,
resource: wgpu::BindingResource::TextureView(thickness_texture_view),
},
wgpu::BindGroupEntry {
binding: 13,
resource: wgpu::BindingResource::Sampler(thickness_sampler),
},
wgpu::BindGroupEntry {
binding: 14,
resource: wgpu::BindingResource::TextureView(specular_texture_view),
},
wgpu::BindGroupEntry {
binding: 15,
resource: wgpu::BindingResource::Sampler(specular_sampler),
},
];
#[cfg(not(target_arch = "wasm32"))]
{
bind_group_entries.push(wgpu::BindGroupEntry {
binding: 16,
resource: wgpu::BindingResource::TextureView(specular_color_texture_view),
});
bind_group_entries.push(wgpu::BindGroupEntry {
binding: 17,
resource: wgpu::BindingResource::Sampler(specular_color_sampler),
});
}
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!(
"Skinned Material {} Texture Bind Group",
material_id
)),
layout: &self.texture_bind_group_layout,
entries: &bind_group_entries,
});
self.material_bind_groups.insert(material_id, bind_group);
self.material_bind_group_cache_key
.insert(material_id, cache_key);
}
let light_result = collect_lights(world, MAX_LIGHTS);
let mut lights_data = light_result.lights_data;
let directional_light = light_result.directional_light;
let entity_to_lights_index = light_result.entity_to_index;
let directional_light_direction = directional_light
.as_ref()
.map(|(_light, transform)| {
let dir = transform.forward_vector();
[dir.x, dir.y, dir.z, 0.0]
})
.unwrap_or([0.0, -1.0, 0.0, 0.0]);
let camera_position = world
.resources
.active_camera
.and_then(|cam| world.get_global_transform(cam))
.map(|t| nalgebra_glm::vec3(t.0[(0, 3)], t.0[(1, 3)], t.0[(2, 3)]))
.unwrap_or_else(|| nalgebra_glm::vec3(0.0, 0.0, 0.0));
let spotlight_result = collect_spotlight_shadows(world, camera_position);
apply_spotlight_shadow_indices(
&mut lights_data,
&spotlight_result.entity_to_shadow_index,
&entity_to_lights_index,
);
if !spotlight_result.shadow_data.is_empty() {
queue.write_buffer(
&self.spotlight_shadow_buffer,
0,
bytemuck::cast_slice(&spotlight_result.shadow_data),
);
}
{
let gpu_lights_buffer_size = self.gpu().lights_buffer_size;
if lights_data.len() > gpu_lights_buffer_size {
let new_size = (lights_data.len() as f32 * 2.0).ceil() as usize;
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
let gpu = world_state.gpu_buffers.as_mut().unwrap();
gpu.lights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Skinned Mesh Lights Buffer (Per-World, Resized)"),
size: (std::mem::size_of::<LightData>() * new_size) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu.lights_buffer_size = new_size;
}
self.rebuild_instance_bind_group(device);
}
if !lights_data.is_empty() {
queue.write_buffer(
&self.gpu().lights_buffer,
0,
bytemuck::cast_slice(&lights_data),
);
}
let cascade_result = calculate_cascade_shadows(world, directional_light.as_ref());
let cascade_view_projections = cascade_result.cascade_view_projections;
let cascade_diameters = cascade_result.cascade_diameters;
let light_view_projection = cascade_result.light_view_projection;
let shadow_bias = cascade_result.shadow_bias;
let shadows_enabled = cascade_result.shadows_enabled;
let cascade_texture_resolution = if cfg!(target_arch = "wasm32") {
2048.0
} else {
4096.0
};
let cascade_atlas_offsets: [[f32; 4]; crate::render::wgpu::passes::NUM_SHADOW_CASCADES] = [
[
0.0,
0.0,
cascade_diameters[0] / cascade_texture_resolution,
0.0,
],
[
0.5,
0.0,
cascade_diameters[1] / cascade_texture_resolution,
0.0,
],
[
0.0,
0.5,
cascade_diameters[2] / cascade_texture_resolution,
0.0,
],
[
0.5,
0.5,
cascade_diameters[3] / cascade_texture_resolution,
0.0,
],
];
if let Some(camera_matrices) =
crate::ecs::camera::queries::query_active_camera_matrices(world)
{
let global_unlit = if world.resources.graphics.unlit_mode {
1.0
} else {
0.0
};
let (snap_resolution, snap_enabled) =
if let Some(ref vertex_snap) = world.resources.graphics.vertex_snap {
(vertex_snap.resolution, 1)
} else {
([320.0, 240.0], 0)
};
let affine_enabled = if world.resources.graphics.affine_texture_mapping {
1
} else {
0
};
let (fog_color, fog_enabled, fog_start, fog_end) =
if let Some(ref fog) = world.resources.graphics.fog {
(fog.color, 1, fog.start, fog.end)
} else {
([0.5, 0.5, 0.6], 0, 5.0, 30.0)
};
let time = world.resources.window.timing.uptime_milliseconds as f32 / 1000.0;
let uniforms = SkinnedMeshUniforms {
view: camera_matrices.view.into(),
projection: camera_matrices.projection.into(),
camera_position: [
camera_matrices.camera_position.x,
camera_matrices.camera_position.y,
camera_matrices.camera_position.z,
1.0,
],
num_lights: [lights_data.len() as u32, 0, 0, 0],
ambient_light: world.resources.graphics.ambient_light,
light_view_projection,
shadow_bias,
shadows_enabled,
global_unlit,
shadow_normal_bias: 1.8,
snap_resolution,
snap_enabled,
affine_enabled,
fog_color,
fog_enabled,
fog_start,
fog_end,
cascade_count: crate::render::wgpu::passes::NUM_SHADOW_CASCADES as u32,
_padding2: 0.0,
cascade_view_projections,
cascade_split_distances: CASCADE_SPLIT_DISTANCES,
cascade_atlas_offsets,
cascade_atlas_scale: [0.5, 0.5, 0.0, 0.0],
time,
pbr_debug_mode: world.resources.graphics.pbr_debug_mode.as_u32(),
texture_debug_stripes: world.resources.graphics.texture_debug_stripes as u32,
texture_debug_stripes_speed: world.resources.graphics.texture_debug_stripes_speed,
directional_light_direction,
ibl_blend_factor: world.resources.graphics.ibl_blend_factor,
_padding3: [0.0; 19],
};
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
let (screen_width, screen_height) =
if let Some((width, height)) = world.resources.window.cached_viewport_size {
(width, height)
} else {
(1920, 1080)
};
let (z_near, z_far) = world
.resources
.active_camera
.and_then(|cam| world.get_camera(cam))
.map(|cam| match &cam.projection {
crate::ecs::camera::components::Projection::Perspective(persp) => {
(persp.z_near, persp.z_far.unwrap_or(1000.0))
}
crate::ecs::camera::components::Projection::Orthographic(ortho) => {
(ortho.z_near, ortho.z_far)
}
})
.unwrap_or((0.1, 1000.0));
let tile_size_x = (screen_width as f32) / (CLUSTER_GRID_X as f32);
let tile_size_y = (screen_height as f32) / (CLUSTER_GRID_Y as f32);
let inverse_projection: [[f32; 4]; 4] =
nalgebra_glm::inverse(&camera_matrices.projection).into();
let cluster_uniforms = ClusterUniforms {
inverse_projection,
screen_size: [screen_width as f32, screen_height as f32],
z_near,
z_far,
cluster_count: [CLUSTER_GRID_X, CLUSTER_GRID_Y, CLUSTER_GRID_Z, 0],
tile_size: [tile_size_x, tile_size_y],
num_lights: lights_data.len() as u32,
num_directional_lights: light_result.num_directional_lights,
};
queue.write_buffer(
&self.gpu().cluster_uniforms_buffer,
0,
bytemuck::cast_slice(&[cluster_uniforms]),
);
}
}
fn execute<'r, 'e>(
&mut self,
context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
) -> crate::render::wgpu::rendergraph::Result<
Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
> {
if self.state().skinned_entities.is_empty() {
return Ok(vec![]);
}
if self.total_joints_to_dispatch > 0 {
let mut compute_pass =
context
.encoder
.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Skinning Compute Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.skinning_compute_pipeline);
compute_pass.set_bind_group(0, &self.skinning_compute_bind_group, &[]);
let num_workgroups = self.total_joints_to_dispatch.div_ceil(64);
compute_pass.dispatch_workgroups(num_workgroups, 1, 1);
}
let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
let (depth_view, depth_load, depth_store) = context.get_depth_attachment("depth")?;
let (shadow_depth_view, _, _) = context.get_depth_attachment("shadow_depth")?;
let (spotlight_shadow_atlas_view, _, _) =
context.get_depth_attachment("spotlight_shadow_atlas")?;
let world_state = self.state();
let brdf_view = world_state
.ibl_brdf_lut_view
.as_ref()
.or(self.brdf_lut_view.as_ref())
.unwrap_or(&self.dummy_white_view);
let irradiance_view = world_state
.ibl_irradiance_view
.as_ref()
.or(self.irradiance_map_view.as_ref())
.unwrap_or(&self.dummy_cube_view);
let prefiltered_view = world_state
.ibl_prefiltered_view
.as_ref()
.or(self.prefiltered_env_view.as_ref())
.unwrap_or(&self.dummy_cube_view);
let irradiance_b_view = world_state
.ibl_irradiance_b_view
.as_ref()
.or(self.irradiance_b_view.as_ref())
.unwrap_or(&self.dummy_cube_view);
let prefiltered_b_view = world_state
.ibl_prefiltered_b_view
.as_ref()
.or(self.prefiltered_b_view.as_ref())
.unwrap_or(&self.dummy_cube_view);
self.shadow_bind_group = context
.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_depth_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(spotlight_shadow_atlas_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(),
},
wgpu::BindGroupEntry {
binding: 14,
resource: wgpu::BindingResource::TextureView(irradiance_b_view),
},
wgpu::BindGroupEntry {
binding: 15,
resource: wgpu::BindingResource::TextureView(prefiltered_b_view),
},
],
});
let default_bind_group = context
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinned Mesh Default Texture Bind Group"),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&self.dummy_black_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(&self.dummy_normal_view),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
wgpu::BindGroupEntry {
binding: 12,
resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
},
wgpu::BindGroupEntry {
binding: 13,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
wgpu::BindGroupEntry {
binding: 14,
resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
},
wgpu::BindGroupEntry {
binding: 15,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
wgpu::BindGroupEntry {
binding: 16,
resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
},
wgpu::BindGroupEntry {
binding: 17,
resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
},
],
});
let entity_to_instance: std::collections::HashMap<crate::ecs::world::Entity, u32> = self
.state()
.skinned_entities
.iter()
.enumerate()
.map(|(index, &entity)| (entity, index as u32))
.collect();
if !self.state().opaque_skinned_entities.is_empty() {
let mut render_pass = context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Skinned Mesh Opaque Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
resolve_target: None,
ops: wgpu::Operations {
load: color_load,
store: color_store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth_view,
depth_ops: Some(wgpu::Operations {
load: depth_load,
store: depth_store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
render_pass.set_bind_group(1, &self.gpu().instance_bind_group, &[]);
render_pass.set_bind_group(3, &self.shadow_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
render_pass.set_index_buffer(
self.skinned_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
let opaque_skinned_entities: Vec<_> = self.state().opaque_skinned_entities.clone();
for &entity in &opaque_skinned_entities {
let instance_index = entity_to_instance[&entity];
let mesh_name = context
.configs
.get_render_mesh(entity)
.map(|rm| rm.name.as_str());
if let Some(name) = mesh_name
&& let Some(&mesh_id) = self.skinned_meshes.get(name)
{
let mesh_data = &self.skinned_mesh_data[mesh_id as usize];
if mesh_data.index_count > 0 {
let material_id = instance_index;
if let Some(bind_group) = self.material_bind_groups.get(&material_id) {
render_pass.set_bind_group(2, bind_group, &[]);
} else {
render_pass.set_bind_group(2, &default_bind_group, &[]);
}
render_pass.draw_indexed(
mesh_data.index_offset
..(mesh_data.index_offset + mesh_data.index_count),
mesh_data.vertex_offset as i32,
instance_index..(instance_index + 1),
);
}
}
}
drop(render_pass);
}
if !self.state().transparent_skinned_entities.is_empty() {
let (width, height) = context.get_texture_size("color")?;
self.resize_oit_textures(context.device, width, height);
{
let mut oit_pass = context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Skinned Mesh OIT Pass"),
color_attachments: &[
Some(wgpu::RenderPassColorAttachment {
view: &self.oit_accum_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
}),
Some(wgpu::RenderPassColorAttachment {
view: &self.oit_reveal_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
}),
],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
oit_pass.set_pipeline(&self.oit_render_pipeline);
oit_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
oit_pass.set_bind_group(1, &self.gpu().instance_bind_group, &[]);
oit_pass.set_bind_group(3, &self.shadow_bind_group, &[]);
oit_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
oit_pass.set_index_buffer(
self.skinned_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
let transparent_skinned_entities: Vec<_> =
self.state().transparent_skinned_entities.clone();
for &entity in &transparent_skinned_entities {
let instance_index = entity_to_instance[&entity];
let mesh_name = context
.configs
.get_render_mesh(entity)
.map(|rm| rm.name.as_str());
if let Some(name) = mesh_name
&& let Some(&mesh_id) = self.skinned_meshes.get(name)
{
let mesh_data = &self.skinned_mesh_data[mesh_id as usize];
if mesh_data.index_count > 0 {
let material_id = instance_index;
if let Some(bind_group) = self.material_bind_groups.get(&material_id) {
oit_pass.set_bind_group(2, bind_group, &[]);
} else {
oit_pass.set_bind_group(2, &default_bind_group, &[]);
}
oit_pass.draw_indexed(
mesh_data.index_offset
..(mesh_data.index_offset + mesh_data.index_count),
mesh_data.vertex_offset as i32,
instance_index..(instance_index + 1),
);
}
}
}
}
let oit_composite_bind_group =
context
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skinned Mesh OIT Composite Bind Group"),
layout: &self.oit_composite_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.oit_accum_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.oit_sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&self.oit_reveal_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&self.oit_sampler),
},
],
});
let mut composite_pass =
context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Skinned Mesh OIT Composite Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
composite_pass.set_pipeline(&self.oit_composite_pipeline);
composite_pass.set_bind_group(0, &oit_composite_bind_group, &[]);
composite_pass.draw(0..3, 0..1);
}
Ok(vec![])
}
}