Skip to content

Use a slotmap for render assets storage#13013

Closed
superdump wants to merge 8 commits intobevyengine:mainfrom
superdump:slotmap-render-assets
Closed

Use a slotmap for render assets storage#13013
superdump wants to merge 8 commits intobevyengine:mainfrom
superdump:slotmap-render-assets

Conversation

@superdump
Copy link
Copy Markdown
Contributor

@superdump superdump commented Apr 18, 2024

Objective

  • Optimise material mesh entity rendering code paths
  • Prepare for 'draw stream' rendering which needs to pack render commands into a Vec<u32>

Solution

  • AssetId is quite large due to supporting a UUID variant and the type is used in very hot loops for batching and rendering. In rendering, we don't need the UUID. We can use a smaller id to refer to the prepared render asset.
  • Use a slotmap as the backing store for RenderAssets
  • Maintain a map from AssetId<A: RenderAsset> to slotmap key inside RenderAssets and use it to look up keys for known render assets at extraction time
  • Add a UpdatePendingRenderAssetKeyPlugin to help with updating asset keys on instance data for assets that were loaded during the current frame, to avoid a 1-frame lag.

Benchmarks

On an M1 Max, running cargo run --release --example bevymark -- --benchmark --waves 160 --per-wave 1000 --mode mesh2d. Yellow is main, red is this PR:

Screenshot 2024-04-18 at 12 20 21

A 6.3% reduction in median frame time.

Main:
Screenshot 2024-04-18 at 12 20 54

PR:
Screenshot 2024-04-18 at 12 21 19

Noteworthy spans:

92.16ms -> 86.00ms : main_pass_2d
19.20ms -> 17.72mz : batch_and_prepare_sorted_render_phase
 6.07ms ->  4.34ms : queue_material2d_meshes
 0.95ms ->  1.71ms : extract_material_meshes_2d

Changelog

  • Added: RenderAssetKey slotmap key type to be used with RenderAssets
  • Added: get_key, get_with_key, and get_with_key_mut methods to RenderAssets to use slotmap keys.
  • Added: UpdatePendingRenderAssetKeyPlugin to help with updating asset keys on instance data for assets that were loaded during the current frame, to avoid a 1-frame lag.
  • Changed: Use slotmap for internal storage for RenderAssets for improved performance due to the smaller asset reference data size of slotmap keys compared to AssetId

diffuse: item.diffuse_map.id(),
specular: item.specular_map.id(),
})
pub fn update_pending_environment_maps(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did want this to use the plugin, but I feel like it's an exception, and I don't want to make the trait for the helper plugin much more complicated just to cover this case. Not certain it's the right choice though.

}
}

pub fn extract_visible_material_instances<M: Material>(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe this simple extraction case can be pluginified.

@alice-i-cecile alice-i-cecile added A-Rendering Drawing game state to the screen C-Performance A change motivated by improving speed, memory usage or compile times M-Release-Note Work that should be called out in the blog due to impact labels Apr 18, 2024
Copy link
Copy Markdown
Contributor

@JMS55 JMS55 left a comment

Choose a reason for hiding this comment

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

Not the cleanest solution, but it's not awful either. I don't love that the extract time goes up so much, nor the extra complexity involved. I won't block on it, but we could use more docs around the logic in the various systems. There's several nested conditions which makes understanding the code at a glance difficult.

I think there's a couple of questions worth asking:

  • Can we remove AssetId::Uuid outright?
  • If we want to move RenderAsset preparation into the asset loader directly, mostly for images in the context of this PR, will it be possible now that there's an extra layer of indirection for render assets?

We definitely should run the screenshot diff tool on all the examples before merging this.


for material_id in render_material_instances.values() {
let Some(material) = render_materials.get(*material_id) else {
for &material_key in render_material_instances.values() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have this fixed in a future PR by changing it to .values().collect::<HashSet<_>>(), but this code is currently bugged and adds too many materials, as it does not deduplicate between multiple entities with the same material (AssetId).

What this code really wants to do is iterate over every instance of material M that exists. I don't think we have a good way of doing this, even with these changes, right?

) {
material_instances.clear();
for (entity, view_visibility, material) in &query {
if view_visibility.get() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This isn't a change from how it is now, but meshlets uses RenderMaterialInstances. But, ViewVisibility should never get computed for MeshletMeshes?? It only gets updated for WithMesh entities, and it defaults to false afaiu. What am I missing here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm, the meshlet example works, so there doesn't seem to be any problem. I'm not sure how it works, but this also didn't really change anything versus main, other than to allow use of RenderAssetKey.

@pcwalton
Copy link
Copy Markdown
Contributor

@cart Weren't you planning on removing AssetId::Uuid at some point?

@superdump superdump marked this pull request as draft April 21, 2024 22:38
@superdump
Copy link
Copy Markdown
Contributor Author

I'm moving this PR to a draft. After the push back about the regression in extraction time, I went down a rabbit hole of changing Handle and AssetId to not be enums, instead to be structs and require always having an AssetIndex member, and optionally a Uuid. See Discord for details, but the short version is it performs even better than this PR, and it feels like a better solution if we can stomach not having support for compile-time const Handle.

@superdump
Copy link
Copy Markdown
Contributor Author

Closing. After discussion with @cart we decided not to do this, but rather wait for the long-term solution.

@superdump superdump closed this Apr 27, 2024
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-Performance A change motivated by improving speed, memory usage or compile times M-Release-Note Work that should be called out in the blog due to impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants