use std::collections::{HashMap, HashSet};
use awsm_materials::MaterialShaderId;
use crate::anti_alias::AntiAliasing;
use crate::dynamic_materials::{BucketEntry, DynamicMaterials};
use crate::error::Result;
use crate::pipeline_layouts::{PipelineLayoutCacheKey, PipelineLayoutKey};
use crate::pipelines::compute_pipeline::{ComputePipelineCacheKey, ComputePipelineKey};
use crate::render_passes::material_opaque::bind_group::MaterialOpaqueBindGroups;
use crate::render_passes::material_opaque::edge_bind_group::MaterialEdgeBindGroupLayouts;
use crate::render_passes::material_opaque::shader::cache_key::{
DynamicShaderInfo, ShaderCacheKeyMaterialOpaque,
};
use crate::render_passes::material_opaque::shader::edge_cache_key::ShaderCacheKeyMaterialFinalBlend;
use crate::shaders::ShaderCacheKey;
#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)]
pub struct EdgeResolvePipelineKeyId {
pub mipmaps: bool,
pub shader_id: MaterialShaderId,
}
#[derive(Clone, Copy, Debug)]
pub enum EdgePipelineSlot {
Shade(EdgeResolvePipelineKeyId),
FinalBlend,
}
pub struct MaterialEdgePipelineDescriptors {
pub shader_cache_keys: Vec<crate::shaders::ShaderCacheKey>,
pub pipeline_layout_keys: Vec<PipelineLayoutKey>,
pub entry_points: Vec<Option<String>>,
pub slots: Vec<EdgePipelineSlot>,
}
pub struct MaterialEdgePipelines {
pub per_shader_shade: HashMap<EdgeResolvePipelineKeyId, ComputePipelineKey>,
pub shade_layout_key: Option<PipelineLayoutKey>,
pub final_blend_pipeline_key: Option<ComputePipelineKey>,
pub final_blend_layout_key: Option<PipelineLayoutKey>,
desired_keys: HashSet<ComputePipelineCacheKey>,
in_flight_keys: HashSet<ComputePipelineCacheKey>,
}
impl Default for MaterialEdgePipelines {
fn default() -> Self {
Self::new()
}
}
impl MaterialEdgePipelines {
pub fn new() -> Self {
Self {
per_shader_shade: HashMap::new(),
shade_layout_key: None,
final_blend_pipeline_key: None,
final_blend_layout_key: None,
desired_keys: HashSet::new(),
in_flight_keys: HashSet::new(),
}
}
pub(crate) fn set_desired_edge_keys(
&mut self,
keys: impl IntoIterator<Item = ComputePipelineCacheKey>,
) {
self.desired_keys = keys.into_iter().collect();
}
pub(crate) fn is_edge_key_desired(&self, key: &ComputePipelineCacheKey) -> bool {
self.desired_keys.contains(key)
}
pub(crate) fn edge_key_in_flight(&self, key: &ComputePipelineCacheKey) -> bool {
self.in_flight_keys.contains(key)
}
pub(crate) fn mark_edge_key_in_flight(&mut self, key: ComputePipelineCacheKey) {
self.in_flight_keys.insert(key);
}
pub(crate) fn clear_edge_key_in_flight(&mut self, key: &ComputePipelineCacheKey) {
self.in_flight_keys.remove(key);
}
pub fn get_shade_pipeline_key(
&self,
anti_aliasing: &AntiAliasing,
shader_id: MaterialShaderId,
) -> Option<ComputePipelineKey> {
anti_aliasing.msaa_sample_count?;
self.per_shader_shade
.get(&EdgeResolvePipelineKeyId {
mipmaps: anti_aliasing.mipmap,
shader_id,
})
.copied()
}
pub fn insert_shade_pipeline(
&mut self,
key_id: EdgeResolvePipelineKeyId,
pipeline_key: ComputePipelineKey,
) -> Option<ComputePipelineKey> {
self.per_shader_shade
.insert(key_id, pipeline_key)
.filter(|displaced| *displaced != pipeline_key)
}
pub fn per_shader_len(&self) -> usize {
self.per_shader_shade.len()
}
pub fn clear_dynamic_pipelines(&mut self) -> Vec<ComputePipelineKey> {
let mut dropped: Vec<ComputePipelineKey> =
self.per_shader_shade.drain().map(|(_, k)| k).collect();
dropped.extend(self.final_blend_pipeline_key.take());
dropped
}
#[allow(clippy::too_many_arguments)]
pub fn build_descriptors(
&mut self,
gpu: &awsm_renderer_core::renderer::AwsmRendererWebGpu,
pipeline_layouts: &mut crate::pipeline_layouts::PipelineLayouts,
bind_group_layouts: &mut crate::bind_group_layout::BindGroupLayouts,
opaque_bind_groups: &MaterialOpaqueBindGroups,
edge_layouts: &MaterialEdgeBindGroupLayouts,
bucket_entries: &[BucketEntry],
anti_aliasing: &AntiAliasing,
color_wgsl_format: &str,
dynamic_registry: Option<&DynamicMaterials>,
max_shadow_casters: u32,
) -> Result<Option<MaterialEdgePipelineDescriptors>> {
if anti_aliasing.msaa_sample_count.is_none() {
return Ok(None);
}
let texture_pool_arrays_len = opaque_bind_groups.texture_pool_arrays_len;
let texture_pool_samplers_len = opaque_bind_groups.texture_pool_sampler_keys.len() as u32;
let mipmaps = anti_aliasing.mipmap;
let main_bgl = opaque_bind_groups.multisampled_main_bind_group_layout_key;
let final_blend_layout_key = pipeline_layouts.get_key(
gpu,
bind_group_layouts,
PipelineLayoutCacheKey::new(vec![edge_layouts.final_blend_group0_layout_key]),
)?;
self.final_blend_layout_key = Some(final_blend_layout_key);
let shade_layout_key = pipeline_layouts.get_key(
gpu,
bind_group_layouts,
PipelineLayoutCacheKey::new(vec![
main_bgl,
opaque_bind_groups.lights_bind_group_layout_key,
opaque_bind_groups.texture_pool_textures_bind_group_layout_key,
edge_layouts.shade_extended_shadows_layout_key,
]),
)?;
self.shade_layout_key = Some(shade_layout_key);
let mut shader_cache_keys: Vec<ShaderCacheKey> = Vec::new();
let mut slots: Vec<EdgePipelineSlot> = Vec::new();
let mut pipeline_layout_keys: Vec<PipelineLayoutKey> = Vec::new();
let mut entry_points: Vec<Option<String>> = Vec::new();
let global_dispatch_hash = dynamic_registry
.map(|r| r.dispatch_hash_cached())
.unwrap_or(0);
{
for entry in bucket_entries.iter() {
let owns_skybox = entry.shader_id == MaterialShaderId::SKYBOX;
let dynamic_shader = if !owns_skybox && entry.shader_id.is_dynamic() {
let Some(registry) = dynamic_registry else {
continue;
};
match registry.get(entry.shader_id) {
Some(reg)
if !matches!(
reg.alpha_mode,
awsm_materials::MaterialAlphaMode::Opaque
) =>
{
continue
}
Some(reg) => Some(DynamicShaderInfo {
shader_includes: reg.shader_includes.resolve(),
struct_decl: awsm_materials::dynamic_layout::generate_wgsl_struct(
"MaterialData",
®.layout,
),
loader_decl: awsm_materials::dynamic_layout::generate_wgsl_loader(
"MaterialData",
"material_data_load",
®.layout,
),
wgsl_fragment: reg.wgsl_fragment.clone(),
}),
None if registry.first_party_variant_of(entry.shader_id).is_some() => None,
None => continue,
}
} else {
None
};
let key = ShaderCacheKeyMaterialOpaque {
texture_pool_arrays_len,
texture_pool_samplers_len,
msaa_sample_count: anti_aliasing.msaa_sample_count,
mipmaps,
max_shadow_casters,
shader_id: entry.shader_id,
base: entry.base,
owns_skybox,
pbr_features: entry.pbr_features,
dispatch_hash: global_dispatch_hash,
dynamic_shader,
bucket_entries: bucket_entries.to_vec(),
};
shader_cache_keys.push(ShaderCacheKey::from(key));
slots.push(EdgePipelineSlot::Shade(EdgeResolvePipelineKeyId {
mipmaps,
shader_id: entry.shader_id,
}));
pipeline_layout_keys.push(shade_layout_key);
entry_points.push(Some("cs_shade".to_string()));
}
}
shader_cache_keys.push(ShaderCacheKey::from(ShaderCacheKeyMaterialFinalBlend {
bucket_entries: bucket_entries.to_vec(),
color_format: color_wgsl_format.to_string(),
}));
slots.push(EdgePipelineSlot::FinalBlend);
pipeline_layout_keys.push(final_blend_layout_key);
entry_points.push(None);
Ok(Some(MaterialEdgePipelineDescriptors {
shader_cache_keys,
slots,
pipeline_layout_keys,
entry_points,
}))
}
#[allow(clippy::too_many_arguments)]
pub async fn ensure_compiled(
&mut self,
gpu: &awsm_renderer_core::renderer::AwsmRendererWebGpu,
shaders: &mut crate::shaders::Shaders,
compute_pipelines: &mut crate::pipelines::compute_pipeline::ComputePipelines,
pipeline_layouts: &mut crate::pipeline_layouts::PipelineLayouts,
bind_group_layouts: &mut crate::bind_group_layout::BindGroupLayouts,
opaque_bind_groups: &MaterialOpaqueBindGroups,
edge_layouts: &MaterialEdgeBindGroupLayouts,
bucket_entries: &[BucketEntry],
anti_aliasing: &AntiAliasing,
color_wgsl_format: &str,
dynamic_registry: Option<&DynamicMaterials>,
max_shadow_casters: u32,
) -> Result<()> {
let Some(descs) = self.build_descriptors(
gpu,
pipeline_layouts,
bind_group_layouts,
opaque_bind_groups,
edge_layouts,
bucket_entries,
anti_aliasing,
color_wgsl_format,
dynamic_registry,
max_shadow_casters,
)?
else {
return Ok(());
};
tracing::info!(
target: "awsm_renderer::boot_timing",
"MaterialEdgePipelines::ensure_compiled: compiling {} buckets + skybox + final_blend",
bucket_entries.len()
);
let shader_keys = shaders
.ensure_keys(gpu, descs.shader_cache_keys.iter().cloned())
.await?;
let pipeline_cache_keys: Vec<ComputePipelineCacheKey> = shader_keys
.iter()
.zip(descs.pipeline_layout_keys.iter())
.zip(descs.entry_points.iter())
.map(|((sk, lk), ep)| {
let base = ComputePipelineCacheKey::new(*sk, *lk);
match ep {
Some(name) => base.with_entry_point(name),
None => base,
}
})
.collect();
self.set_desired_edge_keys(pipeline_cache_keys.iter().cloned());
let pipeline_keys = compute_pipelines
.ensure_keys(gpu, shaders, pipeline_layouts, pipeline_cache_keys)
.await?;
self.merge_resolved(descs.slots, pipeline_keys);
Ok(())
}
pub fn merge_resolved(
&mut self,
slots: Vec<EdgePipelineSlot>,
pipeline_keys: Vec<ComputePipelineKey>,
) {
for (slot, key) in slots.into_iter().zip(pipeline_keys) {
match slot {
EdgePipelineSlot::Shade(id) => {
self.per_shader_shade.insert(id, key);
}
EdgePipelineSlot::FinalBlend => {
self.final_blend_pipeline_key = Some(key);
}
}
}
}
}