Use RenderStartup for SolariPlugins.

This commit is contained in:
andriyDev 2025-07-01 10:56:10 -07:00
parent 1525dff7ad
commit fcdc0822df
5 changed files with 179 additions and 113 deletions

View File

@ -20,8 +20,16 @@ pub mod prelude {
use crate::realtime::SolariLightingPlugin;
use crate::scene::RaytracingScenePlugin;
use bevy_app::{PluginGroup, PluginGroupBuilder};
use bevy_render::settings::WgpuFeatures;
use bevy_app::{App, Plugin, PluginGroup, PluginGroupBuilder};
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 set of plugins for raytraced lighting.
///
@ -36,6 +44,7 @@ pub struct SolariPlugins;
impl PluginGroup for SolariPlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(SolariCorePlugin)
.add(RaytracingScenePlugin)
.add(SolariLightingPlugin)
}
@ -52,3 +61,50 @@ impl SolariPlugins {
| WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY
}
}
struct SolariCorePlugin;
impl Plugin for SolariCorePlugin {
fn build(&self, app: &mut App) {
app.add_systems(RenderStartup, check_solari_has_required_features)
// 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::<HasSolariRequiredFeatures>),
)
.configure_sets(
ExtractSchedule,
SolariSystems.run_if(resource_exists::<HasSolariRequiredFeatures>),
)
.configure_sets(
Render,
SolariSystems.run_if(resource_exists::<HasSolariRequiredFeatures>),
);
}
}
#[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<RenderDevice>) {
let features = render_device.features();
if !features.contains(SolariPlugins::required_wgpu_features()) {
warn!(
"SolariSystems disabled. GPU lacks support for required features: {:?}.",
SolariPlugins::required_wgpu_features().difference(features)
);
return;
}
commands.insert_resource(HasSolariRequiredFeatures);
}

View File

@ -2,22 +2,22 @@ mod extract;
mod node;
mod prepare;
use crate::SolariPlugins;
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::<Pathtracer>();
}
fn finish(&self, app: &mut App) {
let render_app = app.sub_app_mut(RenderApp);
let render_device = render_app.world().resource::<RenderDevice>();
let features = render_device.features();
if !features.contains(SolariPlugins::required_wgpu_features()) {
warn!(
"PathtracingPlugin not loaded. GPU lacks support for required features: {:?}.",
SolariPlugins::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(
RenderStartup,
add_solari_pathtracing_render_graph_nodes
.after(init_raytracing_scene_bindings)
.in_set(SolariSystems),
)
.add_systems(ExtractSchedule, extract_pathtracer.in_set(SolariSystems))
.add_systems(
Render,
prepare_pathtracer_accumulation_texture.in_set(RenderSystems::PrepareResources),
)
.add_render_graph_node::<ViewNodeRunner<PathtracerNode>>(
Core3d,
node::graph::PathtracerNode,
)
.add_render_graph_edges(Core3d, (Node3d::EndMainPass, node::graph::PathtracerNode));
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::<ViewNodeRunner<PathtracerNode>>(
Core3d,
node::graph::PathtracerNode,
)
.add_render_graph_edges(Core3d, (Node3d::EndMainPass, node::graph::PathtracerNode));
}

View File

@ -2,26 +2,26 @@ mod extract;
mod node;
mod prepare;
use crate::SolariPlugins;
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::<SolariLighting>()
.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::<RenderDevice>();
let features = render_device.features();
if !features.contains(SolariPlugins::required_wgpu_features()) {
warn!(
"SolariLightingPlugin not loaded. GPU lacks support for required features: {:?}.",
SolariPlugins::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(
RenderStartup,
add_solari_lighting_render_graph_nodes
.after(init_raytracing_scene_bindings)
.in_set(SolariSystems),
)
.add_systems(
ExtractSchedule,
extract_solari_lighting.in_set(SolariSystems),
)
.add_systems(
Render,
prepare_solari_lighting_resources.in_set(RenderSystems::PrepareResources),
)
.add_render_graph_node::<ViewNodeRunner<SolariLightingNode>>(
Core3d,
node::graph::SolariLightingNode,
)
.add_render_graph_edges(
Core3d,
(Node3d::EndMainPass, node::graph::SolariLightingNode),
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::<ViewNodeRunner<SolariLightingNode>>(
Core3d,
node::graph::SolariLightingNode,
)
.add_render_graph_edges(
Core3d,
(Node3d::EndMainPass, node::graph::SolariLightingNode),
);
}

View File

@ -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::<RenderDevice>();
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<RenderDevice>,
) {
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<T, I: Eq + Hash> {

View File

@ -6,9 +6,11 @@ mod types;
pub use binder::RaytracingSceneBindings;
pub use types::RaytracingMesh3d;
use crate::SolariPlugins;
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::<RaytracingMesh3d>();
}
app.register_type::<RaytracingMesh3d>()
.add_plugins(ExtractResourcePlugin::<StandardMaterialAssets>::default());
fn finish(&self, app: &mut App) {
let render_app = app.sub_app_mut(RenderApp);
let render_device = render_app.world().resource::<RenderDevice>();
let features = render_device.features();
if !features.contains(SolariPlugins::required_wgpu_features()) {
warn!(
"RaytracingScenePlugin not loaded. GPU lacks support for required features: {:?}.",
SolariPlugins::required_wgpu_features().difference(features)
);
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
}
app.add_plugins(ExtractResourcePlugin::<StandardMaterialAssets>::default());
let render_app = app.sub_app_mut(RenderApp);
render_app
.world_mut()
.resource_mut::<MeshAllocator>()
.extra_buffer_usages |= BufferUsages::BLAS_INPUT | BufferUsages::STORAGE;
};
render_app
.init_resource::<BlasManager>()
.init_resource::<StandardMaterialAssets>()
.init_resource::<RaytracingSceneBindings>()
.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::<RenderMesh>)
.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<MeshAllocator>) {
mesh_allocator.extra_buffer_usages |= BufferUsages::BLAS_INPUT | BufferUsages::STORAGE;
}