Renamed + Updated to Bevy 0.14 + Added 0BSD Option
This commit is contained in:
parent
5200536964
commit
d7e1903369
@ -1,11 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_framebuffer_extract"
|
name = "bevy_headless_render"
|
||||||
version = "0.1.2"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
description = "A plugin for the bevy engine which enables headless rendering to an image for use in the main world."
|
||||||
|
license = "0BSD OR MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
oneshot = "0.1.6"
|
oneshot = "0.1.6"
|
||||||
pollster = "0.3.0"
|
pollster = "0.3.0"
|
||||||
|
|
||||||
[dependencies.bevy]
|
[dependencies.bevy]
|
||||||
version = "0.13"
|
version = "0.14"
|
||||||
|
5
LICENSE-0BSD
Normal file
5
LICENSE-0BSD
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Copyright (C) 2024 by Silas Bartha silas@exvacuum.dev
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
42
README.md
42
README.md
@ -1,26 +1,33 @@
|
|||||||
# bevy_framebuffer_extract
|
# bevy_headless_render
|
||||||
|
|
||||||
|
[](https://crates.io/crates/bevy_headless_render)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
[](https://exvacuum.github.io/bevy_framebuffer_extract)
|
[](https://exvacuum.github.io/bevy_headless_render)
|
||||||
|
|
||||||
A plugin for the [Bevy](https://bevyengine.org) engine which allows for exporting framebuffer data from a camera.
|
A plugin for the [Bevy](https://bevyengine.org) engine which allows for headless rendering.
|
||||||
|
|
||||||
Currently it only supports cameras which render to a render texture.
|
Every frame will be copied from `HeadlessRenderSource` render textures into `HeadlessRenderDestination` images each frame.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
| Crate Version | Bevy Version |
|
| Crate Version | Bevy Version |
|
||||||
|--- |--- |
|
|--- |--- |
|
||||||
| 0.1 | 0.13 |
|
| 0.1 | 0.14 |
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### crates.io
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
bevy_headless_render = "0.2"
|
||||||
|
```
|
||||||
|
|
||||||
### Using git URL in Cargo.toml
|
### Using git URL in Cargo.toml
|
||||||
```toml
|
```toml
|
||||||
[dependencies.bevy_framebuffer_extract]
|
[dependencies.bevy_headless_render]
|
||||||
git = "https://github.com/exvacuum/bevy_framebuffer_extract.git"
|
git = "https://github.com/exvacuum/bevy_headless_render.git"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -28,13 +35,13 @@ git = "https://github.com/exvacuum/bevy_framebuffer_extract.git"
|
|||||||
In `main.rs`:
|
In `main.rs`:
|
||||||
```rs
|
```rs
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_framebuffer_extract;
|
use bevy_headless_render;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
DefaultPlugins,
|
DefaultPlugins,
|
||||||
bevy_framebuffer_extract::FramebufferExtractPlugin,
|
bevy_headless_render::HeadlessRenderPlugin,
|
||||||
))
|
))
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
@ -76,11 +83,16 @@ commands.spawn((
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
bevy_framebuffer_extract::ExtractFramebufferBundle {
|
bevy_headless_render::HeadlessRenderBundle {
|
||||||
source: framebuffer_extract_sources.add(FramebufferExtractSource(image_handle.clone())), // ResMut<Assets<FramebufferExtractSource>>
|
source: headless_render_sources.add(HeadlessRenderSource(image_handle.clone())), // ResMut<Assets<HeadlessRenderSource>>
|
||||||
destination: FramebufferExtractDestination::default(),
|
destination: HeadlessRenderDestination::default(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
```
|
```
|
||||||
|
|
||||||
The FramebufferExtractDestination component will contain the extracted image which can be used or saved for whatever you need.
|
The HeadlessRenderDestination component will contain the extracted image which can be used or saved for whatever you need.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This crate is licensed under your choice of 0BSD, Apache-2.0, or MIT license.
|
||||||
|
|
||||||
|
@ -2,18 +2,18 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use bevy::{ecs::query::QueryItem, prelude::*, render::extract_component::ExtractComponent};
|
use bevy::{ecs::query::QueryItem, prelude::*, render::extract_component::ExtractComponent};
|
||||||
|
|
||||||
use crate::render_assets::FramebufferExtractSource;
|
use crate::render_assets::HeadlessRenderSource;
|
||||||
|
|
||||||
/// Framebuffer extraction destination. Contains the image which the framebuffer is extracted to.
|
/// Headless render destination. Contains the image which the rendered frame is copied to.
|
||||||
#[derive(Component, Default, Clone)]
|
#[derive(Component, Default, Clone)]
|
||||||
pub struct FramebufferExtractDestination(pub Arc<Mutex<Image>>);
|
pub struct HeadlessRenderDestination(pub Arc<Mutex<Image>>);
|
||||||
|
|
||||||
impl ExtractComponent for FramebufferExtractDestination {
|
impl ExtractComponent for HeadlessRenderDestination {
|
||||||
type QueryData = (&'static Self, &'static Handle<FramebufferExtractSource>);
|
type QueryData = (&'static Self, &'static Handle<HeadlessRenderSource>);
|
||||||
|
|
||||||
type QueryFilter = ();
|
type QueryFilter = ();
|
||||||
|
|
||||||
type Out = (Self, Handle<FramebufferExtractSource>);
|
type Out = (Self, Handle<HeadlessRenderSource>);
|
||||||
|
|
||||||
fn extract_component(
|
fn extract_component(
|
||||||
(destination, source_handle): QueryItem<'_, Self::QueryData>,
|
(destination, source_handle): QueryItem<'_, Self::QueryData>,
|
||||||
@ -22,11 +22,11 @@ impl ExtractComponent for FramebufferExtractDestination {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bundle containing both a source and destination for framebuffer extraction.
|
/// Bundle containing both a source and destination for headless rendering.
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
pub struct ExtractFramebufferBundle {
|
pub struct HeadlessRenderBundle {
|
||||||
/// Source
|
/// Source
|
||||||
pub source: Handle<FramebufferExtractSource>,
|
pub source: Handle<HeadlessRenderSource>,
|
||||||
/// Destination
|
/// Destination
|
||||||
pub dest: FramebufferExtractDestination,
|
pub dest: HeadlessRenderDestination,
|
||||||
}
|
}
|
||||||
|
33
src/lib.rs
33
src/lib.rs
@ -1,7 +1,6 @@
|
|||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
//! Plugin for the Bevy game engine which provides the ability to extract the frambuffer image after rendering
|
//! Plugin for the Bevy game engine which provides the ability to render to an image headlessly.
|
||||||
//! to use for whatever you want.
|
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -10,9 +9,9 @@ use bevy::{
|
|||||||
render_asset::RenderAssetPlugin, render_graph::RenderGraph, Render, RenderApp, RenderSet,
|
render_asset::RenderAssetPlugin, render_graph::RenderGraph, Render, RenderApp, RenderSet,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use components::FramebufferExtractDestination;
|
use components::HeadlessRenderDestination;
|
||||||
use nodes::{FramebufferExtractLabel, FramebufferExtractNode};
|
use nodes::{HeadlessRenderCopyLabel, HeadlessRenderCopyNode};
|
||||||
use render_assets::FramebufferExtractSource;
|
use render_assets::{HeadlessRenderSource, GpuHeadlessRenderSource};
|
||||||
|
|
||||||
/// Components used by this plugin.
|
/// Components used by this plugin.
|
||||||
pub mod components;
|
pub mod components;
|
||||||
@ -22,28 +21,28 @@ pub mod render_assets;
|
|||||||
mod nodes;
|
mod nodes;
|
||||||
mod systems;
|
mod systems;
|
||||||
|
|
||||||
/// Plugin which handles framebuffer extraction.
|
/// Plugin which handles headless rendering.
|
||||||
pub struct FramebufferExtractPlugin;
|
pub struct HeadlessRenderPlugin;
|
||||||
|
|
||||||
impl Plugin for FramebufferExtractPlugin {
|
impl Plugin for HeadlessRenderPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<FramebufferExtractSource>()
|
app.register_type::<HeadlessRenderSource>()
|
||||||
.init_asset::<FramebufferExtractSource>()
|
.init_asset::<HeadlessRenderSource>()
|
||||||
.register_asset_reflect::<FramebufferExtractSource>()
|
.register_asset_reflect::<HeadlessRenderSource>()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
RenderAssetPlugin::<FramebufferExtractSource>::default(),
|
RenderAssetPlugin::<GpuHeadlessRenderSource>::default(),
|
||||||
ExtractComponentPlugin::<FramebufferExtractDestination>::default(),
|
ExtractComponentPlugin::<HeadlessRenderDestination>::default(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let render_app = app.sub_app_mut(RenderApp);
|
let render_app = app.sub_app_mut(RenderApp);
|
||||||
render_app.add_systems(
|
render_app.add_systems(
|
||||||
Render,
|
Render,
|
||||||
systems::extract_framebuffers
|
systems::copy_buffers
|
||||||
.after(RenderSet::Render)
|
.after(RenderSet::Render)
|
||||||
.before(RenderSet::Cleanup),
|
.before(RenderSet::Cleanup),
|
||||||
);
|
);
|
||||||
let mut graph = render_app.world.resource_mut::<RenderGraph>();
|
let mut graph = render_app.world_mut().resource_mut::<RenderGraph>();
|
||||||
graph.add_node(FramebufferExtractLabel, FramebufferExtractNode);
|
graph.add_node(HeadlessRenderCopyLabel, HeadlessRenderCopyNode);
|
||||||
graph.add_node_edge(CameraDriverLabel, FramebufferExtractLabel);
|
graph.add_node_edge(CameraDriverLabel, HeadlessRenderCopyLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
src/nodes.rs
16
src/nodes.rs
@ -4,19 +4,19 @@ use bevy::{
|
|||||||
render_asset::RenderAssets,
|
render_asset::RenderAssets,
|
||||||
render_graph::{Node, NodeRunError, RenderGraphContext, RenderLabel},
|
render_graph::{Node, NodeRunError, RenderGraphContext, RenderLabel},
|
||||||
render_resource::{ImageCopyBuffer, ImageDataLayout},
|
render_resource::{ImageCopyBuffer, ImageDataLayout},
|
||||||
renderer::RenderContext,
|
renderer::RenderContext, texture::GpuImage,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::render_assets::FramebufferExtractSource;
|
use crate::render_assets::GpuHeadlessRenderSource;
|
||||||
|
|
||||||
#[derive(RenderLabel, Clone, PartialEq, Eq, Debug, Hash)]
|
#[derive(RenderLabel, Clone, PartialEq, Eq, Debug, Hash)]
|
||||||
pub struct FramebufferExtractLabel;
|
pub struct HeadlessRenderCopyLabel;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FramebufferExtractNode;
|
pub struct HeadlessRenderCopyNode;
|
||||||
|
|
||||||
impl Node for FramebufferExtractNode {
|
impl Node for HeadlessRenderCopyNode {
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_graph: &mut RenderGraphContext,
|
_graph: &mut RenderGraphContext,
|
||||||
@ -24,12 +24,12 @@ impl Node for FramebufferExtractNode {
|
|||||||
world: &World,
|
world: &World,
|
||||||
) -> Result<(), NodeRunError> {
|
) -> Result<(), NodeRunError> {
|
||||||
for (_, source) in world
|
for (_, source) in world
|
||||||
.resource::<RenderAssets<FramebufferExtractSource>>()
|
.resource::<RenderAssets<GpuHeadlessRenderSource>>()
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
let Some(gpu_image) = world
|
let Some(gpu_image) = world
|
||||||
.resource::<RenderAssets<Image>>()
|
.resource::<RenderAssets<GpuImage>>()
|
||||||
.get(&source.source_handle)
|
.get(source.source_handle.id())
|
||||||
else {
|
else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -2,14 +2,14 @@ use bevy::{
|
|||||||
ecs::system::{lifetimeless::SRes, SystemParamItem},
|
ecs::system::{lifetimeless::SRes, SystemParamItem},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::{
|
render::{
|
||||||
render_asset::{PrepareAssetError, RenderAsset, RenderAssetUsages, RenderAssets},
|
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
|
||||||
render_resource::{Buffer, BufferDescriptor, BufferUsages, Extent3d, TextureFormat},
|
render_resource::{Buffer, BufferDescriptor, BufferUsages, Extent3d, TextureFormat},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice, texture::GpuImage,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Render-world version of FramebufferExtractSource
|
/// Render-world version of HeadlessRenderSource
|
||||||
pub struct GpuFramebufferExtractSource {
|
pub struct GpuHeadlessRenderSource {
|
||||||
pub(crate) buffer: Buffer,
|
pub(crate) buffer: Buffer,
|
||||||
pub(crate) source_handle: Handle<Image>,
|
pub(crate) source_handle: Handle<Image>,
|
||||||
pub(crate) source_size: Extent3d,
|
pub(crate) source_size: Extent3d,
|
||||||
@ -18,26 +18,22 @@ pub struct GpuFramebufferExtractSource {
|
|||||||
pub(crate) format: TextureFormat,
|
pub(crate) format: TextureFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Framebuffer extraction source. Contains a handle to the render texture which will be extracted
|
/// Headless render source. Contains a handle to the render texture which will be copied
|
||||||
/// from.
|
/// from.
|
||||||
#[derive(Asset, Reflect, Clone, Default)]
|
#[derive(Asset, Reflect, Clone, Default)]
|
||||||
pub struct FramebufferExtractSource(pub Handle<Image>);
|
pub struct HeadlessRenderSource(pub Handle<Image>);
|
||||||
|
|
||||||
impl RenderAsset for FramebufferExtractSource {
|
impl RenderAsset for GpuHeadlessRenderSource {
|
||||||
type PreparedAsset = GpuFramebufferExtractSource;
|
type SourceAsset = HeadlessRenderSource;
|
||||||
type Param = (SRes<RenderDevice>, SRes<RenderAssets<Image>>);
|
type Param = (SRes<RenderDevice>, SRes<RenderAssets<GpuImage>>);
|
||||||
|
|
||||||
fn asset_usage(&self) -> RenderAssetUsages {
|
|
||||||
RenderAssetUsages::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_asset(
|
fn prepare_asset(
|
||||||
self,
|
source_asset: Self::SourceAsset,
|
||||||
(device, images): &mut SystemParamItem<Self::Param>,
|
(device, images): &mut SystemParamItem<Self::Param>,
|
||||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self>> {
|
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
||||||
let Some(gpu_image) = images.get(&self.0) else {
|
let Some(gpu_image) = images.get(source_asset.0.id()) else {
|
||||||
warn!("Failed to get GPU image");
|
warn!("Failed to get GPU image");
|
||||||
return Err(PrepareAssetError::RetryNextUpdate(self));
|
return Err(PrepareAssetError::RetryNextUpdate(source_asset));
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = gpu_image.texture.size();
|
let size = gpu_image.texture.size();
|
||||||
@ -47,18 +43,19 @@ impl RenderAsset for FramebufferExtractSource {
|
|||||||
let padded_bytes_per_row =
|
let padded_bytes_per_row =
|
||||||
RenderDevice::align_copy_bytes_per_row(bytes_per_row as usize) as u32;
|
RenderDevice::align_copy_bytes_per_row(bytes_per_row as usize) as u32;
|
||||||
|
|
||||||
Ok(GpuFramebufferExtractSource {
|
Ok(GpuHeadlessRenderSource {
|
||||||
buffer: device.create_buffer(&BufferDescriptor {
|
buffer: device.create_buffer(&BufferDescriptor {
|
||||||
label: Some("framebuffer_extract_buffer"),
|
label: Some("framebuffer_extract_buffer"),
|
||||||
size: (size.height * padded_bytes_per_row) as u64,
|
size: (size.height * padded_bytes_per_row) as u64,
|
||||||
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
|
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
|
||||||
mapped_at_creation: false,
|
mapped_at_creation: false,
|
||||||
}),
|
}),
|
||||||
source_handle: self.0.clone(),
|
source_handle: source_asset.0.clone(),
|
||||||
source_size: size,
|
source_size: size,
|
||||||
bytes_per_row,
|
bytes_per_row,
|
||||||
padded_bytes_per_row,
|
padded_bytes_per_row,
|
||||||
format,
|
format,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,17 +9,17 @@ use bevy::{
|
|||||||
|
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
|
|
||||||
use crate::{components::FramebufferExtractDestination, render_assets::FramebufferExtractSource};
|
use crate::{components::HeadlessRenderDestination, render_assets::{HeadlessRenderSource, GpuHeadlessRenderSource}};
|
||||||
|
|
||||||
pub fn extract_framebuffers(
|
pub fn copy_buffers(
|
||||||
mut extract_bundles: Query<(
|
mut headless_render_query: Query<(
|
||||||
&Handle<FramebufferExtractSource>,
|
&Handle<HeadlessRenderSource>,
|
||||||
&mut FramebufferExtractDestination,
|
&mut HeadlessRenderDestination,
|
||||||
)>,
|
)>,
|
||||||
sources: Res<RenderAssets<FramebufferExtractSource>>,
|
sources: Res<RenderAssets<GpuHeadlessRenderSource>>,
|
||||||
device: Res<RenderDevice>,
|
device: Res<RenderDevice>,
|
||||||
) {
|
) {
|
||||||
for (source_handle, destination_handle) in extract_bundles.iter_mut() {
|
for (source_handle, destination_handle) in headless_render_query.iter_mut() {
|
||||||
let Some(gpu_source) = sources.get(source_handle) else {
|
let Some(gpu_source) = sources.get(source_handle) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user