Last active
July 21, 2022 07:46
-
-
Save DGriffin91/eb5357292c2c89d2fcbb1fb10c43e617 to your computer and use it in GitHub Desktop.
Copy from GPU texture to CPU with using mutex example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // License: Apache-2.0 / MIT | |
| use std::sync::{Arc, Mutex}; | |
| use bevy::prelude::*; | |
| use bevy::render::camera::RenderTarget; | |
| use bevy::render::render_asset::RenderAssets; | |
| use bevy::render::render_graph::{self, NodeRunError, RenderGraph, RenderGraphContext}; | |
| use bevy::render::renderer::{RenderContext, RenderDevice, RenderQueue}; | |
| use bevy::render::{Extract, RenderApp, RenderStage}; | |
| use bevy::render::render_resource::{ | |
| Buffer, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d, ImageCopyBuffer, | |
| ImageDataLayout, MapMode, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, | |
| }; | |
| use std::sync::atomic::{AtomicBool, Ordering}; | |
| #[derive(Component, Default)] | |
| pub struct CaptureCamera; | |
| #[derive(Component)] | |
| struct Cube { | |
| rotate_speed: f32, | |
| } | |
| #[derive(Component, Deref, DerefMut)] | |
| struct ImageToSave(Handle<Image>); | |
| fn main() { | |
| App::new() | |
| .add_plugins(DefaultPlugins) | |
| .add_plugin(ImageCopyPlugin) | |
| .add_startup_system(setup) | |
| .add_system(cube_rotator_system) | |
| .add_system(get_image_data) | |
| .run(); | |
| } | |
| fn setup( | |
| mut commands: Commands, | |
| mut images: ResMut<Assets<Image>>, | |
| mut meshes: ResMut<Assets<Mesh>>, | |
| mut materials: ResMut<Assets<StandardMaterial>>, | |
| render_device: Res<RenderDevice>, | |
| ) { | |
| let size = Extent3d { | |
| width: 512, | |
| height: 512, | |
| ..Default::default() | |
| }; | |
| // This is the texture that will be rendered to. | |
| let mut render_target_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, | |
| }, | |
| ..Default::default() | |
| }; | |
| render_target_image.resize(size); | |
| let render_target_image_handle = images.add(render_target_image); | |
| // This is the texture that will be copied to. | |
| let mut cpu_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, | |
| }, | |
| ..Default::default() | |
| }; | |
| cpu_image.resize(size); | |
| let cpu_image_handle = images.add(cpu_image); | |
| commands.spawn().insert(ImageCopier::new( | |
| render_target_image_handle.clone(), | |
| cpu_image_handle.clone(), | |
| size, | |
| &render_device, | |
| )); | |
| commands | |
| .spawn() | |
| .insert(ImageToSave(cpu_image_handle.clone())); | |
| 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() | |
| }); | |
| commands | |
| .spawn_bundle(PbrBundle { | |
| mesh: cube_handle, | |
| material: cube_material_handle, | |
| transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), | |
| ..default() | |
| }) | |
| .insert(Cube { rotate_speed: 1.0 }); | |
| commands.spawn_bundle(PointLightBundle { | |
| transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), | |
| ..default() | |
| }); | |
| commands | |
| .spawn_bundle(Camera3dBundle { | |
| transform: Transform::from_xyz(0.7, 0.0, 1.0) | |
| .looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y), | |
| ..default() | |
| }) | |
| .with_children(|parent| { | |
| parent.spawn_bundle(Camera3dBundle { | |
| camera: Camera { | |
| target: RenderTarget::Image(render_target_image_handle), | |
| ..default() | |
| }, | |
| ..default() | |
| }); | |
| }); | |
| } | |
| /// Rotates the cubes | |
| fn cube_rotator_system(time: Res<Time>, mut query: Query<(&mut Transform, &Cube)>) { | |
| for (mut transform, cube) in query.iter_mut() { | |
| transform.rotation *= Quat::from_rotation_x(cube.rotate_speed * time.delta_seconds()); | |
| transform.rotation *= Quat::from_rotation_y(cube.rotate_speed * time.delta_seconds()); | |
| } | |
| } | |
| fn get_image_data(images_to_save: Query<&ImageToSave>, images: Res<Assets<Image>>) { | |
| for image in images_to_save.iter() { | |
| image::save_buffer( | |
| "test.png", | |
| &images.get(image).unwrap().data, | |
| 512, | |
| 512, | |
| image::ColorType::Rgba8, | |
| ) | |
| .unwrap(); | |
| } | |
| } | |
| //------------------------------------------------------ | |
| // ImageCopyPlugin ------------------------------------- | |
| //------------------------------------------------------ | |
| pub fn receive_images( | |
| mut image_copiers: Query<&mut ImageCopier>, | |
| mut images: ResMut<Assets<Image>>, | |
| render_device: Res<RenderDevice>, | |
| ) { | |
| for image_copier in image_copiers.iter_mut() { | |
| if !image_copier.enabled() { | |
| continue; | |
| } | |
| if let Ok(cpu_buffer) = image_copier.buffer.lock() { | |
| let large_buffer_slice = cpu_buffer.slice(..); | |
| render_device.map_buffer(&large_buffer_slice, MapMode::Read); | |
| { | |
| let large_padded_buffer = large_buffer_slice.get_mapped_range(); | |
| if let Some(mut image) = images.get_mut(&image_copier.dst_image) { | |
| image.data = large_padded_buffer.to_vec(); | |
| } | |
| } | |
| cpu_buffer.unmap(); | |
| } | |
| } | |
| } | |
| pub const IMAGE_COPY: &str = "image_copy"; | |
| pub struct ImageCopyPlugin; | |
| impl Plugin for ImageCopyPlugin { | |
| fn build(&self, app: &mut App) { | |
| let render_app = app.add_system(receive_images).sub_app_mut(RenderApp); | |
| render_app.add_system_to_stage(RenderStage::Extract, image_copy_extract); | |
| let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap(); | |
| graph.add_node(IMAGE_COPY, ImageCopyDriver::default()); | |
| graph | |
| .add_node_edge(IMAGE_COPY, bevy::render::main_graph::node::CAMERA_DRIVER) | |
| .unwrap(); | |
| } | |
| } | |
| #[derive(Clone, Component)] | |
| pub struct ImageCopier { | |
| buffer: Arc<Mutex<Buffer>>, | |
| enabled: Arc<AtomicBool>, | |
| src_image: Handle<Image>, | |
| dst_image: Handle<Image>, | |
| } | |
| impl ImageCopier { | |
| fn new( | |
| src_image: Handle<Image>, | |
| dst_image: Handle<Image>, | |
| size: Extent3d, | |
| render_device: &RenderDevice, | |
| ) -> ImageCopier { | |
| let padded_bytes_per_row = | |
| RenderDevice::align_copy_bytes_per_row((size.width) as usize) * 4; | |
| let cpu_buffer = render_device.create_buffer(&BufferDescriptor { | |
| label: None, | |
| size: padded_bytes_per_row as u64 * size.height as u64, | |
| usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, | |
| mapped_at_creation: false, | |
| }); | |
| ImageCopier { | |
| buffer: Arc::new(Mutex::new(cpu_buffer)), | |
| src_image, | |
| dst_image, | |
| enabled: Arc::new(AtomicBool::new(true)), | |
| } | |
| } | |
| fn enable(&self) { | |
| self.enabled.store(true, Ordering::Relaxed) | |
| } | |
| fn disable(&self) { | |
| self.enabled.store(false, Ordering::Relaxed) | |
| } | |
| fn enabled(&self) -> bool { | |
| self.enabled.load(Ordering::Relaxed) | |
| } | |
| } | |
| pub fn image_copy_extract(mut commands: Commands, image_copiers: Extract<Query<&ImageCopier>>) { | |
| commands.insert_resource(image_copiers.iter().cloned().collect::<Vec<ImageCopier>>()); | |
| } | |
| #[derive(Default)] | |
| pub struct ImageCopyDriver; | |
| impl render_graph::Node for ImageCopyDriver { | |
| fn run( | |
| &self, | |
| _graph: &mut RenderGraphContext, | |
| render_context: &mut RenderContext, | |
| world: &World, | |
| ) -> Result<(), NodeRunError> { | |
| let image_copiers = world.get_resource::<Vec<ImageCopier>>().unwrap(); | |
| let gpu_images = world.get_resource::<RenderAssets<Image>>().unwrap(); | |
| for image_copier in image_copiers.iter() { | |
| if !image_copier.enabled() { | |
| continue; | |
| } | |
| let src_image = gpu_images.get(&image_copier.src_image).unwrap(); | |
| let mut encoder = render_context | |
| .render_device | |
| .create_command_encoder(&CommandEncoderDescriptor::default()); | |
| let padded_bytes_per_row = | |
| RenderDevice::align_copy_bytes_per_row((src_image.size.x) as usize) * 4; | |
| let texture_extent = Extent3d { | |
| width: src_image.size.x as u32, | |
| height: src_image.size.y as u32, | |
| depth_or_array_layers: 1, | |
| }; | |
| if let Ok(cpu_buffer) = image_copier.buffer.lock() { | |
| encoder.copy_texture_to_buffer( | |
| src_image.texture.as_image_copy(), | |
| ImageCopyBuffer { | |
| buffer: &cpu_buffer, | |
| layout: ImageDataLayout { | |
| offset: 0, | |
| bytes_per_row: Some( | |
| std::num::NonZeroU32::new(padded_bytes_per_row as u32).unwrap(), | |
| ), | |
| rows_per_image: None, | |
| }, | |
| }, | |
| texture_extent, | |
| ); | |
| } | |
| let render_queue = world.get_resource::<RenderQueue>().unwrap(); | |
| render_queue.submit(std::iter::once(encoder.finish())); | |
| } | |
| Ok(()) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment