diff --git a/crates/bevy_solari/src/lib.rs b/crates/bevy_solari/src/lib.rs index d5a22e014b8c5..6bce9508d539e 100644 --- a/crates/bevy_solari/src/lib.rs +++ b/crates/bevy_solari/src/lib.rs @@ -21,7 +21,15 @@ pub mod prelude { use crate::realtime::SolariLightingPlugin; use crate::scene::RaytracingScenePlugin; use bevy_app::{App, Plugin}; -use bevy_render::settings::WgpuFeatures; +use bevy_ecs::{ + resource::Resource, + schedule::{common_conditions::resource_exists, IntoScheduleConfigs, SystemSet}, + system::{Commands, Res}, +}; +use bevy_render::{ + renderer::RenderDevice, settings::WgpuFeatures, ExtractSchedule, Render, RenderStartup, +}; +use tracing::warn; /// An experimental plugin for raytraced lighting. /// @@ -35,7 +43,23 @@ pub struct SolariPlugin; impl Plugin for SolariPlugin { fn build(&self, app: &mut App) { - app.add_plugins((RaytracingScenePlugin, SolariLightingPlugin)); + app.add_plugins((RaytracingScenePlugin, SolariLightingPlugin)) + // Note: conditions only run once per schedule run. So even though these conditions + // could apply to many systems, they will only be checked once for the entire group. + .configure_sets( + RenderStartup, + SolariSystems + .after(check_solari_has_required_features) + .run_if(resource_exists::), + ) + .configure_sets( + ExtractSchedule, + SolariSystems.run_if(resource_exists::), + ) + .configure_sets( + Render, + SolariSystems.run_if(resource_exists::), + ); } } @@ -50,3 +74,26 @@ impl SolariPlugin { | WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY } } + +#[derive(SystemSet, PartialEq, Eq, Debug, Clone, Hash)] +pub struct SolariSystems; + +/// A resource to track whether the renderer has the required features for Solari systems. +#[derive(Resource)] +struct HasSolariRequiredFeatures; + +/// Check for the Solari required features once in startup, and insert a resource if the features +/// are enabled. +/// +/// Now systems can do a cheap check for if the resource exists. +fn check_solari_has_required_features(mut commands: Commands, render_device: Res) { + let features = render_device.features(); + if !features.contains(SolariPlugin::required_wgpu_features()) { + warn!( + "SolariSystems disabled. GPU lacks support for required features: {:?}.", + SolariPlugin::required_wgpu_features().difference(features) + ); + return; + } + commands.insert_resource(HasSolariRequiredFeatures); +} diff --git a/crates/bevy_solari/src/pathtracer/mod.rs b/crates/bevy_solari/src/pathtracer/mod.rs index 30cc15ba10540..35454dfc616c9 100644 --- a/crates/bevy_solari/src/pathtracer/mod.rs +++ b/crates/bevy_solari/src/pathtracer/mod.rs @@ -2,22 +2,22 @@ mod extract; mod node; mod prepare; -use crate::SolariPlugin; +use crate::{scene::init_raytracing_scene_bindings, SolariSystems}; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; -use bevy_ecs::{component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs}; +use bevy_ecs::{ + component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs, world::World, +}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ render_graph::{RenderGraphExt, ViewNodeRunner}, - renderer::RenderDevice, view::Hdr, - ExtractSchedule, Render, RenderApp, RenderSystems, + ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, }; use extract::extract_pathtracer; use node::PathtracerNode; use prepare::prepare_pathtracer_accumulation_texture; -use tracing::warn; /// Non-realtime pathtracing. /// @@ -30,32 +30,25 @@ impl Plugin for PathtracingPlugin { embedded_asset!(app, "pathtracer.wgsl"); app.register_type::(); - } - - fn finish(&self, app: &mut App) { - let render_app = app.sub_app_mut(RenderApp); - let render_device = render_app.world().resource::(); - let features = render_device.features(); - if !features.contains(SolariPlugin::required_wgpu_features()) { - warn!( - "PathtracingPlugin not loaded. GPU lacks support for required features: {:?}.", - SolariPlugin::required_wgpu_features().difference(features) - ); + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; - } + }; render_app - .add_systems(ExtractSchedule, extract_pathtracer) .add_systems( - Render, - prepare_pathtracer_accumulation_texture.in_set(RenderSystems::PrepareResources), - ) - .add_render_graph_node::>( - Core3d, - node::graph::PathtracerNode, + RenderStartup, + add_solari_pathtracing_render_graph_nodes + .after(init_raytracing_scene_bindings) + .in_set(SolariSystems), ) - .add_render_graph_edges(Core3d, (Node3d::EndMainPass, node::graph::PathtracerNode)); + .add_systems(ExtractSchedule, extract_pathtracer.in_set(SolariSystems)) + .add_systems( + Render, + prepare_pathtracer_accumulation_texture + .in_set(RenderSystems::PrepareResources) + .in_set(SolariSystems), + ); } } @@ -65,3 +58,15 @@ impl Plugin for PathtracingPlugin { pub struct Pathtracer { pub reset: bool, } + +// We only want to add these render graph nodes and edges if Solari required features are present. +// Making this a system that runs at RenderStartup allows a run condition to check for required +// features first. +fn add_solari_pathtracing_render_graph_nodes(world: &mut World) { + world + .add_render_graph_node::>( + Core3d, + node::graph::PathtracerNode, + ) + .add_render_graph_edges(Core3d, (Node3d::EndMainPass, node::graph::PathtracerNode)); +} diff --git a/crates/bevy_solari/src/realtime/mod.rs b/crates/bevy_solari/src/realtime/mod.rs index a8d6235f30831..9bbb24c82be05 100644 --- a/crates/bevy_solari/src/realtime/mod.rs +++ b/crates/bevy_solari/src/realtime/mod.rs @@ -2,26 +2,26 @@ mod extract; mod node; mod prepare; -use crate::SolariPlugin; +use crate::{scene::init_raytracing_scene_bindings, SolariSystems}; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass}, }; -use bevy_ecs::{component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs}; +use bevy_ecs::{ + component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs, world::World, +}; use bevy_pbr::DefaultOpaqueRendererMethod; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ render_graph::{RenderGraphExt, ViewNodeRunner}, - renderer::RenderDevice, view::Hdr, - ExtractSchedule, Render, RenderApp, RenderSystems, + ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, }; use extract::extract_solari_lighting; use node::SolariLightingNode; use prepare::prepare_solari_lighting_resources; -use tracing::warn; pub struct SolariLightingPlugin; @@ -31,33 +31,27 @@ impl Plugin for SolariLightingPlugin { app.register_type::() .insert_resource(DefaultOpaqueRendererMethod::deferred()); - } - - fn finish(&self, app: &mut App) { - let render_app = app.sub_app_mut(RenderApp); - let render_device = render_app.world().resource::(); - let features = render_device.features(); - if !features.contains(SolariPlugin::required_wgpu_features()) { - warn!( - "SolariLightingPlugin not loaded. GPU lacks support for required features: {:?}.", - SolariPlugin::required_wgpu_features().difference(features) - ); + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; - } + }; + render_app - .add_systems(ExtractSchedule, extract_solari_lighting) .add_systems( - Render, - prepare_solari_lighting_resources.in_set(RenderSystems::PrepareResources), + RenderStartup, + add_solari_lighting_render_graph_nodes + .after(init_raytracing_scene_bindings) + .in_set(SolariSystems), ) - .add_render_graph_node::>( - Core3d, - node::graph::SolariLightingNode, + .add_systems( + ExtractSchedule, + extract_solari_lighting.in_set(SolariSystems), ) - .add_render_graph_edges( - Core3d, - (Node3d::EndMainPass, node::graph::SolariLightingNode), + .add_systems( + Render, + prepare_solari_lighting_resources + .in_set(RenderSystems::PrepareResources) + .in_set(SolariSystems), ); } } @@ -87,3 +81,18 @@ impl Default for SolariLighting { } } } + +// We only want to add these render graph nodes and edges if Solari required features are present. +// Making this a system that runs at RenderStartup allows a run condition to check for required +// features first. +fn add_solari_lighting_render_graph_nodes(world: &mut World) { + world + .add_render_graph_node::>( + Core3d, + node::graph::SolariLightingNode, + ) + .add_render_graph_edges( + Core3d, + (Node3d::EndMainPass, node::graph::SolariLightingNode), + ); +} diff --git a/crates/bevy_solari/src/scene/binder.rs b/crates/bevy_solari/src/scene/binder.rs index f14b5dbe23b6f..e2fc139814dca 100644 --- a/crates/bevy_solari/src/scene/binder.rs +++ b/crates/bevy_solari/src/scene/binder.rs @@ -4,8 +4,7 @@ use bevy_color::{ColorToComponents, LinearRgba}; use bevy_ecs::{ entity::{Entity, EntityHashMap}, resource::Resource, - system::{Query, Res, ResMut}, - world::{FromWorld, World}, + system::{Commands, Query, Res, ResMut}, }; use bevy_math::{ops::cos, Mat4, Vec3}; use bevy_pbr::{ExtractedDirectionalLight, MeshMaterial3d, StandardMaterial}; @@ -265,36 +264,35 @@ pub fn prepare_raytracing_scene_bindings( )); } -impl FromWorld for RaytracingSceneBindings { - fn from_world(world: &mut World) -> Self { - let render_device = world.resource::(); - - Self { - bind_group: None, - bind_group_layout: render_device.create_bind_group_layout( - "raytracing_scene_bind_group_layout", - &BindGroupLayoutEntries::sequential( - ShaderStages::COMPUTE, - ( - storage_buffer_read_only_sized(false, None).count(MAX_MESH_SLAB_COUNT), - storage_buffer_read_only_sized(false, None).count(MAX_MESH_SLAB_COUNT), - texture_2d(TextureSampleType::Float { filterable: true }) - .count(MAX_TEXTURE_COUNT), - sampler(SamplerBindingType::Filtering).count(MAX_TEXTURE_COUNT), - storage_buffer_read_only_sized(false, None), - acceleration_structure(), - storage_buffer_read_only_sized(false, None), - storage_buffer_read_only_sized(false, None), - storage_buffer_read_only_sized(false, None), - storage_buffer_read_only_sized(false, None), - storage_buffer_read_only_sized(false, None), - storage_buffer_read_only_sized(false, None), - ), +pub(crate) fn init_raytracing_scene_bindings( + mut commands: Commands, + render_device: Res, +) { + commands.insert_resource(RaytracingSceneBindings { + bind_group: None, + bind_group_layout: render_device.create_bind_group_layout( + "raytracing_scene_bind_group_layout", + &BindGroupLayoutEntries::sequential( + ShaderStages::COMPUTE, + ( + storage_buffer_read_only_sized(false, None).count(MAX_MESH_SLAB_COUNT), + storage_buffer_read_only_sized(false, None).count(MAX_MESH_SLAB_COUNT), + texture_2d(TextureSampleType::Float { filterable: true }) + .count(MAX_TEXTURE_COUNT), + sampler(SamplerBindingType::Filtering).count(MAX_TEXTURE_COUNT), + storage_buffer_read_only_sized(false, None), + acceleration_structure(), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), ), ), - previous_frame_light_entities: Vec::new(), - } - } + ), + previous_frame_light_entities: Vec::new(), + }); } struct CachedBindingArray { diff --git a/crates/bevy_solari/src/scene/mod.rs b/crates/bevy_solari/src/scene/mod.rs index a68e126480a34..c95fc8c26d910 100644 --- a/crates/bevy_solari/src/scene/mod.rs +++ b/crates/bevy_solari/src/scene/mod.rs @@ -6,9 +6,11 @@ mod types; pub use binder::RaytracingSceneBindings; pub use types::RaytracingMesh3d; -use crate::SolariPlugin; +pub(crate) use binder::init_raytracing_scene_bindings; + +use crate::SolariSystems; use bevy_app::{App, Plugin}; -use bevy_ecs::schedule::IntoScheduleConfigs; +use bevy_ecs::{schedule::IntoScheduleConfigs, system::ResMut}; use bevy_render::{ extract_resource::ExtractResourcePlugin, load_shader_library, @@ -18,13 +20,11 @@ use bevy_render::{ }, render_asset::prepare_assets, render_resource::BufferUsages, - renderer::RenderDevice, - ExtractSchedule, Render, RenderApp, RenderSystems, + ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, }; use binder::prepare_raytracing_scene_bindings; use blas::{prepare_raytracing_blas, BlasManager}; use extract::{extract_raytracing_scene, StandardMaterialAssets}; -use tracing::warn; /// Creates acceleration structures and binding arrays of resources for raytracing. pub struct RaytracingScenePlugin; @@ -34,35 +34,28 @@ impl Plugin for RaytracingScenePlugin { load_shader_library!(app, "raytracing_scene_bindings.wgsl"); load_shader_library!(app, "sampling.wgsl"); - app.register_type::(); - } + app.register_type::() + .add_plugins(ExtractResourcePlugin::::default()); - fn finish(&self, app: &mut App) { - let render_app = app.sub_app_mut(RenderApp); - let render_device = render_app.world().resource::(); - let features = render_device.features(); - if !features.contains(SolariPlugin::required_wgpu_features()) { - warn!( - "RaytracingScenePlugin not loaded. GPU lacks support for required features: {:?}.", - SolariPlugin::required_wgpu_features().difference(features) - ); + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; - } - - app.add_plugins(ExtractResourcePlugin::::default()); - - let render_app = app.sub_app_mut(RenderApp); - - render_app - .world_mut() - .resource_mut::() - .extra_buffer_usages |= BufferUsages::BLAS_INPUT | BufferUsages::STORAGE; + }; render_app .init_resource::() .init_resource::() - .init_resource::() - .add_systems(ExtractSchedule, extract_raytracing_scene) + .add_systems( + RenderStartup, + ( + add_raytracing_extra_mesh_buffer_usages, + init_raytracing_scene_bindings, + ) + .in_set(SolariSystems), + ) + .add_systems( + ExtractSchedule, + extract_raytracing_scene.in_set(SolariSystems), + ) .add_systems( Render, ( @@ -71,7 +64,12 @@ impl Plugin for RaytracingScenePlugin { .before(prepare_assets::) .after(allocate_and_free_meshes), prepare_raytracing_scene_bindings.in_set(RenderSystems::PrepareBindGroups), - ), + ) + .in_set(SolariSystems), ); } } + +fn add_raytracing_extra_mesh_buffer_usages(mut mesh_allocator: ResMut) { + mesh_allocator.extra_buffer_usages |= BufferUsages::BLAS_INPUT | BufferUsages::STORAGE; +}