-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
[Merged by Bors] - Frustum Culling (for Sprites) #1492
Conversation
b539af9
to
e588275
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have a benchmark to spot regressions and toss in the patch notes, but I think this PR is good to go and would be a really nice easy win for 0.5.
crates/bevy_sprite/src/lib.rs
Outdated
@@ -48,6 +49,14 @@ impl Plugin for SpritePlugin { | |||
.add_asset::<TextureAtlas>() | |||
.register_type::<Sprite>() | |||
.add_system_to_stage(CoreStage::PostUpdate, sprite_system.system()) | |||
.add_system_to_stage( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are probably going to need an ordering dependency on hierarchy propagation systems, but maybe that's better left out of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they also need to be ordered with the visible_entities system in camera I think.
IIRC they are both called on Update and that might lead to sprites popping up on the camera's borders a frame later. It's barely noticable but it is noticable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately ordering won't be enough because the component insert commands won't be applied until after the PostUpdate stage finishes. There are a number of systems in PostUpdate that filter on that component's existence.
I think we should add the relevant dependencies to be as correct as possible (and to ensure transform updates are applied), but we'll still have a one frame lag to cull / uncull sprites unless we either:
- Ensure all systems that rely on the frustum component live in a stage after PostUpdate. This isn't ideal.
- Do frustum culling later, remove
With<WithinFrustum>
and accept that we'll do unnecessary work for those systems (ex: we only filter out unnecessary draw calls, not the cpu work like shader management). This also isn't ideal. - Move all of the steps out of the ECS (something like the "pipelined rendering" we've been discussing lately). This is a lot of work.
I'm hesitant to do (1) as it feels short sighted. Given how much cpu work is a bottleneck right now (2) doesn't feel like its "worth it".
So imo our only real options are "don't include frustum culling in 0.5 and wait for pipelined rendering" or "accept one frame of cull lag"
This will partially resolve #1333. |
On paper I'm sold, although short lived marker components shouldn't be allowed until after we add support for SparseSet component storage (which will happen before 0.5. the draft pr should be out in the next couple of days). I'll give this a full review after I wrap that up. The only change required to adapt to that will be flagging the |
7f2f932
to
d4e6c7c
Compare
^I broke something when fixing clippy things. Fixed that and squashed commits. |
Where would I put such a bench? Into |
3d10288
to
f0c63d6
Compare
#1503 shall serve as bench / example project for this working. |
With |
That's a great question. I also asked in discord about manually managing visibility (but in a post-spawning scenario). https://discord.com/channels/691052431525675048/743663924229963868/813505768069398529 It sounds like Byteron's going to be looking into using a separate |
Sprite rotation is also currently unaccounted for. |
f0c63d6
to
9112f48
Compare
Helloooo.
This is still uncounted for. I need to figure out the math first in order to do this. |
Could we use something like |
As I understand the Frustum is the visible part. So it makes sense to me that entities with Frustum are visible, while entities without Frustum are not. |
A frustum is a shape, it doesn't really make much sense when we look at other component names:
The first two make it clear what functionality the components add. Hence the suggestion for |
2d8692c
to
549b5f6
Compare
I updated the example locally to non-uniformly scale the sprites let scale = Vec3::new(0.2 + rng.gen::<f32>() * 4.8, 1.0, 1.0); And I am seeing some popping-in and popping-out around the edges that I didn't notice before making that change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having thought about this a bit, I don't think we should use marker components here:
- This won't work for multi-camera situations (ex: render to texture, split screen, editors). Each camera needs its own visibility set, but the marker component effectively merges those sets together.
- As much as possible I'd like Bevy to treat components as "user defined state", which it derives internal state from. That feels "cleaner" to me. I'm not sure we'll always be able to do it, but so far we've been able to.
Instead I think it makes sense for each camera to store a list of entities that are within the frustum (or for the renderer to populate that on the fly). However this makes the "query filter optimizations" you added to the various system (ex: shader def and draw system) harder to do (which would in turn make the wins here smaller).
I think all of those issues go away if we adopt the "pipelined rendering" model we've been discussing in various places, where we extract world state each frame and construct draw state on the fly. That would lend itself nicely to "dynamic per camera visibilty lists"
Buuut that work hasn't even started yet and won't land until well after 0.5. The implementation in this pr (while limited) works for the common case and gives us a nice win. And it doesn't outright break multiple cameras/views (it just makes them draw stuff they don't see).
I think if we can sort out a good solution to the broken 3d (and custom) rendering, we should merge this.
ffcaef3
to
5394f13
Compare
This allows rendering to work as expected without hacks
Just pushed the changes mentioned here: #1492 (comment) This "inverts" the queries so that they search for |
I think there might be a bug with insertion commands, which @Byteron may have already encountered given that they included checks that shouldn't be necessary, which i tried removing and it resulted in a bundle insertion panic. Re-adding the check resolved the panic, but this feels like a nasty bevy_ecs bug that needs solving before 0.5. Investigating now. |
Found the issue. It was a bevy_ecs bug introduced in the "reliable change detection" pr. Simple fix. |
crates/bevy_render/src/draw.rs
Outdated
/// Viewable is used for frustum culling. | ||
/// Any Sprite or AtlasTextureSprite will have this removed if they are outside the camera frustum and thus not be rendered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment needs updating with the recent name change (and inversion of logic).
visible_query: Query<(Entity, &Visible, Option<&RenderLayers>)>, | ||
visible_transform_query: Query<&GlobalTransform, With<Visible>>, | ||
visible_query: Query<(Entity, &Visible, Option<&RenderLayers>), Without<OutsideFrustum>>, | ||
visible_transform_query: Query<&GlobalTransform, Without<OutsideFrustum>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double negatives 🤯
(nothing we can do here, but always causes me to pause for a moment when reading logic like this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I like to avoid double negatives in apis whenever possible. But yeah the tradeoff here for the inverse is too much :)
An issue with using |
We'll also want to switch to only culling using the "active camera" to support multiple cameras of the same "type" (ex: switching back and forth between two "2d" cameras from different perspectives). |
(working on these changes now) |
#1389 is quite mature FWIW. I'd prefer to see a real integration here, but that can wait. |
Integration with multiple views would involve a complete rewrite / moving away from marker components (as discussed above). That definitely needs to wait. |
bors r+ |
This PR adds two systems to the sprite module that culls Sprites and AtlasSprites that are not within the camera's view. This is achieved by removing / adding a new `Viewable` Component dynamically. Some of the render queries now use a `With<Viewable>` filter to only process the sprites that are actually on screen, which improves performance drastically for scene swith a large amount of sprites off-screen. https://streamable.com/vvzh2u This scene shows a map with a 320x320 tiles, with a grid size of 64p. This is exactly 102400 Sprites in the entire scene. Without this PR, this scene runs with 1 to 4 FPS. With this PR.. .. at 720p, there are around 600 visible sprites and runs at ~215 FPS .. at 1440p there are around 2000 visible sprites and runs at ~135 FPS The Systems this PR adds take around 1.2ms (with 100K+ sprites in the scene) Note: This is only implemented for Sprites and AtlasTextureSprites. There is no culling for 3D in this PR. Co-authored-by: Carter Anderson <[email protected]>
Pull request successfully merged into main. Build succeeded: |
This PR adds two systems to the sprite module that culls Sprites and AtlasSprites that are not within the camera's view.
This is achieved by removing / adding a new
Viewable
Component dynamically.Some of the render queries now use a
With<Viewable>
filter to only process the sprites that are actually on screen, which improves performance drastically for scene swith a large amount of sprites off-screen.https://streamable.com/vvzh2u
This scene shows a map with a 320x320 tiles, with a grid size of 64p.
This is exactly 102400 Sprites in the entire scene.
Without this PR, this scene runs with 1 to 4 FPS.
With this PR..
.. at 720p, there are around 600 visible sprites and runs at ~215 FPS
.. at 1440p there are around 2000 visible sprites and runs at ~135 FPS
The Systems this PR adds take around 1.2ms (with 100K+ sprites in the scene)
Note:
This is only implemented for Sprites and AtlasTextureSprites.
There is no culling for 3D in this PR.