From dc597000e0ec6c67cb35676045f308c6895a610b Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:49:30 -0800 Subject: [PATCH 01/50] specialize --- crates/bevy_render/src/render_resource/mod.rs | 1 + .../src/render_resource/specialize.rs | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 crates/bevy_render/src/render_resource/specialize.rs diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 27a08851122c6..a6533ad8e53a4 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -11,6 +11,7 @@ mod pipeline_cache; mod pipeline_specializer; pub mod resource_macros; mod shader; +mod specialize; mod storage_buffer; mod texture; mod uniform_buffer; diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs new file mode 100644 index 0000000000000..ae39a94c2ce5e --- /dev/null +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -0,0 +1,75 @@ +use super::{ + BindGroupLayout, CachedComputePipelineId, CachedRenderPipelineId, ComputePipeline, + ComputePipelineDescriptor, PipelineCache, RenderPipeline, RenderPipelineDescriptor, +}; +use bevy_app::{App, Plugin}; +use bevy_ecs::{ + system::Resource, + world::{FromWorld, World}, +}; +use bevy_utils::hashbrown::HashMap; +use std::{hash::Hash, iter}; + +pub trait SpecializeTarget { + type Descriptor: Send + Sync; + type CachedId; + fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId; +} + +impl SpecializeTarget for RenderPipeline { + type Descriptor = RenderPipelineDescriptor; + type CachedId = CachedRenderPipelineId; + + fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId { + pipeline_cache.queue_render_pipeline(descriptor) + } +} + +impl SpecializeTarget for ComputePipeline { + type Descriptor = ComputePipelineDescriptor; + + type CachedId = CachedComputePipelineId; + + fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId { + pipeline_cache.queue_compute_pipeline(descriptor) + } +} + +pub trait Specialize: FromWorld + Send + Sync + 'static { + type Key: Clone + Hash + Eq; + fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor); +} + +#[derive(Resource)] +pub struct Specializer> { + specializer: S, + user_specializer: Option, + base_descriptor: T::Descriptor, + pipelines: HashMap, +} + +impl> Specializer { + pub fn new( + specializer: S, + user_specializer: Option, + base_descriptor: T::Descriptor, + ) -> Self { + Self { + specializer, + user_specializer, + base_descriptor, + pipelines: Default::default(), + } + } + + pub fn specialize(&mut self, pipeline_cache: &PipelineCache, key: S::Key) -> T::CachedId { + *self.pipelines.entry(key.clone()).or_insert_with(|| { + let mut descriptor = self.base_descriptor.clone(); + self.specializer.specialize(key.clone(), &mut descriptor); + if let Some(user_specializer) = self.user_specializer { + (user_specializer)(key, &mut descriptor); + } + pipeline_cache.queue_render_pipeline(descriptor) + }) + } +} From 9f309134b59788321391502c365fd2db29d4412c Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:25:01 -0800 Subject: [PATCH 02/50] add proc macros --- crates/bevy_render/macros/src/lib.rs | 30 ++++++++ crates/bevy_render/macros/src/specialize.rs | 80 +++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 crates/bevy_render/macros/src/specialize.rs diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 5f88f589ebca1..7009548035805 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -4,10 +4,12 @@ mod as_bind_group; mod extract_component; mod extract_resource; +mod specialize; use bevy_macro_utils::{derive_label, BevyManifest}; use proc_macro::TokenStream; use quote::format_ident; +use specialize::derive_specialize; use syn::{parse_macro_input, DeriveInput}; pub(crate) fn bevy_render_path() -> syn::Path { @@ -107,3 +109,31 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream { dyn_eq_path.segments.push(format_ident!("DynEq").into()); derive_label(input, "RenderSubGraph", &trait_path, &dyn_eq_path) } + +/// Derive macro generating an impl of the trait `Specialize` +/// +/// This only works for structs whose members all implement `Specialize` +#[proc_macro_derive(SpecializeRenderPipeline, attributes(key))] +pub fn derive_specialize_render_pipeline(input: TokenStream) -> TokenStream { + let target_path = { + let mut path = bevy_render_path(); + path.segments.push(format_ident!("render_resource").into()); + path.segments.push(format_ident!("RenderPipeline").into()); + path + }; + derive_specialize(input, target_path) +} + +/// Derive macro generating an impl of the trait `Specialize` +/// +/// This only works for structs whose members all implement `Specialize` +#[proc_macro_derive(SpecializeComputePipeline, attributes(key))] +pub fn derive_specialize_compute_pipeline(input: TokenStream) -> TokenStream { + let target_path = { + let mut path = bevy_render_path(); + path.segments.push(format_ident!("render_resource").into()); + path.segments.push(format_ident!("ComputePipeline").into()); + path + }; + derive_specialize(input, target_path) +} diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs new file mode 100644 index 0000000000000..31092e5ad9c6a --- /dev/null +++ b/crates/bevy_render/macros/src/specialize.rs @@ -0,0 +1,80 @@ +use proc_macro::{Ident, TokenStream}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{ + parse_macro_input, punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataStruct, + DeriveInput, Expr, Index, Member, Meta, MetaNameValue, Path, Token, Type, +}; + +const KEY_ATTR_IDENT: &str = "key"; + +pub fn derive_specialize(input: TokenStream, target_path: Path) -> TokenStream { + let bevy_render_path: Path = crate::bevy_render_path(); + let specialize_path = { + let mut path = bevy_render_path.clone(); + path.segments.push(format_ident!("render_resource").into()); + path.segments.push(format_ident!("specialize").into()); + path + }; + + let ast = parse_macro_input!(input as DeriveInput); + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + let Data::Struct(DataStruct { fields, .. }) = &ast.data else { + let error_span = match ast.data { + Data::Struct(_) => unreachable!(), + Data::Enum(data_enum) => data_enum.enum_token.span(), + Data::Union(data_union) => data_union.union_token.span(), + }; + return syn::Error::new(error_span, "#[derive(QueryData)]` only supports structs") + .into_compile_error() + .into(); + }; + + let mut key_elems: Punctuated = Punctuated::new(); + let mut sub_specializers: Vec = Vec::new(); + + for (index, field) in fields.iter().enumerate() { + let field_ty = field.ty.clone(); + let field_member = field.ident.clone().map_or( + Member::Unnamed(Index { + index: index as u32, + span: field.span(), + }), + Member::Named, + ); + + let mut use_key_field: Result = Ok(Member::Unnamed(Index { + index: key_elems.len() as u32, + span: field.span(), + })); + for attr in &field.attrs { + if let Meta::NameValue(MetaNameValue { path, value, .. }) = &attr.meta { + if path.is_ident(&KEY_ATTR_IDENT) { + use_key_field = Err(value.clone()); + } + } + } + + let key_expr = match use_key_field { + Ok(key_member) => { + key_elems.push(field_ty.clone()); + quote!(key.#key_member) + } + Err(raw_key_expr) => quote!(#raw_key_expr), + }; + + sub_specializers.push(quote!(<#field_ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#field_member, #key_expr, descriptor);)); + } + + TokenStream::from(quote! { + impl #impl_generics #specialize_path::Specialize<#target_path> for #struct_name #type_generics #where_clause { + type Key = (#key_elems); + + fn specialize(&self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::SpecializeTarget>::Descriptor) { + #(#sub_specializers)* + } + } + }) +} From 722dfd6b7784faae8ed3e98a0e421bb3bc277665 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:33:02 -0800 Subject: [PATCH 03/50] minor cleanup --- crates/bevy_render/macros/src/specialize.rs | 29 +++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 31092e5ad9c6a..65d39512a9fb6 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -1,9 +1,9 @@ -use proc_macro::{Ident, TokenStream}; +use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; use syn::{ - parse_macro_input, punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataStruct, - DeriveInput, Expr, Index, Member, Meta, MetaNameValue, Path, Token, Type, + parse_macro_input, punctuated::Punctuated, spanned::Spanned, Data, DataStruct, DeriveInput, + Index, Member, Meta, MetaNameValue, Path, Token, Type, }; const KEY_ATTR_IDENT: &str = "key"; @@ -27,7 +27,7 @@ pub fn derive_specialize(input: TokenStream, target_path: Path) -> TokenStream { Data::Enum(data_enum) => data_enum.enum_token.span(), Data::Union(data_union) => data_union.union_token.span(), }; - return syn::Error::new(error_span, "#[derive(QueryData)]` only supports structs") + return syn::Error::new(error_span, "#[derive(Specialize)]` only supports structs") //TODO: proper name here .into_compile_error() .into(); }; @@ -45,25 +45,20 @@ pub fn derive_specialize(input: TokenStream, target_path: Path) -> TokenStream { Member::Named, ); - let mut use_key_field: Result = Ok(Member::Unnamed(Index { - index: key_elems.len() as u32, - span: field.span(), - })); + let mut key_expr = quote!(key.#{key_elems.len() as u32}); + let mut use_key_field = true; for attr in &field.attrs { if let Meta::NameValue(MetaNameValue { path, value, .. }) = &attr.meta { if path.is_ident(&KEY_ATTR_IDENT) { - use_key_field = Err(value.clone()); + key_expr = value.to_token_stream(); + use_key_field = false; } } } - let key_expr = match use_key_field { - Ok(key_member) => { - key_elems.push(field_ty.clone()); - quote!(key.#key_member) - } - Err(raw_key_expr) => quote!(#raw_key_expr), - }; + if use_key_field { + key_elems.push(field_ty.clone()); + } sub_specializers.push(quote!(<#field_ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#field_member, #key_expr, descriptor);)); } From 30430fc780d19310b8df77cef62a6db1ef3c4291 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:01:48 -0800 Subject: [PATCH 04/50] fix error name --- crates/bevy_render/macros/src/lib.rs | 4 ++-- crates/bevy_render/macros/src/specialize.rs | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 7009548035805..b8f988889a895 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -121,7 +121,7 @@ pub fn derive_specialize_render_pipeline(input: TokenStream) -> TokenStream { path.segments.push(format_ident!("RenderPipeline").into()); path }; - derive_specialize(input, target_path) + derive_specialize(input, target_path, "SpecializeRenderPipeline") } /// Derive macro generating an impl of the trait `Specialize` @@ -135,5 +135,5 @@ pub fn derive_specialize_compute_pipeline(input: TokenStream) -> TokenStream { path.segments.push(format_ident!("ComputePipeline").into()); path }; - derive_specialize(input, target_path) + derive_specialize(input, target_path, "SpecializeComputePipeline") } diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 65d39512a9fb6..97e005179135c 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -8,7 +8,7 @@ use syn::{ const KEY_ATTR_IDENT: &str = "key"; -pub fn derive_specialize(input: TokenStream, target_path: Path) -> TokenStream { +pub fn derive_specialize(input: TokenStream, target_path: Path, derive_name: &str) -> TokenStream { let bevy_render_path: Path = crate::bevy_render_path(); let specialize_path = { let mut path = bevy_render_path.clone(); @@ -27,9 +27,12 @@ pub fn derive_specialize(input: TokenStream, target_path: Path) -> TokenStream { Data::Enum(data_enum) => data_enum.enum_token.span(), Data::Union(data_union) => data_union.union_token.span(), }; - return syn::Error::new(error_span, "#[derive(Specialize)]` only supports structs") //TODO: proper name here - .into_compile_error() - .into(); + return syn::Error::new( + error_span, + format!("#[derive({derive_name})]` only supports structs"), + ) + .into_compile_error() + .into(); }; let mut key_elems: Punctuated = Punctuated::new(); From 3d9d120d48f24a3e75f637129aa161126444696a Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:16:21 -0800 Subject: [PATCH 05/50] imports exports --- crates/bevy_render/src/render_resource/mod.rs | 1 + .../src/render_resource/specialize.rs | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index a6533ad8e53a4..a63f401c753d2 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -27,6 +27,7 @@ pub use pipeline::*; pub use pipeline_cache::*; pub use pipeline_specializer::*; pub use shader::*; +pub use specialize::*; pub use storage_buffer::*; pub use texture::*; pub use uniform_buffer::*; diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index ae39a94c2ce5e..84def21934c21 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -1,18 +1,14 @@ use super::{ - BindGroupLayout, CachedComputePipelineId, CachedRenderPipelineId, ComputePipeline, - ComputePipelineDescriptor, PipelineCache, RenderPipeline, RenderPipelineDescriptor, -}; -use bevy_app::{App, Plugin}; -use bevy_ecs::{ - system::Resource, - world::{FromWorld, World}, + CachedComputePipelineId, CachedRenderPipelineId, ComputePipeline, ComputePipelineDescriptor, + PipelineCache, RenderPipeline, RenderPipelineDescriptor, }; +use bevy_ecs::{system::Resource, world::FromWorld}; use bevy_utils::hashbrown::HashMap; -use std::{hash::Hash, iter}; +use core::hash::Hash; pub trait SpecializeTarget { - type Descriptor: Send + Sync; - type CachedId; + type Descriptor: Clone + Send + Sync; + type CachedId: Clone + Send + Sync; fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId; } @@ -35,11 +31,14 @@ impl SpecializeTarget for ComputePipeline { } } -pub trait Specialize: FromWorld + Send + Sync + 'static { +pub trait Specialize: Send + Sync + 'static { type Key: Clone + Hash + Eq; fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor); } +pub type RenderPipelineSpecializer = Specializer; +pub type ComputePipelineSpecializer = Specializer; + #[derive(Resource)] pub struct Specializer> { specializer: S, @@ -63,13 +62,16 @@ impl> Specializer { } pub fn specialize(&mut self, pipeline_cache: &PipelineCache, key: S::Key) -> T::CachedId { - *self.pipelines.entry(key.clone()).or_insert_with(|| { - let mut descriptor = self.base_descriptor.clone(); - self.specializer.specialize(key.clone(), &mut descriptor); - if let Some(user_specializer) = self.user_specializer { - (user_specializer)(key, &mut descriptor); - } - pipeline_cache.queue_render_pipeline(descriptor) - }) + self.pipelines + .entry(key.clone()) + .or_insert_with(|| { + let mut descriptor = self.base_descriptor.clone(); + self.specializer.specialize(key.clone(), &mut descriptor); + if let Some(user_specializer) = self.user_specializer { + (user_specializer)(key, &mut descriptor); + } + ::queue(pipeline_cache, descriptor) + }) + .clone() } } From 565d0931b393aa202d95bbb247320275e9c08dda Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:21:57 -0800 Subject: [PATCH 06/50] reexport macros --- crates/bevy_render/src/render_resource/specialize.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 84def21934c21..34c84769d7071 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -2,10 +2,12 @@ use super::{ CachedComputePipelineId, CachedRenderPipelineId, ComputePipeline, ComputePipelineDescriptor, PipelineCache, RenderPipeline, RenderPipelineDescriptor, }; -use bevy_ecs::{system::Resource, world::FromWorld}; +use bevy_ecs::system::Resource; use bevy_utils::hashbrown::HashMap; use core::hash::Hash; +pub use bevy_render_macros::{SpecializeComputePipeline, SpecializeRenderPipeline}; + pub trait SpecializeTarget { type Descriptor: Clone + Send + Sync; type CachedId: Clone + Send + Sync; From 568071bd104aef0fc30ec69c205065f728eb0044 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Tue, 14 Jan 2025 12:11:52 -0800 Subject: [PATCH 07/50] rework things for accuracy --- crates/bevy_render/macros/src/specialize.rs | 34 +++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 97e005179135c..52a0237cc7e93 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -2,8 +2,9 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote, ToTokens}; use syn::{ - parse_macro_input, punctuated::Punctuated, spanned::Spanned, Data, DataStruct, DeriveInput, - Index, Member, Meta, MetaNameValue, Path, Token, Type, + parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Data, + DataStruct, DeriveInput, Expr, Index, Member, Meta, MetaList, MetaNameValue, Path, Stmt, Token, + Type, }; const KEY_ATTR_IDENT: &str = "key"; @@ -13,7 +14,6 @@ pub fn derive_specialize(input: TokenStream, target_path: Path, derive_name: &st let specialize_path = { let mut path = bevy_render_path.clone(); path.segments.push(format_ident!("render_resource").into()); - path.segments.push(format_ident!("specialize").into()); path }; @@ -36,7 +36,8 @@ pub fn derive_specialize(input: TokenStream, target_path: Path, derive_name: &st }; let mut key_elems: Punctuated = Punctuated::new(); - let mut sub_specializers: Vec = Vec::new(); + let mut sub_specializers: Vec<(Type, Member, Expr)> = Vec::new(); + let mut single_index = 0; for (index, field) in fields.iter().enumerate() { let field_ty = field.ty.clone(); @@ -47,25 +48,40 @@ pub fn derive_specialize(input: TokenStream, target_path: Path, derive_name: &st }), Member::Named, ); + let key_member = Member::Unnamed(Index { + index: key_elems.len() as u32, + span: field.span(), + }); - let mut key_expr = quote!(key.#{key_elems.len() as u32}); + let mut key_expr: Expr = parse_quote!(key.#key_member); let mut use_key_field = true; for attr in &field.attrs { - if let Meta::NameValue(MetaNameValue { path, value, .. }) = &attr.meta { + if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta { if path.is_ident(&KEY_ATTR_IDENT) { - key_expr = value.to_token_stream(); + let owned_tokens = tokens.clone().into(); + key_expr = parse_macro_input!(owned_tokens as Expr); use_key_field = false; } } } if use_key_field { - key_elems.push(field_ty.clone()); + single_index = index; + key_elems + .push(parse_quote!(<#field_ty as #specialize_path::Specialize<#target_path>>::Key)); } - sub_specializers.push(quote!(<#field_ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#field_member, #key_expr, descriptor);)); + sub_specializers.push((field_ty, field_member, key_expr)); } + if key_elems.len() == 1 { + sub_specializers[single_index].2 = parse_quote!(key); + } + + let sub_specializers = sub_specializers.into_iter().map(|(field_ty, field_member, key_expr)| { + parse_quote!(<#field_ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#field_member, #key_expr, descriptor);) + }).collect::>(); + TokenStream::from(quote! { impl #impl_generics #specialize_path::Specialize<#target_path> for #struct_name #type_generics #where_clause { type Key = (#key_elems); From cc201d0f3e113fa22b3ba2651f044cc16d049cc7 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 14 Jan 2025 13:17:57 -0800 Subject: [PATCH 08/50] port custom_phase_item for demo --- examples/shader/custom_phase_item.rs | 166 +++++++++++++-------------- 1 file changed, 77 insertions(+), 89 deletions(-) diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index be4fb105f834e..090b2cef0e48e 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -24,15 +24,15 @@ use bevy::{ render_resource::{ BufferUsages, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, FragmentState, IndexFormat, MultisampleState, PipelineCache, PrimitiveState, - RawBufferVec, RenderPipelineDescriptor, SpecializedRenderPipeline, - SpecializedRenderPipelines, TextureFormat, VertexAttribute, VertexBufferLayout, - VertexFormat, VertexState, VertexStepMode, + RawBufferVec, RenderPipelineDescriptor, TextureFormat, VertexAttribute, + VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, view::{self, ExtractedView, RenderVisibleEntities, VisibilityClass}, Render, RenderApp, RenderSet, }, }; +use bevy_render::render_resource::{RenderPipeline, Specialize, Specializer}; use bytemuck::{Pod, Zeroable}; /// A marker component that represents an entity that is to be rendered using @@ -47,14 +47,6 @@ use bytemuck::{Pod, Zeroable}; #[component(on_add = view::add_visibility_class::)] struct CustomRenderedEntity; -/// Holds a reference to our shader. -/// -/// This is loaded at app creation time. -#[derive(Resource)] -struct CustomPhasePipeline { - shader: Handle, -} - /// A [`RenderCommand`] that binds the vertex and index buffers and issues the /// draw command for our custom phase item. struct DrawCustomPhaseItem; @@ -170,11 +162,16 @@ fn main() { .add_plugins(ExtractComponentPlugin::::default()) .add_systems(Startup, setup); + let base_pipeline_descriptor = base_pipeline_descriptor(app.world().resource::()); + // We make sure to add these to the render app, not the main app. app.get_sub_app_mut(RenderApp) .unwrap() - .init_resource::() - .init_resource::>() + .insert_resource(Specializer::new( + CustomPhaseSpecializer, + None, + base_pipeline_descriptor, + )) .add_render_command::() .add_systems( Render, @@ -219,10 +216,9 @@ fn prepare_custom_phase_item_buffers(mut commands: Commands) { /// the opaque render phases of each view. fn queue_custom_phase_item( pipeline_cache: Res, - custom_phase_pipeline: Res, mut opaque_render_phases: ResMut>, opaque_draw_functions: Res>, - mut specialized_render_pipelines: ResMut>, + mut specialized_render_pipelines: ResMut>, views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, ) { let draw_custom_phase_item = opaque_draw_functions @@ -244,11 +240,7 @@ fn queue_custom_phase_item( // some per-view settings, such as whether the view is HDR, but for // simplicity's sake we simply hard-code the view's characteristics, // with the exception of number of MSAA samples. - let pipeline_id = specialized_render_pipelines.specialize( - &pipeline_cache, - &custom_phase_pipeline, - *msaa, - ); + let pipeline_id = specialized_render_pipelines.specialize(&pipeline_cache, *msaa); // Add the custom render item. We use the // [`BinnedRenderPhaseType::NonMesh`] type to skip the special @@ -277,66 +269,73 @@ fn queue_custom_phase_item( } } -impl SpecializedRenderPipeline for CustomPhasePipeline { +struct CustomPhaseSpecializer; + +impl Specialize for CustomPhaseSpecializer { type Key = Msaa; - fn specialize(&self, msaa: Self::Key) -> RenderPipelineDescriptor { - RenderPipelineDescriptor { - label: Some("custom render pipeline".into()), - layout: vec![], - push_constant_ranges: vec![], - vertex: VertexState { - shader: self.shader.clone(), - shader_defs: vec![], - entry_point: "vertex".into(), - buffers: vec![VertexBufferLayout { - array_stride: size_of::() as u64, - step_mode: VertexStepMode::Vertex, - // This needs to match the layout of [`Vertex`]. - attributes: vec![ - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 0, - }, - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 16, - shader_location: 1, - }, - ], - }], - }, - fragment: Some(FragmentState { - shader: self.shader.clone(), - shader_defs: vec![], - entry_point: "fragment".into(), - targets: vec![Some(ColorTargetState { - // Ordinarily, you'd want to check whether the view has the - // HDR format and substitute the appropriate texture format - // here, but we omit that for simplicity. - format: TextureFormat::bevy_default(), - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - // Note that if your view has no depth buffer this will need to be - // changed. - depth_stencil: Some(DepthStencilState { - format: CORE_3D_DEPTH_FORMAT, - depth_write_enabled: false, - depth_compare: CompareFunction::Always, - stencil: default(), - bias: default(), - }), - multisample: MultisampleState { - count: msaa.samples(), - mask: !0, - alpha_to_coverage_enabled: false, - }, - zero_initialize_workgroup_memory: false, - } + fn specialize(&self, msaa: Self::Key, descriptor: &mut RenderPipelineDescriptor) { + descriptor.multisample.count = msaa.samples(); + } +} + +fn base_pipeline_descriptor(asset_server: &AssetServer) -> RenderPipelineDescriptor { + let shader = asset_server.load("shaders/custom_phase_item.wgsl"); + RenderPipelineDescriptor { + label: Some("custom render pipeline".into()), + layout: vec![], + push_constant_ranges: vec![], + vertex: VertexState { + shader: shader.clone(), + shader_defs: vec![], + entry_point: "vertex".into(), + buffers: vec![VertexBufferLayout { + array_stride: size_of::() as u64, + step_mode: VertexStepMode::Vertex, + // This needs to match the layout of [`Vertex`]. + attributes: vec![ + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }, + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 16, + shader_location: 1, + }, + ], + }], + }, + fragment: Some(FragmentState { + shader, + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + // Ordinarily, you'd want to check whether the view has the + // HDR format and substitute the appropriate texture format + // here, but we omit that for simplicity. + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + // Note that if your view has no depth buffer this will need to be + // changed. + depth_stencil: Some(DepthStencilState { + format: CORE_3D_DEPTH_FORMAT, + depth_write_enabled: false, + depth_compare: CompareFunction::Always, + stencil: default(), + bias: default(), + }), + multisample: MultisampleState { + count: 0, + mask: !0, + alpha_to_coverage_enabled: false, + }, + zero_initialize_workgroup_memory: false, } } @@ -366,14 +365,3 @@ impl FromWorld for CustomPhaseItemBuffers { } } } - -impl FromWorld for CustomPhasePipeline { - fn from_world(world: &mut World) -> Self { - // Load and compile the shader in the background. - let asset_server = world.resource::(); - - CustomPhasePipeline { - shader: asset_server.load("shaders/custom_phase_item.wgsl"), - } - } -} From 6ea5fd32b2dd95fda5511fcee809a19646ef0d8c Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 14 Jan 2025 13:29:07 -0800 Subject: [PATCH 09/50] add base descriptor trait --- .../src/render_resource/specialize.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 34c84769d7071..3019df257afad 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -2,7 +2,10 @@ use super::{ CachedComputePipelineId, CachedRenderPipelineId, ComputePipeline, ComputePipelineDescriptor, PipelineCache, RenderPipeline, RenderPipelineDescriptor, }; -use bevy_ecs::system::Resource; +use bevy_ecs::{ + system::Resource, + world::{FromWorld, World}, +}; use bevy_utils::hashbrown::HashMap; use core::hash::Hash; @@ -38,8 +41,9 @@ pub trait Specialize: Send + Sync + 'static { fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor); } -pub type RenderPipelineSpecializer = Specializer; -pub type ComputePipelineSpecializer = Specializer; +pub trait HasBaseDescriptor: Specialize { + fn base_descriptor(&self) -> T::Descriptor; +} #[derive(Resource)] pub struct Specializer> { @@ -77,3 +81,11 @@ impl> Specializer { .clone() } } + +impl> FromWorld for Specializer { + fn from_world(world: &mut World) -> Self { + let specializer = S::from_world(world); + let base_descriptor = specializer.base_descriptor(); + Self::new(specializer, None, base_descriptor) + } +} From 7e084d4874f774be972cf62abf5da0b48d2e399d Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 14 Jan 2025 13:45:09 -0800 Subject: [PATCH 10/50] small fixes still dying because of HashMap. Why? --- crates/bevy_render/macros/src/specialize.rs | 8 +- .../src/render_resource/specialize.rs | 2 +- examples/shader/custom_phase_item.rs | 145 ++++++++++-------- 3 files changed, 81 insertions(+), 74 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 52a0237cc7e93..e52e042446136 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -1,10 +1,8 @@ use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote}; use syn::{ - parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Data, - DataStruct, DeriveInput, Expr, Index, Member, Meta, MetaList, MetaNameValue, Path, Stmt, Token, - Type, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Data, DataStruct, + DeriveInput, Expr, Index, Member, Meta, MetaList, Path, Stmt, Token, Type, }; const KEY_ATTR_IDENT: &str = "key"; diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 3019df257afad..0b2e045cf97a1 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -63,7 +63,7 @@ impl> Specializer { specializer, user_specializer, base_descriptor, - pipelines: Default::default(), + pipelines: HashMap::new(), } } diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 090b2cef0e48e..d8e578f080376 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -32,7 +32,7 @@ use bevy::{ Render, RenderApp, RenderSet, }, }; -use bevy_render::render_resource::{RenderPipeline, Specialize, Specializer}; +use bevy_render::render_resource::{HasBaseDescriptor, RenderPipeline, Specialize, Specializer}; use bytemuck::{Pod, Zeroable}; /// A marker component that represents an entity that is to be rendered using @@ -162,16 +162,10 @@ fn main() { .add_plugins(ExtractComponentPlugin::::default()) .add_systems(Startup, setup); - let base_pipeline_descriptor = base_pipeline_descriptor(app.world().resource::()); - // We make sure to add these to the render app, not the main app. app.get_sub_app_mut(RenderApp) .unwrap() - .insert_resource(Specializer::new( - CustomPhaseSpecializer, - None, - base_pipeline_descriptor, - )) + .init_resource::>() .add_render_command::() .add_systems( Render, @@ -218,7 +212,7 @@ fn queue_custom_phase_item( pipeline_cache: Res, mut opaque_render_phases: ResMut>, opaque_draw_functions: Res>, - mut specialized_render_pipelines: ResMut>, + mut specializer: ResMut>, views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, ) { let draw_custom_phase_item = opaque_draw_functions @@ -240,7 +234,7 @@ fn queue_custom_phase_item( // some per-view settings, such as whether the view is HDR, but for // simplicity's sake we simply hard-code the view's characteristics, // with the exception of number of MSAA samples. - let pipeline_id = specialized_render_pipelines.specialize(&pipeline_cache, *msaa); + let pipeline_id = specializer.specialize(&pipeline_cache, *msaa); // Add the custom render item. We use the // [`BinnedRenderPhaseType::NonMesh`] type to skip the special @@ -269,7 +263,21 @@ fn queue_custom_phase_item( } } -struct CustomPhaseSpecializer; +/// Holds a reference to our shader. +/// +/// This is loaded at app creation time. +struct CustomPhaseSpecializer { + shader: Handle, +} + +impl FromWorld for CustomPhaseSpecializer { + fn from_world(world: &mut World) -> Self { + let asset_server = world.resource::(); + Self { + shader: asset_server.load("shaders/custom_phase_item.wgsl"), + } + } +} impl Specialize for CustomPhaseSpecializer { type Key = Msaa; @@ -279,63 +287,64 @@ impl Specialize for CustomPhaseSpecializer { } } -fn base_pipeline_descriptor(asset_server: &AssetServer) -> RenderPipelineDescriptor { - let shader = asset_server.load("shaders/custom_phase_item.wgsl"); - RenderPipelineDescriptor { - label: Some("custom render pipeline".into()), - layout: vec![], - push_constant_ranges: vec![], - vertex: VertexState { - shader: shader.clone(), - shader_defs: vec![], - entry_point: "vertex".into(), - buffers: vec![VertexBufferLayout { - array_stride: size_of::() as u64, - step_mode: VertexStepMode::Vertex, - // This needs to match the layout of [`Vertex`]. - attributes: vec![ - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 0, - }, - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 16, - shader_location: 1, - }, - ], - }], - }, - fragment: Some(FragmentState { - shader, - shader_defs: vec![], - entry_point: "fragment".into(), - targets: vec![Some(ColorTargetState { - // Ordinarily, you'd want to check whether the view has the - // HDR format and substitute the appropriate texture format - // here, but we omit that for simplicity. - format: TextureFormat::bevy_default(), - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - // Note that if your view has no depth buffer this will need to be - // changed. - depth_stencil: Some(DepthStencilState { - format: CORE_3D_DEPTH_FORMAT, - depth_write_enabled: false, - depth_compare: CompareFunction::Always, - stencil: default(), - bias: default(), - }), - multisample: MultisampleState { - count: 0, - mask: !0, - alpha_to_coverage_enabled: false, - }, - zero_initialize_workgroup_memory: false, +impl HasBaseDescriptor for CustomPhaseSpecializer { + fn base_descriptor(&self) -> RenderPipelineDescriptor { + RenderPipelineDescriptor { + label: Some("custom render pipeline".into()), + layout: vec![], + push_constant_ranges: vec![], + vertex: VertexState { + shader: self.shader.clone(), + shader_defs: vec![], + entry_point: "vertex".into(), + buffers: vec![VertexBufferLayout { + array_stride: size_of::() as u64, + step_mode: VertexStepMode::Vertex, + // This needs to match the layout of [`Vertex`]. + attributes: vec![ + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }, + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 16, + shader_location: 1, + }, + ], + }], + }, + fragment: Some(FragmentState { + shader: self.shader.clone(), + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + // Ordinarily, you'd want to check whether the view has the + // HDR format and substitute the appropriate texture format + // here, but we omit that for simplicity. + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + // Note that if your view has no depth buffer this will need to be + // changed. + depth_stencil: Some(DepthStencilState { + format: CORE_3D_DEPTH_FORMAT, + depth_write_enabled: false, + depth_compare: CompareFunction::Always, + stencil: default(), + bias: default(), + }), + multisample: MultisampleState { + count: 0, + mask: !0, + alpha_to_coverage_enabled: false, + }, + zero_initialize_workgroup_memory: false, + } } } From bd877a1a3e1bce1e8f382b1083b8c4356f679561 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:50:51 -0800 Subject: [PATCH 11/50] rename trait --- .../src/render_resource/specialize.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 0b2e045cf97a1..3ee1e74642861 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -11,13 +11,13 @@ use core::hash::Hash; pub use bevy_render_macros::{SpecializeComputePipeline, SpecializeRenderPipeline}; -pub trait SpecializeTarget { +pub trait Specializable { type Descriptor: Clone + Send + Sync; type CachedId: Clone + Send + Sync; fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId; } -impl SpecializeTarget for RenderPipeline { +impl Specializable for RenderPipeline { type Descriptor = RenderPipelineDescriptor; type CachedId = CachedRenderPipelineId; @@ -26,7 +26,7 @@ impl SpecializeTarget for RenderPipeline { } } -impl SpecializeTarget for ComputePipeline { +impl Specializable for ComputePipeline { type Descriptor = ComputePipelineDescriptor; type CachedId = CachedComputePipelineId; @@ -36,24 +36,24 @@ impl SpecializeTarget for ComputePipeline { } } -pub trait Specialize: Send + Sync + 'static { +pub trait Specialize: Send + Sync + 'static { type Key: Clone + Hash + Eq; fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor); } -pub trait HasBaseDescriptor: Specialize { +pub trait HasBaseDescriptor: Specialize { fn base_descriptor(&self) -> T::Descriptor; } #[derive(Resource)] -pub struct Specializer> { +pub struct Specializer> { specializer: S, user_specializer: Option, base_descriptor: T::Descriptor, pipelines: HashMap, } -impl> Specializer { +impl> Specializer { pub fn new( specializer: S, user_specializer: Option, @@ -76,13 +76,13 @@ impl> Specializer { if let Some(user_specializer) = self.user_specializer { (user_specializer)(key, &mut descriptor); } - ::queue(pipeline_cache, descriptor) + ::queue(pipeline_cache, descriptor) }) .clone() } } -impl> FromWorld for Specializer { +impl> FromWorld for Specializer { fn from_world(world: &mut World) -> Self { let specializer = S::from_world(world); let base_descriptor = specializer.base_descriptor(); From 346b5887463c91e4d18e61d6cea65ed343039592 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:29:37 -0800 Subject: [PATCH 12/50] macro v2 --- crates/bevy_render/macros/src/lib.rs | 31 +--- crates/bevy_render/macros/src/specialize.rs | 165 ++++++++++++++++---- 2 files changed, 141 insertions(+), 55 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index b8f988889a895..e85b69fb989aa 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -9,7 +9,6 @@ mod specialize; use bevy_macro_utils::{derive_label, BevyManifest}; use proc_macro::TokenStream; use quote::format_ident; -use specialize::derive_specialize; use syn::{parse_macro_input, DeriveInput}; pub(crate) fn bevy_render_path() -> syn::Path { @@ -110,30 +109,10 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream { derive_label(input, "RenderSubGraph", &trait_path, &dyn_eq_path) } -/// Derive macro generating an impl of the trait `Specialize` +/// Derive macro generating an impl of the trait `Specialize` /// -/// This only works for structs whose members all implement `Specialize` -#[proc_macro_derive(SpecializeRenderPipeline, attributes(key))] -pub fn derive_specialize_render_pipeline(input: TokenStream) -> TokenStream { - let target_path = { - let mut path = bevy_render_path(); - path.segments.push(format_ident!("render_resource").into()); - path.segments.push(format_ident!("RenderPipeline").into()); - path - }; - derive_specialize(input, target_path, "SpecializeRenderPipeline") -} - -/// Derive macro generating an impl of the trait `Specialize` -/// -/// This only works for structs whose members all implement `Specialize` -#[proc_macro_derive(SpecializeComputePipeline, attributes(key))] -pub fn derive_specialize_compute_pipeline(input: TokenStream) -> TokenStream { - let target_path = { - let mut path = bevy_render_path(); - path.segments.push(format_ident!("render_resource").into()); - path.segments.push(format_ident!("ComputePipeline").into()); - path - }; - derive_specialize(input, target_path, "SpecializeComputePipeline") +/// This only works for structs whose members all implement `Specialize` +#[proc_macro_derive(Specialize, attributes(specialize, key))] +pub fn derive_specialize(input: TokenStream) -> TokenStream { + specialize::impl_specialize(input) } diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index e52e042446136..f5d9e81403fcf 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -1,13 +1,73 @@ +use bevy_macro_utils::fq_std::FQDefault; use proc_macro::TokenStream; +use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{ - parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Data, DataStruct, - DeriveInput, Expr, Index, Member, Meta, MetaList, Path, Stmt, Token, Type, + parse::{Parse, ParseStream}, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Data, DataStruct, DeriveInput, Expr, Ident, Index, Member, Meta, MetaList, Path, Stmt, Token, Type }; +const SPECIALIZE_ATTR_IDENT: &str = "specialize"; +const SPECIALIZE_ALL_IDENT: &str = "all"; + const KEY_ATTR_IDENT: &str = "key"; +const KEY_DEFAULT_IDENT: &str = "default"; + +pub enum SpecializeImplTargets { + All, + Specific(Vec), +} + +impl Parse for SpecializeImplTargets { + fn parse(input: ParseStream) -> syn::Result { + if let Ok(ident) = input.parse::() { + if ident == SPECIALIZE_ALL_IDENT { + return Ok(SpecializeImplTargets::All) + } + } + input.parse::>().map(|punctuated| Self::Specific(punctuated.into_iter().collect())) + } +} + +enum Key { + Whole, + Default(Span), + Index(Index), + Custom(Expr, Span), +} + +impl Key { + pub fn expr(&self) -> Expr { + match self { + Key::Whole => parse_quote!(key), + Key::Default(_) => parse_quote!(#FQDefault::default()), + Key::Index(index) => { + let member = Member::Unnamed(index.clone()); + parse_quote!(key.#member) + } + Key::Custom(expr, _) => expr.clone(), + } + } +} + +struct FieldInfo { + ty: Type, + member: Member, + key: Key, +} + +impl FieldInfo { + pub fn key_ty(&self, specialize_path: &Path, target_path: &Path) -> Option { + matches!(self.key, Key::Whole | Key::Index(_)).then_some( + parse_quote!(<#{self.ty} as #specialize_path::Specialize<#target_path>>::Key), + ) + } + + pub fn specialize_stmt(&self, specialize_path: &Path, target_path: &Path) -> Stmt { + parse_quote!(<#{self.ty} as #specialize_path::Specialize<#target_path>>::specialize(&self.#{self.member}, #{self.key.expr()}, descriptor);) + } +} -pub fn derive_specialize(input: TokenStream, target_path: Path, derive_name: &str) -> TokenStream { +pub fn impl_specialize(input: TokenStream) -> TokenStream { let bevy_render_path: Path = crate::bevy_render_path(); let specialize_path = { let mut path = bevy_render_path.clone(); @@ -16,25 +76,37 @@ pub fn derive_specialize(input: TokenStream, target_path: Path, derive_name: &st }; let ast = parse_macro_input!(input as DeriveInput); - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let specialize_attr = ast.attrs.iter().find_map(|attr| { + if attr.path().is_ident(SPECIALIZE_ATTR_IDENT) { + if let Meta::List(meta_list) = &attr.meta { + return Some(meta_list); + } + } + None + }); + let Some(specialize_meta_list) = specialize_attr else { + return syn::Error::new( + Span::call_site(), + "#[derive(Specialize) must be accompanied by #[specialize(..Targets)].\n Example usages: #[specialize(RenderPipeline)], #[specialize(all)]" + ).into_compile_error().into(); + }; + let specialize_attr_tokens = specialize_meta_list.tokens.clone().into(); + let targets = parse_macro_input!(specialize_attr_tokens as SpecializeImplTargets); + let Data::Struct(DataStruct { fields, .. }) = &ast.data else { - let error_span = match ast.data { + let error_span = match &ast.data { Data::Struct(_) => unreachable!(), Data::Enum(data_enum) => data_enum.enum_token.span(), Data::Union(data_union) => data_union.union_token.span(), }; - return syn::Error::new( - error_span, - format!("#[derive({derive_name})]` only supports structs"), - ) - .into_compile_error() - .into(); + return syn::Error::new(error_span, "#[derive(Specialize)]` only supports structs") + .into_compile_error() + .into(); }; - let mut key_elems: Punctuated = Punctuated::new(); - let mut sub_specializers: Vec<(Type, Member, Expr)> = Vec::new(); + let mut field_info: Vec = Vec::new(); + let mut used_count = 0; let mut single_index = 0; for (index, field) in fields.iter().enumerate() { @@ -46,46 +118,81 @@ pub fn derive_specialize(input: TokenStream, target_path: Path, derive_name: &st }), Member::Named, ); - let key_member = Member::Unnamed(Index { - index: key_elems.len() as u32, + let key_index = Index { + index: used_count, span: field.span(), - }); + }; - let mut key_expr: Expr = parse_quote!(key.#key_member); let mut use_key_field = true; + let mut key = Key::Index(key_index); for attr in &field.attrs { if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta { if path.is_ident(&KEY_ATTR_IDENT) { let owned_tokens = tokens.clone().into(); - key_expr = parse_macro_input!(owned_tokens as Expr); + //TODO: handle #[key(default)] + key = Key::Custom(parse_macro_input!(owned_tokens as Expr), attr.span()); use_key_field = false; } } } if use_key_field { + used_count += 1; single_index = index; - key_elems - .push(parse_quote!(<#field_ty as #specialize_path::Specialize<#target_path>>::Key)); } - sub_specializers.push((field_ty, field_member, key_expr)); + field_info.push(FieldInfo { + ty: field_ty, + member: field_member, + key, + }); } - if key_elems.len() == 1 { - sub_specializers[single_index].2 = parse_quote!(key); + if used_count == 1 { + field_info[single_index].key = Key::Whole; } - let sub_specializers = sub_specializers.into_iter().map(|(field_ty, field_member, key_expr)| { - parse_quote!(<#field_ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#field_member, #key_expr, descriptor);) - }).collect::>(); + match targets { + SpecializeImplTargets::All => impl_specialize_all(&specialize_path, &ast, &field_info), + SpecializeImplTargets::Specific(targets) => targets + .iter() + .map(|target| impl_specialize_specific(&specialize_path, &ast, &field_info, target)) + .collect(), + } +} + +pub fn impl_specialize_all( + specialize_path: &Path, + ast: &DeriveInput, + field_info: &[FieldInfo], +) -> TokenStream { + todo!() +} + +pub fn impl_specialize_specific( + specialize_path: &Path, + ast: &DeriveInput, + field_info: &[FieldInfo], + target_path: &Path, +) -> TokenStream { + let key_elems: Vec = field_info + .iter() + .filter_map(|field_info| field_info.key_ty(specialize_path, target_path)) + .collect(); + let specialize_stmts: Vec = field_info + .iter() + .map(|field_info| field_info.specialize_stmt(specialize_path, target_path)) + .collect(); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); TokenStream::from(quote! { impl #impl_generics #specialize_path::Specialize<#target_path> for #struct_name #type_generics #where_clause { - type Key = (#key_elems); + type Key = (#(#key_elems),*); fn specialize(&self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::SpecializeTarget>::Descriptor) { - #(#sub_specializers)* + #(#specialize_stmts)* } } }) From fcf9ab1d3bd9e8930e25589c278a25a1b25ebdf5 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 14 Jan 2025 23:02:27 -0800 Subject: [PATCH 13/50] macro v2 --- crates/bevy_render/macros/src/specialize.rs | 127 ++++++++++++++---- .../src/render_resource/specialize.rs | 2 +- examples/shader/custom_phase_item.rs | 4 + 3 files changed, 108 insertions(+), 25 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index f5d9e81403fcf..978c0bd6e0ce9 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -3,7 +3,11 @@ use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{ - parse::{Parse, ParseStream}, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Data, DataStruct, DeriveInput, Expr, Ident, Index, Member, Meta, MetaList, Path, Stmt, Token, Type + parse::{Parse, ParseStream}, + parse_macro_input, parse_quote, + spanned::Spanned, + Data, DataStruct, DeriveInput, Expr, Ident, Index, Member, Meta, MetaList, Path, Stmt, Token, + Type, WherePredicate, }; const SPECIALIZE_ATTR_IDENT: &str = "specialize"; @@ -12,39 +16,58 @@ const SPECIALIZE_ALL_IDENT: &str = "all"; const KEY_ATTR_IDENT: &str = "key"; const KEY_DEFAULT_IDENT: &str = "default"; -pub enum SpecializeImplTargets { +enum SpecializeImplTargets { All, Specific(Vec), } impl Parse for SpecializeImplTargets { fn parse(input: ParseStream) -> syn::Result { - if let Ok(ident) = input.parse::() { - if ident == SPECIALIZE_ALL_IDENT { - return Ok(SpecializeImplTargets::All) - } - } - input.parse::>().map(|punctuated| Self::Specific(punctuated.into_iter().collect())) + let paths = input.parse_terminated(Path::parse, Token![,])?; + if paths[0].is_ident(SPECIALIZE_ALL_IDENT) { + Ok(SpecializeImplTargets::All) + } else { + Ok(SpecializeImplTargets::Specific(paths.into_iter().collect())) + } } } enum Key { Whole, - Default(Span), + Default, Index(Index), - Custom(Expr, Span), + Custom(Expr), } impl Key { pub fn expr(&self) -> Expr { match self { Key::Whole => parse_quote!(key), - Key::Default(_) => parse_quote!(#FQDefault::default()), + Key::Default => parse_quote!(#FQDefault::default()), Key::Index(index) => { let member = Member::Unnamed(index.clone()); parse_quote!(key.#member) } - Key::Custom(expr, _) => expr.clone(), + Key::Custom(expr) => expr.clone(), + } + } +} + +const KEY_ERROR_MSG: &str = "Invalid key override. Must be either `default` or a valid Rust expression of the correct key type"; + +impl Parse for Key { + fn parse(input: ParseStream) -> syn::Result { + if let Ok(ident) = input.parse::() { + if ident == KEY_DEFAULT_IDENT { + Ok(Key::Default) + } else { + Err(syn::Error::new_spanned(ident, KEY_ERROR_MSG)) + } + } else { + input.parse::().map(Key::Custom).map_err(|mut err| { + err.extend(syn::Error::new(err.span(), KEY_ERROR_MSG)); + err + }) } } } @@ -57,13 +80,24 @@ struct FieldInfo { impl FieldInfo { pub fn key_ty(&self, specialize_path: &Path, target_path: &Path) -> Option { - matches!(self.key, Key::Whole | Key::Index(_)).then_some( - parse_quote!(<#{self.ty} as #specialize_path::Specialize<#target_path>>::Key), - ) + let ty = &self.ty; + matches!(self.key, Key::Whole | Key::Index(_)) + .then_some(parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::Key)) } pub fn specialize_stmt(&self, specialize_path: &Path, target_path: &Path) -> Stmt { - parse_quote!(<#{self.ty} as #specialize_path::Specialize<#target_path>>::specialize(&self.#{self.member}, #{self.key.expr()}, descriptor);) + let FieldInfo { ty, member, key } = &self; + let key_expr = key.expr(); + parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#member, #key_expr, descriptor);) + } + + pub fn predicate(&self, specialize_path: &Path, target_path: &Path) -> WherePredicate { + let ty = &self.ty; + if matches!(&self.key, Key::Default) { + parse_quote!(#ty: #specialize_path::Specialize<#target_path, Key: #FQDefault>) + } else { + parse_quote!(#ty: #specialize_path::Specialize<#target_path>) + } } } @@ -86,14 +120,13 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { }); let Some(specialize_meta_list) = specialize_attr else { return syn::Error::new( - Span::call_site(), + Span::call_site(), "#[derive(Specialize) must be accompanied by #[specialize(..Targets)].\n Example usages: #[specialize(RenderPipeline)], #[specialize(all)]" ).into_compile_error().into(); }; let specialize_attr_tokens = specialize_meta_list.tokens.clone().into(); let targets = parse_macro_input!(specialize_attr_tokens as SpecializeImplTargets); - let Data::Struct(DataStruct { fields, .. }) = &ast.data else { let error_span = match &ast.data { Data::Struct(_) => unreachable!(), @@ -129,8 +162,18 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta { if path.is_ident(&KEY_ATTR_IDENT) { let owned_tokens = tokens.clone().into(); - //TODO: handle #[key(default)] - key = Key::Custom(parse_macro_input!(owned_tokens as Expr), attr.span()); + key = parse_macro_input!(owned_tokens as Key); + if matches!( + (&key, &targets), + (Key::Custom(_), SpecializeImplTargets::All) + ) { + return syn::Error::new( + tokens.span(), + "#[key(default)] is the only override allowed with #[specialize(all)]", + ) + .into_compile_error() + .into(); + } use_key_field = false; } } @@ -161,15 +204,51 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { } } -pub fn impl_specialize_all( +fn impl_specialize_all( specialize_path: &Path, ast: &DeriveInput, field_info: &[FieldInfo], ) -> TokenStream { - todo!() + let target_path = Path::from(format_ident!("T")); + let key_elems: Vec = field_info + .iter() + .filter_map(|field_info| field_info.key_ty(specialize_path, &target_path)) + .collect(); + let specialize_stmts: Vec = field_info + .iter() + .map(|field_info| field_info.specialize_stmt(specialize_path, &target_path)) + .collect(); + + let struct_name = &ast.ident; + let mut generics = ast.generics.clone(); + generics.params.insert( + 0, + parse_quote!(#target_path: #specialize_path::Specializable), + ); + + if !field_info.is_empty() { + let where_clause = generics.make_where_clause(); + for field in field_info { + where_clause + .predicates + .push(field.predicate(specialize_path, &target_path)); + } + } + + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics #specialize_path::Specialize<#target_path> for #struct_name #type_generics #where_clause { + type Key = (#(#key_elems),*); + + fn specialize(&self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::Specializable>::Descriptor) { + #(#specialize_stmts)* + } + } + }) } -pub fn impl_specialize_specific( +fn impl_specialize_specific( specialize_path: &Path, ast: &DeriveInput, field_info: &[FieldInfo], @@ -191,7 +270,7 @@ pub fn impl_specialize_specific( impl #impl_generics #specialize_path::Specialize<#target_path> for #struct_name #type_generics #where_clause { type Key = (#(#key_elems),*); - fn specialize(&self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::SpecializeTarget>::Descriptor) { + fn specialize(&self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::Specializable>::Descriptor) { #(#specialize_stmts)* } } diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 3ee1e74642861..02a819fcade76 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -9,7 +9,7 @@ use bevy_ecs::{ use bevy_utils::hashbrown::HashMap; use core::hash::Hash; -pub use bevy_render_macros::{SpecializeComputePipeline, SpecializeRenderPipeline}; +pub use bevy_render_macros::Specialize; pub trait Specializable { type Descriptor: Clone + Send + Sync; diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index d8e578f080376..d2a930722aa2a 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -287,6 +287,10 @@ impl Specialize for CustomPhaseSpecializer { } } +#[derive(Specialize)] +#[specialize(all)] +struct Wrapper(#[key(default)] CustomPhaseSpecializer); + impl HasBaseDescriptor for CustomPhaseSpecializer { fn base_descriptor(&self) -> RenderPipelineDescriptor { RenderPipelineDescriptor { From 5a33a2181b2ca4eea826e64cb2d7187d388cba81 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Wed, 15 Jan 2025 12:14:46 -0800 Subject: [PATCH 14/50] error msg --- crates/bevy_render/macros/src/specialize.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 978c0bd6e0ce9..467cc03439c18 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -121,7 +121,7 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { let Some(specialize_meta_list) = specialize_attr else { return syn::Error::new( Span::call_site(), - "#[derive(Specialize) must be accompanied by #[specialize(..Targets)].\n Example usages: #[specialize(RenderPipeline)], #[specialize(all)]" + "#[derive(Specialize) must be accompanied by #[specialize(..targets)].\n Example usages: #[specialize(RenderPipeline)], #[specialize(all)]" ).into_compile_error().into(); }; let specialize_attr_tokens = specialize_meta_list.tokens.clone().into(); @@ -169,7 +169,7 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { ) { return syn::Error::new( tokens.span(), - "#[key(default)] is the only override allowed with #[specialize(all)]", + "#[key(default)] is the only key override type allowed with #[specialize(all)]", ) .into_compile_error() .into(); From ba5225d3cc8e34c6170c49a411b471169c95e361 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Wed, 15 Jan 2025 14:33:07 -0800 Subject: [PATCH 15/50] macro v2 done --- crates/bevy_render/macros/src/specialize.rs | 3 ++- examples/shader/custom_phase_item.rs | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 467cc03439c18..fac11f5b4ed58 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -235,7 +235,8 @@ fn impl_specialize_all( } } - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let (_, type_generics, _) = ast.generics.split_for_impl(); + let (impl_generics, _, where_clause) = &generics.split_for_impl(); TokenStream::from(quote! { impl #impl_generics #specialize_path::Specialize<#target_path> for #struct_name #type_generics #where_clause { diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index d2a930722aa2a..d8e578f080376 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -287,10 +287,6 @@ impl Specialize for CustomPhaseSpecializer { } } -#[derive(Specialize)] -#[specialize(all)] -struct Wrapper(#[key(default)] CustomPhaseSpecializer); - impl HasBaseDescriptor for CustomPhaseSpecializer { fn base_descriptor(&self) -> RenderPipelineDescriptor { RenderPipelineDescriptor { From 04b9c94c289878ce12dec845c4e47e675bf7e63a Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Wed, 15 Jan 2025 21:21:30 -0800 Subject: [PATCH 16/50] macro v3 --- crates/bevy_render/macros/src/lib.rs | 7 + crates/bevy_render/macros/src/specialize.rs | 264 ++++++++++++++---- .../src/render_resource/specialize.rs | 16 +- 3 files changed, 233 insertions(+), 54 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index e85b69fb989aa..67112ece705d7 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -116,3 +116,10 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream { pub fn derive_specialize(input: TokenStream) -> TokenStream { specialize::impl_specialize(input) } + +/// Derive macro generating an impl of the trait `HasBaseDescriptor` +/// by deferring to the `HasBaseDescriptor` impl of a chosen field. +#[proc_macro_derive(HasBaseDescriptor, attributes(specialize, base_descriptor))] +pub fn derive_has_base_descriptor(input: TokenStream) -> TokenStream { + specialize::impl_has_base_descriptor(input) +} diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index fac11f5b4ed58..7df180cfe4df0 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -3,11 +3,12 @@ use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{ + parse, parse::{Parse, ParseStream}, parse_macro_input, parse_quote, spanned::Spanned, - Data, DataStruct, DeriveInput, Expr, Ident, Index, Member, Meta, MetaList, Path, Stmt, Token, - Type, WherePredicate, + Data, DataStruct, DeriveInput, Expr, Fields, Ident, Index, Member, Meta, MetaList, Path, Stmt, + Token, Type, WherePredicate, }; const SPECIALIZE_ATTR_IDENT: &str = "specialize"; @@ -16,6 +17,8 @@ const SPECIALIZE_ALL_IDENT: &str = "all"; const KEY_ATTR_IDENT: &str = "key"; const KEY_DEFAULT_IDENT: &str = "default"; +const BASE_DESCRIPTOR_ATTR_IDENT: &str = "base_descriptor"; + enum SpecializeImplTargets { All, Specific(Vec), @@ -24,7 +27,7 @@ enum SpecializeImplTargets { impl Parse for SpecializeImplTargets { fn parse(input: ParseStream) -> syn::Result { let paths = input.parse_terminated(Path::parse, Token![,])?; - if paths[0].is_ident(SPECIALIZE_ALL_IDENT) { + if !paths.is_empty() && paths[0].is_ident(SPECIALIZE_ALL_IDENT) { Ok(SpecializeImplTargets::All) } else { Ok(SpecializeImplTargets::Specific(paths.into_iter().collect())) @@ -32,6 +35,7 @@ impl Parse for SpecializeImplTargets { } } +#[derive(Clone)] enum Key { Whole, Default, @@ -72,10 +76,12 @@ impl Parse for Key { } } +#[derive(Clone)] struct FieldInfo { ty: Type, member: Member, key: Key, + use_base_descriptor: bool, } impl FieldInfo { @@ -86,12 +92,18 @@ impl FieldInfo { } pub fn specialize_stmt(&self, specialize_path: &Path, target_path: &Path) -> Stmt { - let FieldInfo { ty, member, key } = &self; + let FieldInfo { + ty, member, key, .. + } = &self; let key_expr = key.expr(); parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#member, #key_expr, descriptor);) } - pub fn predicate(&self, specialize_path: &Path, target_path: &Path) -> WherePredicate { + pub fn specialize_predicate( + &self, + specialize_path: &Path, + target_path: &Path, + ) -> WherePredicate { let ty = &self.ty; if matches!(&self.key, Key::Default) { parse_quote!(#ty: #specialize_path::Specialize<#target_path, Key: #FQDefault>) @@ -99,49 +111,21 @@ impl FieldInfo { parse_quote!(#ty: #specialize_path::Specialize<#target_path>) } } -} -pub fn impl_specialize(input: TokenStream) -> TokenStream { - let bevy_render_path: Path = crate::bevy_render_path(); - let specialize_path = { - let mut path = bevy_render_path.clone(); - path.segments.push(format_ident!("render_resource").into()); - path - }; - - let ast = parse_macro_input!(input as DeriveInput); - let specialize_attr = ast.attrs.iter().find_map(|attr| { - if attr.path().is_ident(SPECIALIZE_ATTR_IDENT) { - if let Meta::List(meta_list) = &attr.meta { - return Some(meta_list); - } - } - None - }); - let Some(specialize_meta_list) = specialize_attr else { - return syn::Error::new( - Span::call_site(), - "#[derive(Specialize) must be accompanied by #[specialize(..targets)].\n Example usages: #[specialize(RenderPipeline)], #[specialize(all)]" - ).into_compile_error().into(); - }; - let specialize_attr_tokens = specialize_meta_list.tokens.clone().into(); - let targets = parse_macro_input!(specialize_attr_tokens as SpecializeImplTargets); - - let Data::Struct(DataStruct { fields, .. }) = &ast.data else { - let error_span = match &ast.data { - Data::Struct(_) => unreachable!(), - Data::Enum(data_enum) => data_enum.enum_token.span(), - Data::Union(data_union) => data_union.union_token.span(), - }; - return syn::Error::new(error_span, "#[derive(Specialize)]` only supports structs") - .into_compile_error() - .into(); - }; + pub fn has_base_descriptor_predicate( + &self, + specialize_path: &Path, + target_path: &Path, + ) -> WherePredicate { + let ty = &self.ty; + parse_quote!(#ty: #specialize_path::HasBaseDescriptor<#target_path>) + } +} +fn get_field_info(fields: &Fields, targets: &SpecializeImplTargets) -> syn::Result> { let mut field_info: Vec = Vec::new(); let mut used_count = 0; let mut single_index = 0; - for (index, field) in fields.iter().enumerate() { let field_ty = field.ty.clone(); let field_member = field.ident.clone().map_or( @@ -158,24 +142,33 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { let mut use_key_field = true; let mut key = Key::Index(key_index); + let mut use_base_descriptor = false; for attr in &field.attrs { - if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta { - if path.is_ident(&KEY_ATTR_IDENT) { + match &attr.meta { + Meta::Path(path) if path.is_ident(&BASE_DESCRIPTOR_ATTR_IDENT) => { + use_base_descriptor = true; + } + Meta::List(MetaList { path, tokens, .. }) if path.is_ident(&KEY_ATTR_IDENT) => { let owned_tokens = tokens.clone().into(); - key = parse_macro_input!(owned_tokens as Key); + let Ok(parsed_key) = parse::(owned_tokens) else { + return Err(syn::Error::new( + attr.span(), + "Invalid key override attribute", + )); + }; + key = parsed_key; if matches!( (&key, &targets), (Key::Custom(_), SpecializeImplTargets::All) ) { - return syn::Error::new( - tokens.span(), + return Err(syn::Error::new( + attr.span(), "#[key(default)] is the only key override type allowed with #[specialize(all)]", - ) - .into_compile_error() - .into(); + )); } use_key_field = false; } + _ => {} } } @@ -188,6 +181,7 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { ty: field_ty, member: field_member, key, + use_base_descriptor, }); } @@ -195,6 +189,66 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { field_info[single_index].key = Key::Whole; } + Ok(field_info) +} + +fn get_struct_fields<'a>(ast: &'a DeriveInput, derive_name: &str) -> syn::Result<&'a Fields> { + match &ast.data { + Data::Struct(DataStruct { fields, .. }) => Ok(fields), + Data::Enum(data_enum) => Err(syn::Error::new( + data_enum.enum_token.span(), + format!("#[derive({derive_name})] only supports structs."), + )), + Data::Union(data_union) => Err(syn::Error::new( + data_union.union_token.span(), + format!("#[derive({derive_name})] only supports structs."), + )), + } +} + +fn get_specialize_targets( + ast: &DeriveInput, + derive_name: &str, +) -> syn::Result { + let specialize_attr = ast.attrs.iter().find_map(|attr| { + if attr.path().is_ident(SPECIALIZE_ATTR_IDENT) { + if let Meta::List(meta_list) = &attr.meta { + return Some(meta_list); + } + } + None + }); + let Some(specialize_meta_list) = specialize_attr else { + return Err(syn::Error::new( + Span::call_site(), + format!("#[derive({derive_name})] must be accompanied by #[specialize(..targets)].\n Example usages: #[specialize(RenderPipeline)], #[specialize(all)]") + )); + }; + parse::(specialize_meta_list.tokens.clone().into()) +} + +macro_rules! guard { + ($expr: expr) => { + match $expr { + Ok(__val) => __val, + Err(err) => return err.to_compile_error().into(), + } + }; +} + +pub fn impl_specialize(input: TokenStream) -> TokenStream { + let bevy_render_path: Path = crate::bevy_render_path(); + let specialize_path = { + let mut path = bevy_render_path.clone(); + path.segments.push(format_ident!("render_resource").into()); + path + }; + + let ast = parse_macro_input!(input as DeriveInput); + let targets = guard!(get_specialize_targets(&ast, "Specialize")); + let fields = guard!(get_struct_fields(&ast, "Specialize")); + let field_info = guard!(get_field_info(fields, &targets)); + match targets { SpecializeImplTargets::All => impl_specialize_all(&specialize_path, &ast, &field_info), SpecializeImplTargets::Specific(targets) => targets @@ -231,7 +285,7 @@ fn impl_specialize_all( for field in field_info { where_clause .predicates - .push(field.predicate(specialize_path, &target_path)); + .push(field.specialize_predicate(specialize_path, &target_path)); } } @@ -277,3 +331,109 @@ fn impl_specialize_specific( } }) } + +pub fn impl_has_base_descriptor(input: TokenStream) -> TokenStream { + let bevy_render_path: Path = crate::bevy_render_path(); + let specialize_path = { + let mut path = bevy_render_path.clone(); + path.segments.push(format_ident!("render_resource").into()); + path + }; + + let ast = parse_macro_input!(input as DeriveInput); + let targets = guard!(get_specialize_targets(&ast, "Specialize")); + let fields = guard!(get_struct_fields(&ast, "Specialize")); + let field_info = guard!(get_field_info(fields, &targets)); + + let base_descriptor_fields = field_info + .iter() + .filter(|field| field.use_base_descriptor) + .collect::>(); + + let base_descriptor_field = if field_info.len() == 1 { + field_info[0].clone() + } else if base_descriptor_fields.is_empty() { + return syn::Error::new( + Span::call_site(), + "#[base_descriptor] attribute not found. It must be present on exactly one field", + ) + .into_compile_error() + .into(); + } else if base_descriptor_fields.len() > 1 { + return syn::Error::new( + Span::call_site(), + "Too many #[base_descriptor] attributes found. It must be present on exactly one field", + ) + .into_compile_error() + .into(); + } else { + base_descriptor_fields[0].clone() + }; + + match targets { + SpecializeImplTargets::All => { + impl_has_base_descriptor_all(&specialize_path, &ast, &base_descriptor_field) + } + SpecializeImplTargets::Specific(targets) => targets + .iter() + .map(|target| { + impl_has_base_descriptor_specific( + &specialize_path, + &ast, + &base_descriptor_field, + target, + ) + }) + .collect(), + } +} + +fn impl_has_base_descriptor_specific( + specialize_path: &Path, + ast: &DeriveInput, + base_descriptor_field_info: &FieldInfo, + target_path: &Path, +) -> TokenStream { + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let field_ty = &base_descriptor_field_info.ty; + let field_member = &base_descriptor_field_info.member; + TokenStream::from(quote!( + impl #impl_generics #specialize_path::HasBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { + fn base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { + <#field_ty as #specialize_path::HasBaseDescriptor<#target_path>>::base_descriptor(&self.#field_member) + } + } + )) +} + +fn impl_has_base_descriptor_all( + specialize_path: &Path, + ast: &DeriveInput, + base_descriptor_field_info: &FieldInfo, +) -> TokenStream { + let target_path = Path::from(format_ident!("T")); + let struct_name = &ast.ident; + let mut generics = ast.generics.clone(); + generics.params.insert( + 0, + parse_quote!(#target_path: #specialize_path::Specializable), + ); + + let where_clause = generics.make_where_clause(); + where_clause.predicates.push( + base_descriptor_field_info.has_base_descriptor_predicate(specialize_path, &target_path), + ); + + let (_, type_generics, _) = ast.generics.split_for_impl(); + let (impl_generics, _, where_clause) = &generics.split_for_impl(); + let field_ty = &base_descriptor_field_info.ty; + let field_member = &base_descriptor_field_info.member; + TokenStream::from(quote! { + impl #impl_generics #specialize_path::HasBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { + fn base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { + <#field_ty as #specialize_path::HasBaseDescriptor<#target_path>>::base_descriptor(&self.#field_member) + } + } + }) +} diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 02a819fcade76..fecdfb9106239 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -7,9 +7,9 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_utils::hashbrown::HashMap; -use core::hash::Hash; +use core::{hash::Hash, marker::PhantomData}; -pub use bevy_render_macros::Specialize; +pub use bevy_render_macros::{HasBaseDescriptor, Specialize}; pub trait Specializable { type Descriptor: Clone + Send + Sync; @@ -41,6 +41,18 @@ pub trait Specialize: Send + Sync + 'static { fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor); } +impl Specialize for () { + type Key = (); + + fn specialize(&self, _key: Self::Key, _descriptor: &mut T::Descriptor) {} +} + +impl Specialize for PhantomData { + type Key = (); + + fn specialize(&self, _key: Self::Key, _descriptor: &mut ::Descriptor) {} +} + pub trait HasBaseDescriptor: Specialize { fn base_descriptor(&self) -> T::Descriptor; } From 4119bb352002bf2d084bac9c4107f46e37bae2b0 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Wed, 15 Jan 2025 21:27:23 -0800 Subject: [PATCH 17/50] fix internal imports --- examples/shader/custom_phase_item.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index d8e578f080376..02e6c9820f467 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -23,16 +23,16 @@ use bevy::{ }, render_resource::{ BufferUsages, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, - FragmentState, IndexFormat, MultisampleState, PipelineCache, PrimitiveState, - RawBufferVec, RenderPipelineDescriptor, TextureFormat, VertexAttribute, - VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + FragmentState, HasBaseDescriptor, IndexFormat, MultisampleState, PipelineCache, + PrimitiveState, RawBufferVec, RenderPipeline, RenderPipelineDescriptor, Specialize, + Specializer, TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, + VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, view::{self, ExtractedView, RenderVisibleEntities, VisibilityClass}, Render, RenderApp, RenderSet, }, }; -use bevy_render::render_resource::{HasBaseDescriptor, RenderPipeline, Specialize, Specializer}; use bytemuck::{Pod, Zeroable}; /// A marker component that represents an entity that is to be rendered using From 6d3d69f73771666568489af780067cb5f7e95671 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Wed, 15 Jan 2025 22:41:38 -0800 Subject: [PATCH 18/50] style --- crates/bevy_render/src/render_resource/specialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index fecdfb9106239..836cc223ec910 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -50,7 +50,7 @@ impl Specialize for () { impl Specialize for PhantomData { type Key = (); - fn specialize(&self, _key: Self::Key, _descriptor: &mut ::Descriptor) {} + fn specialize(&self, _key: Self::Key, _descriptor: &mut T::Descriptor) {} } pub trait HasBaseDescriptor: Specialize { From 9d6ab8f5d038179ba73c936988fb30f9339a0787 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:25:21 -0800 Subject: [PATCH 19/50] docs --- .../src/render_resource/specialize.rs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 836cc223ec910..66e3482dcbace 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -11,6 +11,9 @@ use core::{hash::Hash, marker::PhantomData}; pub use bevy_render_macros::{HasBaseDescriptor, Specialize}; +/// Defines a type that is able to be "specialized" and cached by creating and transforming +/// its descriptor type. This is implemented for [`RenderPipeline`] and [`ComputePipeline`], and +/// likely will not have much utility for other types. pub trait Specializable { type Descriptor: Clone + Send + Sync; type CachedId: Clone + Send + Sync; @@ -36,6 +39,69 @@ impl Specializable for ComputePipeline { } } +/// Defines a type that is able to transform descriptors for a specializable +/// type T, based on a known hashable key type. +/// +/// This is mainly used when "specializing" render +/// pipelines, i.e. specifying shader defs and binding layout based on the key, +/// the result of which can then be cached and accessed quickly later. +/// +/// This trait can be derived with `#[derive(Specialize)]` for structs whose +/// fields all implement [`Specialize`]. The key type will be tuple of the keys +/// of each field, and their specialization logic will be applied in field +/// order. Since derive macros can't have generic parameters, the derive macro +/// requires an additional `#[specialize(..targets)]` attribute to specify a +/// list of types to target for the implementation. `#[specialize(all)]` is +/// also allowed, and will generate a fully generic implementation at the cost +/// of slightly worse error messages. +/// +/// Additionally, each field can optionally take a `#[key]` attribute to +/// specify a "key override". This will "hide" that field's key from being +/// exposed by the wrapper, and always use the value given by the attribute. +/// Values for this attribute may either be `default` which will use the key's +/// [`Default`](core::default::Default) implementation, or a valid rust +/// expression of the key type. +/// +/// Example: +/// ```rs +/// struct A; +/// struct B; +/// +/// impl Specialize for A { +/// type Key = (); +/// +/// fn specialize(&self, _key: (), _descriptor: &mut RenderPipelineDescriptor) { +/// //... +/// } +/// } +/// +/// impl Specialize for B { +/// type Key = u32; +/// +/// fn specialize(&self, _key: u32, _descriptor: &mut RenderPipelineDescriptor) { +/// //... +/// } +/// } +/// +/// #[derive(Specialize)] +/// #[specialize(RenderPipeline)] +/// struct C { +/// #[key(default)] +/// a: A, +/// b: B, +/// } +/// +/// /* +/// The generated implementation: +/// impl Specialize for C { +/// type Key = u32; +/// fn specialize(&self, key: u32, descriptor: &mut RenderPipelineDescriptor) { +/// self.a.specialize((), descriptor); +/// self.b.specialize(key, descriptor); +/// } +/// } +/// */ +/// ``` pub trait Specialize: Send + Sync + 'static { type Key: Clone + Hash + Eq; fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor); @@ -53,10 +119,78 @@ impl Specialize for PhantomData for A { +/// type Key = (); +/// +/// fn specialize(&self, _key: (), _descriptor: &mut RenderPipelineDescriptor) { +/// //... +/// } +/// } +/// +/// impl Specialize for B { +/// type Key = u32; +/// +/// fn specialize(&self, _key: u32, _descriptor: &mut RenderPipelineDescriptor) { +/// //... +/// } +/// } +/// +/// impl HasBaseDescriptor for B { +/// fn base_descriptor(&self) -> RenderPipelineDescriptor { +/// # todo!() +/// //... +/// } +/// } +/// +/// +/// #[derive(Specialize, HasBaseDescriptor)] +/// #[specialize(RenderPipeline)] +/// struct C { +/// #[key(default)] +/// a: A, +/// #[base_descriptor] +/// b: B, +/// } +/// +/// /* +/// The generated implementation: +/// impl HasBaseDescriptor for C { +/// fn base_descriptor(&self) -> RenderPipelineDescriptor { +/// self.b.base_descriptor() +/// } +/// } +/// */ +/// ``` pub trait HasBaseDescriptor: Specialize { fn base_descriptor(&self) -> T::Descriptor; } +/// A cache for specializable resources. For a given key type the resulting +/// resource will only be created if it is missing, retrieving it from the +/// cache otherwise. #[derive(Resource)] pub struct Specializer> { specializer: S, @@ -66,6 +200,10 @@ pub struct Specializer> { } impl> Specializer { + /// Creates a new [`Specializer`] from a [`Specialize`] implementation, + /// an optional "user specializer", and a base descriptor. The user + /// specializer is applied after the [`Specialize`] implementation, with + /// the same key. pub fn new( specializer: S, user_specializer: Option, @@ -79,6 +217,7 @@ impl> Specializer { } } + /// Specializes a resource given the [`Specialize`] implementation's key type. pub fn specialize(&mut self, pipeline_cache: &PipelineCache, key: S::Key) -> T::CachedId { self.pipelines .entry(key.clone()) @@ -94,6 +233,10 @@ impl> Specializer { } } +/// [`Specializer`] implements [`FromWorld`] for [`Specialize`] implementations +/// that also satisfy [`FromWorld`] and [`HasBaseDescriptor`]. This will create +/// a [`Specializer`] with no user specializer, and the base descriptor taken +/// from the [`Specialize`] implementation. impl> FromWorld for Specializer { fn from_world(world: &mut World) -> Self { let specializer = S::from_world(world); From e73df3ae4052546b1685153868adb8d7d9360715 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:28:47 -0800 Subject: [PATCH 20/50] typos --- crates/bevy_render/src/render_resource/specialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 66e3482dcbace..c45e427c5a9cc 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -134,7 +134,7 @@ impl Specialize for PhantomData Date: Thu, 16 Jan 2025 12:42:07 -0800 Subject: [PATCH 21/50] fix docs --- crates/bevy_render/src/render_resource/specialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index c45e427c5a9cc..6aa45de9f91e7 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -59,7 +59,7 @@ impl Specializable for ComputePipeline { /// specify a "key override". This will "hide" that field's key from being /// exposed by the wrapper, and always use the value given by the attribute. /// Values for this attribute may either be `default` which will use the key's -/// [`Default`](core::default::Default) implementation, or a valid rust +/// [`Default`] implementation, or a valid rust /// expression of the key type. /// /// Example: From ac33d13faff205d9d4cc586138189c0c37a2f772 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:08:00 -0800 Subject: [PATCH 22/50] fix CI --- crates/bevy_render/src/render_resource/specialize.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 6aa45de9f91e7..d97c55b44963c 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ system::Resource, world::{FromWorld, World}, }; -use bevy_utils::hashbrown::HashMap; +use bevy_utils::HashMap; use core::{hash::Hash, marker::PhantomData}; pub use bevy_render_macros::{HasBaseDescriptor, Specialize}; @@ -213,7 +213,7 @@ impl> Specializer { specializer, user_specializer, base_descriptor, - pipelines: HashMap::new(), + pipelines: Default::default(), } } From 41a98bdba435a0b5881f961a6bf6e6f7466d6ace Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Thu, 16 Jan 2025 17:43:11 -0800 Subject: [PATCH 23/50] function type alias --- crates/bevy_render/src/render_resource/specialize.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index d97c55b44963c..d170158d2f6ea 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -188,13 +188,15 @@ pub trait HasBaseDescriptor: Specialize { fn base_descriptor(&self) -> T::Descriptor; } +pub type SpecializeFn = fn(>::Key, &mut ::Descriptor); + /// A cache for specializable resources. For a given key type the resulting /// resource will only be created if it is missing, retrieving it from the /// cache otherwise. #[derive(Resource)] pub struct Specializer> { specializer: S, - user_specializer: Option, + user_specializer: Option>, base_descriptor: T::Descriptor, pipelines: HashMap, } @@ -206,7 +208,7 @@ impl> Specializer { /// the same key. pub fn new( specializer: S, - user_specializer: Option, + user_specializer: Option>, base_descriptor: T::Descriptor, ) -> Self { Self { From 8cd525dd4f77c45d805a73fefb189b82bbcfacc5 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sat, 1 Feb 2025 17:34:14 -0800 Subject: [PATCH 24/50] cleanup --- crates/bevy_render/macros/src/specialize.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 7df180cfe4df0..193efc08a6748 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -44,7 +44,7 @@ enum Key { } impl Key { - pub fn expr(&self) -> Expr { + fn expr(&self) -> Expr { match self { Key::Whole => parse_quote!(key), Key::Default => parse_quote!(#FQDefault::default()), @@ -85,13 +85,13 @@ struct FieldInfo { } impl FieldInfo { - pub fn key_ty(&self, specialize_path: &Path, target_path: &Path) -> Option { + fn key_ty(&self, specialize_path: &Path, target_path: &Path) -> Option { let ty = &self.ty; matches!(self.key, Key::Whole | Key::Index(_)) .then_some(parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::Key)) } - pub fn specialize_stmt(&self, specialize_path: &Path, target_path: &Path) -> Stmt { + fn specialize_stmt(&self, specialize_path: &Path, target_path: &Path) -> Stmt { let FieldInfo { ty, member, key, .. } = &self; @@ -99,11 +99,7 @@ impl FieldInfo { parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#member, #key_expr, descriptor);) } - pub fn specialize_predicate( - &self, - specialize_path: &Path, - target_path: &Path, - ) -> WherePredicate { + fn specialize_predicate(&self, specialize_path: &Path, target_path: &Path) -> WherePredicate { let ty = &self.ty; if matches!(&self.key, Key::Default) { parse_quote!(#ty: #specialize_path::Specialize<#target_path, Key: #FQDefault>) @@ -112,7 +108,7 @@ impl FieldInfo { } } - pub fn has_base_descriptor_predicate( + fn has_base_descriptor_predicate( &self, specialize_path: &Path, target_path: &Path, From 9e05804867f55acbbaead9f7cfe70f2f384a70ba Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 3 Feb 2025 21:09:46 -0800 Subject: [PATCH 25/50] Update specialize.rs Co-authored-by: Tim Overbeek <158390905+Bleachfuel@users.noreply.github.com> --- crates/bevy_render/macros/src/specialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 193efc08a6748..9226bfe0170a6 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -27,7 +27,7 @@ enum SpecializeImplTargets { impl Parse for SpecializeImplTargets { fn parse(input: ParseStream) -> syn::Result { let paths = input.parse_terminated(Path::parse, Token![,])?; - if !paths.is_empty() && paths[0].is_ident(SPECIALIZE_ALL_IDENT) { + if paths.first().is_some_and(|p| p.is_ident(SPECIALIZE_ALL_IDENT)) { Ok(SpecializeImplTargets::All) } else { Ok(SpecializeImplTargets::Specific(paths.into_iter().collect())) From 8ced8d188900cd1ffaa13d9158ddcb75fb93bf97 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Wed, 5 Feb 2025 13:39:40 -0800 Subject: [PATCH 26/50] clarity --- crates/bevy_render/src/render_resource/specialize.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index d170158d2f6ea..e39ad9f444afd 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -198,7 +198,7 @@ pub struct Specializer> { specializer: S, user_specializer: Option>, base_descriptor: T::Descriptor, - pipelines: HashMap, + specialized: HashMap, } impl> Specializer { @@ -215,13 +215,13 @@ impl> Specializer { specializer, user_specializer, base_descriptor, - pipelines: Default::default(), + specialized: Default::default(), } } /// Specializes a resource given the [`Specialize`] implementation's key type. pub fn specialize(&mut self, pipeline_cache: &PipelineCache, key: S::Key) -> T::CachedId { - self.pipelines + self.specialized .entry(key.clone()) .or_insert_with(|| { let mut descriptor = self.base_descriptor.clone(); @@ -239,7 +239,11 @@ impl> Specializer { /// that also satisfy [`FromWorld`] and [`HasBaseDescriptor`]. This will create /// a [`Specializer`] with no user specializer, and the base descriptor taken /// from the [`Specialize`] implementation. -impl> FromWorld for Specializer { +impl FromWorld for Specializer +where + T: Specializable, + S: FromWorld + Specialize + HasBaseDescriptor, +{ fn from_world(world: &mut World) -> Self { let specializer = S::from_world(world); let base_descriptor = specializer.base_descriptor(); From 7e5b23fb8e4d63df03d459b7e4888116398bac1f Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 8 Apr 2025 14:13:08 -0700 Subject: [PATCH 27/50] fix imports --- crates/bevy_render/src/render_resource/specialize.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index e39ad9f444afd..61c6cf9919628 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -3,10 +3,10 @@ use super::{ PipelineCache, RenderPipeline, RenderPipelineDescriptor, }; use bevy_ecs::{ - system::Resource, + resource::Resource, world::{FromWorld, World}, }; -use bevy_utils::HashMap; +use bevy_platform_support::collections::HashMap; use core::{hash::Hash, marker::PhantomData}; pub use bevy_render_macros::{HasBaseDescriptor, Specialize}; From 1a8acac16989e7c1e312d7a260b356ee4041c4db Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 22 Jun 2025 12:59:50 -0700 Subject: [PATCH 28/50] fix import --- crates/bevy_render/src/render_resource/specialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 61c6cf9919628..8503372409796 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ resource::Resource, world::{FromWorld, World}, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::{hash::Hash, marker::PhantomData}; pub use bevy_render_macros::{HasBaseDescriptor, Specialize}; From fbd87b14eeebe6cdf6b965d09a52742c8dbaa845 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 22 Jun 2025 13:00:14 -0700 Subject: [PATCH 29/50] rename to `GetBaseDescriptor` --- crates/bevy_render/src/render_resource/specialize.rs | 4 ++-- examples/shader/custom_phase_item.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 8503372409796..37717fd6bf8bb 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -184,7 +184,7 @@ impl Specialize for PhantomData: Specialize { +pub trait GetBaseDescriptor: Specialize { fn base_descriptor(&self) -> T::Descriptor; } @@ -242,7 +242,7 @@ impl> Specializer { impl FromWorld for Specializer where T: Specializable, - S: FromWorld + Specialize + HasBaseDescriptor, + S: FromWorld + Specialize + GetBaseDescriptor, { fn from_world(world: &mut World) -> Self { let specializer = S::from_world(world); diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index c376ab92cb2b5..70692a6e783c9 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -25,7 +25,7 @@ use bevy::{ }, render_resource::{ BufferUsages, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, - FragmentState, HasBaseDescriptor, IndexFormat, MultisampleState, PipelineCache, + FragmentState, GetBaseDescriptor, IndexFormat, MultisampleState, PipelineCache, PrimitiveState, RawBufferVec, RenderPipeline, RenderPipelineDescriptor, Specialize, Specializer, TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, @@ -296,7 +296,7 @@ impl Specialize for CustomPhaseSpecializer { } } -impl HasBaseDescriptor for CustomPhaseSpecializer { +impl GetBaseDescriptor for CustomPhaseSpecializer { fn base_descriptor(&self) -> RenderPipelineDescriptor { RenderPipelineDescriptor { label: Some("custom render pipeline".into()), From 30fdac5281ad8757c3d84666a24b87a5515fe08c Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 22 Jun 2025 14:55:40 -0700 Subject: [PATCH 30/50] make specialization fallible --- crates/bevy_render/macros/src/lib.rs | 4 ++ crates/bevy_render/macros/src/specialize.rs | 33 +++++++++++--- .../src/render_resource/specialize.rs | 44 ++++++++++++++----- examples/shader/custom_phase_item.rs | 4 +- 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 4a6e3da72389f..1143e01db5a05 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -15,6 +15,10 @@ pub(crate) fn bevy_render_path() -> syn::Path { BevyManifest::shared().get_path("bevy_render") } +pub(crate) fn bevy_ecs_path() -> syn::Path { + BevyManifest::shared().get_path("bevy_ecs") +} + #[proc_macro_derive(ExtractResource)] pub fn derive_extract_resource(input: TokenStream) -> TokenStream { extract_resource::derive_extract_resource(input) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 9226bfe0170a6..1b745f1499ce8 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -1,4 +1,4 @@ -use bevy_macro_utils::fq_std::FQDefault; +use bevy_macro_utils::fq_std::{FQDefault, FQResult}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; @@ -27,7 +27,10 @@ enum SpecializeImplTargets { impl Parse for SpecializeImplTargets { fn parse(input: ParseStream) -> syn::Result { let paths = input.parse_terminated(Path::parse, Token![,])?; - if paths.first().is_some_and(|p| p.is_ident(SPECIALIZE_ALL_IDENT)) { + if paths + .first() + .is_some_and(|p| p.is_ident(SPECIALIZE_ALL_IDENT)) + { Ok(SpecializeImplTargets::All) } else { Ok(SpecializeImplTargets::Specific(paths.into_iter().collect())) @@ -96,7 +99,7 @@ impl FieldInfo { ty, member, key, .. } = &self; let key_expr = key.expr(); - parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#member, #key_expr, descriptor);) + parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#member, #key_expr, descriptor)?;) } fn specialize_predicate(&self, specialize_path: &Path, target_path: &Path) -> WherePredicate { @@ -240,22 +243,29 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { path }; + let ecs_path = crate::bevy_ecs_path(); + let ast = parse_macro_input!(input as DeriveInput); let targets = guard!(get_specialize_targets(&ast, "Specialize")); let fields = guard!(get_struct_fields(&ast, "Specialize")); let field_info = guard!(get_field_info(fields, &targets)); match targets { - SpecializeImplTargets::All => impl_specialize_all(&specialize_path, &ast, &field_info), + SpecializeImplTargets::All => { + impl_specialize_all(&specialize_path, &ecs_path, &ast, &field_info) + } SpecializeImplTargets::Specific(targets) => targets .iter() - .map(|target| impl_specialize_specific(&specialize_path, &ast, &field_info, target)) + .map(|target| { + impl_specialize_specific(&specialize_path, &ecs_path, &ast, &field_info, target) + }) .collect(), } } fn impl_specialize_all( specialize_path: &Path, + ecs_path: &Path, ast: &DeriveInput, field_info: &[FieldInfo], ) -> TokenStream { @@ -292,7 +302,11 @@ fn impl_specialize_all( impl #impl_generics #specialize_path::Specialize<#target_path> for #struct_name #type_generics #where_clause { type Key = (#(#key_elems),*); - fn specialize(&self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::Specializable>::Descriptor) { + fn specialize( + &self, + key: Self::Key, + descriptor: &mut <#target_path as #specialize_path::Specializable>::Descriptor + ) -> #FQResult<#ecs_path::error::BevyError> { #(#specialize_stmts)* } } @@ -301,6 +315,7 @@ fn impl_specialize_all( fn impl_specialize_specific( specialize_path: &Path, + ecs_path: &Path, ast: &DeriveInput, field_info: &[FieldInfo], target_path: &Path, @@ -321,7 +336,11 @@ fn impl_specialize_specific( impl #impl_generics #specialize_path::Specialize<#target_path> for #struct_name #type_generics #where_clause { type Key = (#(#key_elems),*); - fn specialize(&self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::Specializable>::Descriptor) { + fn specialize( + &self, + key: Self::Key, + descriptor: &mut <#target_path as #specialize_path::Specializable>::Descriptor + ) -> #FQResult<#ecs_path::error::BevyError> { #(#specialize_stmts)* } } diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 37717fd6bf8bb..397c67620571d 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -3,10 +3,11 @@ use super::{ PipelineCache, RenderPipeline, RenderPipelineDescriptor, }; use bevy_ecs::{ + error::BevyError, resource::Resource, world::{FromWorld, World}, }; -use bevy_platform::collections::HashMap; +use bevy_platform::collections::{hash_map::Entry, HashMap}; use core::{hash::Hash, marker::PhantomData}; pub use bevy_render_macros::{HasBaseDescriptor, Specialize}; @@ -104,19 +105,31 @@ impl Specializable for ComputePipeline { /// ``` pub trait Specialize: Send + Sync + 'static { type Key: Clone + Hash + Eq; - fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor); + fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor) -> Result<(), BevyError>; } impl Specialize for () { type Key = (); - fn specialize(&self, _key: Self::Key, _descriptor: &mut T::Descriptor) {} + fn specialize( + &self, + _key: Self::Key, + _descriptor: &mut T::Descriptor, + ) -> Result<(), BevyError> { + Ok(()) + } } impl Specialize for PhantomData { type Key = (); - fn specialize(&self, _key: Self::Key, _descriptor: &mut T::Descriptor) {} + fn specialize( + &self, + _key: Self::Key, + _descriptor: &mut T::Descriptor, + ) -> Result<(), BevyError> { + Ok(()) + } } /// Defines a specializer that can also provide a "base descriptor". @@ -220,18 +233,25 @@ impl> Specializer { } /// Specializes a resource given the [`Specialize`] implementation's key type. - pub fn specialize(&mut self, pipeline_cache: &PipelineCache, key: S::Key) -> T::CachedId { - self.specialized - .entry(key.clone()) - .or_insert_with(|| { + pub fn specialize( + &mut self, + pipeline_cache: &PipelineCache, + key: S::Key, + ) -> Result { + let entry = self.specialized.entry(key.clone()); + match entry { + Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()), + Entry::Vacant(vacant_entry) => { let mut descriptor = self.base_descriptor.clone(); - self.specializer.specialize(key.clone(), &mut descriptor); + self.specializer.specialize(key.clone(), &mut descriptor)?; if let Some(user_specializer) = self.user_specializer { (user_specializer)(key, &mut descriptor); } - ::queue(pipeline_cache, descriptor) - }) - .clone() + let cached_id = ::queue(pipeline_cache, descriptor); + vacant_entry.insert(cached_id.clone()); + Ok(cached_id) + } + } } } diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 70692a6e783c9..93837f091e3a1 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -237,7 +237,9 @@ fn queue_custom_phase_item( // some per-view settings, such as whether the view is HDR, but for // simplicity's sake we simply hard-code the view's characteristics, // with the exception of number of MSAA samples. - let pipeline_id = specializer.specialize(&pipeline_cache, *msaa); + let Ok(pipeline_id) = specializer.specialize(&pipeline_cache, *msaa) else { + continue; + }; // Bump the change tick in order to force Bevy to rebuild the bin. let this_tick = next_tick.get() + 1; From 63f3ce6a0820905aca23ba9680fd8a8328e339ab Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 22 Jun 2025 18:22:24 -0700 Subject: [PATCH 31/50] fix rename --- crates/bevy_render/macros/src/lib.rs | 10 +++++----- crates/bevy_render/macros/src/specialize.rs | 18 +++++++++--------- .../src/render_resource/specialize.rs | 14 +++++++------- examples/shader/custom_phase_item.rs | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 1143e01db5a05..8dbb9154a7557 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -115,9 +115,9 @@ pub fn derive_specialize(input: TokenStream) -> TokenStream { specialize::impl_specialize(input) } -/// Derive macro generating an impl of the trait `HasBaseDescriptor` -/// by deferring to the `HasBaseDescriptor` impl of a chosen field. -#[proc_macro_derive(HasBaseDescriptor, attributes(specialize, base_descriptor))] -pub fn derive_has_base_descriptor(input: TokenStream) -> TokenStream { - specialize::impl_has_base_descriptor(input) +/// Derive macro generating an impl of the trait `GetBaseDescriptor` +/// by deferring to the `GetBaseDescriptor` impl of a chosen field. +#[proc_macro_derive(GetBaseDescriptor, attributes(specialize, base_descriptor))] +pub fn derive_get_base_descriptor(input: TokenStream) -> TokenStream { + specialize::impl_get_base_descriptor(input) } diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 1b745f1499ce8..217da82861a0c 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -347,7 +347,7 @@ fn impl_specialize_specific( }) } -pub fn impl_has_base_descriptor(input: TokenStream) -> TokenStream { +pub fn impl_get_base_descriptor(input: TokenStream) -> TokenStream { let bevy_render_path: Path = crate::bevy_render_path(); let specialize_path = { let mut path = bevy_render_path.clone(); @@ -387,12 +387,12 @@ pub fn impl_has_base_descriptor(input: TokenStream) -> TokenStream { match targets { SpecializeImplTargets::All => { - impl_has_base_descriptor_all(&specialize_path, &ast, &base_descriptor_field) + impl_get_base_descriptor_all(&specialize_path, &ast, &base_descriptor_field) } SpecializeImplTargets::Specific(targets) => targets .iter() .map(|target| { - impl_has_base_descriptor_specific( + impl_get_base_descriptor_specific( &specialize_path, &ast, &base_descriptor_field, @@ -403,7 +403,7 @@ pub fn impl_has_base_descriptor(input: TokenStream) -> TokenStream { } } -fn impl_has_base_descriptor_specific( +fn impl_get_base_descriptor_specific( specialize_path: &Path, ast: &DeriveInput, base_descriptor_field_info: &FieldInfo, @@ -414,15 +414,15 @@ fn impl_has_base_descriptor_specific( let field_ty = &base_descriptor_field_info.ty; let field_member = &base_descriptor_field_info.member; TokenStream::from(quote!( - impl #impl_generics #specialize_path::HasBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { - fn base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { + impl #impl_generics #specialize_path::GetBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { + fn get_base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { <#field_ty as #specialize_path::HasBaseDescriptor<#target_path>>::base_descriptor(&self.#field_member) } } )) } -fn impl_has_base_descriptor_all( +fn impl_get_base_descriptor_all( specialize_path: &Path, ast: &DeriveInput, base_descriptor_field_info: &FieldInfo, @@ -445,8 +445,8 @@ fn impl_has_base_descriptor_all( let field_ty = &base_descriptor_field_info.ty; let field_member = &base_descriptor_field_info.member; TokenStream::from(quote! { - impl #impl_generics #specialize_path::HasBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { - fn base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { + impl #impl_generics #specialize_path::GetBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { + fn get_base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { <#field_ty as #specialize_path::HasBaseDescriptor<#target_path>>::base_descriptor(&self.#field_member) } } diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 397c67620571d..849d97cc9acc6 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ use bevy_platform::collections::{hash_map::Entry, HashMap}; use core::{hash::Hash, marker::PhantomData}; -pub use bevy_render_macros::{HasBaseDescriptor, Specialize}; +pub use bevy_render_macros::{GetBaseDescriptor, Specialize}; /// Defines a type that is able to be "specialized" and cached by creating and transforming /// its descriptor type. This is implemented for [`RenderPipeline`] and [`ComputePipeline`], and @@ -171,8 +171,8 @@ impl Specialize for PhantomData for B { -/// fn base_descriptor(&self) -> RenderPipelineDescriptor { +/// impl GetBaseDescriptor for B { +/// fn get_base_descriptor(&self) -> RenderPipelineDescriptor { /// # todo!() /// //... /// } @@ -190,15 +190,15 @@ impl Specialize for PhantomData RenderPipelineDescriptor { +/// impl GetBaseDescriptor for C { +/// fn get_base_descriptor(&self) -> RenderPipelineDescriptor { /// self.b.base_descriptor() /// } /// } /// */ /// ``` pub trait GetBaseDescriptor: Specialize { - fn base_descriptor(&self) -> T::Descriptor; + fn get_base_descriptor(&self) -> T::Descriptor; } pub type SpecializeFn = fn(>::Key, &mut ::Descriptor); @@ -266,7 +266,7 @@ where { fn from_world(world: &mut World) -> Self { let specializer = S::from_world(world); - let base_descriptor = specializer.base_descriptor(); + let base_descriptor = specializer.get_base_descriptor(); Self::new(specializer, None, base_descriptor) } } diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 93837f091e3a1..b40bb296d0485 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -299,7 +299,7 @@ impl Specialize for CustomPhaseSpecializer { } impl GetBaseDescriptor for CustomPhaseSpecializer { - fn base_descriptor(&self) -> RenderPipelineDescriptor { + fn get_base_descriptor(&self) -> RenderPipelineDescriptor { RenderPipelineDescriptor { label: Some("custom render pipeline".into()), layout: vec![], From bb35b21507cb3cae29601813342107d91015733b Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 22 Jun 2025 18:55:48 -0700 Subject: [PATCH 32/50] merge derive macros --- crates/bevy_render/macros/src/lib.rs | 9 +---- crates/bevy_render/macros/src/specialize.rs | 40 ++++++++++++++++--- .../src/render_resource/specialize.rs | 17 +++----- examples/shader/custom_phase_item.rs | 7 +++- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 8dbb9154a7557..212cd6bb66832 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -110,14 +110,7 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream { /// Derive macro generating an impl of the trait `Specialize` /// /// This only works for structs whose members all implement `Specialize` -#[proc_macro_derive(Specialize, attributes(specialize, key))] +#[proc_macro_derive(Specialize, attributes(specialize, key, base_descriptor))] pub fn derive_specialize(input: TokenStream) -> TokenStream { specialize::impl_specialize(input) } - -/// Derive macro generating an impl of the trait `GetBaseDescriptor` -/// by deferring to the `GetBaseDescriptor` impl of a chosen field. -#[proc_macro_derive(GetBaseDescriptor, attributes(specialize, base_descriptor))] -pub fn derive_get_base_descriptor(input: TokenStream) -> TokenStream { - specialize::impl_get_base_descriptor(input) -} diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 217da82861a0c..d107bb99be1f3 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -250,16 +250,44 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { let fields = guard!(get_struct_fields(&ast, "Specialize")); let field_info = guard!(get_field_info(fields, &targets)); + let base_descriptor_fields = field_info + .iter() + .filter(|field| field.use_base_descriptor) + .collect::>(); + + if base_descriptor_fields.len() > 1 { + return syn::Error::new( + Span::call_site(), + "Too many #[base_descriptor] attributes found. It must be present on exactly one field", + ) + .into_compile_error() + .into(); + } + + let base_descriptor_field = base_descriptor_fields.first().copied(); + match targets { SpecializeImplTargets::All => { - impl_specialize_all(&specialize_path, &ecs_path, &ast, &field_info) + let specialize_impl = + impl_specialize_all(&specialize_path, &ecs_path, &ast, &field_info); + let get_base_descriptor_impl = base_descriptor_field + .map(|field_info| impl_get_base_descriptor_all(&specialize_path, &ast, field_info)) + .unwrap_or_default(); + [specialize_impl, get_base_descriptor_impl] + .into_iter() + .collect() } - SpecializeImplTargets::Specific(targets) => targets - .iter() - .map(|target| { + SpecializeImplTargets::Specific(targets) => { + let specialize_impls = targets.iter().map(|target| { impl_specialize_specific(&specialize_path, &ecs_path, &ast, &field_info, target) - }) - .collect(), + }); + let get_base_descriptor_impls = targets.iter().filter_map(|target| { + base_descriptor_field.map(|field_info| { + impl_get_base_descriptor_specific(&specialize_path, &ast, field_info, target) + }) + }); + specialize_impls.chain(get_base_descriptor_impls).collect() + } } } diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 849d97cc9acc6..9b7d924a3e082 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ use bevy_platform::collections::{hash_map::Entry, HashMap}; use core::{hash::Hash, marker::PhantomData}; -pub use bevy_render_macros::{GetBaseDescriptor, Specialize}; +pub use bevy_render_macros::Specialize; /// Defines a type that is able to be "specialized" and cached by creating and transforming /// its descriptor type. This is implemented for [`RenderPipeline`] and [`ComputePipeline`], and @@ -139,16 +139,11 @@ impl Specialize for PhantomData Specialize for PhantomData for CustomPhaseSpecializer { type Key = Msaa; - fn specialize(&self, msaa: Self::Key, descriptor: &mut RenderPipelineDescriptor) { + fn specialize( + &self, + msaa: Self::Key, + descriptor: &mut RenderPipelineDescriptor, + ) -> Result<(), BevyError> { descriptor.multisample.count = msaa.samples(); + Ok(()) } } From f5fec5cae5ce911f3ea63020b5be70c46cecc9be Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 22 Jun 2025 21:11:03 -0700 Subject: [PATCH 33/50] add roundabout todo: derive macro codegen --- .../src/render_resource/pipeline.rs | 2 +- .../src/render_resource/specialize.rs | 137 +++++++++++++++--- examples/shader/custom_phase_item.rs | 19 ++- 3 files changed, 134 insertions(+), 24 deletions(-) diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index b76174cac38b3..35020a43c3623 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -138,7 +138,7 @@ pub struct FragmentState { } /// Describes a compute pipeline. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ComputePipelineDescriptor { pub label: Option>, pub layout: Vec, diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 9b7d924a3e082..f57542f55b64d 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -7,8 +7,16 @@ use bevy_ecs::{ resource::Resource, world::{FromWorld, World}, }; -use bevy_platform::collections::{hash_map::Entry, HashMap}; +use bevy_platform::{ + collections::{ + hash_map::{Entry, VacantEntry}, + HashMap, + }, + hash::FixedHasher, +}; use core::{hash::Hash, marker::PhantomData}; +use tracing::error; +use variadics_please::all_tuples; pub use bevy_render_macros::Specialize; @@ -16,9 +24,10 @@ pub use bevy_render_macros::Specialize; /// its descriptor type. This is implemented for [`RenderPipeline`] and [`ComputePipeline`], and /// likely will not have much utility for other types. pub trait Specializable { - type Descriptor: Clone + Send + Sync; + type Descriptor: PartialEq + Clone + Send + Sync; type CachedId: Clone + Send + Sync; fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId; + fn get_descriptor(pipeline_cache: &PipelineCache, id: Self::CachedId) -> &Self::Descriptor; } impl Specializable for RenderPipeline { @@ -28,6 +37,13 @@ impl Specializable for RenderPipeline { fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId { pipeline_cache.queue_render_pipeline(descriptor) } + + fn get_descriptor( + pipeline_cache: &PipelineCache, + id: CachedRenderPipelineId, + ) -> &Self::Descriptor { + pipeline_cache.get_render_pipeline_descriptor(id) + } } impl Specializable for ComputePipeline { @@ -38,6 +54,13 @@ impl Specializable for ComputePipeline { fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId { pipeline_cache.queue_compute_pipeline(descriptor) } + + fn get_descriptor( + pipeline_cache: &PipelineCache, + id: CachedComputePipelineId, + ) -> &Self::Descriptor { + pipeline_cache.get_compute_pipeline_descriptor(id) + } } /// Defines a type that is able to transform descriptors for a specializable @@ -104,10 +127,21 @@ impl Specializable for ComputePipeline { /// */ /// ``` pub trait Specialize: Send + Sync + 'static { - type Key: Clone + Hash + Eq; - fn specialize(&self, key: Self::Key, descriptor: &mut T::Descriptor) -> Result<(), BevyError>; + type Key: SpecializationKey; + fn specialize( + &self, + key: Self::Key, + descriptor: &mut T::Descriptor, + ) -> Result, BevyError>; +} + +pub trait SpecializationKey: Clone + Hash + Eq { + const CANONICAL: bool; + type Canonical: Hash + Eq; } +pub type Canonical = ::Canonical; + impl Specialize for () { type Key = (); @@ -132,6 +166,17 @@ impl Specialize for PhantomData { + impl <$($T: SpecializationKey),*> SpecializationKey for ($($T,)*) { + const CANONICAL: bool = true $(&& <$T as SpecializationKey>::CANONICAL)*; + type Canonical = ($(Canonical<$T>,)*); + } + }; +} + +all_tuples!(impl_specialization_key_tuple, 0, 12, T); + /// Defines a specializer that can also provide a "base descriptor". /// /// In order to be composable, [`Specialize`] implementers don't create full @@ -196,7 +241,8 @@ pub trait GetBaseDescriptor: Specialize { fn get_base_descriptor(&self) -> T::Descriptor; } -pub type SpecializeFn = fn(>::Key, &mut ::Descriptor); +pub type SpecializeFn = + fn(>::Key, &mut ::Descriptor) -> Result<(), BevyError>; /// A cache for specializable resources. For a given key type the resulting /// resource will only be created if it is missing, retrieving it from the @@ -206,7 +252,8 @@ pub struct Specializer> { specializer: S, user_specializer: Option>, base_descriptor: T::Descriptor, - specialized: HashMap, + primary_cache: HashMap, + secondary_cache: HashMap, T::CachedId>, } impl> Specializer { @@ -214,6 +261,7 @@ impl> Specializer { /// an optional "user specializer", and a base descriptor. The user /// specializer is applied after the [`Specialize`] implementation, with /// the same key. + #[inline] pub fn new( specializer: S, user_specializer: Option>, @@ -223,30 +271,83 @@ impl> Specializer { specializer, user_specializer, base_descriptor, - specialized: Default::default(), + primary_cache: Default::default(), + secondary_cache: Default::default(), } } /// Specializes a resource given the [`Specialize`] implementation's key type. + #[inline] pub fn specialize( &mut self, pipeline_cache: &PipelineCache, key: S::Key, ) -> Result { - let entry = self.specialized.entry(key.clone()); + let entry = self.primary_cache.entry(key.clone()); match entry { - Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()), - Entry::Vacant(vacant_entry) => { - let mut descriptor = self.base_descriptor.clone(); - self.specializer.specialize(key.clone(), &mut descriptor)?; - if let Some(user_specializer) = self.user_specializer { - (user_specializer)(key, &mut descriptor); + Entry::Occupied(entry) => Ok(entry.get().clone()), + Entry::Vacant(entry) => Self::specialize_slow( + &self.specializer, + self.user_specializer, + self.base_descriptor.clone(), + pipeline_cache, + key, + entry, + &mut self.secondary_cache, + ), + } + } + + #[cold] + fn specialize_slow( + specializer: &S, + user_specializer: Option>, + base_descriptor: T::Descriptor, + pipeline_cache: &PipelineCache, + key: S::Key, + primary_entry: VacantEntry, + secondary_cache: &mut HashMap, T::CachedId>, + ) -> Result { + let mut descriptor = base_descriptor.clone(); + let canonical_key = specializer.specialize(key.clone(), &mut descriptor)?; + + if let Some(user_specializer) = user_specializer { + (user_specializer)(key, &mut descriptor)?; + } + + // if the whole key is canonical, the secondary cache isn't needed. + if ::CANONICAL { + return Ok(primary_entry + .insert(::queue(pipeline_cache, descriptor)) + .clone()); + } + + let id = match secondary_cache.entry(canonical_key) { + Entry::Occupied(entry) => { + if cfg!(debug_assertions) { + let stored_descriptor = + ::get_descriptor(pipeline_cache, entry.get().clone()); + if &descriptor != stored_descriptor { + error!( + "Invalid Specialize<{}> impl for {}: the cached descriptor \ + is not equal to the generated descriptor for the given key. \ + This means the Specialize implementation uses unused information \ + from the key to specialize the pipeline. This is not allowed \ + because it would invalidate the cache.", + core::any::type_name::(), + core::any::type_name::() + ); + } } - let cached_id = ::queue(pipeline_cache, descriptor); - vacant_entry.insert(cached_id.clone()); - Ok(cached_id) + entry.into_mut().clone() } - } + Entry::Vacant(entry) => entry + .insert(::queue(pipeline_cache, descriptor)) + .clone(), + }; + + primary_entry.insert(id.clone()); + Ok(id) } } diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 626292a0fd099..eab0b812c500f 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -35,6 +35,7 @@ use bevy::{ Render, RenderApp, RenderSystems, }, }; +use bevy_render::render_resource::{Canonical, SpecializationKey}; use bytemuck::{Pod, Zeroable}; /// A marker component that represents an entity that is to be rendered using @@ -290,16 +291,24 @@ impl FromWorld for CustomPhaseSpecializer { } } +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct CustomPhaseKey(Msaa); + +impl SpecializationKey for CustomPhaseKey { + const CANONICAL: bool = true; + type Canonical = Self; +} + impl Specialize for CustomPhaseSpecializer { - type Key = Msaa; + type Key = CustomPhaseKey; fn specialize( &self, - msaa: Self::Key, + key: Self::Key, descriptor: &mut RenderPipelineDescriptor, - ) -> Result<(), BevyError> { - descriptor.multisample.count = msaa.samples(); - Ok(()) + ) -> Result, BevyError> { + descriptor.multisample.count = key.0.samples(); + Ok(key) } } From ef1b18d232eb17da43133cc92f32852b8f7d1176 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 11:27:59 -0700 Subject: [PATCH 34/50] fix macro codegen --- crates/bevy_render/macros/src/specialize.rs | 123 ++++++++---------- .../src/render_resource/specialize.rs | 12 +- examples/shader/custom_phase_item.rs | 7 +- 3 files changed, 63 insertions(+), 79 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index d107bb99be1f3..bcf3cace71447 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -7,7 +7,7 @@ use syn::{ parse::{Parse, ParseStream}, parse_macro_input, parse_quote, spanned::Spanned, - Data, DataStruct, DeriveInput, Expr, Fields, Ident, Index, Member, Meta, MetaList, Path, Stmt, + Data, DataStruct, DeriveInput, Expr, Fields, Ident, Index, Member, Meta, MetaList, Pat, Path, Token, Type, WherePredicate, }; @@ -94,12 +94,16 @@ impl FieldInfo { .then_some(parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::Key)) } - fn specialize_stmt(&self, specialize_path: &Path, target_path: &Path) -> Stmt { + fn key_ident(&self, ident: Ident) -> Option { + matches!(self.key, Key::Whole | Key::Index(_)).then_some(ident) + } + + fn specialize_expr(&self, specialize_path: &Path, target_path: &Path) -> Expr { let FieldInfo { ty, member, key, .. } = &self; let key_expr = key.expr(); - parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#member, #key_expr, descriptor)?;) + parse_quote!(<#ty as #specialize_path::Specialize<#target_path>>::specialize(&self.#member, #key_expr, descriptor)) } fn specialize_predicate(&self, specialize_path: &Path, target_path: &Path) -> WherePredicate { @@ -250,6 +254,21 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { let fields = guard!(get_struct_fields(&ast, "Specialize")); let field_info = guard!(get_field_info(fields, &targets)); + let key_idents: Vec> = field_info + .iter() + .enumerate() + .map(|(i, field_info)| field_info.key_ident(format_ident!("key{i}"))) + .collect(); + let key_tuple_idents: Vec = key_idents.iter().flatten().cloned().collect(); + let ignore_pat: Pat = parse_quote!(_); + let key_patterns: Vec = key_idents + .iter() + .map(|key_ident| match key_ident { + Some(key_ident) => parse_quote!(#key_ident), + None => ignore_pat.clone(), + }) + .collect(); + let base_descriptor_fields = field_info .iter() .filter(|field| field.use_base_descriptor) @@ -268,8 +287,14 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { match targets { SpecializeImplTargets::All => { - let specialize_impl = - impl_specialize_all(&specialize_path, &ecs_path, &ast, &field_info); + let specialize_impl = impl_specialize_all( + &specialize_path, + &ecs_path, + &ast, + &field_info, + &key_patterns, + &key_tuple_idents, + ); let get_base_descriptor_impl = base_descriptor_field .map(|field_info| impl_get_base_descriptor_all(&specialize_path, &ast, field_info)) .unwrap_or_default(); @@ -279,7 +304,15 @@ pub fn impl_specialize(input: TokenStream) -> TokenStream { } SpecializeImplTargets::Specific(targets) => { let specialize_impls = targets.iter().map(|target| { - impl_specialize_specific(&specialize_path, &ecs_path, &ast, &field_info, target) + impl_specialize_specific( + &specialize_path, + &ecs_path, + &ast, + &field_info, + target, + &key_patterns, + &key_tuple_idents, + ) }); let get_base_descriptor_impls = targets.iter().filter_map(|target| { base_descriptor_field.map(|field_info| { @@ -296,15 +329,17 @@ fn impl_specialize_all( ecs_path: &Path, ast: &DeriveInput, field_info: &[FieldInfo], + key_patterns: &[Pat], + key_tuple_idents: &[Ident], ) -> TokenStream { let target_path = Path::from(format_ident!("T")); let key_elems: Vec = field_info .iter() .filter_map(|field_info| field_info.key_ty(specialize_path, &target_path)) .collect(); - let specialize_stmts: Vec = field_info + let specialize_exprs: Vec = field_info .iter() - .map(|field_info| field_info.specialize_stmt(specialize_path, &target_path)) + .map(|field_info| field_info.specialize_expr(specialize_path, &target_path)) .collect(); let struct_name = &ast.ident; @@ -334,8 +369,9 @@ fn impl_specialize_all( &self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::Specializable>::Descriptor - ) -> #FQResult<#ecs_path::error::BevyError> { - #(#specialize_stmts)* + ) -> #FQResult<#specialize_path::Canonical, #ecs_path::error::BevyError> { + #(let #key_patterns = #specialize_exprs?;)* + #FQResult::Ok((#(#key_tuple_idents),*)) } } }) @@ -347,14 +383,16 @@ fn impl_specialize_specific( ast: &DeriveInput, field_info: &[FieldInfo], target_path: &Path, + key_patterns: &[Pat], + key_tuple_idents: &[Ident], ) -> TokenStream { let key_elems: Vec = field_info .iter() .filter_map(|field_info| field_info.key_ty(specialize_path, target_path)) .collect(); - let specialize_stmts: Vec = field_info + let specialize_exprs: Vec = field_info .iter() - .map(|field_info| field_info.specialize_stmt(specialize_path, target_path)) + .map(|field_info| field_info.specialize_expr(specialize_path, target_path)) .collect(); let struct_name = &ast.ident; @@ -368,69 +406,14 @@ fn impl_specialize_specific( &self, key: Self::Key, descriptor: &mut <#target_path as #specialize_path::Specializable>::Descriptor - ) -> #FQResult<#ecs_path::error::BevyError> { - #(#specialize_stmts)* + ) -> #FQResult<#specialize_path::Canonical, #ecs_path::error::BevyError> { + #(let #key_patterns = #specialize_exprs?;)* + #FQResult::Ok((#(#key_tuple_idents),*)) } } }) } -pub fn impl_get_base_descriptor(input: TokenStream) -> TokenStream { - let bevy_render_path: Path = crate::bevy_render_path(); - let specialize_path = { - let mut path = bevy_render_path.clone(); - path.segments.push(format_ident!("render_resource").into()); - path - }; - - let ast = parse_macro_input!(input as DeriveInput); - let targets = guard!(get_specialize_targets(&ast, "Specialize")); - let fields = guard!(get_struct_fields(&ast, "Specialize")); - let field_info = guard!(get_field_info(fields, &targets)); - - let base_descriptor_fields = field_info - .iter() - .filter(|field| field.use_base_descriptor) - .collect::>(); - - let base_descriptor_field = if field_info.len() == 1 { - field_info[0].clone() - } else if base_descriptor_fields.is_empty() { - return syn::Error::new( - Span::call_site(), - "#[base_descriptor] attribute not found. It must be present on exactly one field", - ) - .into_compile_error() - .into(); - } else if base_descriptor_fields.len() > 1 { - return syn::Error::new( - Span::call_site(), - "Too many #[base_descriptor] attributes found. It must be present on exactly one field", - ) - .into_compile_error() - .into(); - } else { - base_descriptor_fields[0].clone() - }; - - match targets { - SpecializeImplTargets::All => { - impl_get_base_descriptor_all(&specialize_path, &ast, &base_descriptor_field) - } - SpecializeImplTargets::Specific(targets) => targets - .iter() - .map(|target| { - impl_get_base_descriptor_specific( - &specialize_path, - &ast, - &base_descriptor_field, - target, - ) - }) - .collect(), - } -} - fn impl_get_base_descriptor_specific( specialize_path: &Path, ast: &DeriveInput, diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index f57542f55b64d..be07feae0c6b3 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -127,7 +127,7 @@ impl Specializable for ComputePipeline { /// */ /// ``` pub trait Specialize: Send + Sync + 'static { - type Key: SpecializationKey; + type Key: SpecializeKey; fn specialize( &self, key: Self::Key, @@ -135,12 +135,12 @@ pub trait Specialize: Send + Sync + 'static { ) -> Result, BevyError>; } -pub trait SpecializationKey: Clone + Hash + Eq { +pub trait SpecializeKey: Clone + Hash + Eq { const CANONICAL: bool; type Canonical: Hash + Eq; } -pub type Canonical = ::Canonical; +pub type Canonical = ::Canonical; impl Specialize for () { type Key = (); @@ -168,8 +168,8 @@ impl Specialize for PhantomData { - impl <$($T: SpecializationKey),*> SpecializationKey for ($($T,)*) { - const CANONICAL: bool = true $(&& <$T as SpecializationKey>::CANONICAL)*; + impl <$($T: SpecializeKey),*> SpecializeKey for ($($T,)*) { + const CANONICAL: bool = true $(&& <$T as SpecializeKey>::CANONICAL)*; type Canonical = ($(Canonical<$T>,)*); } }; @@ -316,7 +316,7 @@ impl> Specializer { } // if the whole key is canonical, the secondary cache isn't needed. - if ::CANONICAL { + if ::CANONICAL { return Ok(primary_entry .insert(::queue(pipeline_cache, descriptor)) .clone()); diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index eab0b812c500f..4ec108bc82452 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -35,7 +35,7 @@ use bevy::{ Render, RenderApp, RenderSystems, }, }; -use bevy_render::render_resource::{Canonical, SpecializationKey}; +use bevy_render::render_resource::{Canonical, SpecializeKey}; use bytemuck::{Pod, Zeroable}; /// A marker component that represents an entity that is to be rendered using @@ -238,7 +238,8 @@ fn queue_custom_phase_item( // some per-view settings, such as whether the view is HDR, but for // simplicity's sake we simply hard-code the view's characteristics, // with the exception of number of MSAA samples. - let Ok(pipeline_id) = specializer.specialize(&pipeline_cache, *msaa) else { + let Ok(pipeline_id) = specializer.specialize(&pipeline_cache, CustomPhaseKey(*msaa)) + else { continue; }; @@ -294,7 +295,7 @@ impl FromWorld for CustomPhaseSpecializer { #[derive(Copy, Clone, PartialEq, Eq, Hash)] struct CustomPhaseKey(Msaa); -impl SpecializationKey for CustomPhaseKey { +impl SpecializeKey for CustomPhaseKey { const CANONICAL: bool = true; type Canonical = Self; } From a4abde32d5e52b945219d805591529eb55a17ff6 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 15:00:44 -0700 Subject: [PATCH 35/50] add docs --- .../src/render_resource/specialize.rs | 46 ++++++++++++++++--- examples/shader/custom_phase_item.rs | 2 +- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index be07feae0c6b3..0e494f85e77ed 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -64,7 +64,7 @@ impl Specializable for ComputePipeline { } /// Defines a type that is able to transform descriptors for a specializable -/// type T, based on a known hashable key type. +/// type T, based on a hashable key type. /// /// This is mainly used when "specializing" render /// pipelines, i.e. specifying shader defs and binding layout based on the key, @@ -88,22 +88,37 @@ impl Specializable for ComputePipeline { /// /// Example: /// ```rs +/// # use super::RenderPipeline; +/// # use super::RenderPipelineDescriptor; +/// # use bevy_ecs::error::BevyError; +/// /// struct A; /// struct B; +/// #[derive(Copy, Clone, PartialEq, Eq, Hash)] +/// struct BKey; /// /// impl Specialize for A { /// type Key = (); /// -/// fn specialize(&self, _key: (), _descriptor: &mut RenderPipelineDescriptor) { +/// fn specialize(&self, key: (), descriptor: &mut RenderPipelineDescriptor) -> Result<(), BevyError> { +/// # let _ = (key, descriptor); /// //... +/// Ok(()) /// } /// } /// +/// impl SpecializeKey for B { +/// const CANONICAL: bool = true; +/// type Canonical = Self; +/// } +/// /// impl Specialize for B { -/// type Key = u32; +/// type Key = BKey; /// -/// fn specialize(&self, _key: u32, _descriptor: &mut RenderPipelineDescriptor) { +/// fn specialize(&self, _key: Bkey, _descriptor: &mut RenderPipelineDescriptor) -> Result { +/// # let _ = (key, descriptor); /// //... +/// Ok(BKey) /// } /// } /// @@ -135,8 +150,25 @@ pub trait Specialize: Send + Sync + 'static { ) -> Result, BevyError>; } +/// Defines a type that is able to be used as a key for types that `impl Specialize` +/// +/// **Most types should implement this trait with `IS_CANONICAL = true` and `Canonical = Self`**. +/// +/// In this case, "canonical" means that each unique value of this type will produce +/// a unique specialized result, which isn't true in general. `MeshVertexBufferLayout` +/// is a good example of a type that's `Eq + Hash`, but that isn't canonical: vertex +/// attributes could be specified in any order, or there could be more attributes +/// provided than the specialized pipeline requires. Its `Canonical` key type would +/// be `VertexBufferLayout`, the final layout required by the pipeline. +/// +/// Processing keys into canonical keys this way allows the `Specializer` to reuse +/// resources more eagerly where possible. pub trait SpecializeKey: Clone + Hash + Eq { - const CANONICAL: bool; + /// Denotes whether this key is canonical or not. This should only be `true` + /// if and only if `Canonical = Self`. + const IS_CANONICAL: bool; + + /// The canonical key type to convert this into during specialization. type Canonical: Hash + Eq; } @@ -169,7 +201,7 @@ impl Specialize for PhantomData { impl <$($T: SpecializeKey),*> SpecializeKey for ($($T,)*) { - const CANONICAL: bool = true $(&& <$T as SpecializeKey>::CANONICAL)*; + const IS_CANONICAL: bool = true $(&& <$T as SpecializeKey>::IS_CANONICAL)*; type Canonical = ($(Canonical<$T>,)*); } }; @@ -316,7 +348,7 @@ impl> Specializer { } // if the whole key is canonical, the secondary cache isn't needed. - if ::CANONICAL { + if ::IS_CANONICAL { return Ok(primary_entry .insert(::queue(pipeline_cache, descriptor)) .clone()); diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 4ec108bc82452..06a93ec602121 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -296,7 +296,7 @@ impl FromWorld for CustomPhaseSpecializer { struct CustomPhaseKey(Msaa); impl SpecializeKey for CustomPhaseKey { - const CANONICAL: bool = true; + const IS_CANONICAL: bool = true; type Canonical = Self; } From 93bbde87c5ea7f978da05a6d9a2db52dd9a953a0 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 15:17:16 -0700 Subject: [PATCH 36/50] add derive macro for SpecializeKey --- crates/bevy_render/macros/src/lib.rs | 6 ++++++ crates/bevy_render/macros/src/specialize.rs | 18 ++++++++++++++++++ .../src/render_resource/specialize.rs | 3 ++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 212cd6bb66832..44b5d358de7f3 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -114,3 +114,9 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream { pub fn derive_specialize(input: TokenStream) -> TokenStream { specialize::impl_specialize(input) } + +/// Derive macro generating the most common impl of the trait `SpecializeKey` +#[proc_macro_derive(SpecializeKey)] +pub fn derive_specialize_key(input: TokenStream) -> TokenStream { + specialize::impl_specialize_key(input) +} diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index bcf3cace71447..11fe27747f59e 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -463,3 +463,21 @@ fn impl_get_base_descriptor_all( } }) } + +pub fn impl_specialize_key(input: TokenStream) -> TokenStream { + let bevy_render_path: Path = crate::bevy_render_path(); + let specialize_path = { + let mut path = bevy_render_path.clone(); + path.segments.push(format_ident!("render_resource").into()); + path + }; + + let ast = parse_macro_input!(input as DeriveInput); + let ident = ast.ident; + TokenStream::from(quote!( + impl #specialize_path::SpecializeKey for #ident { + const IS_CANONICAL: bool = true; + type Canonical = Self; + } + )) +} diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 0e494f85e77ed..b36654d3875a6 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -18,7 +18,7 @@ use core::{hash::Hash, marker::PhantomData}; use tracing::error; use variadics_please::all_tuples; -pub use bevy_render_macros::Specialize; +pub use bevy_render_macros::{Specialize, SpecializeKey}; /// Defines a type that is able to be "specialized" and cached by creating and transforming /// its descriptor type. This is implemented for [`RenderPipeline`] and [`ComputePipeline`], and @@ -153,6 +153,7 @@ pub trait Specialize: Send + Sync + 'static { /// Defines a type that is able to be used as a key for types that `impl Specialize` /// /// **Most types should implement this trait with `IS_CANONICAL = true` and `Canonical = Self`**. +/// This is the implementation generated by `#[derive(SpecializeKey)]` /// /// In this case, "canonical" means that each unique value of this type will produce /// a unique specialized result, which isn't true in general. `MeshVertexBufferLayout` From 7c4caca47799e32bdde9c2cab38751de5b3bb6a1 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 16:08:45 -0700 Subject: [PATCH 37/50] add migration guide --- .../composable_specialization.md | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 release-content/migration-guides/composable_specialization.md diff --git a/release-content/migration-guides/composable_specialization.md b/release-content/migration-guides/composable_specialization.md new file mode 100644 index 0000000000000..abd3a13094b89 --- /dev/null +++ b/release-content/migration-guides/composable_specialization.md @@ -0,0 +1,146 @@ +--- +title: Composable Specialization +pull_requests: [17373] +--- + +The existing pipeline specialization APIs (`SpecializedRenderPipeline` etc.) have +been replaced with a single `Specialize` trait and `Specializer` collection: + +```rs +pub trait Specialize: Send + Sync + 'static { + type Key: SpecializeKey; + fn specialize( + &self, + key: Self::Key, + descriptor: &mut T::Descriptor, + ) -> Result, BevyError>; +} + +pub struct Specializer>{ ... }; +``` + +The main difference is the change from *producing* a pipeline descriptor to +*mutating* one based on a key. The "base descriptor" that the `Specializer` +passes to the `Specialize` implementation can either be specified manually +with `Specializer::new` or by implementing `GetBaseDescriptor`. Also, there's +a new key for specialization keys, `SpecializeKey`, that can be derived with +the included macro in most cases. + +```rs +pub struct MySpecializer { + layout: BindGroupLayout, + layout_msaa: BindGroupLayout, + vertex: Handle, + fragment: Handle, +} + +// before +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +// after +#[derive(Clone, Copy, PartialEq, Eq, Hash, SpecializeKey)] +pub struct MyKey { + blend_state: BlendState, + msaa: Msaa, +} + +impl FromWorld for MySpecializer { + fn from_world(&mut World) -> Self { + ... + } +} + +// before +impl SpecializedRenderPipeline for MySpecializer { + type Key = MyKey; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + RenderPipelineDescriptor { + label: Some("my_pipeline".into()), + layout: vec![ + if key.msaa.samples() > 0 { + self.layout_msaa.clone() + } else { + self.layout.clone() + } + ], + push_constant_ranges: vec![], + vertex: VertexState { + shader: self.vertex.clone(), + shader_defs: vec![], + entry_point: "vertex".into(), + buffers: vec![], + }, + primitive: Default::default(), + depth_stencil: None, + multisample: MultisampleState { + count: key.msaa.samples(), + ..Default::default() + }, + fragment: Some(FragmentState { + shader: self.fragment.clone(), + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::Rgba8Unorm, + blend: Some(key.blend_state), + write_mask: ColorWrites::all(), + })], + }), + zero_initialize_workgroup_memory: false, + }, + } +} + +app.init_resource::>(); + +// after +impl Specialize for MySpecializer { + type Key = MyKey; + + fn specialize( + &self, + key: Self::Key, + descriptor: &mut RenderPipeline, + ) -> Result, BevyError> { + descriptor.multisample.count = key.msaa.samples(); + descriptor.layout[0] = if key.msaa.samples() > 0 { + self.layout_msaa.clone() + } else { + self.layout.clone() + }; + descriptor.fragment.targets[0].as_mut().unwrap().blend_mode = key.blend_state; + } +} + +impl GetBaseDescriptor for MySpecializer { + fn get_base_descriptor(&self) -> RenderPipelineDescriptor { + RenderPipelineDescriptor { + label: Some("my_pipeline".into()), + layout: vec![self.layout.clone()], + push_constant_ranges: vec![], + vertex: VertexState { + shader: self.vertex.clone(), + shader_defs: vec![], + entry_point: "vertex".into(), + buffers: vec![], + }, + primitive: Default::default(), + depth_stencil: None, + multisample: MultiSampleState::default(), + fragment: Some(FragmentState { + shader: self.fragment.clone(), + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::Rgba8Unorm, + blend: None, + write_mask: ColorWrites::all(), + })], + }), + zero_initialize_workgroup_memory: false, + }, + } +} + +app.init_resource::>(); +``` From 931b93556ad711e38c21184e7806c21efed48f8a Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 16:14:13 -0700 Subject: [PATCH 38/50] edit migration guide --- .../migration-guides/composable_specialization.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/release-content/migration-guides/composable_specialization.md b/release-content/migration-guides/composable_specialization.md index abd3a13094b89..c217c70f48c37 100644 --- a/release-content/migration-guides/composable_specialization.md +++ b/release-content/migration-guides/composable_specialization.md @@ -26,6 +26,10 @@ with `Specializer::new` or by implementing `GetBaseDescriptor`. Also, there's a new key for specialization keys, `SpecializeKey`, that can be derived with the included macro in most cases. +Composing multiple different specializers together with the `derive(Specialize)` +macro can be a lot more powerful (see the `Specialize` docs), but migrating +individual specializers is fairly simple: + ```rs pub struct MySpecializer { layout: BindGroupLayout, @@ -34,9 +38,9 @@ pub struct MySpecializer { fragment: Handle, } -// before +// before #[derive(Clone, Copy, PartialEq, Eq, Hash)] -// after +// after #[derive(Clone, Copy, PartialEq, Eq, Hash, SpecializeKey)] pub struct MyKey { blend_state: BlendState, From cc6179c7621fe4619725dacbfc6bc8e39761b88e Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 16:20:05 -0700 Subject: [PATCH 39/50] fix imports --- examples/shader/custom_phase_item.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 06a93ec602121..89f3975b89175 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -24,18 +24,17 @@ use bevy::{ ViewBinnedRenderPhases, }, render_resource::{ - BufferUsages, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, - FragmentState, GetBaseDescriptor, IndexFormat, MultisampleState, PipelineCache, - PrimitiveState, RawBufferVec, RenderPipeline, RenderPipelineDescriptor, Specialize, - Specializer, TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, - VertexState, VertexStepMode, + BufferUsages, Canonical, ColorTargetState, ColorWrites, CompareFunction, + DepthStencilState, FragmentState, GetBaseDescriptor, IndexFormat, MultisampleState, + PipelineCache, PrimitiveState, RawBufferVec, RenderPipeline, RenderPipelineDescriptor, + Specialize, SpecializeKey, Specializer, TextureFormat, VertexAttribute, + VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, view::{self, ExtractedView, RenderVisibleEntities, VisibilityClass}, Render, RenderApp, RenderSystems, }, }; -use bevy_render::render_resource::{Canonical, SpecializeKey}; use bytemuck::{Pod, Zeroable}; /// A marker component that represents an entity that is to be rendered using From 786dad9e3f3b449e1231fe3b9463caad830a3885 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 16:39:58 -0700 Subject: [PATCH 40/50] fix docs and macro --- crates/bevy_render/macros/src/specialize.rs | 10 +++++----- crates/bevy_render/src/render_resource/specialize.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 11fe27747f59e..ab3eb448222e5 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -115,13 +115,13 @@ impl FieldInfo { } } - fn has_base_descriptor_predicate( + fn get_base_descriptor_predicate( &self, specialize_path: &Path, target_path: &Path, ) -> WherePredicate { let ty = &self.ty; - parse_quote!(#ty: #specialize_path::HasBaseDescriptor<#target_path>) + parse_quote!(#ty: #specialize_path::GetBaseDescriptor<#target_path>) } } @@ -427,7 +427,7 @@ fn impl_get_base_descriptor_specific( TokenStream::from(quote!( impl #impl_generics #specialize_path::GetBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { fn get_base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { - <#field_ty as #specialize_path::HasBaseDescriptor<#target_path>>::base_descriptor(&self.#field_member) + <#field_ty as #specialize_path::GetBaseDescriptor<#target_path>>::base_descriptor(&self.#field_member) } } )) @@ -448,7 +448,7 @@ fn impl_get_base_descriptor_all( let where_clause = generics.make_where_clause(); where_clause.predicates.push( - base_descriptor_field_info.has_base_descriptor_predicate(specialize_path, &target_path), + base_descriptor_field_info.get_base_descriptor_predicate(specialize_path, &target_path), ); let (_, type_generics, _) = ast.generics.split_for_impl(); @@ -458,7 +458,7 @@ fn impl_get_base_descriptor_all( TokenStream::from(quote! { impl #impl_generics #specialize_path::GetBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { fn get_base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { - <#field_ty as #specialize_path::HasBaseDescriptor<#target_path>>::base_descriptor(&self.#field_member) + <#field_ty as #specialize_path::GetBaseDescriptor<#target_path>>::base_descriptor(&self.#field_member) } } }) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index b36654d3875a6..be60a441148a4 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -385,7 +385,7 @@ impl> Specializer { } /// [`Specializer`] implements [`FromWorld`] for [`Specialize`] implementations -/// that also satisfy [`FromWorld`] and [`HasBaseDescriptor`]. This will create +/// that also satisfy [`FromWorld`] and [`GetBaseDescriptor`]. This will create /// a [`Specializer`] with no user specializer, and the base descriptor taken /// from the [`Specialize`] implementation. impl FromWorld for Specializer From 035ef0cf166d3027e28e2623e6e417c0f10a76b1 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 16:41:36 -0700 Subject: [PATCH 41/50] use derive in example --- examples/shader/custom_phase_item.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 89f3975b89175..96028e554448a 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -291,14 +291,9 @@ impl FromWorld for CustomPhaseSpecializer { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializeKey)] struct CustomPhaseKey(Msaa); -impl SpecializeKey for CustomPhaseKey { - const IS_CANONICAL: bool = true; - type Canonical = Self; -} - impl Specialize for CustomPhaseSpecializer { type Key = CustomPhaseKey; From 22b27b206a6608ade2355b049f970c92aa5a9f4f Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 16:42:41 -0700 Subject: [PATCH 42/50] fix migration guide --- release-content/migration-guides/composable_specialization.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-content/migration-guides/composable_specialization.md b/release-content/migration-guides/composable_specialization.md index c217c70f48c37..bf93ffe6ae47c 100644 --- a/release-content/migration-guides/composable_specialization.md +++ b/release-content/migration-guides/composable_specialization.md @@ -113,6 +113,7 @@ impl Specialize for MySpecializer { self.layout.clone() }; descriptor.fragment.targets[0].as_mut().unwrap().blend_mode = key.blend_state; + Ok(key) } } From e1b0a1136a40d666df1071fa52ef36c752b69390 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 16:46:14 -0700 Subject: [PATCH 43/50] Update composable_specialization.md --- release-content/migration-guides/composable_specialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/migration-guides/composable_specialization.md b/release-content/migration-guides/composable_specialization.md index bf93ffe6ae47c..67df0fb2c2aa2 100644 --- a/release-content/migration-guides/composable_specialization.md +++ b/release-content/migration-guides/composable_specialization.md @@ -105,7 +105,7 @@ impl Specialize for MySpecializer { &self, key: Self::Key, descriptor: &mut RenderPipeline, - ) -> Result, BevyError> { + ) -> Result, BevyError> { descriptor.multisample.count = key.msaa.samples(); descriptor.layout[0] = if key.msaa.samples() > 0 { self.layout_msaa.clone() From 41ee9164787244b40d6a8f5635502754d9df0c0c Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 23 Jun 2025 18:52:05 -0700 Subject: [PATCH 44/50] fix docs --- .../src/render_resource/specialize.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index be60a441148a4..5ea8657a67e7b 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -94,7 +94,7 @@ impl Specializable for ComputePipeline { /// /// struct A; /// struct B; -/// #[derive(Copy, Clone, PartialEq, Eq, Hash)] +/// #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializeKey)] /// struct BKey; /// /// impl Specialize for A { @@ -107,11 +107,6 @@ impl Specializable for ComputePipeline { /// } /// } /// -/// impl SpecializeKey for B { -/// const CANONICAL: bool = true; -/// type Canonical = Self; -/// } -/// /// impl Specialize for B { /// type Key = BKey; /// @@ -133,10 +128,15 @@ impl Specializable for ComputePipeline { /// /* /// The generated implementation: /// impl Specialize for C { -/// type Key = u32; -/// fn specialize(&self, key: u32, descriptor: &mut RenderPipelineDescriptor) { -/// self.a.specialize((), descriptor); -/// self.b.specialize(key, descriptor); +/// type Key = BKey; +/// fn specialize( +/// &self, +/// key: Self::Key, +/// descriptor: &mut RenderPipelineDescriptor +/// ) -> Result, BevyError> { +/// let _ = self.a.specialize((), descriptor); +/// let key = self.b.specialize(key, descriptor); +/// Ok(key) /// } /// } /// */ From 5594740c69883ec977275ea7c2cdc08043bc3321 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sat, 28 Jun 2025 10:22:44 -0700 Subject: [PATCH 45/50] wording --- release-content/migration-guides/composable_specialization.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/release-content/migration-guides/composable_specialization.md b/release-content/migration-guides/composable_specialization.md index 67df0fb2c2aa2..4f829c9570667 100644 --- a/release-content/migration-guides/composable_specialization.md +++ b/release-content/migration-guides/composable_specialization.md @@ -28,7 +28,9 @@ the included macro in most cases. Composing multiple different specializers together with the `derive(Specialize)` macro can be a lot more powerful (see the `Specialize` docs), but migrating -individual specializers is fairly simple: +individual specializers is fairly simple. All static parts of the pipeline +should be specified in the base descriptor, while the `Specialize` impl +should mutate the key as little as necessary to match the key. ```rs pub struct MySpecializer { From 75d870d7fcffeedb03e725650f16bd2a833d8b07 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 29 Jun 2025 22:04:20 -0700 Subject: [PATCH 46/50] rename to SpecializationKey --- crates/bevy_render/macros/src/lib.rs | 4 ++-- crates/bevy_render/macros/src/specialize.rs | 2 +- .../src/render_resource/specialize.rs | 18 +++++++++--------- examples/shader/custom_phase_item.rs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 44b5d358de7f3..2a89d700e36bc 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -115,8 +115,8 @@ pub fn derive_specialize(input: TokenStream) -> TokenStream { specialize::impl_specialize(input) } -/// Derive macro generating the most common impl of the trait `SpecializeKey` -#[proc_macro_derive(SpecializeKey)] +/// Derive macro generating the most common impl of the trait `SpecializationKey` +#[proc_macro_derive(SpecializationKey)] pub fn derive_specialize_key(input: TokenStream) -> TokenStream { specialize::impl_specialize_key(input) } diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index ab3eb448222e5..66beb8030a38e 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -475,7 +475,7 @@ pub fn impl_specialize_key(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let ident = ast.ident; TokenStream::from(quote!( - impl #specialize_path::SpecializeKey for #ident { + impl #specialize_path::SpecializationKey for #ident { const IS_CANONICAL: bool = true; type Canonical = Self; } diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 5ea8657a67e7b..4dd72dbd19910 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -18,7 +18,7 @@ use core::{hash::Hash, marker::PhantomData}; use tracing::error; use variadics_please::all_tuples; -pub use bevy_render_macros::{Specialize, SpecializeKey}; +pub use bevy_render_macros::{Specialize, SpecializationKey}; /// Defines a type that is able to be "specialized" and cached by creating and transforming /// its descriptor type. This is implemented for [`RenderPipeline`] and [`ComputePipeline`], and @@ -94,7 +94,7 @@ impl Specializable for ComputePipeline { /// /// struct A; /// struct B; -/// #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializeKey)] +/// #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializationKey)] /// struct BKey; /// /// impl Specialize for A { @@ -142,7 +142,7 @@ impl Specializable for ComputePipeline { /// */ /// ``` pub trait Specialize: Send + Sync + 'static { - type Key: SpecializeKey; + type Key: SpecializationKey; fn specialize( &self, key: Self::Key, @@ -153,7 +153,7 @@ pub trait Specialize: Send + Sync + 'static { /// Defines a type that is able to be used as a key for types that `impl Specialize` /// /// **Most types should implement this trait with `IS_CANONICAL = true` and `Canonical = Self`**. -/// This is the implementation generated by `#[derive(SpecializeKey)]` +/// This is the implementation generated by `#[derive(SpecializationKey)]` /// /// In this case, "canonical" means that each unique value of this type will produce /// a unique specialized result, which isn't true in general. `MeshVertexBufferLayout` @@ -164,7 +164,7 @@ pub trait Specialize: Send + Sync + 'static { /// /// Processing keys into canonical keys this way allows the `Specializer` to reuse /// resources more eagerly where possible. -pub trait SpecializeKey: Clone + Hash + Eq { +pub trait SpecializationKey: Clone + Hash + Eq { /// Denotes whether this key is canonical or not. This should only be `true` /// if and only if `Canonical = Self`. const IS_CANONICAL: bool; @@ -173,7 +173,7 @@ pub trait SpecializeKey: Clone + Hash + Eq { type Canonical: Hash + Eq; } -pub type Canonical = ::Canonical; +pub type Canonical = ::Canonical; impl Specialize for () { type Key = (); @@ -201,8 +201,8 @@ impl Specialize for PhantomData { - impl <$($T: SpecializeKey),*> SpecializeKey for ($($T,)*) { - const IS_CANONICAL: bool = true $(&& <$T as SpecializeKey>::IS_CANONICAL)*; + impl <$($T: SpecializationKey),*> SpecializationKey for ($($T,)*) { + const IS_CANONICAL: bool = true $(&& <$T as SpecializationKey>::IS_CANONICAL)*; type Canonical = ($(Canonical<$T>,)*); } }; @@ -349,7 +349,7 @@ impl> Specializer { } // if the whole key is canonical, the secondary cache isn't needed. - if ::IS_CANONICAL { + if ::IS_CANONICAL { return Ok(primary_entry .insert(::queue(pipeline_cache, descriptor)) .clone()); diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 96028e554448a..eee9279b289df 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -27,7 +27,7 @@ use bevy::{ BufferUsages, Canonical, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, FragmentState, GetBaseDescriptor, IndexFormat, MultisampleState, PipelineCache, PrimitiveState, RawBufferVec, RenderPipeline, RenderPipelineDescriptor, - Specialize, SpecializeKey, Specializer, TextureFormat, VertexAttribute, + SpecializationKey, Specialize, Specializer, TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, @@ -291,7 +291,7 @@ impl FromWorld for CustomPhaseSpecializer { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializeKey)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializationKey)] struct CustomPhaseKey(Msaa); impl Specialize for CustomPhaseSpecializer { From 98b896ea7a37abeef00ee0af1039626601a17a43 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 29 Jun 2025 22:10:28 -0700 Subject: [PATCH 47/50] rename macros --- crates/bevy_render/macros/src/lib.rs | 4 ++-- crates/bevy_render/macros/src/specialize.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 2a89d700e36bc..c764664a9a37d 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -117,6 +117,6 @@ pub fn derive_specialize(input: TokenStream) -> TokenStream { /// Derive macro generating the most common impl of the trait `SpecializationKey` #[proc_macro_derive(SpecializationKey)] -pub fn derive_specialize_key(input: TokenStream) -> TokenStream { - specialize::impl_specialize_key(input) +pub fn derive_specialization_key(input: TokenStream) -> TokenStream { + specialize::impl_specialization_key(input) } diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 66beb8030a38e..0177501258c3f 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -464,7 +464,7 @@ fn impl_get_base_descriptor_all( }) } -pub fn impl_specialize_key(input: TokenStream) -> TokenStream { +pub fn impl_specialization_key(input: TokenStream) -> TokenStream { let bevy_render_path: Path = crate::bevy_render_path(); let specialize_path = { let mut path = bevy_render_path.clone(); From 5e2089ab18292f6067c4d8a504e5da14a963ab5c Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 30 Jun 2025 17:40:00 -0700 Subject: [PATCH 48/50] rename caches and keys --- crates/bevy_render/macros/src/lib.rs | 8 ++--- crates/bevy_render/macros/src/specialize.rs | 4 +-- .../src/render_resource/specialize.rs | 32 +++++++++---------- examples/shader/custom_phase_item.rs | 8 ++--- .../composable_specialization.md | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index c764664a9a37d..063757bfe6fd9 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -115,8 +115,8 @@ pub fn derive_specialize(input: TokenStream) -> TokenStream { specialize::impl_specialize(input) } -/// Derive macro generating the most common impl of the trait `SpecializationKey` -#[proc_macro_derive(SpecializationKey)] -pub fn derive_specialization_key(input: TokenStream) -> TokenStream { - specialize::impl_specialization_key(input) +/// Derive macro generating the most common impl of the trait `SpecializerKey` +#[proc_macro_derive(SpecializerKey)] +pub fn derive_specializer_key(input: TokenStream) -> TokenStream { + specialize::impl_specializer_key(input) } diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specialize.rs index 0177501258c3f..092de6e8d785c 100644 --- a/crates/bevy_render/macros/src/specialize.rs +++ b/crates/bevy_render/macros/src/specialize.rs @@ -464,7 +464,7 @@ fn impl_get_base_descriptor_all( }) } -pub fn impl_specialization_key(input: TokenStream) -> TokenStream { +pub fn impl_specializer_key(input: TokenStream) -> TokenStream { let bevy_render_path: Path = crate::bevy_render_path(); let specialize_path = { let mut path = bevy_render_path.clone(); @@ -475,7 +475,7 @@ pub fn impl_specialization_key(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let ident = ast.ident; TokenStream::from(quote!( - impl #specialize_path::SpecializationKey for #ident { + impl #specialize_path::SpecializerKey for #ident { const IS_CANONICAL: bool = true; type Canonical = Self; } diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 4dd72dbd19910..669ccd177720d 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -18,7 +18,7 @@ use core::{hash::Hash, marker::PhantomData}; use tracing::error; use variadics_please::all_tuples; -pub use bevy_render_macros::{Specialize, SpecializationKey}; +pub use bevy_render_macros::{Specialize, SpecializerKey}; /// Defines a type that is able to be "specialized" and cached by creating and transforming /// its descriptor type. This is implemented for [`RenderPipeline`] and [`ComputePipeline`], and @@ -94,7 +94,7 @@ impl Specializable for ComputePipeline { /// /// struct A; /// struct B; -/// #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializationKey)] +/// #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)] /// struct BKey; /// /// impl Specialize for A { @@ -142,7 +142,7 @@ impl Specializable for ComputePipeline { /// */ /// ``` pub trait Specialize: Send + Sync + 'static { - type Key: SpecializationKey; + type Key: SpecializerKey; fn specialize( &self, key: Self::Key, @@ -153,7 +153,7 @@ pub trait Specialize: Send + Sync + 'static { /// Defines a type that is able to be used as a key for types that `impl Specialize` /// /// **Most types should implement this trait with `IS_CANONICAL = true` and `Canonical = Self`**. -/// This is the implementation generated by `#[derive(SpecializationKey)]` +/// This is the implementation generated by `#[derive(SpecializerKey)]` /// /// In this case, "canonical" means that each unique value of this type will produce /// a unique specialized result, which isn't true in general. `MeshVertexBufferLayout` @@ -162,9 +162,9 @@ pub trait Specialize: Send + Sync + 'static { /// provided than the specialized pipeline requires. Its `Canonical` key type would /// be `VertexBufferLayout`, the final layout required by the pipeline. /// -/// Processing keys into canonical keys this way allows the `Specializer` to reuse +/// Processing keys into canonical keys this way allows the `SpecializedCache` to reuse /// resources more eagerly where possible. -pub trait SpecializationKey: Clone + Hash + Eq { +pub trait SpecializerKey: Clone + Hash + Eq { /// Denotes whether this key is canonical or not. This should only be `true` /// if and only if `Canonical = Self`. const IS_CANONICAL: bool; @@ -173,7 +173,7 @@ pub trait SpecializationKey: Clone + Hash + Eq { type Canonical: Hash + Eq; } -pub type Canonical = ::Canonical; +pub type Canonical = ::Canonical; impl Specialize for () { type Key = (); @@ -201,8 +201,8 @@ impl Specialize for PhantomData { - impl <$($T: SpecializationKey),*> SpecializationKey for ($($T,)*) { - const IS_CANONICAL: bool = true $(&& <$T as SpecializationKey>::IS_CANONICAL)*; + impl <$($T: SpecializerKey),*> SpecializerKey for ($($T,)*) { + const IS_CANONICAL: bool = true $(&& <$T as SpecializerKey>::IS_CANONICAL)*; type Canonical = ($(Canonical<$T>,)*); } }; @@ -281,7 +281,7 @@ pub type SpecializeFn = /// resource will only be created if it is missing, retrieving it from the /// cache otherwise. #[derive(Resource)] -pub struct Specializer> { +pub struct SpecializedCache> { specializer: S, user_specializer: Option>, base_descriptor: T::Descriptor, @@ -289,8 +289,8 @@ pub struct Specializer> { secondary_cache: HashMap, T::CachedId>, } -impl> Specializer { - /// Creates a new [`Specializer`] from a [`Specialize`] implementation, +impl> SpecializedCache { + /// Creates a new [`SpecializedCache`] from a [`Specialize`] implementation, /// an optional "user specializer", and a base descriptor. The user /// specializer is applied after the [`Specialize`] implementation, with /// the same key. @@ -349,7 +349,7 @@ impl> Specializer { } // if the whole key is canonical, the secondary cache isn't needed. - if ::IS_CANONICAL { + if ::IS_CANONICAL { return Ok(primary_entry .insert(::queue(pipeline_cache, descriptor)) .clone()); @@ -384,11 +384,11 @@ impl> Specializer { } } -/// [`Specializer`] implements [`FromWorld`] for [`Specialize`] implementations +/// [`SpecializedCache`] implements [`FromWorld`] for [`Specialize`] implementations /// that also satisfy [`FromWorld`] and [`GetBaseDescriptor`]. This will create -/// a [`Specializer`] with no user specializer, and the base descriptor taken +/// a [`SpecializedCache`] with no user specializer, and the base descriptor taken /// from the [`Specialize`] implementation. -impl FromWorld for Specializer +impl FromWorld for SpecializedCache where T: Specializable, S: FromWorld + Specialize + GetBaseDescriptor, diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index eee9279b289df..1af13ea14d043 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -27,7 +27,7 @@ use bevy::{ BufferUsages, Canonical, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, FragmentState, GetBaseDescriptor, IndexFormat, MultisampleState, PipelineCache, PrimitiveState, RawBufferVec, RenderPipeline, RenderPipelineDescriptor, - SpecializationKey, Specialize, Specializer, TextureFormat, VertexAttribute, + Specialize, SpecializedCache, SpecializerKey, TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, @@ -167,7 +167,7 @@ fn main() { // We make sure to add these to the render app, not the main app. app.get_sub_app_mut(RenderApp) .unwrap() - .init_resource::>() + .init_resource::>() .add_render_command::() .add_systems( Render, @@ -214,7 +214,7 @@ fn queue_custom_phase_item( pipeline_cache: Res, mut opaque_render_phases: ResMut>, opaque_draw_functions: Res>, - mut specializer: ResMut>, + mut specializer: ResMut>, views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, mut next_tick: Local, ) { @@ -291,7 +291,7 @@ impl FromWorld for CustomPhaseSpecializer { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializationKey)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)] struct CustomPhaseKey(Msaa); impl Specialize for CustomPhaseSpecializer { diff --git a/release-content/migration-guides/composable_specialization.md b/release-content/migration-guides/composable_specialization.md index 4f829c9570667..a8eb45f080fbc 100644 --- a/release-content/migration-guides/composable_specialization.md +++ b/release-content/migration-guides/composable_specialization.md @@ -4,7 +4,7 @@ pull_requests: [17373] --- The existing pipeline specialization APIs (`SpecializedRenderPipeline` etc.) have -been replaced with a single `Specialize` trait and `Specializer` collection: +been replaced with a single `Specialize` trait and `SpecializedCache` collection: ```rs pub trait Specialize: Send + Sync + 'static { From 9c36c3943f526b19ed575b135a7e0191d5f6d528 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 30 Jun 2025 18:10:10 -0700 Subject: [PATCH 49/50] rename trait --- .../src/render_resource/specialize.rs | 81 +++++++++---------- examples/shader/custom_phase_item.rs | 4 +- .../composable_specialization.md | 28 +++---- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specialize.rs index 669ccd177720d..c2269b1a781bb 100644 --- a/crates/bevy_render/src/render_resource/specialize.rs +++ b/crates/bevy_render/src/render_resource/specialize.rs @@ -70,8 +70,8 @@ impl Specializable for ComputePipeline { /// pipelines, i.e. specifying shader defs and binding layout based on the key, /// the result of which can then be cached and accessed quickly later. /// -/// This trait can be derived with `#[derive(Specialize)]` for structs whose -/// fields all implement [`Specialize`]. The key type will be tuple of the keys +/// This trait can be derived with `#[derive(Specializer)]` for structs whose +/// fields all implement [`Specializer`]. The key type will be tuple of the keys /// of each field, and their specialization logic will be applied in field /// order. Since derive macros can't have generic parameters, the derive macro /// requires an additional `#[specialize(..targets)]` attribute to specify a @@ -97,17 +97,17 @@ impl Specializable for ComputePipeline { /// #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)] /// struct BKey; /// -/// impl Specialize for A { +/// impl Specializer for A { /// type Key = (); /// -/// fn specialize(&self, key: (), descriptor: &mut RenderPipelineDescriptor) -> Result<(), BevyError> { +/// fn specializer(&self, key: (), descriptor: &mut RenderPipelineDescriptor) -> Result<(), BevyError> { /// # let _ = (key, descriptor); /// //... /// Ok(()) /// } /// } /// -/// impl Specialize for B { +/// impl Specializer for B { /// type Key = BKey; /// /// fn specialize(&self, _key: Bkey, _descriptor: &mut RenderPipelineDescriptor) -> Result { @@ -117,7 +117,7 @@ impl Specializable for ComputePipeline { /// } /// } /// -/// #[derive(Specialize)] +/// #[derive(Specializer)] /// #[specialize(RenderPipeline)] /// struct C { /// #[key(default)] @@ -127,7 +127,7 @@ impl Specializable for ComputePipeline { /// /// /* /// The generated implementation: -/// impl Specialize for C { +/// impl Specializer for C { /// type Key = BKey; /// fn specialize( /// &self, @@ -141,7 +141,7 @@ impl Specializable for ComputePipeline { /// } /// */ /// ``` -pub trait Specialize: Send + Sync + 'static { +pub trait Specializer: Send + Sync + 'static { type Key: SpecializerKey; fn specialize( &self, @@ -175,7 +175,7 @@ pub trait SpecializerKey: Clone + Hash + Eq { pub type Canonical = ::Canonical; -impl Specialize for () { +impl Specializer for () { type Key = (); fn specialize( @@ -187,7 +187,7 @@ impl Specialize for () { } } -impl Specialize for PhantomData { +impl Specializer for PhantomData { type Key = (); fn specialize( @@ -212,23 +212,22 @@ all_tuples!(impl_specialization_key_tuple, 0, 12, T); /// Defines a specializer that can also provide a "base descriptor". /// -/// In order to be composable, [`Specialize`] implementers don't create full -/// descriptors, only transform them. However, [`Specializer`]s need a "base -/// descriptor" at creation time in order to have something for the -/// [`Specialize`] implementation to work off of. This trait allows -/// [`Specializer`] to impl [`FromWorld`] for [`Specialize`] implementationss -/// that also satisfy [`FromWorld`] and [`GetBaseDescriptor`]. +/// In order to be composable, [`Specializer`] implementers don't create full +/// descriptors, only transform them. However, [`SpecializedCache`]s need a +/// "base descriptor" at creation time in order to have something for the +/// [`Specializer`] implementation to work off of. This trait allows +/// [`SpecializedCache`] to impl [`FromWorld`] for [`Specializer`] +/// implementations that also satisfy [`FromWorld`] and [`GetBaseDescriptor`]. /// -/// This trait can be also derived with `#[derive(Specialize)]`, by marking -/// a field with `#[base_descriptor]` to use its base [`GetBaseDescriptor`] -/// implementation. +/// This trait can be also derived with `#[derive(Specializer)]`, by marking +/// a field with `#[base_descriptor]` to use its [`GetBaseDescriptor`] implementation. /// /// Example: /// ```rs /// struct A; /// struct B; /// -/// impl Specialize for A { +/// impl Specializer for A { /// type Key = (); /// /// fn specialize(&self, _key: (), _descriptor: &mut RenderPipelineDescriptor) { @@ -236,7 +235,7 @@ all_tuples!(impl_specialization_key_tuple, 0, 12, T); /// } /// } /// -/// impl Specialize for B { +/// impl Specializer for B { /// type Key = u32; /// /// fn specialize(&self, _key: u32, _descriptor: &mut RenderPipelineDescriptor) { @@ -252,7 +251,7 @@ all_tuples!(impl_specialization_key_tuple, 0, 12, T); /// } /// /// -/// #[derive(Specialize)] +/// #[derive(Specializer)] /// #[specialize(RenderPipeline)] /// struct C { /// #[key(default)] @@ -270,34 +269,34 @@ all_tuples!(impl_specialization_key_tuple, 0, 12, T); /// } /// */ /// ``` -pub trait GetBaseDescriptor: Specialize { +pub trait GetBaseDescriptor: Specializer { fn get_base_descriptor(&self) -> T::Descriptor; } -pub type SpecializeFn = - fn(>::Key, &mut ::Descriptor) -> Result<(), BevyError>; +pub type SpecializerFn = + fn(>::Key, &mut ::Descriptor) -> Result<(), BevyError>; /// A cache for specializable resources. For a given key type the resulting /// resource will only be created if it is missing, retrieving it from the /// cache otherwise. #[derive(Resource)] -pub struct SpecializedCache> { +pub struct SpecializedCache> { specializer: S, - user_specializer: Option>, + user_specializer: Option>, base_descriptor: T::Descriptor, primary_cache: HashMap, secondary_cache: HashMap, T::CachedId>, } -impl> SpecializedCache { - /// Creates a new [`SpecializedCache`] from a [`Specialize`] implementation, - /// an optional "user specializer", and a base descriptor. The user - /// specializer is applied after the [`Specialize`] implementation, with +impl> SpecializedCache { + /// Creates a new [`SpecializedCache`] from a [`Specializer`], + /// an optional "user specializer", and a base descriptor. The + /// user specializer is applied after the [`Specializer`], with /// the same key. #[inline] pub fn new( specializer: S, - user_specializer: Option>, + user_specializer: Option>, base_descriptor: T::Descriptor, ) -> Self { Self { @@ -309,7 +308,7 @@ impl> SpecializedCache { } } - /// Specializes a resource given the [`Specialize`] implementation's key type. + /// Specializes a resource given the [`Specializer`]'s key type. #[inline] pub fn specialize( &mut self, @@ -334,7 +333,7 @@ impl> SpecializedCache { #[cold] fn specialize_slow( specializer: &S, - user_specializer: Option>, + user_specializer: Option>, base_descriptor: T::Descriptor, pipeline_cache: &PipelineCache, key: S::Key, @@ -362,9 +361,9 @@ impl> SpecializedCache { ::get_descriptor(pipeline_cache, entry.get().clone()); if &descriptor != stored_descriptor { error!( - "Invalid Specialize<{}> impl for {}: the cached descriptor \ + "Invalid Specializer<{}> impl for {}: the cached descriptor \ is not equal to the generated descriptor for the given key. \ - This means the Specialize implementation uses unused information \ + This means the Specializer implementation uses unused information \ from the key to specialize the pipeline. This is not allowed \ because it would invalidate the cache.", core::any::type_name::(), @@ -384,14 +383,14 @@ impl> SpecializedCache { } } -/// [`SpecializedCache`] implements [`FromWorld`] for [`Specialize`] implementations -/// that also satisfy [`FromWorld`] and [`GetBaseDescriptor`]. This will create -/// a [`SpecializedCache`] with no user specializer, and the base descriptor taken -/// from the [`Specialize`] implementation. +/// [`SpecializedCache`] implements [`FromWorld`] for [`Specializer`]s +/// that also satisfy [`FromWorld`] and [`GetBaseDescriptor`]. This will +/// create a [`SpecializedCache`] with no user specializer, and the base +/// descriptor take from the specializer's [`GetBaseDescriptor`] implementation. impl FromWorld for SpecializedCache where T: Specializable, - S: FromWorld + Specialize + GetBaseDescriptor, + S: FromWorld + Specializer + GetBaseDescriptor, { fn from_world(world: &mut World) -> Self { let specializer = S::from_world(world); diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 1af13ea14d043..f06aba14033b6 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -27,7 +27,7 @@ use bevy::{ BufferUsages, Canonical, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, FragmentState, GetBaseDescriptor, IndexFormat, MultisampleState, PipelineCache, PrimitiveState, RawBufferVec, RenderPipeline, RenderPipelineDescriptor, - Specialize, SpecializedCache, SpecializerKey, TextureFormat, VertexAttribute, + SpecializedCache, Specializer, SpecializerKey, TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, @@ -294,7 +294,7 @@ impl FromWorld for CustomPhaseSpecializer { #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)] struct CustomPhaseKey(Msaa); -impl Specialize for CustomPhaseSpecializer { +impl Specializer for CustomPhaseSpecializer { type Key = CustomPhaseKey; fn specialize( diff --git a/release-content/migration-guides/composable_specialization.md b/release-content/migration-guides/composable_specialization.md index a8eb45f080fbc..f87beef8cbbdb 100644 --- a/release-content/migration-guides/composable_specialization.md +++ b/release-content/migration-guides/composable_specialization.md @@ -4,11 +4,11 @@ pull_requests: [17373] --- The existing pipeline specialization APIs (`SpecializedRenderPipeline` etc.) have -been replaced with a single `Specialize` trait and `SpecializedCache` collection: +been replaced with a single `Specializer` trait and `SpecializedCache` collection: ```rs -pub trait Specialize: Send + Sync + 'static { - type Key: SpecializeKey; +pub trait Specializer: Send + Sync + 'static { + type Key: SpecializerKey; fn specialize( &self, key: Self::Key, @@ -16,20 +16,19 @@ pub trait Specialize: Send + Sync + 'static { ) -> Result, BevyError>; } -pub struct Specializer>{ ... }; +pub struct SpecializedCache>{ ... }; ``` The main difference is the change from *producing* a pipeline descriptor to -*mutating* one based on a key. The "base descriptor" that the `Specializer` -passes to the `Specialize` implementation can either be specified manually -with `Specializer::new` or by implementing `GetBaseDescriptor`. Also, there's -a new key for specialization keys, `SpecializeKey`, that can be derived with -the included macro in most cases. +*mutating* one based on a key. The "base descriptor" that the `SpecializedCache` +passes to the `Specializer` can either be specified manually with `Specializer::new` +or by implementing `GetBaseDescriptor`. There's also a new trait for specialization +keys, `SpecializeKey`, that can be derived with the included macro in most cases. -Composing multiple different specializers together with the `derive(Specialize)` +Composing multiple different specializers together with the `derive(Specializer)` macro can be a lot more powerful (see the `Specialize` docs), but migrating individual specializers is fairly simple. All static parts of the pipeline -should be specified in the base descriptor, while the `Specialize` impl +should be specified in the base descriptor, while the `Specializer` impl should mutate the key as little as necessary to match the key. ```rs @@ -43,7 +42,8 @@ pub struct MySpecializer { // before #[derive(Clone, Copy, PartialEq, Eq, Hash)] // after -#[derive(Clone, Copy, PartialEq, Eq, Hash, SpecializeKey)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, SpecializerKey)] + pub struct MyKey { blend_state: BlendState, msaa: Msaa, @@ -100,7 +100,7 @@ impl SpecializedRenderPipeline for MySpecializer { app.init_resource::>(); // after -impl Specialize for MySpecializer { +impl Specializer for MySpecializer { type Key = MyKey; fn specialize( @@ -149,5 +149,5 @@ impl GetBaseDescriptor for MySpecializer { } } -app.init_resource::>(); +app.init_resource::>(); ``` From 0eb25e491dd62cfe439617cd5224eb908941f04f Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Mon, 30 Jun 2025 18:34:45 -0700 Subject: [PATCH 50/50] rename modules --- crates/bevy_render/macros/src/lib.rs | 6 +++--- .../macros/src/{specialize.rs => specializer.rs} | 0 crates/bevy_render/src/render_resource/mod.rs | 4 ++-- .../src/render_resource/{specialize.rs => specializer.rs} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename crates/bevy_render/macros/src/{specialize.rs => specializer.rs} (100%) rename crates/bevy_render/src/render_resource/{specialize.rs => specializer.rs} (100%) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 35990f465f4c0..93f6e12bdb304 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -4,7 +4,7 @@ mod as_bind_group; mod extract_component; mod extract_resource; -mod specialize; +mod specializer; use bevy_macro_utils::{derive_label, BevyManifest}; use proc_macro::TokenStream; @@ -112,13 +112,13 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream { /// This only works for structs whose members all implement `Specialize` #[proc_macro_derive(Specialize, attributes(specialize, key, base_descriptor))] pub fn derive_specialize(input: TokenStream) -> TokenStream { - specialize::impl_specialize(input) + specializer::impl_specialize(input) } /// Derive macro generating the most common impl of the trait `SpecializerKey` #[proc_macro_derive(SpecializerKey)] pub fn derive_specializer_key(input: TokenStream) -> TokenStream { - specialize::impl_specializer_key(input) + specializer::impl_specializer_key(input) } #[proc_macro_derive(ShaderLabel)] diff --git a/crates/bevy_render/macros/src/specialize.rs b/crates/bevy_render/macros/src/specializer.rs similarity index 100% rename from crates/bevy_render/macros/src/specialize.rs rename to crates/bevy_render/macros/src/specializer.rs diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 9233d9e4c4281..f156b0ecb0feb 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -12,7 +12,7 @@ mod pipeline_cache; mod pipeline_specializer; pub mod resource_macros; mod shader; -mod specialize; +mod specializer; mod storage_buffer; mod texture; mod uniform_buffer; @@ -29,7 +29,7 @@ pub use pipeline::*; pub use pipeline_cache::*; pub use pipeline_specializer::*; pub use shader::*; -pub use specialize::*; +pub use specializer::*; pub use storage_buffer::*; pub use texture::*; pub use uniform_buffer::*; diff --git a/crates/bevy_render/src/render_resource/specialize.rs b/crates/bevy_render/src/render_resource/specializer.rs similarity index 100% rename from crates/bevy_render/src/render_resource/specialize.rs rename to crates/bevy_render/src/render_resource/specializer.rs