Skip to content

Conversation

@robtfm
Copy link
Contributor

@robtfm robtfm commented Jan 16, 2026

Objective

RenderAssetBytesPerFrame limits per-frame gpu transfers to avoid frame hiccups, but doesn't provide any way to prioritise particular assets.

  • users (i.e. me) need being able to prioritise more important assets based on their own criteria. e.g. currently when i have a video streaming (uploading frames from cpu), it can block meshes from ever uploading by saturating the gpu transfer limit on its own.
  • some plugins or engine features are dependent on their assets being available immediately and cause crashes or errors if they're not available (e.g. tonemapping LUTs).

also, when potential gpu-transfer delays are introduced, we may see flickering as old images/meshes/materials are unloaded and new ones are not immediately uploaded. when not using the gpu transfer throttle you can work around this by preloading the assets or by waiting for the server to signal the asset is loaded. with the throttle it's unavoidable. this can result in flickering e.g. for text, every time a new glyph is added to a font atlas.

Solution

  • add a priority enum RenderAssetTransferPriority with an Immediate variant and a Priority(i16) variant.
  • expose it in constructors for Mesh and Image
  • set key resources (font atlases and tonemapping luts) to Immedate
  • push Immediate assets immediately regardless of bytes already transferred.
  • push other assets in order depending on their priority
  • stop immediately removing previous versions of modified assets: we used to always remove stale assets immediately to make sure that shaders expecting a particular shape of data would not crash on seeing an older asset. we now have the mechanism to avoid this with RenderAssetTransferPriority::Immediate, so we can leave the existing asset until the new one is ready, avoiding flickering.
  • add a stale flag to RenderAssets and make as_bind_group take only non-stale assets, to ensure that materials don't get processed when the image they rely on is not yet available, but an older version is still available

Testing

this is a forward-port of code i have built against bevy 16, so there may be issues. i need to write a proper example/test for it.
added gpu_transfer_limits example to demonstrate.

@robtfm robtfm added C-Feature A new feature, making something new possible A-Rendering Drawing game state to the screen labels Jan 16, 2026
@robtfm
Copy link
Contributor Author

robtfm commented Jan 16, 2026

i'm unsure about the current RenderAsset vs ErasedRenderAsset split. it seems like ErasedRenderAssetis only used for MaterialMesh3d, so i had to modify the AFTER dependency of ErasedRenderAsset to point back to a normal RenderAsset in order to manage the ordering vs GpuImages. i think this is fine for now, since it would be odd for a material to need to be ordered after another material.

@robtfm
Copy link
Contributor Author

robtfm commented Jan 18, 2026

perf (including the fast fail fix):

  • using RenderAssetBytesPerFrame::Unlimited (the default) there is no impact.
  • using RenderAssetBytesPerFrame::MaxBytes or RenderAssetBytesPerFrame::MaxBytesPerFrame: costs around 2-3% (0.5ms for 60k failed materials in the example) vs previous single-atomic version.
image

@hukasu hukasu added the S-Needs-SME Decision or review from an SME is required label Jan 19, 2026
github-merge-queue bot pushed a commit that referenced this pull request Jan 20, 2026
# Objective

while writing the tests for #22557 i noticed that the perf was much
worse for failed materials compared to older bevy versions pre #19667.
this is probably because of the allocations in `ErasedMaterialKey::new`
which was called regardless of whether the material was ready or not.

## Solution

fail faster by checking bindgroup (the only fallible part of the
material preparation) before doing any other work.

## Testing

using the `gpu_transfer_limits` example from #22557, the time spent
preparing failing materials drops by ~80%

<img width="744" height="346" alt="image"
src="https://github.com/user-attachments/assets/5123dac7-b90b-41a4-b139-2cc3890fc05b"
/>
/// Bytes written this frame.
pub bytes_written: AtomicUsize,
pub bytes_per_frame: RenderAssetBytesPerFrame,
bytes_written: bevy_platform::sync::RwLock<
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this now private because people would be expected to use the new Stats struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, i felt exposing an RwLock was asking for deadlock trouble, and a bit obscure. there shouldn't be a reason to interact with it directly except for getting debug information anyway (the request_bytes and write_bytes functions are pub so the supported interactions can be accessed by user code).

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, that's what I expected, makes sense.

Comment on lines +177 to +183
fn extract_stats(
channel: Extract<Res<StatsChannel>>,
limiter: Res<RenderAssetBytesPerFrameLimiter>,
) {
let stats = limiter.stats();
let _ = channel.sender.send(stats);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like the stats stuff should probably be more integrated with the Diagnostic system and transparently do that readback to main world.

This can be done in a separate PR though.

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.

This needs a release note but other than that and the minor comments LGTM

@IceSentry IceSentry added the M-Release-Note Work that should be called out in the blog due to impact label Jan 22, 2026
@github-actions
Copy link
Contributor

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

@IceSentry
Copy link
Contributor

Actually, it also needs a migration guide for all the assets that now need the new Priority stuff.

@IceSentry IceSentry added the M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide label Jan 22, 2026
@github-actions
Copy link
Contributor

It looks like your PR is a breaking change, but you didn't provide a migration guide.

Please review the instructions for writing migration guides, then expand or revise the content in the migration guides directory to reflect your changes.

@robtfm
Copy link
Contributor Author

robtfm commented Jan 22, 2026

i have a related pr that i hope to land this cycle too, i'll aim to combine the release notes at least, maybe the migration guide too.

@IceSentry
Copy link
Contributor

I would suggest at least adding the necessary files but keep them as a short description or even just a TODO

@robtfm robtfm force-pushed the gpu-transfer-priorities branch from a64c7ef to acc59ba Compare January 22, 2026 23:21
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-Feature A new feature, making something new possible M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide M-Release-Note Work that should be called out in the blog due to impact S-Needs-SME Decision or review from an SME is required

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants