Skip to content

Commit fbb75d4

Browse files
authored
RenderCreation refactor (#22714)
# Objective - work towards renderer recovery ## Solution - make render creation reusable - document weird stuff thats going ## Testing - ci note: reviewing by commit is easier. when a big block moves its just a copy paste, the minor tweaks are separated into other commits
1 parent cf32081 commit fbb75d4

File tree

2 files changed

+93
-82
lines changed

2 files changed

+93
-82
lines changed

crates/bevy_render/src/lib.rs

Lines changed: 38 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ use alloc::sync::Arc;
9090
use batching::gpu_preprocessing::BatchingPlugin;
9191
use bevy_app::{App, AppLabel, Plugin, SubApp};
9292
use bevy_asset::{AssetApp, AssetServer};
93+
use bevy_derive::{Deref, DerefMut};
9394
use bevy_ecs::{
9495
prelude::*,
9596
schedule::{ScheduleBuildSettings, ScheduleLabel},
@@ -99,7 +100,6 @@ use bevy_shader::{load_shader_library, Shader, ShaderLoader};
99100
use bevy_utils::prelude::default;
100101
use bevy_window::{PrimaryWindow, RawHandleWrapperHolder};
101102
use bitflags::bitflags;
102-
use core::ops::{Deref, DerefMut};
103103
use globals::GlobalsPlugin;
104104
use occlusion_culling::OcclusionCullingPlugin;
105105
use render_asset::{
@@ -260,25 +260,11 @@ pub struct ExtractSchedule;
260260
/// This resource is only available during [`ExtractSchedule`] and not
261261
/// during command application of that schedule.
262262
/// See [`Extract`] for more details.
263-
#[derive(Resource, Default)]
263+
#[derive(Resource, Default, Deref, DerefMut)]
264264
pub struct MainWorld(World);
265265

266-
impl Deref for MainWorld {
267-
type Target = World;
268-
269-
fn deref(&self) -> &Self::Target {
270-
&self.0
271-
}
272-
}
273-
274-
impl DerefMut for MainWorld {
275-
fn deref_mut(&mut self) -> &mut Self::Target {
276-
&mut self.0
277-
}
278-
}
279-
280-
#[derive(Resource)]
281-
struct FutureRenderResources(Arc<Mutex<Option<RenderResources>>>);
266+
#[derive(Resource, Default, Clone, Deref)]
267+
pub(crate) struct FutureRenderResources(Arc<Mutex<Option<RenderResources>>>);
282268

283269
/// A label for the rendering sub-app.
284270
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
@@ -290,64 +276,31 @@ impl Plugin for RenderPlugin {
290276
app.init_asset::<Shader>()
291277
.init_asset_loader::<ShaderLoader>();
292278

293-
match &self.render_creation {
294-
RenderCreation::Manual(resources) => {
295-
let future_render_resources_wrapper = Arc::new(Mutex::new(Some(resources.clone())));
296-
app.insert_resource(FutureRenderResources(
297-
future_render_resources_wrapper.clone(),
298-
));
299-
// SAFETY: Plugins should be set up on the main thread.
300-
unsafe { initialize_render_app(app) };
301-
}
302-
RenderCreation::Automatic(render_creation) => {
303-
if let Some(backends) = render_creation.backends {
304-
let future_render_resources_wrapper = Arc::new(Mutex::new(None));
305-
app.insert_resource(FutureRenderResources(
306-
future_render_resources_wrapper.clone(),
307-
));
308-
309-
let primary_window = app
310-
.world_mut()
311-
.query_filtered::<&RawHandleWrapperHolder, With<PrimaryWindow>>()
312-
.single(app.world())
313-
.ok()
314-
.cloned();
315-
316-
let settings = render_creation.clone();
317-
318-
#[cfg(feature = "raw_vulkan_init")]
319-
let raw_vulkan_init_settings = app
320-
.world_mut()
321-
.get_resource::<renderer::raw_vulkan_init::RawVulkanInitSettings>()
322-
.cloned()
323-
.unwrap_or_default();
324-
325-
let async_renderer = async move {
326-
let render_resources = renderer::initialize_renderer(
327-
backends,
328-
primary_window,
329-
&settings,
330-
#[cfg(feature = "raw_vulkan_init")]
331-
raw_vulkan_init_settings,
332-
)
333-
.await;
334-
335-
*future_render_resources_wrapper.lock().unwrap() = Some(render_resources);
336-
};
337-
338-
// In wasm, spawn a task and detach it for execution
339-
#[cfg(target_arch = "wasm32")]
340-
bevy_tasks::IoTaskPool::get()
341-
.spawn_local(async_renderer)
342-
.detach();
343-
// Otherwise, just block for it to complete
344-
#[cfg(not(target_arch = "wasm32"))]
345-
bevy_tasks::block_on(async_renderer);
346-
347-
// SAFETY: Plugins should be set up on the main thread.
348-
unsafe { initialize_render_app(app) };
349-
}
350-
}
279+
let primary_window = app
280+
.world_mut()
281+
.query_filtered::<&RawHandleWrapperHolder, With<PrimaryWindow>>()
282+
.single(app.world())
283+
.ok()
284+
.cloned();
285+
286+
#[cfg(feature = "raw_vulkan_init")]
287+
let raw_vulkan_init_settings = app
288+
.world_mut()
289+
.get_resource::<renderer::raw_vulkan_init::RawVulkanInitSettings>()
290+
.cloned()
291+
.unwrap_or_default();
292+
293+
let future_resources = FutureRenderResources::default();
294+
if self.render_creation.create_render(
295+
future_resources.clone(),
296+
primary_window,
297+
#[cfg(feature = "raw_vulkan_init")]
298+
raw_vulkan_init_settings,
299+
) {
300+
// Note that `future_resources` is not necessarily populated here yet.
301+
app.insert_resource(future_resources);
302+
// SAFETY: Plugins should be set up on the main thread.
303+
unsafe { initialize_render_app(app) };
351304
};
352305

353306
app.add_plugins((
@@ -383,9 +336,17 @@ impl Plugin for RenderPlugin {
383336
}
384337

385338
fn ready(&self, app: &App) -> bool {
339+
// This is a little tricky. `FutureRenderResources` is added in `build`, which runs synchronously before `ready`.
340+
// It is only added if there is a wgpu backend and thus the renderer can be created.
341+
// Hence, if we try and get the resource and it is not present, that means we are ready, because we dont need it.
342+
// On the other hand, if the resource is present, then we try and lock on it. The lock can fail, in which case
343+
// we currently can assume that means the `FutureRenderResources` is in the act of being populated, because
344+
// that is the only other place the lock may be held. If it is being populated, we can assume we're ready. This
345+
// happens via the `and_then` falling through to the same `unwrap_or(true)` case as when there's no resource.
346+
// If the lock succeeds, we can straightforwardly check if it is populated. If it is not, then we're not ready.
386347
app.world()
387348
.get_resource::<FutureRenderResources>()
388-
.and_then(|frr| frr.0.try_lock().map(|locked| locked.is_some()).ok())
349+
.and_then(|frr| frr.try_lock().map(|locked| locked.is_some()).ok())
389350
.unwrap_or(true)
390351
}
391352

crates/bevy_render/src/settings.rs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
use crate::renderer::{
2-
RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
1+
use crate::{
2+
renderer::{self, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue},
3+
FutureRenderResources,
34
};
45
use alloc::borrow::Cow;
6+
use bevy_window::RawHandleWrapperHolder;
57

68
pub use wgpu::{
79
Backends, Dx12Compiler, Features as WgpuFeatures, Gles3MinorVersion, InstanceFlags,
@@ -151,8 +153,7 @@ pub struct RenderResources(
151153
pub RenderAdapterInfo,
152154
pub RenderAdapter,
153155
pub RenderInstance,
154-
#[cfg(feature = "raw_vulkan_init")]
155-
pub crate::renderer::raw_vulkan_init::AdditionalVulkanFeatures,
156+
#[cfg(feature = "raw_vulkan_init")] pub renderer::raw_vulkan_init::AdditionalVulkanFeatures,
156157
);
157158

158159
/// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin).
@@ -176,7 +177,7 @@ impl RenderCreation {
176177
adapter: RenderAdapter,
177178
instance: RenderInstance,
178179
#[cfg(feature = "raw_vulkan_init")]
179-
additional_vulkan_features: crate::renderer::raw_vulkan_init::AdditionalVulkanFeatures,
180+
additional_vulkan_features: renderer::raw_vulkan_init::AdditionalVulkanFeatures,
180181
) -> Self {
181182
RenderResources(
182183
device,
@@ -189,6 +190,55 @@ impl RenderCreation {
189190
)
190191
.into()
191192
}
193+
194+
/// Creates [`RenderResources`] from this [`RenderCreation`] and an optional primary window
195+
/// and writes them into `future_resources`, possibly asynchronously.
196+
///
197+
/// Returns true if creation was successful, false otherwise.
198+
///
199+
/// Note: [`RenderCreation::Manual`] will ignore the provided primary window.
200+
pub(crate) fn create_render(
201+
&self,
202+
future_resources: FutureRenderResources,
203+
primary_window: Option<RawHandleWrapperHolder>,
204+
#[cfg(feature = "raw_vulkan_init")]
205+
raw_vulkan_init_settings: renderer::raw_vulkan_init::RawVulkanInitSettings,
206+
) -> bool {
207+
match self {
208+
RenderCreation::Manual(resources) => {
209+
*future_resources.lock().unwrap() = Some(resources.clone());
210+
}
211+
RenderCreation::Automatic(render_creation) => {
212+
let Some(backends) = render_creation.backends else {
213+
return false;
214+
};
215+
let settings = render_creation.clone();
216+
217+
let async_renderer = async move {
218+
let render_resources = renderer::initialize_renderer(
219+
backends,
220+
primary_window,
221+
&settings,
222+
#[cfg(feature = "raw_vulkan_init")]
223+
raw_vulkan_init_settings,
224+
)
225+
.await;
226+
227+
*future_resources.lock().unwrap() = Some(render_resources);
228+
};
229+
230+
// In wasm, spawn a task and detach it for execution
231+
#[cfg(target_arch = "wasm32")]
232+
bevy_tasks::IoTaskPool::get()
233+
.spawn_local(async_renderer)
234+
.detach();
235+
// Otherwise, just block for it to complete
236+
#[cfg(not(target_arch = "wasm32"))]
237+
bevy_tasks::block_on(async_renderer);
238+
}
239+
}
240+
true
241+
}
192242
}
193243

194244
impl From<RenderResources> for RenderCreation {

0 commit comments

Comments
 (0)