feat: enable custom shader compilers#23204
feat: enable custom shader compilers#23204videobitva wants to merge 25 commits intobevyengine:mainfrom
Conversation
|
You added a new feature but didn't update the readme. Please run |
| [dependencies] | ||
| bevy_internal = { path = "crates/bevy_internal", version = "0.19.0-dev", default-features = false } | ||
| tracing = { version = "0.1", default-features = false, optional = true } | ||
| shaderc = { version = "0.10", optional = true } |
There was a problem hiding this comment.
this shouldn't be added as a top level dependency
| shader_format_wesl = ["bevy_internal/shader_format_wesl"] | ||
|
|
||
| # Enable the shaderc compiler (used by the shader_material_hlsl example) | ||
| shader_compiler_shaderc = ["dep:shaderc"] |
There was a problem hiding this comment.
this isn't a feature of Bevy
yeah, but otherwise i don't know how to add this dependency as an optional, so that it is to be compiled only for this specific example...
There was a problem hiding this comment.
hmm, can probably move the shaderc dep to dev-dependecies with bring-your-own shaderc lib feature flag. this way it won't be much of a burden for running other examples, though for the one I added the user will need to have shaderc installed on their system
use shaderc-dyn (lightweight interface dyn-loading shaderc) insetad of heavy shaderc (which compiles/links to shaderc c++ lib). move the shaderc/shaderc-dyn dependency from bevy-level to dev-dependencies.
| serde_json = "1.0.140" | ||
| bytemuck = "1" | ||
| # Needed for shader_material_hlsl example | ||
| shaderc-dyn = "0.10.1" |
There was a problem hiding this comment.
I would prefer to not compile this by default for all examples. Its a small dep, and libloading does insulate us from non-existence of the library. But it is a dependency managed by a single person who is a new contributor to the bevy project, and for a number of reasons (trust, attack surface area, build breakage, increased dependency counts, etc) I think making this a dependency that every bevy developer consumes would be a mistake.
Additionally, including the HLSL shader example alongside our others implies a degree of support that we are not ready for.
I'd prefer it if this was reframed as a "custom shader compiler" example with a stub implementation, and a link to a repo that contains the full HLSL impl with this dependency.
There was a problem hiding this comment.
i've though a bit before about just pointing to a crate with example impl, but did not know whether this is fine for you or not. from your response it seems that this is ok, so will do that 👍
1. merge import resolver and shader compilers trait into one, as it made no sense in the end to keep them separated 2. remove intermediate types representing shader state 3. consistent feature flags for different shading langs 4. a bit better public interface for registering compilers
| let render_app = app.sub_app_mut(bevy::render::RenderApp); | ||
| let mut pipeline_cache = render_app.world_mut().resource_mut::<PipelineCache>(); | ||
|
|
||
| pipeline_cache | ||
| .register_shader_compiler(ShaderLanguage::Custom("custom"), CustomShaderCompiler); |
There was a problem hiding this comment.
i dont really like how the compiler registration looks rn, would be nice for it to be done the same way the AssetLoader does it, i.e. just a method on App. but need to look into how to do that
| Utf8(#[from] std::string::FromUtf8Error), | ||
| } | ||
|
|
||
| impl AssetLoader for CustomShaderLoader { |
There was a problem hiding this comment.
for any non-bevy-native shader lang you'll need to impl an asset loader. hypothetically this can be bypassed by integrating the info about a shader into the ShaderCompiler trait and next somehow pass it through to the already existing ShaderLoader impl
| compilers.insert(ShaderLanguage::Wgsl, naga_oil.clone()); | ||
| #[cfg(feature = "shader_format_glsl")] | ||
| compilers.insert(ShaderLanguage::Glsl, naga_oil.clone()); | ||
| #[cfg(feature = "shader_format_wesl")] | ||
| compilers.insert( | ||
| ShaderLanguage::Wesl, | ||
| Arc::new(Mutex::new(WeslCompiler::new())), | ||
| ); | ||
| #[cfg(feature = "shader_format_spirv")] | ||
| compilers.insert( | ||
| ShaderLanguage::SpirV, | ||
| Arc::new(Mutex::new(SpirVPassthroughCompiler)), | ||
| ); |
There was a problem hiding this comment.
thinking about a possibility to move the default compilers registration out from the ShaderCache::new to a some more user-friendly place, so that adding new compilers/overriding existing could be more sound
(guess this is the same issue as in #23204 (comment))
8560cde to
7194e00
Compare
|
ok, i kinda found a way to tackle the issues i've mentioned in the comments above, but it will take some time to refine the impl. so to not waste maintainers time and not distract them from other pull requests i'll mark this one as draft for now |
|
note to myself: should probably ask what is the plan for wgsl to wesl migration, as with every change in this branch the bevy_shader itself and its interactions with bevy_render get refactored further and further |
|
Is there a way to add more information to materials or customise material compilation behaviour beyond just the path to the shader? For my experimental mew compiler for example, for generics you need to be able to specify the entry point to compile the shader from mew to wigs. For example: In a way it's part of specialisation, except the function exposed in the material for that is way too late in the pipeline for that to work correctly. |
Objective
Pluggable shader compiler & import resolver
Fixes #8342
Solution
TLDR:
how it works on main
--- title: "shader compilation pipeline" --- flowchart TD classDef asset fill:#fff2cc,stroke:#d6b656,color:#000 classDef bevy fill:#dae8fc,stroke:#6c8ebf,color:#000 classDef naga fill:#d5e8d4,stroke:#82b366,color:#000 classDef decision fill:#f8cecc,stroke:#b85450,color:#000 classDef lang fill:#e1d5e7,stroke:#9673a6,color:#000 A["Shader Asset<br>(.wgsl / .glsl / .spv / .wesl)"]:::asset B["ShaderLoader"]:::bevy C["ShaderCache.set_shader()"]:::bevy D["<b>ShaderCache.get(id, shader_defs)</b>"]:::bevy E{"match shader.source"}:::decision A --> B --> C --> D --> E %% SpirV branch F["Source::SpirV<br><i>pass-through</i>"]:::lang E -- "SpirV" --> F %% WGSL / GLSL branch subgraph hardcoded ["Hardcoded in ShaderCache"] G["Source::Wgsl / Glsl"]:::lang H["naga_oil::Composer<br>add_composable_module()<br>make_naga_module()"]:::naga I["naga::Module"]:::naga G --> H --> I end E -- "Wgsl / Glsl" --> G %% WESL branch J["Source::Wesl<br>wesl::compile()<br><i>inline in ShaderCache</i>"]:::lang E -- "Wesl" --> J %% decoupled_naga decision K{"decoupled_naga?"}:::decision I --> K L["WGSL text<br>naga::back::wgsl"]:::naga M["Naga IR<br><i>direct</i>"]:::naga K -- "yes" --> L K -- "no" --> M %% Converge to ShaderCacheSource N["<b>ShaderCacheSource</b><br>SpirV | Wgsl | Naga"]:::asset F --> N L --> N M --> N J --> N %% GPU path O["load_module()"]:::bevy P["wgpu::create_shader_module()"]:::naga Q["PipelineCache.process_queue()"]:::bevy R["device.create_render_pipeline()<br>"]:::naga N --> O --> P --> Q --> Rhow it works in this pr
--- title: "shader compilation pipeline" --- flowchart TD classDef asset fill:#fff2cc,stroke:#d6b656,color:#000 classDef bevy fill:#dae8fc,stroke:#6c8ebf,color:#000 classDef naga fill:#d5e8d4,stroke:#82b366,color:#000 classDef dispatch fill:#f8cecc,stroke:#b85450,color:#000 classDef plugin fill:#e1d5e7,stroke:#9673a6,color:#000,stroke-dasharray:5 5 classDef trait fill:#f5f5f5,stroke:#0050ef,color:#000,stroke-width:2px A["Shader Asset<br>(.wgsl / .glsl / .spv / .wesl / .custom)"]:::asset B["ShaderLoader"]:::bevy C["ShaderCache.set_shader()"]:::bevy D["<b>ShaderCache.get(id, shader_defs)</b>"]:::bevy E["Lookup compiler by ShaderLanguage<br><i>HashMap<ShaderLanguage, Box<dyn ShaderCompiler>></i>"]:::dispatch A --> B --> C --> D --> E %% Compiler implementations subgraph trait_boundary ["trait ShaderCompiler"] direction TB subgraph iface [" "] direction LR T1["fn add_import"]:::trait T2["fn remove_import"]:::trait T3["fn compile -> Result<CompiledShader>"]:::trait end F["<b>NagaOilCompiler</b><br><i>Wgsl + Glsl</i><br>───────────────<br>owns naga_oil::Composer<br>add_import -> add_composable_module<br>compile -> make_naga_module()"]:::naga G["<b>WeslCompiler</b><br><i>Wesl</i><br>───────────────<br>stores sources by path<br>compile -> wesl::compile()<br>returns WGSL text"]:::naga H["<b>SpirVPassthrough Compiler</b><br><i>SpirV</i><br>───────────────<br>compile -> pass bytes through"]:::naga I["<b>Custom Compiler</b><br><i>Custom(&str)</i><br>───────────────<br>compile -> ???<br><i>user-defined logic</i>"]:::plugin end E --> F E --> G E --> H E --> I %% CompiledShader J["<b>CompiledShader</b><br>SpirV | Wgsl | Naga"]:::asset F --> J G --> J H --> J I --> J %% GPU path K["load_module()"]:::bevy L["wgpu::create_shader_module()"]:::naga M["PipelineCache.process_queue()"]:::bevy N["device.create_render_pipeline()<br>"]:::naga J --> K --> L --> M --> N %% Registration API note R["<b>Registration API</b><br>pipeline_cache.register_shader_compiler(<br> ShaderLanguage::Custom, MyCompiler<br>)"]:::asset style trait_boundary fill:none,stroke:#0050ef,stroke-width:2px,stroke-dasharray:5 5 style iface fill:none,stroke:noneIntroduces pluggable
ShaderCompilertrait inbevy_shader, registered perShaderLanguageviaPipelineCache::register_shader_compiler.The existing WGSL, GLSL, WESL, SPIR-V, and paths are refactored into default trait implementations (
NagaCompiler,WeslCompiler,SpirVPassthroughCompiler) with no behavioral changes.A new
ShaderLanguage::Customvariant andShader::from_customсonstructor allow plugins to define new shader languages.An end-to-end stub example demonstrates the full workflow.
Testing
.. was done on Windows 10 / Vulkan and macOS M4 / Metal.
run thethis one has been cut from the pr for reasons, a separate repo with this example is in progressshader_material_hlslexampleshader_material_wesl,shader_material_glsl, andshader_materialexamples to verify existing pathsShowcase
See the stub example in
examples/shader/custom_shader_compiler.rs. The pattern is:Click to view showcase
ShaderCompilerfor your language:PipelineCache:Material: