Skip to content

Conversation

ecoskey
Copy link
Contributor

@ecoskey ecoskey commented Jan 14, 2025

Currently, our specialization API works through a series of wrapper structs and traits, which make things confusing to follow and difficult to generalize.

This pr takes a different approach, where "specializers" (types that implement Specialize) are composable, but "flat" rather than composed of a series of wrappers. The key is that specializers don't produce pipeline descriptors, but instead modify existing ones:

pub trait Specialize<T: Specializable> {
    type Key: SpecializeKey;
    
    fn specialize(
        &self, 
        key: Self::Key, 
        descriptor: &mut T::Descriptor
    ) -> Result<Canonical<Self::Key>, BevyError>;
}

This lets us use some derive magic to stick multiple specializers together:

pub struct A;
pub struct B;

impl Specialize<RenderPipeline> for A { ... }
impl Specialize<RenderPipeline> for A { ... }

#[derive(Specialize)]
#[specialize(RenderPipeline)]
struct C {
    // specialization is applied in struct field order
    applied_first: A,
    applied_second: B,
}

type C::Key = (A::Key, B::Key);

This approach is much easier to understand, IMO, and also lets us separate concerns better. Specializers can be placed in fully separate crates/modules, and key computation can be shared as well.

The only real breaking change here is that since specializers only modify descriptors, we need a "base" descriptor to work off of. This can either be manually supplied when constructing a Specializer (the new collection replacing Specialized[Render/Compute]Pipelines), or supplied by implementing HasBaseDescriptor on a specializer. See examples/shader/custom_phase_item.rs for an example implementation.

Testing

  • Did some simple manual testing of the derive macro, it seems robust.

Showcase

#[derive(Specialize, HasBaseDescriptor)]
#[specialize(RenderPipeline)]
pub struct SpecializeMeshMaterial<M: Material> {
    // set mesh bind group layout and shader defs
    mesh: SpecializeMesh,
    // set view bind group layout and shader defs
    view: SpecializeView,
    // since type SpecializeMaterial::Key = (), 
    // we can hide it from the wrapper's external API
    #[key(default)]
    // defer to the GetBaseDescriptor impl of SpecializeMaterial, 
    // since it carries the vertex and fragment handles
    #[base_descriptor]
    // set material bind group layout, etc
    material: SpecializeMaterial<M>,
}

// implementation generated by the derive macro
impl <M: Material> Specialize<RenderPipeline> for SpecializeMeshMaterial<M> {
    type Key = (MeshKey, ViewKey);

    fn specialize(
        &self, 
        key: Self::Key, 
        descriptor: &mut RenderPipelineDescriptor
    ) -> Result<Canonical<Self::Key>, BevyError>  {
        let mesh_key = self.mesh.specialize(key.0, descriptor)?;
        let view_key = self.view.specialize(key.1, descriptor)?;
        let _ = self.material.specialize((), descriptor)?;
        Ok((mesh_key, view_key));
    }
}

impl <M: Material> HasBaseDescriptor<RenderPipeline> for SpecializeMeshMaterial<M> {
    fn base_descriptor(&self) -> RenderPipelineDescriptor {
        self.material.base_descriptor()
    }
}

@alice-i-cecile alice-i-cecile added A-Rendering Drawing game state to the screen C-Usability A targeted quality-of-life change that makes Bevy easier to use M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 14, 2025
still dying because of HashMap. Why?
Copy link
Contributor

@atlv24 atlv24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree with the overall direction of this

@superdump
Copy link
Contributor

From the high level description, I’m on board with this. I feel like the naming is a bit off. SpecializeMeshPipeline for example does feel like it’s missing a d which would make it the same as the existing name. I’d say make the names feel right, break APIs, and document migrations.

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it S-Needs-SME Decision or review from an SME is required labels Jun 30, 2025
@alice-i-cecile alice-i-cecile enabled auto-merge July 1, 2025 01:14
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jul 1, 2025
auto-merge was automatically disabled July 1, 2025 01:34

Head branch was pushed to by a user without write access

Merged via the queue into bevyengine:main with commit bdd3ef7 Jul 1, 2025
@github-project-automation github-project-automation bot moved this from In Review to Done in Rendering Jul 1, 2025
This was referenced Jul 1, 2025
github-merge-queue bot pushed a commit that referenced this pull request Jul 3, 2025
- renamed `spec_v2` related modules, that commit slipped through the
other pr #17373
- revised struct and trait docs for clarity, and gave a short intro to
specialization
- turns out the derive macro was broken, fixed that too
This was referenced Jul 5, 2025
@ecoskey ecoskey mentioned this pull request Jul 14, 2025
@ecoskey ecoskey deleted the new_specialize branch July 31, 2025 04:47
@cart cart moved this from Respond (With Priority) to Responded in @cart's attention Aug 27, 2025
@cart cart removed this from @cart's attention Aug 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Macros Code that generates Rust code D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants