Cache material shader handles per type (#23887)
# Objective - Optimize the performance of material preparation in scenes with many material instances (e.g., the [many_materials stress test](https://github.com/bevyengine/bevy/blob/main/examples/stress_tests/many_materials.rs)). - Currently, `ErasedRenderAsset::prepare_asset` resolves `ShaderRef::Path` and interns shader labels per material instance every time they are prepared. ## Solution - Introduced `MaterialShaders<M>` resource. This resource caches the resolved `Handle<Shader>`s and pre-interned `InternedShaderLabels` for a given material type. It is eagerly initialized in `MaterialPlugin::build()`, ensuring all path resolutions and label interning happen exactly once per type at startup. - Removed `AssetServer` from `prepare_asset`, because shader paths are now fully resolved in the `MaterialShaders` cache. - Updated SmallVec capacities in `MaterialProperties` for shaders from 3 to 6 (StandardMaterial requires 6 shaders: Vertex/Fragment for Main, Prepass, and Deferred passes. The old size of 3 caused a heap allocation on every material preparation). ## Testing - Tested using the [many_materials](https://github.com/bevyengine/bevy/blob/main/examples/stress_tests/many_materials.rs) example. - Profiled using Tracy before and after the fix. - Used macOS Instruments before and after the fix. --- ## Showcase This PR improves CPU performance in scenes with many materials by eliminating redundant string parsing, asset loading, and heap allocations. After fixing the issue, my FPS increased from 278 to 302 in [many_materials](https://github.com/bevyengine/bevy/blob/main/examples/stress_tests/many_materials.rs) (using other laptop with ryzen 5800H and iGPU). ### MacOs profiling I found a hotspot in `ErasedRenderAsset::prepare_asset` where, for every single material instance, the engine was repeatedly doing expensive string joins and path manipulations to resolve `ShaderRef::Path` into actual shader handles. Specifically, `_embedded_asset_path` was calling `Path::_join`, `Path::_strip_prefix`, and `Path::_ends_with`, alongside redundant `LoadBuilder::load_typed_internal` calls and `Interner::intern` operations. <img width="1538" height="697" alt="Screenshot 2026-04-19 at 23 22 09" src="https://github.com/user-attachments/assets/ac4e2540-1d45-4856-bf96-b00dfd8f01e1" /> ### Tracy Profiler <img width="2016" height="1377" alt="Screenshot_20260419_231345" src="https://github.com/user-attachments/assets/dd59b4ef-90f5-45a5-a263-f47c190678a4" /> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
R
Rostyslav Toch committed
398f824f389c0a7eb05ac32c7cf9704dba339fb8
Parent: dbfa384
Committed by GitHub <noreply@github.com>
on 5/22/2026, 11:03:35 AM