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

Deferred render init #4913

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ pub struct App {
/// Typically, it is not configured manually, but set by one of Bevy's built-in plugins.
/// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin).
pub runner: Box<dyn Fn(App)>,
/// The [render init functions](Self::add_render_init) are responsible for completing
/// the initialization of plugins once the `runner` has determined that the system is
/// ready to support rendering.
pub render_inits: Vec<Box<dyn FnOnce(&mut App)>>,
Copy link
Member

Choose a reason for hiding this comment

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

On the topic of "should this be included in 0.8": I think that largely comes down to "can we make android work better than it does on main / with #5130". If yes, I think I'm happy to merge either as-is or with RenderPlugin-like changes (see my last comment). If no, I think it would be better to hold off and take our time until we find something that improves on the current state of things.

/// A container of [`Stage`]s set to be run in a linear order.
pub schedule: Schedule,
sub_apps: HashMap<Box<dyn AppLabel>, SubApp>,
Expand Down Expand Up @@ -100,6 +104,7 @@ impl App {
world: Default::default(),
schedule: Default::default(),
runner: Box::new(run_once),
render_inits: vec![],
sub_apps: HashMap::default(),
}
}
Expand Down Expand Up @@ -750,6 +755,59 @@ impl App {
self
}

/// Adds a function that will be called when the runner wants to initialize rendering state
///
/// Each renderer init function `init_fn` is called only once by the app runner once it has
/// determined that the system is in a suitable state for rendering state to be initialized.
///
/// Any number of render init callbacks may be added while building plugins and each added
/// function will be called exactly once. The functions are called in the same order that they
/// are added, after which all added functions are cleared.
///
/// In case there are interdependencies between plugins then `add_render_init` may be called
/// before or after adding additional plugins (for dependency or dependant initialization
/// respectively).
///
/// It can be assumed that the runner will block all system updates for this `App` until
/// after all render init functions have been called (as well as updates for any sub
/// applications). For example this means that events that are sent while building
/// plugins can be assumed to survive until after all render init functions have run.
///
/// For example on Android a runner may wait until the application has reached a 'resumed'
/// state with an associated surface view before trying to initialize rendering state.
///
/// `add_render_init` is typically only used by Bevy-internal plugins (e.g. `RenderPlugin`)
/// that need to defer initialization that depends on other render state being initialized.
///
/// # Examples
///
/// ```
/// # use bevy_app::{prelude::*, AppLabel};
/// # fn find_instance() {}
/// # fn open_device() {}
/// # fn create_context() {}
/// # #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
/// # pub struct RenderApp;
/// fn my_render_init(app: &mut App) {
/// let instance = find_instance();
/// let device = open_device();
/// let context = create_context();
/// let render_app = App::empty();
/// app.add_sub_app(RenderApp, render_app, move |world, render_app| {
/// // prepare
/// // render
/// });
/// }
///
/// App::new()
/// .add_render_init(my_render_init);
/// ```
pub fn add_render_init(&mut self, init_fn: impl FnOnce(&mut App) + 'static) -> &mut Self {
let init_fn = Box::new(init_fn);
self.render_inits.push(init_fn);
self
}

/// Adds a single [`Plugin`].
///
/// One of Bevy's core principles is modularity. All Bevy engine features are implemented
Expand Down Expand Up @@ -926,6 +984,29 @@ impl App {
.map(|sub_app| &sub_app.app)
.ok_or(label)
}

/// Finishes plugin setup once the `App` runner has determined system is ready for rendering
///
/// This should be called by the `runner` function once it has determined that the system is
/// in a suitable state to be able to initialize render state, such as creating a GPU
/// context.
///
/// For example, on Android the runner will wait until the application is first 'resumed' and
/// it has a valid surface view.
///
/// This will invoke all registered [render init functions](Self::add_render_init) in the
/// same order that they were added.
///
/// All registered callbacks are cleared before returning, so this can only be (meaningfully)
/// called once.
pub fn render_init(&mut self) {
let render_inits = std::mem::take(&mut self.render_inits);

// Initialize in the same order that render_init callbacks were registered
for callback in render_inits.into_iter() {
callback(self);
}
}
}

fn run_once(mut app: App) {
Expand Down
58 changes: 30 additions & 28 deletions crates/bevy_core_pipeline/src/core_2d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,35 +37,37 @@ impl Plugin for Core2dPlugin {
app.register_type::<Camera2d>()
.add_plugin(ExtractComponentPlugin::<Camera2d>::default());

let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};

render_app
.init_resource::<DrawFunctions<Transparent2d>>()
.add_system_to_stage(RenderStage::Extract, extract_core_2d_camera_phases)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
.add_system_to_stage(RenderStage::PhaseSort, batch_phase_system::<Transparent2d>);

let pass_node_2d = MainPass2dNode::new(&mut render_app.world);
let mut graph = render_app.world.resource_mut::<RenderGraph>();

let mut draw_2d_graph = RenderGraph::default();
draw_2d_graph.add_node(graph::node::MAIN_PASS, pass_node_2d);
let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new(
graph::input::VIEW_ENTITY,
SlotType::Entity,
)]);
draw_2d_graph
.add_slot_edge(
input_node_id,
app.add_render_init(move |app| {
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};

render_app
.init_resource::<DrawFunctions<Transparent2d>>()
.add_system_to_stage(RenderStage::Extract, extract_core_2d_camera_phases)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
.add_system_to_stage(RenderStage::PhaseSort, batch_phase_system::<Transparent2d>);

let pass_node_2d = MainPass2dNode::new(&mut render_app.world);
let mut graph = render_app.world.resource_mut::<RenderGraph>();

let mut draw_2d_graph = RenderGraph::default();
draw_2d_graph.add_node(graph::node::MAIN_PASS, pass_node_2d);
let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new(
graph::input::VIEW_ENTITY,
graph::node::MAIN_PASS,
MainPass2dNode::IN_VIEW,
)
.unwrap();
graph.add_sub_graph(graph::NAME, draw_2d_graph);
SlotType::Entity,
)]);
draw_2d_graph
.add_slot_edge(
input_node_id,
graph::input::VIEW_ENTITY,
graph::node::MAIN_PASS,
MainPass2dNode::IN_VIEW,
)
.unwrap();
graph.add_sub_graph(graph::NAME, draw_2d_graph);
});
}
}

Expand Down
66 changes: 34 additions & 32 deletions crates/bevy_core_pipeline/src/core_3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,39 +43,41 @@ impl Plugin for Core3dPlugin {
app.register_type::<Camera3d>()
.add_plugin(ExtractComponentPlugin::<Camera3d>::default());

let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};

render_app
.init_resource::<DrawFunctions<Opaque3d>>()
.init_resource::<DrawFunctions<AlphaMask3d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.add_system_to_stage(RenderStage::Extract, extract_core_3d_camera_phases)
.add_system_to_stage(RenderStage::Prepare, prepare_core_3d_depth_textures)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);

let pass_node_3d = MainPass3dNode::new(&mut render_app.world);
let mut graph = render_app.world.resource_mut::<RenderGraph>();

let mut draw_3d_graph = RenderGraph::default();
draw_3d_graph.add_node(graph::node::MAIN_PASS, pass_node_3d);
let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new(
graph::input::VIEW_ENTITY,
SlotType::Entity,
)]);
draw_3d_graph
.add_slot_edge(
input_node_id,
app.add_render_init(move |app| {
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};

render_app
.init_resource::<DrawFunctions<Opaque3d>>()
.init_resource::<DrawFunctions<AlphaMask3d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.add_system_to_stage(RenderStage::Extract, extract_core_3d_camera_phases)
.add_system_to_stage(RenderStage::Prepare, prepare_core_3d_depth_textures)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);

let pass_node_3d = MainPass3dNode::new(&mut render_app.world);
let mut graph = render_app.world.resource_mut::<RenderGraph>();

let mut draw_3d_graph = RenderGraph::default();
draw_3d_graph.add_node(graph::node::MAIN_PASS, pass_node_3d);
let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new(
graph::input::VIEW_ENTITY,
graph::node::MAIN_PASS,
MainPass3dNode::IN_VIEW,
)
.unwrap();
graph.add_sub_graph(graph::NAME, draw_3d_graph);
SlotType::Entity,
)]);
draw_3d_graph
.add_slot_edge(
input_node_id,
graph::input::VIEW_ENTITY,
graph::node::MAIN_PASS,
MainPass3dNode::IN_VIEW,
)
.unwrap();
graph.add_sub_graph(graph::NAME, draw_3d_graph);
});
}
}

Expand Down
122 changes: 62 additions & 60 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,67 +149,69 @@ impl Plugin for PbrPlugin {
},
);

let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};
app.add_render_init(move |app| {
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};

render_app
.add_system_to_stage(
RenderStage::Extract,
render::extract_clusters.label(RenderLightSystems::ExtractClusters),
)
.add_system_to_stage(
RenderStage::Extract,
render::extract_lights.label(RenderLightSystems::ExtractLights),
)
.add_system_to_stage(
RenderStage::Prepare,
// this is added as an exclusive system because it contributes new views. it must run (and have Commands applied)
// _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out
render::prepare_lights
.exclusive_system()
.label(RenderLightSystems::PrepareLights),
)
.add_system_to_stage(
RenderStage::Prepare,
// NOTE: This needs to run after prepare_lights. As prepare_lights is an exclusive system,
// just adding it to the non-exclusive systems in the Prepare stage means it runs after
// prepare_lights.
render::prepare_clusters.label(RenderLightSystems::PrepareClusters),
)
.add_system_to_stage(
RenderStage::Queue,
render::queue_shadows.label(RenderLightSystems::QueueShadows),
)
.add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>)
.init_resource::<ShadowPipeline>()
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<LightMeta>()
.init_resource::<GlobalLightMeta>()
.init_resource::<SpecializedMeshPipelines<ShadowPipeline>>();
render_app
.add_system_to_stage(
RenderStage::Extract,
render::extract_clusters.label(RenderLightSystems::ExtractClusters),
)
.add_system_to_stage(
RenderStage::Extract,
render::extract_lights.label(RenderLightSystems::ExtractLights),
)
.add_system_to_stage(
RenderStage::Prepare,
// this is added as an exclusive system because it contributes new views. it must run (and have Commands applied)
// _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out
render::prepare_lights
.exclusive_system()
.label(RenderLightSystems::PrepareLights),
)
.add_system_to_stage(
RenderStage::Prepare,
// NOTE: This needs to run after prepare_lights. As prepare_lights is an exclusive system,
// just adding it to the non-exclusive systems in the Prepare stage means it runs after
// prepare_lights.
render::prepare_clusters.label(RenderLightSystems::PrepareClusters),
)
.add_system_to_stage(
RenderStage::Queue,
render::queue_shadows.label(RenderLightSystems::QueueShadows),
)
.add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>)
.init_resource::<ShadowPipeline>()
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<LightMeta>()
.init_resource::<GlobalLightMeta>()
.init_resource::<SpecializedMeshPipelines<ShadowPipeline>>();

let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
render_app.add_render_command::<Shadow, DrawShadowMesh>();
let mut graph = render_app.world.resource_mut::<RenderGraph>();
let draw_3d_graph = graph
.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME)
.unwrap();
draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node);
draw_3d_graph
.add_node_edge(
draw_3d_graph::node::SHADOW_PASS,
bevy_core_pipeline::core_3d::graph::node::MAIN_PASS,
)
.unwrap();
draw_3d_graph
.add_slot_edge(
draw_3d_graph.input_node().unwrap().id,
bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY,
draw_3d_graph::node::SHADOW_PASS,
ShadowPassNode::IN_VIEW,
)
.unwrap();
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
render_app.add_render_command::<Shadow, DrawShadowMesh>();
let mut graph = render_app.world.resource_mut::<RenderGraph>();
let draw_3d_graph = graph
.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME)
.unwrap();
draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node);
draw_3d_graph
.add_node_edge(
draw_3d_graph::node::SHADOW_PASS,
bevy_core_pipeline::core_3d::graph::node::MAIN_PASS,
)
.unwrap();
draw_3d_graph
.add_slot_edge(
draw_3d_graph.input_node().unwrap().id,
bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY,
draw_3d_graph::node::SHADOW_PASS,
ShadowPassNode::IN_VIEW,
)
.unwrap();
});
}
}
Loading