-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Add capability to render to cpu buffer or double buffer #4572
Changes from all commits
0cd2e89
ad1e2f1
71cab6c
e5dd62b
29445c2
21957de
1fe10a6
e65cdba
041aaef
d07edbb
fdfdb78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,16 @@ use crate::{ | |
}; | ||
use bevy_ecs::prelude::*; | ||
use bevy_render::{ | ||
camera::ExtractedCamera, | ||
camera::{ExtractedCamera, RenderTarget}, | ||
prelude::Image, | ||
render_asset::RenderAssets, | ||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, | ||
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass}, | ||
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor}, | ||
renderer::RenderContext, | ||
render_resource::{ | ||
CommandEncoderDescriptor, Extent3d, LoadOp, Operations, RenderPassDepthStencilAttachment, | ||
RenderPassDescriptor, | ||
}, | ||
renderer::{RenderContext, RenderQueue}, | ||
view::{ExtractedView, ViewDepthTexture, ViewTarget}, | ||
}; | ||
#[cfg(feature = "trace")] | ||
|
@@ -194,6 +199,31 @@ impl Node for MainPass3dNode { | |
} | ||
} | ||
|
||
// TODO: should this live here or in a separate node? | ||
// TODO: don't just have duplicated code between MainPass3dNode/MainPass2dNode | ||
if let RenderTarget::BufferedImage(image_handle, buffer_image_handle) = &camera.target { | ||
let gpu_images = world.get_resource::<RenderAssets<Image>>().unwrap(); | ||
let gpu_image = gpu_images.get(&image_handle).unwrap(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe source and destination images? i.e. src_image, dst_image |
||
|
||
let mut encoder = render_context | ||
.render_device | ||
.create_command_encoder(&CommandEncoderDescriptor::default()); | ||
|
||
let target_image = gpu_images.get(&buffer_image_handle).unwrap(); | ||
encoder.copy_texture_to_texture( | ||
gpu_image.texture.as_image_copy(), | ||
target_image.texture.as_image_copy(), | ||
Extent3d { | ||
width: gpu_image.size.x as u32, | ||
height: gpu_image.size.y as u32, | ||
depth_or_array_layers: 1, | ||
}, | ||
); | ||
|
||
let render_queue = world.get_resource::<RenderQueue>().unwrap(); | ||
render_queue.submit(std::iter::once(encoder.finish())); | ||
} | ||
|
||
// WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't | ||
// reset for the next render pass so add an empty render pass without a custom viewport | ||
#[cfg(feature = "webgl")] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -247,6 +247,8 @@ pub enum RenderTarget { | |
Window(WindowId), | ||
/// Image to which the camera's view is rendered. | ||
Image(Handle<Image>), | ||
/// Buffered Image to which the camera's view is rendered. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like this description could be clearer. The first image is used for rendering into and is then copied into the second image, right? |
||
BufferedImage(Handle<Image>, Handle<Image>), | ||
} | ||
|
||
impl Default for RenderTarget { | ||
|
@@ -268,6 +270,9 @@ impl RenderTarget { | |
RenderTarget::Image(image_handle) => { | ||
images.get(image_handle).map(|image| &image.texture_view) | ||
} | ||
RenderTarget::BufferedImage(image_handle, _buffer_image_handle) => { | ||
images.get(image_handle).map(|image| &image.texture_view) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -292,6 +297,14 @@ impl RenderTarget { | |
scale_factor: 1.0, | ||
} | ||
} | ||
RenderTarget::BufferedImage(image_handle, _buffered_image_handle) => { | ||
let image = images.get(image_handle)?; | ||
let Extent3d { width, height, .. } = image.texture_descriptor.size; | ||
RenderTargetInfo { | ||
physical_size: UVec2::new(width, height), | ||
scale_factor: 1.0, | ||
} | ||
} | ||
}) | ||
} | ||
// Check if this render target is contained in the given changed windows or images. | ||
|
@@ -303,6 +316,10 @@ impl RenderTarget { | |
match self { | ||
RenderTarget::Window(window_id) => changed_window_ids.contains(window_id), | ||
RenderTarget::Image(image_handle) => changed_image_handles.contains(&image_handle), | ||
RenderTarget::BufferedImage(image_handle, buffered_image_handle) => { | ||
changed_image_handles.contains(&image_handle) | ||
|| changed_image_handles.contains(&buffered_image_handle) | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
//! Shows how to render to a double buffered texture that can then be used on the same render layer. | ||
|
||
use bevy::prelude::*; | ||
use bevy::render::camera::{CameraPlugin, RenderTarget}; | ||
|
||
use bevy::render::render_resource::{ | ||
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, | ||
}; | ||
|
||
#[derive(Component, Default)] | ||
pub struct CaptureCamera; | ||
|
||
// Marks the first pass cube (rendered to a texture.) | ||
#[derive(Component)] | ||
struct FirstPassCube; | ||
|
||
// Marks the main pass cube, to which the texture is applied. | ||
#[derive(Component)] | ||
struct MainPassCube; | ||
|
||
fn main() { | ||
App::new() | ||
.insert_resource(Msaa { samples: 4 }) // Use 4x MSAA | ||
.insert_resource(AmbientLight { | ||
color: Color::WHITE, | ||
brightness: 1.0 / 5.0f32, | ||
}) | ||
.add_plugins(DefaultPlugins) | ||
.add_plugin(CameraPlugin::default()) | ||
.add_startup_system(setup) | ||
.add_system(cube_rotator_system) | ||
.add_system(rotator_system) | ||
.run(); | ||
} | ||
|
||
fn setup( | ||
mut commands: Commands, | ||
mut images: ResMut<Assets<Image>>, | ||
mut meshes: ResMut<Assets<Mesh>>, | ||
mut materials: ResMut<Assets<StandardMaterial>>, | ||
) { | ||
let size = Extent3d { | ||
width: 512, | ||
height: 512, | ||
..Default::default() | ||
}; | ||
|
||
// This is the texture that will be rendered to. | ||
let mut image = Image { | ||
texture_descriptor: TextureDescriptor { | ||
label: None, | ||
size, | ||
dimension: TextureDimension::D2, | ||
format: TextureFormat::Rgba8UnormSrgb, | ||
mip_level_count: 1, | ||
sample_count: 1, | ||
usage: TextureUsages::TEXTURE_BINDING | ||
| TextureUsages::COPY_DST | ||
| TextureUsages::COPY_SRC | ||
| TextureUsages::RENDER_ATTACHMENT, | ||
Comment on lines
+57
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this one only needs RENDER_ATTACHMENT | COPY_SRC. |
||
}, | ||
..Default::default() | ||
}; | ||
image.resize(size); | ||
let gpu_image = images.add(image); | ||
|
||
// This is the buffered texture that copied to. | ||
let mut buffered_image = Image { | ||
texture_descriptor: TextureDescriptor { | ||
label: None, | ||
size, | ||
dimension: TextureDimension::D2, | ||
format: TextureFormat::Rgba8UnormSrgb, | ||
mip_level_count: 1, | ||
sample_count: 1, | ||
usage: TextureUsages::TEXTURE_BINDING | ||
| TextureUsages::COPY_DST | ||
| TextureUsages::COPY_SRC | ||
| TextureUsages::RENDER_ATTACHMENT, | ||
Comment on lines
+76
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this one only needs COPY_DST | TEXTURE_BINDING. |
||
}, | ||
..Default::default() | ||
}; | ||
buffered_image.resize(size); | ||
let gpu_buffered_image = images.add(buffered_image); | ||
|
||
let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 0.25 })); | ||
let cube_material_handle = materials.add(StandardMaterial { | ||
base_color: Color::rgb(0.8, 0.7, 0.6), | ||
reflectance: 0.02, | ||
unlit: false, | ||
..default() | ||
}); | ||
|
||
// The cube that will be rendered to the texture. | ||
commands | ||
.spawn_bundle(PbrBundle { | ||
mesh: cube_handle, | ||
material: cube_material_handle, | ||
transform: Transform::from_translation(Vec3::new(0.0, 0.25, 0.0)), | ||
..default() | ||
}) | ||
.insert(FirstPassCube); | ||
|
||
commands.spawn_bundle(PointLightBundle { | ||
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), | ||
..default() | ||
}); | ||
|
||
let cube_size = 0.25; | ||
let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size))); | ||
|
||
// This material has the texture that has been rendered. | ||
let material_handle = materials.add(StandardMaterial { | ||
base_color_texture: Some(gpu_buffered_image.clone()), | ||
reflectance: 0.02, | ||
unlit: false, | ||
..default() | ||
}); | ||
// Main pass cube, with material containing the rendered first pass texture. | ||
commands | ||
.spawn_bundle(PbrBundle { | ||
mesh: cube_handle, | ||
material: material_handle, | ||
transform: Transform { | ||
translation: Vec3::new(0.0, 0.5, 0.0), | ||
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0), | ||
..default() | ||
}, | ||
..default() | ||
}) | ||
.insert(MainPassCube); | ||
|
||
commands | ||
.spawn_bundle(Camera3dBundle { | ||
transform: Transform::from_xyz(0.7, 0.7, 1.0) | ||
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), | ||
..default() | ||
}) | ||
.with_children(|parent| { | ||
let render_target = | ||
RenderTarget::BufferedImage(gpu_image.clone(), gpu_buffered_image.clone()); | ||
parent.spawn_bundle(Camera3dBundle { | ||
camera: Camera { | ||
target: render_target, | ||
..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()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate node imo.