1use crate::{
2 render_resource::AsBindGroupError, ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
3};
4use bevy_app::{App, Plugin, SubApp};
5pub use bevy_asset::RenderAssetUsages;
6use bevy_asset::{Asset, AssetEvent, AssetId, Assets};
7use bevy_ecs::{
8 prelude::{Commands, EventReader, IntoSystemConfigs, ResMut, Resource},
9 schedule::SystemConfigs,
10 system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
11 world::{FromWorld, Mut},
12};
13use bevy_render_macros::ExtractResource;
14use bevy_utils::{
15 tracing::{debug, error},
16 HashMap, HashSet,
17};
18use core::marker::PhantomData;
19use derive_more::derive::{Display, Error};
20
21#[derive(Debug, Error, Display)]
22pub enum PrepareAssetError<E: Send + Sync + 'static> {
23 #[display("Failed to prepare asset")]
24 RetryNextUpdate(E),
25 #[display("Failed to build bind group: {_0}")]
26 AsBindGroupError(AsBindGroupError),
27}
28
29pub trait RenderAsset: Send + Sync + 'static + Sized {
37 type SourceAsset: Asset + Clone;
39
40 type Param: SystemParam;
44
45 #[inline]
47 fn asset_usage(_source_asset: &Self::SourceAsset) -> RenderAssetUsages {
48 RenderAssetUsages::default()
49 }
50
51 #[inline]
54 #[allow(unused_variables)]
55 fn byte_len(source_asset: &Self::SourceAsset) -> Option<usize> {
56 None
57 }
58
59 fn prepare_asset(
63 source_asset: Self::SourceAsset,
64 param: &mut SystemParamItem<Self::Param>,
65 ) -> Result<Self, PrepareAssetError<Self::SourceAsset>>;
66}
67
68pub struct RenderAssetPlugin<A: RenderAsset, AFTER: RenderAssetDependency + 'static = ()> {
79 phantom: PhantomData<fn() -> (A, AFTER)>,
80}
81
82impl<A: RenderAsset, AFTER: RenderAssetDependency + 'static> Default
83 for RenderAssetPlugin<A, AFTER>
84{
85 fn default() -> Self {
86 Self {
87 phantom: Default::default(),
88 }
89 }
90}
91
92impl<A: RenderAsset, AFTER: RenderAssetDependency + 'static> Plugin
93 for RenderAssetPlugin<A, AFTER>
94{
95 fn build(&self, app: &mut App) {
96 app.init_resource::<CachedExtractRenderAssetSystemState<A>>();
97 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
98 render_app
99 .init_resource::<ExtractedAssets<A>>()
100 .init_resource::<RenderAssets<A>>()
101 .init_resource::<PrepareNextFrameAssets<A>>()
102 .add_systems(ExtractSchedule, extract_render_asset::<A>);
103 AFTER::register_system(
104 render_app,
105 prepare_assets::<A>.in_set(RenderSet::PrepareAssets),
106 );
107 }
108 }
109}
110
111pub trait RenderAssetDependency {
113 fn register_system(render_app: &mut SubApp, system: SystemConfigs);
114}
115
116impl RenderAssetDependency for () {
117 fn register_system(render_app: &mut SubApp, system: SystemConfigs) {
118 render_app.add_systems(Render, system);
119 }
120}
121
122impl<A: RenderAsset> RenderAssetDependency for A {
123 fn register_system(render_app: &mut SubApp, system: SystemConfigs) {
124 render_app.add_systems(Render, system.after(prepare_assets::<A>));
125 }
126}
127
128#[derive(Resource)]
130pub struct ExtractedAssets<A: RenderAsset> {
131 pub extracted: Vec<(AssetId<A::SourceAsset>, A::SourceAsset)>,
133
134 pub removed: HashSet<AssetId<A::SourceAsset>>,
138
139 pub added: HashSet<AssetId<A::SourceAsset>>,
141}
142
143impl<A: RenderAsset> Default for ExtractedAssets<A> {
144 fn default() -> Self {
145 Self {
146 extracted: Default::default(),
147 removed: Default::default(),
148 added: Default::default(),
149 }
150 }
151}
152
153#[derive(Resource)]
156pub struct RenderAssets<A: RenderAsset>(HashMap<AssetId<A::SourceAsset>, A>);
157
158impl<A: RenderAsset> Default for RenderAssets<A> {
159 fn default() -> Self {
160 Self(Default::default())
161 }
162}
163
164impl<A: RenderAsset> RenderAssets<A> {
165 pub fn get(&self, id: impl Into<AssetId<A::SourceAsset>>) -> Option<&A> {
166 self.0.get(&id.into())
167 }
168
169 pub fn get_mut(&mut self, id: impl Into<AssetId<A::SourceAsset>>) -> Option<&mut A> {
170 self.0.get_mut(&id.into())
171 }
172
173 pub fn insert(&mut self, id: impl Into<AssetId<A::SourceAsset>>, value: A) -> Option<A> {
174 self.0.insert(id.into(), value)
175 }
176
177 pub fn remove(&mut self, id: impl Into<AssetId<A::SourceAsset>>) -> Option<A> {
178 self.0.remove(&id.into())
179 }
180
181 pub fn iter(&self) -> impl Iterator<Item = (AssetId<A::SourceAsset>, &A)> {
182 self.0.iter().map(|(k, v)| (*k, v))
183 }
184
185 pub fn iter_mut(&mut self) -> impl Iterator<Item = (AssetId<A::SourceAsset>, &mut A)> {
186 self.0.iter_mut().map(|(k, v)| (*k, v))
187 }
188}
189
190#[derive(Resource)]
191struct CachedExtractRenderAssetSystemState<A: RenderAsset> {
192 state: SystemState<(
193 EventReader<'static, 'static, AssetEvent<A::SourceAsset>>,
194 ResMut<'static, Assets<A::SourceAsset>>,
195 )>,
196}
197
198impl<A: RenderAsset> FromWorld for CachedExtractRenderAssetSystemState<A> {
199 fn from_world(world: &mut bevy_ecs::world::World) -> Self {
200 Self {
201 state: SystemState::new(world),
202 }
203 }
204}
205
206pub(crate) fn extract_render_asset<A: RenderAsset>(
209 mut commands: Commands,
210 mut main_world: ResMut<MainWorld>,
211) {
212 main_world.resource_scope(
213 |world, mut cached_state: Mut<CachedExtractRenderAssetSystemState<A>>| {
214 let (mut events, mut assets) = cached_state.state.get_mut(world);
215
216 let mut changed_assets = HashSet::default();
217 let mut removed = HashSet::default();
218
219 for event in events.read() {
220 #[allow(clippy::match_same_arms)]
221 match event {
222 AssetEvent::Added { id } | AssetEvent::Modified { id } => {
223 changed_assets.insert(*id);
224 }
225 AssetEvent::Removed { .. } => {}
226 AssetEvent::Unused { id } => {
227 changed_assets.remove(id);
228 removed.insert(*id);
229 }
230 AssetEvent::LoadedWithDependencies { .. } => {
231 }
233 }
234 }
235
236 let mut extracted_assets = Vec::new();
237 let mut added = HashSet::new();
238 for id in changed_assets.drain() {
239 if let Some(asset) = assets.get(id) {
240 let asset_usage = A::asset_usage(asset);
241 if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) {
242 if asset_usage == RenderAssetUsages::RENDER_WORLD {
243 if let Some(asset) = assets.remove(id) {
244 extracted_assets.push((id, asset));
245 added.insert(id);
246 }
247 } else {
248 extracted_assets.push((id, asset.clone()));
249 added.insert(id);
250 }
251 }
252 }
253 }
254
255 commands.insert_resource(ExtractedAssets::<A> {
256 extracted: extracted_assets,
257 removed,
258 added,
259 });
260 cached_state.state.apply(world);
261 },
262 );
263}
264
265#[derive(Resource)]
268pub struct PrepareNextFrameAssets<A: RenderAsset> {
269 assets: Vec<(AssetId<A::SourceAsset>, A::SourceAsset)>,
270}
271
272impl<A: RenderAsset> Default for PrepareNextFrameAssets<A> {
273 fn default() -> Self {
274 Self {
275 assets: Default::default(),
276 }
277 }
278}
279
280pub fn prepare_assets<A: RenderAsset>(
283 mut extracted_assets: ResMut<ExtractedAssets<A>>,
284 mut render_assets: ResMut<RenderAssets<A>>,
285 mut prepare_next_frame: ResMut<PrepareNextFrameAssets<A>>,
286 param: StaticSystemParam<<A as RenderAsset>::Param>,
287 mut bpf: ResMut<RenderAssetBytesPerFrame>,
288) {
289 let mut wrote_asset_count = 0;
290
291 let mut param = param.into_inner();
292 let queued_assets = core::mem::take(&mut prepare_next_frame.assets);
293 for (id, extracted_asset) in queued_assets {
294 if extracted_assets.removed.contains(&id) || extracted_assets.added.contains(&id) {
295 continue;
297 }
298
299 let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) {
300 if bpf.exhausted() {
305 prepare_next_frame.assets.push((id, extracted_asset));
306 continue;
307 }
308 size
309 } else {
310 0
311 };
312
313 match A::prepare_asset(extracted_asset, &mut param) {
314 Ok(prepared_asset) => {
315 render_assets.insert(id, prepared_asset);
316 bpf.write_bytes(write_bytes);
317 wrote_asset_count += 1;
318 }
319 Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
320 prepare_next_frame.assets.push((id, extracted_asset));
321 }
322 Err(PrepareAssetError::AsBindGroupError(e)) => {
323 error!(
324 "{} Bind group construction failed: {e}",
325 core::any::type_name::<A>()
326 );
327 }
328 }
329 }
330
331 for removed in extracted_assets.removed.drain() {
332 render_assets.remove(removed);
333 }
334
335 for (id, extracted_asset) in extracted_assets.extracted.drain(..) {
336 render_assets.remove(id);
340
341 let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) {
342 if bpf.exhausted() {
343 prepare_next_frame.assets.push((id, extracted_asset));
344 continue;
345 }
346 size
347 } else {
348 0
349 };
350
351 match A::prepare_asset(extracted_asset, &mut param) {
352 Ok(prepared_asset) => {
353 render_assets.insert(id, prepared_asset);
354 bpf.write_bytes(write_bytes);
355 wrote_asset_count += 1;
356 }
357 Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
358 prepare_next_frame.assets.push((id, extracted_asset));
359 }
360 Err(PrepareAssetError::AsBindGroupError(e)) => {
361 error!(
362 "{} Bind group construction failed: {e}",
363 core::any::type_name::<A>()
364 );
365 }
366 }
367 }
368
369 if bpf.exhausted() && !prepare_next_frame.assets.is_empty() {
370 debug!(
371 "{} write budget exhausted with {} assets remaining (wrote {})",
372 core::any::type_name::<A>(),
373 prepare_next_frame.assets.len(),
374 wrote_asset_count
375 );
376 }
377}
378
379#[derive(Resource, Default, Debug, Clone, Copy, ExtractResource)]
383pub struct RenderAssetBytesPerFrame {
384 pub max_bytes: Option<usize>,
385 pub available: usize,
386}
387
388impl RenderAssetBytesPerFrame {
389 pub fn new(max_bytes: usize) -> Self {
395 Self {
396 max_bytes: Some(max_bytes),
397 available: 0,
398 }
399 }
400
401 pub fn reset(&mut self) {
403 self.available = self.max_bytes.unwrap_or(usize::MAX);
404 }
405
406 pub fn available_bytes(&self, required_bytes: usize) -> usize {
408 if self.max_bytes.is_none() {
409 return required_bytes;
410 }
411
412 required_bytes.min(self.available)
413 }
414
415 fn write_bytes(&mut self, bytes: usize) {
417 if self.max_bytes.is_none() {
418 return;
419 }
420
421 let write_bytes = bytes.min(self.available);
422 self.available -= write_bytes;
423 }
424
425 fn exhausted(&self) -> bool {
427 self.max_bytes.is_some() && self.available == 0
428 }
429}