0fc670fbd4
1 Commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
![]() |
bdd3ef71b8
|
Composable Pipeline Specialization (#17373)
Currently, our specialization API works through a series of wrapper structs and traits, which make things confusing to follow and difficult to generalize. This pr takes a different approach, where "specializers" (types that implement `Specialize`) are composable, but "flat" rather than composed of a series of wrappers. The key is that specializers don't *produce* pipeline descriptors, but instead *modify* existing ones: ```rs pub trait Specialize<T: Specializable> { type Key: SpecializeKey; fn specialize( &self, key: Self::Key, descriptor: &mut T::Descriptor ) -> Result<Canonical<Self::Key>, BevyError>; } ``` This lets us use some derive magic to stick multiple specializers together: ```rs pub struct A; pub struct B; impl Specialize<RenderPipeline> for A { ... } impl Specialize<RenderPipeline> for A { ... } #[derive(Specialize)] #[specialize(RenderPipeline)] struct C { // specialization is applied in struct field order applied_first: A, applied_second: B, } type C::Key = (A::Key, B::Key); ``` This approach is much easier to understand, IMO, and also lets us separate concerns better. Specializers can be placed in fully separate crates/modules, and key computation can be shared as well. The only real breaking change here is that since specializers only modify descriptors, we need a "base" descriptor to work off of. This can either be manually supplied when constructing a `Specializer` (the new collection replacing `Specialized[Render/Compute]Pipelines`), or supplied by implementing `HasBaseDescriptor` on a specializer. See `examples/shader/custom_phase_item.rs` for an example implementation. ## Testing - Did some simple manual testing of the derive macro, it seems robust. --- ## Showcase ```rs #[derive(Specialize, HasBaseDescriptor)] #[specialize(RenderPipeline)] pub struct SpecializeMeshMaterial<M: Material> { // set mesh bind group layout and shader defs mesh: SpecializeMesh, // set view bind group layout and shader defs view: SpecializeView, // since type SpecializeMaterial::Key = (), // we can hide it from the wrapper's external API #[key(default)] // defer to the GetBaseDescriptor impl of SpecializeMaterial, // since it carries the vertex and fragment handles #[base_descriptor] // set material bind group layout, etc material: SpecializeMaterial<M>, } // implementation generated by the derive macro impl <M: Material> Specialize<RenderPipeline> for SpecializeMeshMaterial<M> { type Key = (MeshKey, ViewKey); fn specialize( &self, key: Self::Key, descriptor: &mut RenderPipelineDescriptor ) -> Result<Canonical<Self::Key>, BevyError> { let mesh_key = self.mesh.specialize(key.0, descriptor)?; let view_key = self.view.specialize(key.1, descriptor)?; let _ = self.material.specialize((), descriptor)?; Ok((mesh_key, view_key)); } } impl <M: Material> HasBaseDescriptor<RenderPipeline> for SpecializeMeshMaterial<M> { fn base_descriptor(&self) -> RenderPipelineDescriptor { self.material.base_descriptor() } } ``` --------- Co-authored-by: Tim Overbeek <158390905+Bleachfuel@users.noreply.github.com> |