mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 22:20:20 +00:00
Reuse texture when resolving multiple passes (#3552)
# Objective Fixes https://github.com/bevyengine/bevy/issues/3499 ## Solution Uses a `HashMap` from `RenderTarget` to sampled textures when preparing `ViewTarget`s to ensure that two passes with the same render target get sampled to the same texture. This builds on and depends on https://github.com/bevyengine/bevy/pull/3412, so this will be a draft PR until #3412 is merged. All changes for this PR are in the last commit.
This commit is contained in:
parent
193e8c4ada
commit
5a297d7903
7 changed files with 280 additions and 37 deletions
|
@ -216,6 +216,10 @@ path = "examples/3d/texture.rs"
|
||||||
name = "render_to_texture"
|
name = "render_to_texture"
|
||||||
path = "examples/3d/render_to_texture.rs"
|
path = "examples/3d/render_to_texture.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "two_passes"
|
||||||
|
path = "examples/3d/two_passes.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "update_gltf_scene"
|
name = "update_gltf_scene"
|
||||||
path = "examples/3d/update_gltf_scene.rs"
|
path = "examples/3d/update_gltf_scene.rs"
|
||||||
|
|
|
@ -23,7 +23,7 @@ use bevy_app::{App, Plugin};
|
||||||
use bevy_core::FloatOrd;
|
use bevy_core::FloatOrd;
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::{ActiveCamera, Camera2d, Camera3d, RenderTarget},
|
camera::{ActiveCamera, Camera2d, Camera3d, ExtractedCamera, RenderTarget},
|
||||||
color::Color,
|
color::Color,
|
||||||
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
|
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
|
||||||
render_phase::{
|
render_phase::{
|
||||||
|
@ -390,7 +390,7 @@ pub fn prepare_core_views_system(
|
||||||
msaa: Res<Msaa>,
|
msaa: Res<Msaa>,
|
||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
views_3d: Query<
|
views_3d: Query<
|
||||||
(Entity, &ExtractedView),
|
(Entity, &ExtractedView, Option<&ExtractedCamera>),
|
||||||
(
|
(
|
||||||
With<RenderPhase<Opaque3d>>,
|
With<RenderPhase<Opaque3d>>,
|
||||||
With<RenderPhase<AlphaMask3d>>,
|
With<RenderPhase<AlphaMask3d>>,
|
||||||
|
@ -398,8 +398,10 @@ pub fn prepare_core_views_system(
|
||||||
),
|
),
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
for (entity, view) in views_3d.iter() {
|
let mut textures = HashMap::default();
|
||||||
let cached_texture = texture_cache.get(
|
for (entity, view, camera) in views_3d.iter() {
|
||||||
|
let mut get_cached_texture = || {
|
||||||
|
texture_cache.get(
|
||||||
&render_device,
|
&render_device,
|
||||||
TextureDescriptor {
|
TextureDescriptor {
|
||||||
label: Some("view_depth_texture"),
|
label: Some("view_depth_texture"),
|
||||||
|
@ -415,7 +417,16 @@ pub fn prepare_core_views_system(
|
||||||
* bit depth for better performance */
|
* bit depth for better performance */
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
};
|
||||||
|
let cached_texture = if let Some(camera) = camera {
|
||||||
|
textures
|
||||||
|
.entry(camera.target.clone())
|
||||||
|
.or_insert_with(get_cached_texture)
|
||||||
|
.clone()
|
||||||
|
} else {
|
||||||
|
get_cached_texture()
|
||||||
|
};
|
||||||
commands.entity(entity).insert(ViewDepthTexture {
|
commands.entity(entity).insert(ViewDepthTexture {
|
||||||
texture: cached_texture.texture,
|
texture: cached_texture.texture,
|
||||||
view: cached_texture.default_view,
|
view: cached_texture.default_view,
|
||||||
|
|
|
@ -142,12 +142,15 @@ impl Node for MainPass3dNode {
|
||||||
})],
|
})],
|
||||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||||
view: &depth.view,
|
view: &depth.view,
|
||||||
// NOTE: For the transparent pass we load the depth buffer but do not write to it.
|
// NOTE: For the transparent pass we load the depth buffer. There should be no
|
||||||
|
// need to write to it, but store is set to `true` as a workaround for issue #3776,
|
||||||
|
// https://github.com/bevyengine/bevy/issues/3776
|
||||||
|
// so that wgpu does not clear the depth buffer.
|
||||||
// As the opaque and alpha mask passes run first, opaque meshes can occlude
|
// As the opaque and alpha mask passes run first, opaque meshes can occlude
|
||||||
// transparent ones.
|
// transparent ones.
|
||||||
depth_ops: Some(Operations {
|
depth_ops: Some(Operations {
|
||||||
load: LoadOp::Load,
|
load: LoadOp::Load,
|
||||||
store: false,
|
store: true,
|
||||||
}),
|
}),
|
||||||
stencil_ops: None,
|
stencil_ops: None,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct CachedTextureMeta {
|
||||||
/// A cached GPU [`Texture`] with corresponding [`TextureView`].
|
/// A cached GPU [`Texture`] with corresponding [`TextureView`].
|
||||||
/// This is useful for textures that are created repeatedly (each frame) in the rendering process
|
/// This is useful for textures that are created repeatedly (each frame) in the rendering process
|
||||||
/// to reduce the amount of GPU memory allocations.
|
/// to reduce the amount of GPU memory allocations.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct CachedTexture {
|
pub struct CachedTexture {
|
||||||
pub texture: Texture,
|
pub texture: Texture,
|
||||||
pub default_view: TextureView,
|
pub default_view: TextureView,
|
||||||
|
|
|
@ -21,6 +21,7 @@ use bevy_app::{App, Plugin};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_math::{Mat4, Vec3};
|
use bevy_math::{Mat4, Vec3};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
|
use bevy_utils::HashMap;
|
||||||
|
|
||||||
pub struct ViewPlugin;
|
pub struct ViewPlugin;
|
||||||
|
|
||||||
|
@ -181,11 +182,15 @@ fn prepare_view_targets(
|
||||||
mut texture_cache: ResMut<TextureCache>,
|
mut texture_cache: ResMut<TextureCache>,
|
||||||
cameras: Query<(Entity, &ExtractedCamera)>,
|
cameras: Query<(Entity, &ExtractedCamera)>,
|
||||||
) {
|
) {
|
||||||
|
let mut sampled_textures = HashMap::default();
|
||||||
for (entity, camera) in cameras.iter() {
|
for (entity, camera) in cameras.iter() {
|
||||||
if let Some(size) = camera.physical_size {
|
if let Some(size) = camera.physical_size {
|
||||||
if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) {
|
if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) {
|
||||||
let sampled_target = if msaa.samples > 1 {
|
let sampled_target = if msaa.samples > 1 {
|
||||||
let sampled_texture = texture_cache.get(
|
let sampled_texture = sampled_textures
|
||||||
|
.entry(camera.target.clone())
|
||||||
|
.or_insert_with(|| {
|
||||||
|
texture_cache.get(
|
||||||
&render_device,
|
&render_device,
|
||||||
TextureDescriptor {
|
TextureDescriptor {
|
||||||
label: Some("sampled_color_attachment_texture"),
|
label: Some("sampled_color_attachment_texture"),
|
||||||
|
@ -200,7 +205,8 @@ fn prepare_view_targets(
|
||||||
format: TextureFormat::bevy_default(),
|
format: TextureFormat::bevy_default(),
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
});
|
||||||
Some(sampled_texture.default_view.clone())
|
Some(sampled_texture.default_view.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
217
examples/3d/two_passes.rs
Normal file
217
examples/3d/two_passes.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
use bevy::{
|
||||||
|
core_pipeline::{draw_3d_graph, node, AlphaMask3d, Opaque3d, Transparent3d},
|
||||||
|
prelude::*,
|
||||||
|
render::{
|
||||||
|
camera::{ActiveCamera, Camera, CameraTypePlugin, RenderTarget},
|
||||||
|
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue},
|
||||||
|
render_phase::RenderPhase,
|
||||||
|
renderer::RenderContext,
|
||||||
|
view::RenderLayers,
|
||||||
|
RenderApp, RenderStage,
|
||||||
|
},
|
||||||
|
window::WindowId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The name of the final node of the first pass.
|
||||||
|
pub const FIRST_PASS_DRIVER: &str = "first_pass_driver";
|
||||||
|
|
||||||
|
// Marks the camera that determines the view rendered in the first pass.
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
struct FirstPassCamera;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.insert_resource(Msaa { samples: 4 })
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugin(CameraTypePlugin::<FirstPassCamera>::default())
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(cube_rotator_system)
|
||||||
|
.add_system(rotator_system)
|
||||||
|
.add_system(toggle_msaa);
|
||||||
|
|
||||||
|
let render_app = app.sub_app_mut(RenderApp);
|
||||||
|
let driver = FirstPassCameraDriver::new(&mut render_app.world);
|
||||||
|
|
||||||
|
// This will add 3D render phases for the new camera.
|
||||||
|
render_app.add_system_to_stage(RenderStage::Extract, extract_first_pass_camera_phases);
|
||||||
|
|
||||||
|
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
|
||||||
|
|
||||||
|
// Add a node for the first pass.
|
||||||
|
graph.add_node(FIRST_PASS_DRIVER, driver);
|
||||||
|
|
||||||
|
// The first pass's dependencies include those of the main pass.
|
||||||
|
graph
|
||||||
|
.add_node_edge(node::MAIN_PASS_DEPENDENCIES, FIRST_PASS_DRIVER)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Insert the first pass node: CLEAR_PASS_DRIVER -> FIRST_PASS_DRIVER -> MAIN_PASS_DRIVER
|
||||||
|
graph
|
||||||
|
.add_node_edge(node::CLEAR_PASS_DRIVER, FIRST_PASS_DRIVER)
|
||||||
|
.unwrap();
|
||||||
|
graph
|
||||||
|
.add_node_edge(FIRST_PASS_DRIVER, node::MAIN_PASS_DRIVER)
|
||||||
|
.unwrap();
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add 3D render phases for FirstPassCamera.
|
||||||
|
fn extract_first_pass_camera_phases(
|
||||||
|
mut commands: Commands,
|
||||||
|
active: Res<ActiveCamera<FirstPassCamera>>,
|
||||||
|
) {
|
||||||
|
if let Some(entity) = active.get() {
|
||||||
|
commands.get_or_spawn(entity).insert_bundle((
|
||||||
|
RenderPhase::<Opaque3d>::default(),
|
||||||
|
RenderPhase::<AlphaMask3d>::default(),
|
||||||
|
RenderPhase::<Transparent3d>::default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A node for the first pass camera that runs draw_3d_graph with this camera.
|
||||||
|
struct FirstPassCameraDriver {
|
||||||
|
query: QueryState<Entity, With<FirstPassCamera>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FirstPassCameraDriver {
|
||||||
|
pub fn new(render_world: &mut World) -> Self {
|
||||||
|
Self {
|
||||||
|
query: QueryState::new(render_world),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node for FirstPassCameraDriver {
|
||||||
|
fn update(&mut self, world: &mut World) {
|
||||||
|
self.query.update_archetypes(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
graph: &mut RenderGraphContext,
|
||||||
|
_render_context: &mut RenderContext,
|
||||||
|
world: &World,
|
||||||
|
) -> Result<(), NodeRunError> {
|
||||||
|
for camera in self.query.iter_manual(world) {
|
||||||
|
graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(camera)])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marks the first pass cube.
|
||||||
|
#[derive(Component)]
|
||||||
|
struct FirstPassCube;
|
||||||
|
|
||||||
|
// Marks the main pass cube.
|
||||||
|
#[derive(Component)]
|
||||||
|
struct MainPassCube;
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 4.0 }));
|
||||||
|
let cube_material_handle = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::GREEN,
|
||||||
|
reflectance: 0.02,
|
||||||
|
unlit: false,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let split = 2.0;
|
||||||
|
|
||||||
|
// This specifies the layer used for the first pass, which will be attached to the first pass camera and cube.
|
||||||
|
let first_pass_layer = RenderLayers::layer(1);
|
||||||
|
|
||||||
|
// The first pass cube.
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PbrBundle {
|
||||||
|
mesh: cube_handle,
|
||||||
|
material: cube_material_handle,
|
||||||
|
transform: Transform::from_translation(Vec3::new(-split, 0.0, 1.0)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert(FirstPassCube)
|
||||||
|
.insert(first_pass_layer);
|
||||||
|
|
||||||
|
// Light
|
||||||
|
// NOTE: Currently lights are shared between passes - see https://github.com/bevyengine/bevy/issues/3462
|
||||||
|
commands.spawn_bundle(PointLightBundle {
|
||||||
|
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// First pass camera
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PerspectiveCameraBundle::<FirstPassCamera> {
|
||||||
|
camera: Camera {
|
||||||
|
target: RenderTarget::Window(WindowId::primary()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0))
|
||||||
|
.looking_at(Vec3::default(), Vec3::Y),
|
||||||
|
..PerspectiveCameraBundle::new()
|
||||||
|
})
|
||||||
|
.insert(first_pass_layer);
|
||||||
|
|
||||||
|
let cube_size = 4.0;
|
||||||
|
let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size)));
|
||||||
|
|
||||||
|
let material_handle = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::RED,
|
||||||
|
reflectance: 0.02,
|
||||||
|
unlit: false,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Main pass cube.
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PbrBundle {
|
||||||
|
mesh: cube_handle,
|
||||||
|
material: material_handle,
|
||||||
|
transform: Transform {
|
||||||
|
translation: Vec3::new(split, 0.0, -4.5),
|
||||||
|
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert(MainPassCube);
|
||||||
|
|
||||||
|
// The main pass camera.
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0))
|
||||||
|
.looking_at(Vec3::default(), Vec3::Y),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rotates the inner cube (first pass)
|
||||||
|
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<FirstPassCube>>) {
|
||||||
|
for mut transform in query.iter_mut() {
|
||||||
|
transform.rotation *= Quat::from_rotation_x(1.5 * time.delta_seconds());
|
||||||
|
transform.rotation *= Quat::from_rotation_z(1.3 * time.delta_seconds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rotates the outer cube (main pass)
|
||||||
|
fn cube_rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<MainPassCube>>) {
|
||||||
|
for mut transform in query.iter_mut() {
|
||||||
|
transform.rotation *= Quat::from_rotation_x(1.0 * time.delta_seconds());
|
||||||
|
transform.rotation *= Quat::from_rotation_y(0.7 * time.delta_seconds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_msaa(input: Res<Input<KeyCode>>, mut msaa: ResMut<Msaa>) {
|
||||||
|
if input.just_pressed(KeyCode::M) {
|
||||||
|
if msaa.samples == 4 {
|
||||||
|
info!("Not using MSAA");
|
||||||
|
msaa.samples = 1;
|
||||||
|
} else {
|
||||||
|
info!("Using 4x MSAA");
|
||||||
|
msaa.samples = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,6 +109,7 @@ Example | File | Description
|
||||||
`parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
|
`parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
|
||||||
`pbr` | [`3d/pbr.rs`](./3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
|
`pbr` | [`3d/pbr.rs`](./3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
|
||||||
`render_to_texture` | [`3d/render_to_texture.rs`](./3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
|
`render_to_texture` | [`3d/render_to_texture.rs`](./3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
|
||||||
|
`two_passes` | [`3d/two_passes.rs`](./3d/two_passes.rs) | Shows how to render multiple passes to the same window, useful for rendering different views or drawing an object on top regardless of depth
|
||||||
`shadow_caster_receiver` | [`3d/shadow_caster_receiver.rs`](./3d/shadow_caster_receiver.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
|
`shadow_caster_receiver` | [`3d/shadow_caster_receiver.rs`](./3d/shadow_caster_receiver.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
|
||||||
`shadow_biases` | [`3d/shadow_biases.rs`](./3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
|
`shadow_biases` | [`3d/shadow_biases.rs`](./3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
|
||||||
`spherical_area_lights` | [`3d/spherical_area_lights.rs`](./3d/spherical_area_lights.rs) | Demonstrates how point light radius values affect light behavior.
|
`spherical_area_lights` | [`3d/spherical_area_lights.rs`](./3d/spherical_area_lights.rs) | Demonstrates how point light radius values affect light behavior.
|
||||||
|
|
Loading…
Reference in a new issue