|
1 | 1 | use crate::{
|
2 |
| - render_resource::AsBindGroupError, ExtractSchedule, MainWorld, Render, RenderApp, RenderSet, |
| 2 | + render_resource::AsBindGroupError, Extract, ExtractSchedule, MainWorld, Render, RenderApp, |
| 3 | + RenderSet, |
3 | 4 | };
|
4 | 5 | use bevy_app::{App, Plugin, SubApp};
|
5 | 6 | pub use bevy_asset::RenderAssetUsages;
|
6 | 7 | use bevy_asset::{Asset, AssetEvent, AssetId, Assets};
|
7 | 8 | use bevy_ecs::{
|
8 |
| - prelude::{Commands, EventReader, IntoSystemConfigs, ResMut, Resource}, |
| 9 | + prelude::{Commands, EventReader, IntoSystemConfigs, Res, ResMut, Resource}, |
9 | 10 | schedule::{SystemConfigs, SystemSet},
|
10 | 11 | system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
|
11 | 12 | world::{FromWorld, Mut},
|
12 | 13 | };
|
13 | 14 | use bevy_platform_support::collections::{HashMap, HashSet};
|
14 |
| -use bevy_render_macros::ExtractResource; |
15 | 15 | use core::marker::PhantomData;
|
| 16 | +use core::sync::atomic::{AtomicUsize, Ordering}; |
16 | 17 | use thiserror::Error;
|
17 | 18 | use tracing::{debug, error};
|
18 | 19 |
|
@@ -308,7 +309,7 @@ pub fn prepare_assets<A: RenderAsset>(
|
308 | 309 | mut render_assets: ResMut<RenderAssets<A>>,
|
309 | 310 | mut prepare_next_frame: ResMut<PrepareNextFrameAssets<A>>,
|
310 | 311 | param: StaticSystemParam<<A as RenderAsset>::Param>,
|
311 |
| - mut bpf: ResMut<RenderAssetBytesPerFrame>, |
| 312 | + bpf: Res<RenderAssetBytesPerFrameLimiter>, |
312 | 313 | ) {
|
313 | 314 | let mut wrote_asset_count = 0;
|
314 | 315 |
|
@@ -401,54 +402,94 @@ pub fn prepare_assets<A: RenderAsset>(
|
401 | 402 | }
|
402 | 403 | }
|
403 | 404 |
|
404 |
| -/// A resource that attempts to limit the amount of data transferred from cpu to gpu |
405 |
| -/// each frame, preventing choppy frames at the cost of waiting longer for gpu assets |
406 |
| -/// to become available |
407 |
| -#[derive(Resource, Default, Debug, Clone, Copy, ExtractResource)] |
| 405 | +pub fn reset_render_asset_bytes_per_frame( |
| 406 | + mut bpf_limiter: ResMut<RenderAssetBytesPerFrameLimiter>, |
| 407 | +) { |
| 408 | + bpf_limiter.reset(); |
| 409 | +} |
| 410 | + |
| 411 | +pub fn extract_render_asset_bytes_per_frame( |
| 412 | + bpf: Extract<Res<RenderAssetBytesPerFrame>>, |
| 413 | + mut bpf_limiter: ResMut<RenderAssetBytesPerFrameLimiter>, |
| 414 | +) { |
| 415 | + bpf_limiter.max_bytes = bpf.max_bytes; |
| 416 | +} |
| 417 | + |
| 418 | +/// A resource that defines the amount of data allowed to be transferred from CPU to GPU |
| 419 | +/// each frame, preventing choppy frames at the cost of waiting longer for GPU assets |
| 420 | +/// to become available. |
| 421 | +#[derive(Resource, Default)] |
408 | 422 | pub struct RenderAssetBytesPerFrame {
|
409 | 423 | pub max_bytes: Option<usize>,
|
410 |
| - pub available: usize, |
411 | 424 | }
|
412 | 425 |
|
413 | 426 | impl RenderAssetBytesPerFrame {
|
414 | 427 | /// `max_bytes`: the number of bytes to write per frame.
|
415 |
| - /// this is a soft limit: only full assets are written currently, uploading stops |
| 428 | + /// |
| 429 | + /// This is a soft limit: only full assets are written currently, uploading stops |
416 | 430 | /// after the first asset that exceeds the limit.
|
| 431 | + /// |
417 | 432 | /// To participate, assets should implement [`RenderAsset::byte_len`]. If the default
|
418 | 433 | /// is not overridden, the assets are assumed to be small enough to upload without restriction.
|
419 | 434 | pub fn new(max_bytes: usize) -> Self {
|
420 | 435 | Self {
|
421 | 436 | max_bytes: Some(max_bytes),
|
422 |
| - available: 0, |
423 | 437 | }
|
424 | 438 | }
|
| 439 | +} |
| 440 | + |
| 441 | +/// A render-world resource that facilitates limiting the data transferred from CPU to GPU |
| 442 | +/// each frame, preventing choppy frames at the cost of waiting longer for GPU assets |
| 443 | +/// to become available. |
| 444 | +#[derive(Resource, Default)] |
| 445 | +pub struct RenderAssetBytesPerFrameLimiter { |
| 446 | + /// Populated by [`RenderAssetBytesPerFrame`] during extraction. |
| 447 | + pub max_bytes: Option<usize>, |
| 448 | + /// Bytes written this frame. |
| 449 | + pub bytes_written: AtomicUsize, |
| 450 | +} |
425 | 451 |
|
426 |
| - /// Reset the available bytes. Called once per frame by the [`crate::RenderPlugin`]. |
| 452 | +impl RenderAssetBytesPerFrameLimiter { |
| 453 | + /// Reset the available bytes. Called once per frame during extraction by [`crate::RenderPlugin`]. |
427 | 454 | pub fn reset(&mut self) {
|
428 |
| - self.available = self.max_bytes.unwrap_or(usize::MAX); |
| 455 | + if self.max_bytes.is_none() { |
| 456 | + return; |
| 457 | + } |
| 458 | + self.bytes_written.store(0, Ordering::Relaxed); |
429 | 459 | }
|
430 | 460 |
|
431 |
| - /// check how many bytes are available since the last reset |
| 461 | + /// Check how many bytes are available for writing. |
432 | 462 | pub fn available_bytes(&self, required_bytes: usize) -> usize {
|
433 |
| - if self.max_bytes.is_none() { |
434 |
| - return required_bytes; |
| 463 | + if let Some(max_bytes) = self.max_bytes { |
| 464 | + let total_bytes = self |
| 465 | + .bytes_written |
| 466 | + .fetch_add(required_bytes, Ordering::Relaxed); |
| 467 | + |
| 468 | + // The bytes available is the inverse of the amount we overshot max_bytes |
| 469 | + if total_bytes >= max_bytes { |
| 470 | + required_bytes.saturating_sub(total_bytes - max_bytes) |
| 471 | + } else { |
| 472 | + required_bytes |
| 473 | + } |
| 474 | + } else { |
| 475 | + required_bytes |
435 | 476 | }
|
436 |
| - |
437 |
| - required_bytes.min(self.available) |
438 | 477 | }
|
439 | 478 |
|
440 |
| - /// decrease the available bytes for the current frame |
441 |
| - fn write_bytes(&mut self, bytes: usize) { |
442 |
| - if self.max_bytes.is_none() { |
443 |
| - return; |
| 479 | + /// Decreases the available bytes for the current frame. |
| 480 | + fn write_bytes(&self, bytes: usize) { |
| 481 | + if self.max_bytes.is_some() && bytes > 0 { |
| 482 | + self.bytes_written.fetch_add(bytes, Ordering::Relaxed); |
444 | 483 | }
|
445 |
| - |
446 |
| - let write_bytes = bytes.min(self.available); |
447 |
| - self.available -= write_bytes; |
448 | 484 | }
|
449 | 485 |
|
450 |
| - // check if any bytes remain available for writing this frame |
| 486 | + /// Returns `true` if there are no remaining bytes available for writing this frame. |
451 | 487 | fn exhausted(&self) -> bool {
|
452 |
| - self.max_bytes.is_some() && self.available == 0 |
| 488 | + if let Some(max_bytes) = self.max_bytes { |
| 489 | + let bytes_written = self.bytes_written.load(Ordering::Relaxed); |
| 490 | + bytes_written >= max_bytes |
| 491 | + } else { |
| 492 | + false |
| 493 | + } |
453 | 494 | }
|
454 | 495 | }
|
0 commit comments