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

Throttle render assets #12622

Merged
merged 16 commits into from
Apr 26, 2024
Merged

Throttle render assets #12622

merged 16 commits into from
Apr 26, 2024

Conversation

robtfm
Copy link
Contributor

@robtfm robtfm commented Mar 21, 2024

Objective

allow throttling of gpu uploads to prevent choppy framerate when many textures/meshes are loaded in.

Solution

  • RenderAssets can implement byte_len() which reports their size. implemented this for Mesh and Image
  • users can add a RenderAssetBytesPerFrame which specifies max bytes to attempt to upload in a frame
  • render_assets::<A> checks how many bytes have been written before attempting to upload assets. the limit is a soft cap: assets will be written until the total has exceeded the cap, to ensure some forward progress every frame

notes:

issues

  • fonts sometimes only partially upload. i have no clue why, needs to be fixed fixed now.
  • choosing the #bytes is tricky as it should be hardware / framerate dependent
  • many features are not tested (env maps, light probes, etc) - they won't break unless RenderAssetBytesPerFrame is explicitly used though

@robtfm robtfm added C-Enhancement A new feature A-Rendering Drawing game state to the screen labels Mar 21, 2024
@JMS55
Copy link
Contributor

JMS55 commented Mar 21, 2024

Is this about saving CPU time, or GPU/PCIE transfers?

For the former, we should move render asset preparation to background tasks (I wish we had a better way to mark systems as background instead of having to manually spawn tasks from a system). I have an issue open for this.

For the latter, yeah I think we need separate hardware queues. But I can see the value of this until then.

@robtfm
Copy link
Contributor Author

robtfm commented Mar 21, 2024

it's about transfers. currently if you load a bunch of large renderassets the whole app stalls in prepare_asset::<A> until they're all done

@robtfm
Copy link
Contributor Author

robtfm commented Mar 21, 2024

actually now you say that i'm not sure... prepare_asset<Image> uses create_texture_with_data which should just be submitting writes to the queue, so i don't know exactly why i see the stall point in prepare_asset<Image> itself. maybe it would be resolved using cpu-background uploading.

@IceSentry
Copy link
Contributor

IceSentry commented Apr 11, 2024

I just tried upgrading this to main. It's pretty simple, just need to do the same trick used for asset_usage where the param is Self::SourceAsset instead of self.

I tested it in a simple app that just spawns a bunch of helmets to see how it would behave and it doesn't seem to do anything. It still spawns every helmet at the same time with a major stutter. I never see the exhausted debug log. Maybe I broke something while porting it. The code seems like it should work though, so not sure what I'm missing.

@robtfm
Copy link
Contributor Author

robtfm commented Apr 11, 2024

thanks for updating it - just making sure, you did add the resource as well? something like app.insert_resource(RenderAssetBytesPerFrame::new(16777216)) ?

i definitely see the impact on 0.13 and can't see any reason it should have changed with the flip to implementing RenderAsset on targets ... although in my scenario images are by far the largest part of the problem, possibly i messed up the mesh size calculation.

Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
@IceSentry
Copy link
Contributor

IceSentry commented Apr 12, 2024

Yeah, I did insert the resource, but I did it in a separate branch where I copied code manually so I may have missed something

@IceSentry
Copy link
Contributor

IceSentry commented Apr 12, 2024

I tested again based on the branch before the changes and it did work, so I probably messed up something when I was copying the code.

With the fix I suggested all that will be needed is to merge main I think.

@robtfm
Copy link
Contributor Author

robtfm commented Apr 12, 2024

ok cool, i'll go through properly at some point this weekend.

Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
@JMS55 JMS55 added this to the 0.14 milestone Apr 18, 2024
@robtfm
Copy link
Contributor Author

robtfm commented Apr 20, 2024

i got a bit tangled up with the merge so i just applied the changes onto a new branch and merged it in (with a couple of tweaks).

i can't really test very well since my project is not on main - i tried with 20 copies of the flight helmet and it seems to throttle correctly according to the debug output, but its not really enough data to see the impact visually.

@mockersf
Copy link
Member

tried on a project I have that loads 65536 meshes with their textures, this works as expected. Bevy freezes without setting RenderAssetBytesPerFrame, works fine with it. FPS is still bad because of the number unique meshes / textures, and you're not supposed to display all the high details one at the same time 😄

throttling.mp4

Co-authored-by: François Mockers <francois.mockers@vleue.com>
@JMS55
Copy link
Contributor

JMS55 commented Apr 20, 2024

@mockersf can we add this as a cargo example? I may work on further asset loading perf improvements in the future, and it would be amazing to have this as a test bench.

Copy link
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

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

LGTM

Would be nice to have a large_asset_loading example or something like that, but I don't want to block this PR on that.

@mockersf
Copy link
Member

@mockersf can we add this as a cargo example? I may work on further asset loading perf improvements in the future, and it would be amazing to have this as a test bench.

I can do that in a few days, but it'll definitely never be merged in Bevy given how ugly it is 😄
Everything is loaded from a 65MB 7zip file

Would be nice to have a large_asset_loading example or something like that, but I don't want to block this PR on that.

I can try to get something prettier (and smaller) than what I currently have later

@IceSentry IceSentry added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Apr 23, 2024
let mut param = param.into_inner();
let queued_assets = std::mem::take(&mut prepare_next_frame.assets);
for (id, extracted_asset) in queued_assets {
if extracted_assets.removed.contains(&id) {
if extracted_assets.removed.contains(&id) || extracted_assets.added.contains(&id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of skipping newly added assets here?

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 is the retry loop for assets that were previously extracted and were not ready in the previous frame. If the asset has been added again, there are now 2 versions and we don’t want to prepare the old one.

This was the source of the font bug I had where some characters would not render:

The code in prepare_sprites clears its cache on asset change events, then checks for presence in RenderAssets to repopulate the cache. If we prepare the old version then the code in the sprite module will see it as ready and will cache the previous version, resulting in the old asset being permanently bound for some glyphs.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahhh, makes sense. Could you add a comment about this?

@@ -161,13 +170,15 @@ impl<A: RenderAsset> RenderAssetDependency for A {
pub struct ExtractedAssets<A: RenderAsset> {
extracted: Vec<(AssetId<A::SourceAsset>, A::SourceAsset)>,
removed: Vec<AssetId<A::SourceAsset>>,
added: Vec<AssetId<A::SourceAsset>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be a HashSet over a Vec.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, will change this and removed as well

@mockersf mockersf added this pull request to the merge queue Apr 26, 2024
Merged via the queue into bevyengine:main with commit 91a393a Apr 26, 2024
27 checks passed
@IceSentry IceSentry added the C-Needs-Release-Note Work that should be called out in the blog due to impact label Apr 27, 2024
github-merge-queue bot pushed a commit that referenced this pull request Apr 27, 2024
# Objective

- Since #12622 example `compute_shader_game_of_life` crashes
```
thread 'Compute Task Pool (2)' panicked at examples/shader/compute_shader_game_of_life.rs:137:65:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `compute_shader_game_of_life::prepare_bind_group`!
thread '<unnamed>' panicked at examples/shader/compute_shader_game_of_life.rs:254:34:
Requested resource compute_shader_game_of_life::GameOfLifeImageBindGroups does not exist in the `World`.
                Did you forget to add it using `app.insert_resource` / `app.init_resource`?
                Resources are also implicitly added via `app.add_event`,
                and can be added by plugins.
Encountered a panic in system `bevy_render::renderer::render_system`!
```

## Solution

- `exhausted()` now checks that there is a limit
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-Enhancement A new feature C-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants