aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLibravatar Silas Bartha <silas@exvacuum.dev>2024-04-24 21:05:55 -0400
committerLibravatar Silas Bartha <silas@exvacuum.dev>2024-04-24 21:05:55 -0400
commit98d21d244bb92a1a8d35b1dff35d9c10bdcab19a (patch)
tree8d1729071750d879ca2123e69df2c6bfbf1857c5 /src
Render Texture Extraction
Diffstat (limited to 'src')
-rw-r--r--src/components.rs33
-rw-r--r--src/lib.rs41
-rw-r--r--src/nodes.rs38
-rw-r--r--src/render_assets.rs52
-rw-r--r--src/resources.rs0
-rw-r--r--src/systems.rs65
6 files changed, 229 insertions, 0 deletions
diff --git a/src/components.rs b/src/components.rs
new file mode 100644
index 0000000..3179f0c
--- /dev/null
+++ b/src/components.rs
@@ -0,0 +1,33 @@
+use std::sync::{Mutex, Arc};
+
+use bevy::{prelude::*, render::extract_component::ExtractComponent, ecs::query::QueryItem};
+
+use crate::render_assets::FramebufferExtractSource;
+
+#[derive(Component, Default, Clone)]
+pub struct FramebufferExtractDestination(pub Arc<Mutex<Image>>);
+
+impl ExtractComponent for FramebufferExtractDestination {
+ type QueryData = (
+ &'static Self,
+ &'static Handle<FramebufferExtractSource>,
+ );
+
+ type QueryFilter = ();
+
+ type Out = (Self, Handle<FramebufferExtractSource>);
+
+ fn extract_component((destination, source_handle): QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
+ Some((
+ destination.clone(),
+ source_handle.clone(),
+ ))
+ }
+}
+
+#[derive(Bundle)]
+pub struct ExtractFramebufferBundle {
+ pub source: Handle<FramebufferExtractSource>,
+ pub dest: FramebufferExtractDestination,
+}
+
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..9c6fb1b
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,41 @@
+use bevy::{prelude::*, render::{render_asset::RenderAssetPlugin, extract_component::ExtractComponentPlugin, RenderApp, Render, RenderSet, render_graph::RenderGraph, graph::CameraDriverLabel}};
+use components::FramebufferExtractDestination;
+use nodes::{FramebufferExtractNode, FramebufferExtractLabel};
+use render_assets::FramebufferExtractSource;
+
+pub mod components;
+mod systems;
+mod nodes;
+pub mod render_assets;
+
+
+pub enum FramebufferExtractSet {
+ Set
+}
+
+pub struct FramebufferExtractPlugin;
+
+impl Plugin for FramebufferExtractPlugin {
+ fn build(&self, app: &mut App) {
+ app
+ .register_type::<FramebufferExtractSource>()
+ .init_asset::<FramebufferExtractSource>()
+ .register_asset_reflect::<FramebufferExtractSource>()
+ .add_plugins((
+ RenderAssetPlugin::<FramebufferExtractSource>::default(),
+ ExtractComponentPlugin::<FramebufferExtractDestination>::default(),
+ ));
+
+ let render_app = app.sub_app_mut(RenderApp);
+ render_app
+ .add_systems(
+ Render,
+ systems::extract_framebuffers
+ .after(RenderSet::Render)
+ .before(RenderSet::Cleanup),
+ );
+ let mut graph = render_app.world.resource_mut::<RenderGraph>();
+ graph.add_node(FramebufferExtractLabel, FramebufferExtractNode);
+ graph.add_node_edge(CameraDriverLabel, FramebufferExtractLabel);
+ }
+}
diff --git a/src/nodes.rs b/src/nodes.rs
new file mode 100644
index 0000000..bccd861
--- /dev/null
+++ b/src/nodes.rs
@@ -0,0 +1,38 @@
+use bevy::{prelude::*, render::{render_graph::{Node, RenderGraphContext, NodeRunError, RenderLabel}, renderer::RenderContext, render_asset::RenderAssets, render_resource::{ImageCopyBuffer, ImageDataLayout}}};
+
+use crate::render_assets::FramebufferExtractSource;
+
+#[derive(RenderLabel, Clone, PartialEq, Eq, Debug, Hash)]
+pub struct FramebufferExtractLabel;
+
+#[derive(Default)]
+pub struct FramebufferExtractNode;
+
+impl Node for FramebufferExtractNode {
+ fn run(
+ &self,
+ graph: &mut RenderGraphContext,
+ render_context: &mut RenderContext,
+ world: &World,
+ ) -> Result<(), NodeRunError> {
+ for (_, source) in world.resource::<RenderAssets<FramebufferExtractSource>>().iter() {
+ let Some(gpu_image) = world.resource::<RenderAssets<Image>>().get(&source.source_handle) else {
+ return Ok(())
+ };
+
+ render_context.command_encoder().copy_texture_to_buffer(
+ gpu_image.texture.as_image_copy(),
+ ImageCopyBuffer {
+ buffer: &source.buffer,
+ layout: ImageDataLayout {
+ offset: 0,
+ bytes_per_row: Some(source.padded_bytes_per_row),
+ rows_per_image: None,
+ },
+ },
+ source.source_size,
+ );
+ }
+ Ok(())
+ }
+}
diff --git a/src/render_assets.rs b/src/render_assets.rs
new file mode 100644
index 0000000..1615e01
--- /dev/null
+++ b/src/render_assets.rs
@@ -0,0 +1,52 @@
+use bevy::{prelude::*, render::{render_asset::{RenderAsset, RenderAssets, RenderAssetUsages, PrepareAssetError}, render_resource::{Buffer, Extent3d, BufferUsages, BufferDescriptor, TextureFormat}, renderer::RenderDevice}, ecs::system::{lifetimeless::SRes, SystemParamItem}};
+
+pub struct GpuFramebufferExtractSource {
+ pub buffer: Buffer,
+ pub source_handle: Handle<Image>,
+ pub source_size: Extent3d,
+ pub bytes_per_row: u32,
+ pub padded_bytes_per_row: u32,
+ pub format: TextureFormat,
+}
+
+#[derive(Asset, Reflect, Clone, Default)]
+pub struct FramebufferExtractSource(pub Handle<Image>);
+
+impl RenderAsset for FramebufferExtractSource {
+ type PreparedAsset = GpuFramebufferExtractSource;
+ type Param = (SRes<RenderDevice>, SRes<RenderAssets<Image>>);
+
+ fn asset_usage(&self) -> RenderAssetUsages {
+ RenderAssetUsages::default()
+ }
+
+ fn prepare_asset(
+ self,
+ (device, images): &mut SystemParamItem<Self::Param>,
+ ) -> Result<Self::PreparedAsset, PrepareAssetError<Self>> {
+
+ let Some(gpu_image) = images.get(&self.0) else {
+ warn!("Failed to get GPU image");
+ return Err(PrepareAssetError::RetryNextUpdate(self))
+ };
+
+ let size = gpu_image.texture.size();
+ let format = gpu_image.texture_format;
+ let bytes_per_row = (size.width / format.block_dimensions().0) * format.block_copy_size(None).unwrap();
+ let padded_bytes_per_row = RenderDevice::align_copy_bytes_per_row(bytes_per_row as usize) as u32;
+
+ Ok(GpuFramebufferExtractSource {
+ buffer: device.create_buffer(&BufferDescriptor {
+ label: Some("framebuffer_extract_buffer"),
+ size: (size.height * padded_bytes_per_row) as u64,
+ usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
+ mapped_at_creation: false,
+ }),
+ source_handle: self.0.clone(),
+ source_size: size,
+ bytes_per_row,
+ padded_bytes_per_row,
+ format,
+ })
+ }
+}
diff --git a/src/resources.rs b/src/resources.rs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/resources.rs
diff --git a/src/systems.rs b/src/systems.rs
new file mode 100644
index 0000000..25b25dc
--- /dev/null
+++ b/src/systems.rs
@@ -0,0 +1,65 @@
+use std::time::Duration;
+
+use bevy::{prelude::*, render::{render_asset::{RenderAssets, RenderAssetUsages}, renderer::RenderDevice, render_resource::{MapMode, Maintain, Extent3d, TextureDimension}}};
+
+use pollster::FutureExt;
+
+use crate::{render_assets::FramebufferExtractSource, components::FramebufferExtractDestination};
+
+pub fn extract_framebuffers(
+ mut extract_bundles: Query<(&Handle<FramebufferExtractSource>, &mut FramebufferExtractDestination)>,
+ sources: Res<RenderAssets<FramebufferExtractSource>>,
+ device: Res<RenderDevice>,
+) {
+ for (source_handle, destination_handle) in extract_bundles.iter_mut() {
+ let Some(gpu_source) = sources.get(source_handle) else {
+ continue;
+ };
+
+ let mut image_bytes = {
+ let slice = gpu_source.buffer.slice(..);
+
+ {
+ let (tx, rx) = oneshot::channel();
+ device.map_buffer(&slice, MapMode::Read, move |res| {
+ tx.send(res).unwrap();
+ });
+ device.poll(Maintain::Wait);
+ rx.block_on().unwrap().unwrap();
+ }
+
+ slice.get_mapped_range().to_vec()
+ };
+
+ gpu_source.buffer.unmap();
+
+ let bytes_per_row = gpu_source.bytes_per_row as usize;
+ let padded_bytes_per_row = gpu_source.padded_bytes_per_row as usize;
+ let source_size = gpu_source.source_size;
+ let destination_handle = destination_handle.clone();
+ let source_format = gpu_source.format;
+
+ std::thread::spawn(move || {
+ if bytes_per_row != padded_bytes_per_row {
+ let mut unpadded_bytes = Vec::<u8>::with_capacity(source_size.height as usize * bytes_per_row);
+ for padded_row in image_bytes.chunks(padded_bytes_per_row) {
+ unpadded_bytes.extend_from_slice(&padded_row[..bytes_per_row]);
+ }
+ image_bytes = unpadded_bytes;
+ }
+
+ *destination_handle.0.lock().unwrap() = Image::new(
+ Extent3d {
+ width: source_size.width,
+ height: source_size.height,
+ depth_or_array_layers: 1,
+ },
+ TextureDimension::D2,
+ image_bytes,
+ source_format,
+ RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
+ );
+ });
+ }
+
+}