Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Pipeline Specialization, Shader Assets, and Shader Preprocessing #3031

Closed

Conversation

cart
Copy link
Member

@cart cart commented Oct 26, 2021

New Features

This adds the following to the new renderer:

  • Shader Assets
    • Shaders are assets again! Users no longer need to call include_str! for their shaders
    • Shader hot-reloading
  • Shader Defs / Shader Preprocessing
    • Shaders now support # ifdef NAME, # ifndef NAME, and # endif preprocessor directives
  • Bevy RenderPipelineDescriptor and RenderPipelineCache
    • Bevy now provides its own RenderPipelineDescriptor and the wgpu version is now exported as RawRenderPipelineDescriptor. This allows users to define pipelines with Handle<Shader> instead of needing to manually compile and reference ShaderModules, enables passing in shader defs to configure the shader preprocessor, makes hot reloading possible (because the descriptor can be owned and used to create new pipelines when a shader changes), and opens the doors to pipeline specialization.
    • The RenderPipelineCache now handles compiling and re-compiling Bevy RenderPipelineDescriptors. It has internal PipelineLayout and ShaderModule caches. Users receive a CachedPipelineId, which can be used to look up the actual &RenderPipeline during rendering.
  • Pipeline Specialization
    • This enables defining per-entity-configurable pipelines that specialize on arbitrary custom keys. In practice this will involve specializing based on things like MSAA values, Shader Defs, Bind Group existence, and Vertex Layouts.
    • Adds a SpecializedPipeline trait and SpecializedPipelines<MyPipeline> resource. This is a simple layer that generates Bevy RenderPipelineDescriptors based on a custom key defined for the pipeline.
    • Specialized pipelines are also hot-reloadable.
    • This was the result of experimentation with two different approaches:
      1. "generic immediate mode multi-key hash pipeline specialization"
      • breaks up the pipeline into multiple "identities" (the core pipeline definition, shader defs, mesh layout, bind group layout). each of these identities has its own key. looking up / compiling a specific version of a pipeline requires composing all of these keys together
      • the benefit of this approach is that it works for all pipelines / the pipeline is fully identified by the keys. the multiple keys allow pre-hashing parts of the pipeline identity where possible (ex: pre compute the mesh identity for all meshes)
      • the downside is that any per-entity data that informs the values of these keys could require expensive re-hashes. computing each key for each sprite tanked bevymark performance (sprites don't actually need this level of specialization yet ... but things like pbr and future sprite scenarios might).
      • this is the approach rafx used last time i checked
      1. "custom key specialization"
      • Pipelines by default are not specialized
      • Pipelines that need specialization implement a SpecializedPipeline trait with a custom key associated type
      • This allows specialization keys to encode exactly the amount of information required (instead of needing to be a combined hash of the entire pipeline). Generally this should fit in a small number of bytes. Per-entity specialization barely registers anymore on things like bevymark. It also makes things like "shader defs" way cheaper to hash because we can use context specific bitflags instead of strings.
      • Despite the extra trait, it actually generally makes pipeline definitions + lookups simpler: managing multiple keys (and making the appropriate calls to manage these keys) was way more complicated.
    • I opted for custom key specialization. It performs better generally and in my opinion is better UX. Fortunately the way this is implemented also allows for custom caches as this all builds on a common abstraction: the RenderPipelineCache. The built in custom key trait is just a simple / pre-defined way to interact with the cache

Callouts

  • The SpecializedPipeline trait makes it easy to inherit pipeline configuration in custom pipelines. The changes to custom_shader_pipelined and the new shader_defs_pipelined example illustrate how much simpler it is to define custom pipelines based on the PbrPipeline.
  • The shader preprocessor is currently pretty naive (it just uses regexes to process each line). Ultimately we might want to build a more custom parser for more performance + better error handling, but for now I'm happy to optimize for "easy to implement and understand".

Next Steps

  • Port compute pipelines to the new system
  • Add more preprocessor directives (else, elif, import)
  • More flexible vertex attribute specialization / enable cheaply specializing on specific mesh vertex layouts

@cart cart added the A-Rendering Drawing game state to the screen label Oct 26, 2021
@cart cart added this to the Bevy 0.6 milestone Oct 26, 2021
@alice-i-cecile alice-i-cecile added the C-Feature A new feature, making something new possible label Oct 26, 2021
@superdump
Copy link
Contributor

superdump commented Oct 26, 2021

Awesome! I’ve already used this in my MSAA PR and it makes the pipeline management way simpler. I’m going to update my normal maps branch next and I already see the simplifications. It feels simple, explicit, and flexible to me, and results in not much code to write either. Like!

@Weibye
Copy link
Contributor

Weibye commented Oct 26, 2021

shader_defs_pipelined-example should be added to examples/README.md :)

if key.0 {
shader_defs.push("IS_RED".to_string());
}
let mut descriptor = self.pbr_pipeline.specialize(PbrPipelineKey::empty());
Copy link
Contributor

@jakobhellermann jakobhellermann Oct 27, 2021

Choose a reason for hiding this comment

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

Suggested change
let mut descriptor = self.pbr_pipeline.specialize(PbrPipelineKey::empty());
let mut descriptor = self.pbr_pipeline.specialize(PbrPipelineKey::empty());
descriptor.label = Some("shader_defs_pipeline".into());

without the label override, it would use pbr_pipeline.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree with you in theory, but in practice I think pipeline labeling is a niche feature that we shouldn't put at the forefront in examples like this one. The majority of users shouldn't need to care about labeling their pipelines. Given that this "inherits" from pbr_pipeline, I think i'm comfortable not labeling here. But we should definitely provide proper labels for internal pipelines.


let pbr_pipeline = world.get_resource::<PbrPipeline>().unwrap();
let mut descriptor = pbr_pipeline.specialize(PbrPipelineKey::empty());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let mut descriptor = pbr_pipeline.specialize(PbrPipelineKey::empty());
let mut descriptor = pbr_pipeline.specialize(PbrPipelineKey::empty());
descriptor.label = Some("custom_pipeline".into());

Copy link
Contributor

@jakobhellermann jakobhellermann left a comment

Choose a reason for hiding this comment

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

Excited to see this landing 🙂 I have some more examples like a minimal shader example and one for instancing that I'll open a PR for when this is merged.

@@ -192,6 +200,41 @@ impl PhaseItem for Transparent3d {
}
}

pub struct SetItemPipeline;
Copy link
Contributor

Choose a reason for hiding this comment

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

How about making this generic over PhaseItems, and add a fn pipeline(&self) -> CachePipelinedId to that trait?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about that, but I'm not sure I want to dictate that all PhaseItems include a CachedPipelineId. Its true that all of the current PhaseItems do that, but I imagine there might be a Phase where PhaseItems don't use the CachedPipeline abstraction (ex: they use a direct manually constructed RenderPipeline instead).

I do have a plan for another PR that will make RenderCommands a more opinionated abstraction: make them reusable across phases by using entities instead of generic PhaseItems. We could consider tacking on the CachedPipelineId assumption to that.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is definitely worth considering, but I'd like to get the merge train moving first, then revisit this when we rework RenderCommands.

@cart
Copy link
Member Author

cart commented Oct 28, 2021

bors r+

bors bot pushed a commit that referenced this pull request Oct 28, 2021
## New Features
This adds the following to the new renderer:

* **Shader Assets**
  * Shaders are assets again! Users no longer need to call `include_str!` for their shaders
  * Shader hot-reloading
* **Shader Defs / Shader Preprocessing**
  * Shaders now support `# ifdef NAME`, `# ifndef NAME`, and `# endif` preprocessor directives
* **Bevy RenderPipelineDescriptor and RenderPipelineCache**
  * Bevy now provides its own `RenderPipelineDescriptor` and the wgpu version is now exported as `RawRenderPipelineDescriptor`. This allows users to define pipelines with `Handle<Shader>` instead of needing to manually compile and reference `ShaderModules`, enables passing in shader defs to configure the shader preprocessor, makes hot reloading possible (because the descriptor can be owned and used to create new pipelines when a shader changes), and opens the doors to pipeline specialization.
  * The `RenderPipelineCache` now handles compiling and re-compiling Bevy RenderPipelineDescriptors. It has internal PipelineLayout and ShaderModule caches. Users receive a `CachedPipelineId`, which can be used to look up the actual `&RenderPipeline` during rendering. 
* **Pipeline Specialization**
  * This enables defining per-entity-configurable pipelines that specialize on arbitrary custom keys. In practice this will involve specializing based on things like MSAA values, Shader Defs, Bind Group existence, and Vertex Layouts.
  * Adds a `SpecializedPipeline` trait and `SpecializedPipelines<MyPipeline>` resource. This is a simple layer that generates Bevy RenderPipelineDescriptors based on a custom key defined for the pipeline.
  * Specialized pipelines are also hot-reloadable.
  * This was the result of experimentation with two different approaches:
    1. **"generic immediate mode multi-key hash pipeline specialization"**
      * breaks up the pipeline into multiple "identities" (the core pipeline definition, shader defs, mesh layout, bind group layout). each of these identities has its own key. looking up / compiling a specific version of a pipeline requires composing all of these keys together
      * the benefit of this approach is that it works for all pipelines / the pipeline is fully identified by the keys. the multiple keys allow pre-hashing parts of the pipeline identity where possible (ex: pre compute the mesh identity for all meshes)
      * the downside is that any per-entity data that informs the values of these keys could require expensive re-hashes. computing each key for each sprite tanked bevymark performance (sprites don't actually need this level of specialization yet ... but things like pbr and future sprite scenarios might). 
      * this is the approach rafx used last time i checked
    2. **"custom key specialization"**
      * Pipelines by default are not specialized
      * Pipelines that need specialization implement a SpecializedPipeline trait with a custom key associated type
      * This allows specialization keys to encode exactly the amount of information required (instead of needing to be a combined hash of the entire pipeline). Generally this should fit in a small number of bytes. Per-entity specialization barely registers anymore on things like bevymark. It also makes things like "shader defs" way cheaper to hash because we can use context specific bitflags instead of strings.
      * Despite the extra trait, it actually generally makes pipeline definitions + lookups simpler: managing multiple keys (and making the appropriate calls to manage these keys) was way more complicated.
  * I opted for custom key specialization. It performs better generally and in my opinion is better UX. Fortunately the way this is implemented also allows for custom caches as this all builds on a common abstraction: the RenderPipelineCache. The built in custom key trait is just a simple / pre-defined way to interact with the cache 

## Callouts

* The SpecializedPipeline trait makes it easy to inherit pipeline configuration in custom pipelines. The changes to `custom_shader_pipelined` and the new `shader_defs_pipelined` example illustrate how much simpler it is to define custom pipelines based on the PbrPipeline.
* The shader preprocessor is currently pretty naive (it just uses regexes to process each line). Ultimately we might want to build a more custom parser for more performance + better error handling, but for now I'm happy to optimize for "easy to implement and understand". 

## Next Steps

* Port compute pipelines to the new system
* Add more preprocessor directives (else, elif, import)
* More flexible vertex attribute specialization / enable cheaply specializing on specific mesh vertex layouts
@bors
Copy link
Contributor

bors bot commented Oct 28, 2021

@bors bors bot changed the title Pipeline Specialization, Shader Assets, and Shader Preprocessing [Merged by Bors] - Pipeline Specialization, Shader Assets, and Shader Preprocessing Oct 28, 2021
@bors bors bot closed this Oct 28, 2021
bors bot pushed a commit that referenced this pull request Oct 29, 2021
Adds support for MSAA to the new renderer. This is done using the new [pipeline specialization](#3031) support to specialize on sample count. This is an alternative implementation to #2541 that cuts out the need for complicated render graph edge management by moving the relevant target information into View entities. This reuses @superdump's clever MSAA bitflag range code from #2541.

Note that wgpu currently only supports 1 or 4 samples due to those being the values supported by WebGPU. However they do plan on exposing ways to [enable/query for natively supported sample counts](gfx-rs/wgpu#1832). When this happens we should integrate
bors bot pushed a commit that referenced this pull request Dec 25, 2021
This adds "high level" `Material` and `SpecializedMaterial` traits, which can be used with a `MaterialPlugin<T: SpecializedMaterial>`. `MaterialPlugin` automatically registers the appropriate resources, draw functions, and queue systems. The `Material` trait is simpler, and should cover most use cases. `SpecializedMaterial` is like `Material`, but it also requires defining a "specialization key" (see #3031). `Material` has a trivial blanket impl of `SpecializedMaterial`, which allows us to use the same types + functions for both.

This makes defining custom 3d materials much simpler (see the `shader_material` example diff) and ensures consistent behavior across all 3d materials (both built in and custom). I ported the built in `StandardMaterial` to `MaterialPlugin`. There is also a new `MaterialMeshBundle<T: SpecializedMaterial>`, which `PbrBundle` aliases to.
bors bot pushed a commit that referenced this pull request Jan 5, 2022
based on #3031 

Adds some examples showing of how to use the new pipelined rendering for custom shaders.

- a minimal shader example which doesn't use render assets
- the same but using glsl
- an example showing how to render instanced data
- a shader which uses the seconds since startup to animate some textures


Instancing shader:
![grafik](https://user-images.githubusercontent.com/22177966/139299294-e176b62a-53d1-4287-9a66-02fb55affc02.png)
Animated shader:
![animate_shader](https://user-images.githubusercontent.com/22177966/139299718-2940c0f3-8480-4ee0-98d7-b6ba40dc1472.gif)
(the gif makes it look a bit ugly)

Co-authored-by: Carter Anderson <[email protected]>
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-Feature A new feature, making something new possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants