SIGN IN SIGN UP
bevyengine / bevy UNCLAIMED

A refreshingly simple data-driven game engine built in Rust

0 0 59 Rust

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