Skip to content

Use RenderStartup for screen space reflections. #20194

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

Merged
merged 2 commits into from
Jul 21, 2025
Merged
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
182 changes: 87 additions & 95 deletions crates/bevy_pbr/src/ssr/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Screen space reflections implemented via raymarching.

use bevy_app::{App, Plugin};
use bevy_asset::{load_embedded_asset, Handle};
use bevy_asset::{load_embedded_asset, AssetServer, Handle};
use bevy_core_pipeline::{
core_3d::{
graph::{Core3d, Node3d},
Expand All @@ -19,7 +19,7 @@ use bevy_ecs::{
resource::Resource,
schedule::IntoScheduleConfigs as _,
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
world::{FromWorld, World},
world::World,
};
use bevy_image::BevyDefault as _;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
Expand All @@ -36,7 +36,7 @@ use bevy_render::{
},
renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset},
Render, RenderApp, RenderSystems,
Render, RenderApp, RenderStartup, RenderSystems,
};
use bevy_render::{load_shader_library, render_graph::RenderGraph};
use bevy_utils::{once, prelude::default};
Expand Down Expand Up @@ -190,50 +190,41 @@ impl Plugin for ScreenSpaceReflectionsPlugin {

render_app
.init_resource::<ScreenSpaceReflectionsBuffer>()
.init_resource::<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>()
.add_systems(
RenderStartup,
(
init_screen_space_reflections_pipeline,
add_screen_space_reflections_render_graph_edges,
),
)
.add_systems(Render, prepare_ssr_pipelines.in_set(RenderSystems::Prepare))
.add_systems(
Render,
prepare_ssr_settings.in_set(RenderSystems::PrepareResources),
)
// Note: we add this node here but then we add edges in
// `add_screen_space_reflections_render_graph_edges`.
.add_render_graph_node::<ViewNodeRunner<ScreenSpaceReflectionsNode>>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any reason why you didn't move this? Is it just to keep behaviour as close as possible? Oh, could it break something later if the node isn't added in build?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mostly to try to keep the diff small, and also because initializing ViewNodeRunner requires &mut World, so I was hoping to avoid requiring &mut World in the system.

In fact I was considering going the other way and instead moving the one required graph edge here, and then only do the conditional edge in the system. But alas I decided to try to keep the edges together at least.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, keeping the diff small is fine by me. We'll likely want to do a cleanup pass after 0.18 but it's probably good to keep everything as close as what it was before for now.

Core3d,
NodePbr::ScreenSpaceReflections,
);
}
}

fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
fn add_screen_space_reflections_render_graph_edges(mut render_graph: ResMut<RenderGraph>) {
let subgraph = render_graph.sub_graph_mut(Core3d);

render_app
.init_resource::<ScreenSpaceReflectionsPipeline>()
.init_resource::<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>();

// only reference the default deferred lighting pass
// if it has been added
let has_default_deferred_lighting_pass = render_app
.world_mut()
.resource_mut::<RenderGraph>()
.sub_graph(Core3d)
.get_node_state(NodePbr::DeferredLightingPass)
.is_ok();

if has_default_deferred_lighting_pass {
render_app.add_render_graph_edges(
Core3d,
(
NodePbr::DeferredLightingPass,
NodePbr::ScreenSpaceReflections,
Node3d::MainOpaquePass,
),
);
} else {
render_app.add_render_graph_edges(
Core3d,
(NodePbr::ScreenSpaceReflections, Node3d::MainOpaquePass),
);
}
subgraph.add_node_edge(NodePbr::ScreenSpaceReflections, Node3d::MainOpaquePass);

if subgraph
.get_node_state(NodePbr::DeferredLightingPass)
.is_ok()
{
subgraph.add_node_edge(
NodePbr::DeferredLightingPass,
NodePbr::ScreenSpaceReflections,
);
}
}

Expand Down Expand Up @@ -343,68 +334,69 @@ impl ViewNode for ScreenSpaceReflectionsNode {
}
}

impl FromWorld for ScreenSpaceReflectionsPipeline {
fn from_world(world: &mut World) -> Self {
let mesh_view_layouts = world.resource::<MeshPipelineViewLayouts>().clone();
let render_device = world.resource::<RenderDevice>();
let render_adapter = world.resource::<RenderAdapter>();

// Create the bind group layout.
let bind_group_layout = render_device.create_bind_group_layout(
"SSR bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
binding_types::sampler(SamplerBindingType::Filtering),
binding_types::sampler(SamplerBindingType::Filtering),
binding_types::sampler(SamplerBindingType::NonFiltering),
),
pub fn init_screen_space_reflections_pipeline(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_adapter: Res<RenderAdapter>,
mesh_view_layouts: Res<MeshPipelineViewLayouts>,
fullscreen_shader: Res<FullscreenShader>,
asset_server: Res<AssetServer>,
) {
// Create the bind group layout.
let bind_group_layout = render_device.create_bind_group_layout(
"SSR bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
binding_types::sampler(SamplerBindingType::Filtering),
binding_types::sampler(SamplerBindingType::Filtering),
binding_types::sampler(SamplerBindingType::NonFiltering),
),
);

// Create the samplers we need.

let color_sampler = render_device.create_sampler(&SamplerDescriptor {
label: "SSR color sampler".into(),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..default()
});

let depth_linear_sampler = render_device.create_sampler(&SamplerDescriptor {
label: "SSR depth linear sampler".into(),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..default()
});

let depth_nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
label: "SSR depth nearest sampler".into(),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
mag_filter: FilterMode::Nearest,
min_filter: FilterMode::Nearest,
..default()
});
),
);

Self {
mesh_view_layouts,
color_sampler,
depth_linear_sampler,
depth_nearest_sampler,
bind_group_layout,
binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter),
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
// Even though ssr was loaded using load_shader_library, we can still access it like a
// normal embedded asset (so we can use it as both a library or a kernel).
fragment_shader: load_embedded_asset!(world, "ssr.wgsl"),
}
}
// Create the samplers we need.

let color_sampler = render_device.create_sampler(&SamplerDescriptor {
label: "SSR color sampler".into(),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..default()
});

let depth_linear_sampler = render_device.create_sampler(&SamplerDescriptor {
label: "SSR depth linear sampler".into(),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..default()
});

let depth_nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
label: "SSR depth nearest sampler".into(),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
mag_filter: FilterMode::Nearest,
min_filter: FilterMode::Nearest,
..default()
});

commands.insert_resource(ScreenSpaceReflectionsPipeline {
mesh_view_layouts: mesh_view_layouts.clone(),
color_sampler,
depth_linear_sampler,
depth_nearest_sampler,
bind_group_layout,
binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter),
fullscreen_shader: fullscreen_shader.clone(),
// Even though ssr was loaded using load_shader_library, we can still access it like a
// normal embedded asset (so we can use it as both a library or a kernel).
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "ssr.wgsl"),
});
}

/// Sets up screen space reflection pipelines for each applicable view.
Expand Down
1 change: 1 addition & 0 deletions release-content/migration-guides/render_startup.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The following are the (public) resources that are now initialized in `RenderStar
- `PrepassPipeline`
- `PrepassViewBindGroup`
- `Wireframe3dPipeline`
- `ScreenSpaceReflectionsPipeline`
- `MaterialPipeline`
- `Wireframe2dPipeline`
- `Material2dPipeline`
Expand Down
Loading