use super::{state_descriptors::PrimitiveTopology, IndexFormat, PipelineDescriptor}; use crate::{ pipeline::{BindType, VertexBufferLayout}, renderer::RenderResourceContext, shader::{Shader, ShaderError}, }; use bevy_asset::{Assets, Handle}; use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_utils::{HashMap, HashSet}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; #[derive(Clone, Eq, PartialEq, Debug, Reflect)] #[reflect(PartialEq)] pub struct PipelineSpecialization { pub shader_specialization: ShaderSpecialization, pub primitive_topology: PrimitiveTopology, pub dynamic_bindings: HashSet, pub strip_index_format: Option, pub vertex_buffer_layout: VertexBufferLayout, pub sample_count: u32, } impl Default for PipelineSpecialization { fn default() -> Self { Self { sample_count: 1, strip_index_format: None, shader_specialization: Default::default(), primitive_topology: Default::default(), dynamic_bindings: Default::default(), vertex_buffer_layout: Default::default(), } } } impl PipelineSpecialization { pub fn empty() -> &'static PipelineSpecialization { pub static EMPTY: Lazy = Lazy::new(PipelineSpecialization::default); &EMPTY } } #[derive(Clone, Eq, PartialEq, Debug, Default, Reflect, Serialize, Deserialize)] #[reflect(PartialEq, Serialize, Deserialize)] pub struct ShaderSpecialization { pub shader_defs: HashSet, } #[derive(Debug)] struct SpecializedShader { shader: Handle, specialization: ShaderSpecialization, } #[derive(Debug)] struct SpecializedPipeline { pipeline: Handle, specialization: PipelineSpecialization, } #[derive(Debug, Default)] pub struct PipelineCompiler { specialized_shaders: HashMap, Vec>, specialized_shader_pipelines: HashMap, Vec>>, specialized_pipelines: HashMap, Vec>, } impl PipelineCompiler { fn compile_shader( &mut self, render_resource_context: &dyn RenderResourceContext, shaders: &mut Assets, shader_handle: &Handle, shader_specialization: &ShaderSpecialization, ) -> Result, ShaderError> { let specialized_shaders = self .specialized_shaders .entry(shader_handle.clone_weak()) .or_insert_with(Vec::new); let shader = shaders.get(shader_handle).unwrap(); if let Some(specialized_shader) = specialized_shaders .iter() .find(|current_specialized_shader| { current_specialized_shader.specialization == *shader_specialization }) { // if shader has already been compiled with current configuration, use existing shader Ok(specialized_shader.shader.clone_weak()) } else { // if no shader exists with the current configuration, create new shader and compile let shader_def_vec = shader_specialization .shader_defs .iter() .cloned() .collect::>(); let compiled_shader = render_resource_context.get_specialized_shader(shader, Some(&shader_def_vec))?; let specialized_handle = shaders.add(compiled_shader); let weak_specialized_handle = specialized_handle.clone_weak(); specialized_shaders.push(SpecializedShader { shader: specialized_handle, specialization: shader_specialization.clone(), }); Ok(weak_specialized_handle) } } pub fn get_specialized_pipeline( &self, pipeline: &Handle, specialization: &PipelineSpecialization, ) -> Option> { self.specialized_pipelines .get(pipeline) .and_then(|specialized_pipelines| { specialized_pipelines .iter() .find(|current_specialized_pipeline| { ¤t_specialized_pipeline.specialization == specialization }) }) .map(|specialized_pipeline| specialized_pipeline.pipeline.clone_weak()) } pub fn compile_pipeline( &mut self, render_resource_context: &dyn RenderResourceContext, pipelines: &mut Assets, shaders: &mut Assets, source_pipeline: &Handle, pipeline_specialization: &PipelineSpecialization, ) -> Handle { let source_descriptor = pipelines.get(source_pipeline).unwrap(); let mut specialized_descriptor = source_descriptor.clone(); let specialized_vertex_shader = self .compile_shader( render_resource_context, shaders, &specialized_descriptor.shader_stages.vertex, &pipeline_specialization.shader_specialization, ) .unwrap_or_else(|e| panic_shader_error(e)); specialized_descriptor.shader_stages.vertex = specialized_vertex_shader.clone_weak(); let mut specialized_fragment_shader = None; specialized_descriptor.shader_stages.fragment = specialized_descriptor .shader_stages .fragment .as_ref() .map(|fragment| { let shader = self .compile_shader( render_resource_context, shaders, fragment, &pipeline_specialization.shader_specialization, ) .unwrap_or_else(|e| panic_shader_error(e)); specialized_fragment_shader = Some(shader.clone_weak()); shader }); let mut layout = render_resource_context.reflect_pipeline_layout( shaders, &specialized_descriptor.shader_stages, true, ); if !pipeline_specialization.dynamic_bindings.is_empty() { // set binding uniforms to dynamic if render resource bindings use dynamic for bind_group in layout.bind_groups.iter_mut() { let mut binding_changed = false; for binding in bind_group.bindings.iter_mut() { if pipeline_specialization .dynamic_bindings .iter() .any(|b| b == &binding.name) { if let BindType::Uniform { ref mut has_dynamic_offset, .. } = binding.bind_type { *has_dynamic_offset = true; binding_changed = true; } } } if binding_changed { bind_group.update_id(); } } } specialized_descriptor.layout = Some(layout); // create a vertex layout that provides all attributes from either the specialized vertex // buffers or a zero buffer let mut pipeline_layout = specialized_descriptor.layout.as_mut().unwrap(); // the vertex buffer descriptor of the mesh let mesh_vertex_buffer_layout = &pipeline_specialization.vertex_buffer_layout; // the vertex buffer descriptor that will be used for this pipeline let mut compiled_vertex_buffer_descriptor = VertexBufferLayout { step_mode: mesh_vertex_buffer_layout.step_mode, stride: mesh_vertex_buffer_layout.stride, ..Default::default() }; for shader_vertex_attribute in pipeline_layout.vertex_buffer_descriptors.iter() { let shader_vertex_attribute = shader_vertex_attribute .attributes .get(0) .expect("Reflected layout has no attributes."); if let Some(target_vertex_attribute) = mesh_vertex_buffer_layout .attributes .iter() .find(|x| x.name == shader_vertex_attribute.name) { // copy shader location from reflected layout let mut compiled_vertex_attribute = target_vertex_attribute.clone(); compiled_vertex_attribute.shader_location = shader_vertex_attribute.shader_location; compiled_vertex_buffer_descriptor .attributes .push(compiled_vertex_attribute); } else { panic!( "Attribute {} is required by shader, but not supplied by mesh. Either remove the attribute from the shader or supply the attribute ({}) to the mesh.", shader_vertex_attribute.name, shader_vertex_attribute.name, ); } } // TODO: add other buffers (like instancing) here let mut vertex_buffer_descriptors = Vec::::default(); if !pipeline_layout.vertex_buffer_descriptors.is_empty() { vertex_buffer_descriptors.push(compiled_vertex_buffer_descriptor); } pipeline_layout.vertex_buffer_descriptors = vertex_buffer_descriptors; specialized_descriptor.multisample.count = pipeline_specialization.sample_count; specialized_descriptor.primitive.topology = pipeline_specialization.primitive_topology; specialized_descriptor.primitive.strip_index_format = pipeline_specialization.strip_index_format; let specialized_pipeline_handle = pipelines.add(specialized_descriptor); render_resource_context.create_render_pipeline( specialized_pipeline_handle.clone_weak(), pipelines.get(&specialized_pipeline_handle).unwrap(), shaders, ); // track specialized shader pipelines self.specialized_shader_pipelines .entry(specialized_vertex_shader) .or_insert_with(Default::default) .push(source_pipeline.clone_weak()); if let Some(specialized_fragment_shader) = specialized_fragment_shader { self.specialized_shader_pipelines .entry(specialized_fragment_shader) .or_insert_with(Default::default) .push(source_pipeline.clone_weak()); } let specialized_pipelines = self .specialized_pipelines .entry(source_pipeline.clone_weak()) .or_insert_with(Vec::new); let weak_specialized_pipeline_handle = specialized_pipeline_handle.clone_weak(); specialized_pipelines.push(SpecializedPipeline { pipeline: specialized_pipeline_handle, specialization: pipeline_specialization.clone(), }); weak_specialized_pipeline_handle } pub fn iter_compiled_pipelines( &self, pipeline_handle: Handle, ) -> Option>> { self.specialized_pipelines .get(&pipeline_handle) .map(|compiled_pipelines| { compiled_pipelines .iter() .map(|specialized_pipeline| &specialized_pipeline.pipeline) }) } pub fn iter_all_compiled_pipelines(&self) -> impl Iterator> { self.specialized_pipelines .values() .map(|compiled_pipelines| { compiled_pipelines .iter() .map(|specialized_pipeline| &specialized_pipeline.pipeline) }) .flatten() } /// Update specialized shaders and remove any related specialized /// pipelines and assets. pub fn update_shader( &mut self, shader: &Handle, pipelines: &mut Assets, shaders: &mut Assets, render_resource_context: &dyn RenderResourceContext, ) -> Result<(), ShaderError> { if let Some(specialized_shaders) = self.specialized_shaders.get_mut(shader) { for specialized_shader in specialized_shaders { // Recompile specialized shader. If it fails, we bail immediately. let shader_def_vec = specialized_shader .specialization .shader_defs .iter() .cloned() .collect::>(); let new_handle = shaders.add(render_resource_context.get_specialized_shader( shaders.get(shader).unwrap(), Some(&shader_def_vec), )?); // Replace handle and remove old from assets. let old_handle = std::mem::replace(&mut specialized_shader.shader, new_handle); shaders.remove(&old_handle); // Find source pipelines that use the old specialized // shader, and remove from tracking. if let Some(source_pipelines) = self.specialized_shader_pipelines.remove(&old_handle) { // Remove all specialized pipelines from tracking // and asset storage. They will be rebuilt on next // draw. for source_pipeline in source_pipelines { if let Some(specialized_pipelines) = self.specialized_pipelines.remove(&source_pipeline) { for p in specialized_pipelines { pipelines.remove(p.pipeline); } } } } } } Ok(()) } } fn panic_shader_error(error: ShaderError) -> ! { let msg = error.to_string(); let msg = msg .trim_end() .trim_end_matches("Debug log:") // if this matches, then there wasn't a debug log anyways .trim_end(); panic!("{}\n", msg); }