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

Move sprite batches to resource #17636

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

tychedelia
Copy link
Contributor

Objective

Currently, prepare_sprite_image_bind_group spawns sprite batches onto an individual representative entity of the batch. This poses significant problems for multi-camera setups, since an entity may appear in multiple phase instances.

Solution

Instead, move batches into a resource that is keyed off the view and the representative entity. Long term we should switch to mesh2d and use the existing BinnedRenderPhase functionality rather than naively queueing into transparent and doing our own ad-hoc batching logic.

Fixes #16867, #17351

Testing

Tested repros in above issues.

Currently, `prepare_sprite_image_bind_group` spawns sprite batches onto
an individual representative entity of the batch. This poses significant
problems for multi-camera setups, since an entity may appear in multiple
phase instances.

Instead, move batches into a resource that is keyed off the view and the
representative entity. Long term we should switch to mesh2d and use the
existing BinnedRenderPhase functionality rather than naively queueing
into transparent and doing our own ad-hoc batching logic.

Fixes bevyengine#16867, bevyengine#17351
@tychedelia tychedelia requested a review from ickshonpe February 1, 2025 20:37
@tychedelia tychedelia added C-Bug An unexpected or incorrect behavior A-Rendering Drawing game state to the screen S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Feb 1, 2025
@tychedelia tychedelia requested a review from IceSentry February 1, 2025 20:38
@tychedelia tychedelia added this to the 0.15.2 milestone Feb 1, 2025
@tychedelia
Copy link
Contributor Author

I guess this is technically a breaking change but can probably be safely picked into 0.15.2. Your call @alice-i-cecile.

@hukasu
Copy link
Contributor

hukasu commented Feb 1, 2025

Ran codes provided by each of the linked issues and they seem to work as intended now

Copy link
Contributor

@hukasu hukasu left a comment

Choose a reason for hiding this comment

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

While i was looking for info on the issues, i saw line 565

let index = extracted_sprite.original_entity.unwrap_or(*entity).index();

should the value inside the unwrap_or be main_entity.id()?

@tychedelia
Copy link
Contributor Author

While i was looking for info on the issues, i saw line 565

let index = extracted_sprite.original_entity.unwrap_or(*entity).index();

should the value inside the unwrap_or be main_entity.id()?

Yes, the original_entity field in ExtractedSprite should likely be MainEntity, but that's unrelated to the issue here and mostly a correctness issue (it's easy to accidentally use a RenderEntity for something that's keyed off of MainEntity, e.g.).

@@ -650,7 +653,8 @@ pub fn prepare_sprite_image_bind_groups(

let image_bind_groups = &mut *image_bind_groups;

for transparent_phase in phases.values_mut() {
for (retained_view, transparent_phase) in phases.iter_mut() {
let mut batch_key = None;
Copy link
Contributor

Choose a reason for hiding this comment

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

I dislike how the way the flow of the method requires that this is initialized with None, i would personally collect all distinct phase items into something like a HashMap<AssetId<Image>, Vec<Entity>>

Copy link
Contributor

Choose a reason for hiding this comment

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

Without having reminded myself of how this code works, not having thought enough about what you mean, using HashMap to hold collections of entities with not-highly-optimised keys seems likely to introduce significant performance regressions.

@superdump
Copy link
Contributor

Do you have any Tracy traces to show the performance of bevymark using the sprite mode with say 100-150k sprites?

@@ -483,7 +484,10 @@ pub struct SpriteViewBindGroup {
pub value: BindGroup,
}

#[derive(Component, PartialEq, Eq, Clone)]
#[derive(Resource, Deref, DerefMut, Default)]
pub struct SpriteBatches(HashMap<(RetainedViewEntity, MainEntity), SpriteBatch>);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have an optimised hash of a pair of entities yet? If not then perhaps we need one? In the degenerate case with one batch per sprite I suppose this would hurt. But, sprites should use sprite sheets so that should not happen for large numbers of sprites in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think using EntityHash should work no matter how many entities you use in the key. I'll include that instead of the default hasher and confirm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This didn't seem to matter in my testing.

@tychedelia
Copy link
Contributor Author

tychedelia commented Feb 1, 2025

This appears to be solidly faster, which is somewhat surprising to me but maybe the cost of batch spawning is a lot greater than even all the hashing?

image

cargo run --example bevymark --release --features bevy/trace_tracy -- --waves 100 --per-wave 1000

@@ -690,8 +694,7 @@ pub fn prepare_sprite_image_bind_groups(
});

batch_item_index = item_index;
batches.push((
item.entity(),
current_batch = Some(batches.entry((*retained_view, item.main_entity())).insert(
Copy link
Contributor

@ickshonpe ickshonpe Feb 1, 2025

Choose a reason for hiding this comment

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

Do we need to use the whole RetainedViewEntity in the key, it seems like just using retained_view.main_entity would be enough to avoid collisions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it probably would be fine, especially because this is not yet retained and we are rebuilding this every frame, but we're moving towards using RetainedViewEntity everywhere for safety reasons. In my brief benching, using just the MainEntity didn't seem to have a notable performance impact.

Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

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

Looks good. The batches aren't overwritten any more, the example from #17351 works correctly now.

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Feb 2, 2025
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-Bug An unexpected or incorrect behavior S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

the render layers: they are broken
5 participants