Skip to content

Commit bdd3ef7

Browse files
ecoskeyBleachfuel
andauthored
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>
1 parent f98727c commit bdd3ef7

File tree

7 files changed

+1103
-38
lines changed

7 files changed

+1103
-38
lines changed

crates/bevy_render/macros/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
mod as_bind_group;
55
mod extract_component;
66
mod extract_resource;
7+
mod specialize;
78

89
use bevy_macro_utils::{derive_label, BevyManifest};
910
use proc_macro::TokenStream;
@@ -14,6 +15,10 @@ pub(crate) fn bevy_render_path() -> syn::Path {
1415
BevyManifest::shared().get_path("bevy_render")
1516
}
1617

18+
pub(crate) fn bevy_ecs_path() -> syn::Path {
19+
BevyManifest::shared().get_path("bevy_ecs")
20+
}
21+
1722
#[proc_macro_derive(ExtractResource)]
1823
pub fn derive_extract_resource(input: TokenStream) -> TokenStream {
1924
extract_resource::derive_extract_resource(input)
@@ -102,6 +107,20 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream {
102107
derive_label(input, "RenderSubGraph", &trait_path)
103108
}
104109

110+
/// Derive macro generating an impl of the trait `Specialize`
111+
///
112+
/// This only works for structs whose members all implement `Specialize`
113+
#[proc_macro_derive(Specialize, attributes(specialize, key, base_descriptor))]
114+
pub fn derive_specialize(input: TokenStream) -> TokenStream {
115+
specialize::impl_specialize(input)
116+
}
117+
118+
/// Derive macro generating the most common impl of the trait `SpecializerKey`
119+
#[proc_macro_derive(SpecializerKey)]
120+
pub fn derive_specializer_key(input: TokenStream) -> TokenStream {
121+
specialize::impl_specializer_key(input)
122+
}
123+
105124
#[proc_macro_derive(ShaderLabel)]
106125
pub fn derive_shader_label(input: TokenStream) -> TokenStream {
107126
let input = parse_macro_input!(input as DeriveInput);

0 commit comments

Comments
 (0)