Renamed + Updated to Bevy 0.14 + Added 0BSD Option

This commit is contained in:
Silas Bartha 2024-07-24 11:46:40 -04:00
parent 5200536964
commit d7e1903369
Signed by: soaos
GPG Key ID: 9BD3DCC0D56A09B2
8 changed files with 94 additions and 79 deletions

View File

@ -1,11 +1,13 @@
[package]
name = "bevy_framebuffer_extract"
version = "0.1.2"
name = "bevy_headless_render"
version = "0.1.0"
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]
oneshot = "0.1.6"
pollster = "0.3.0"
[dependencies.bevy]
version = "0.13"
version = "0.14"

5
LICENSE-0BSD Normal file
View 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.

View File

@ -1,26 +1,33 @@
# bevy_framebuffer_extract
# bevy_headless_render
[![Crates](https://img.shields.io/crates/v/bevy_headless_render)](https://crates.io/crates/bevy_headless_render)
![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)
![Tag](https://img.shields.io/github/v/tag/exvacuum/bevy_framebuffer_extract)
![Build](https://img.shields.io/github/actions/workflow/status/exvacuum/bevy_framebuffer_extract/rust.yml)
[![Docs](https://img.shields.io/website?url=https%3A%2F%2Fexvacuum.github.io%2Fbevy_framebuffer_extract%2F&label=docs)](https://exvacuum.github.io/bevy_framebuffer_extract)
![Tag](https://img.shields.io/github/v/tag/exvacuum/bevy_headless_render)
![Build](https://img.shields.io/github/actions/workflow/status/exvacuum/bevy_headless_render/rust.yml)
[![Docs](https://img.shields.io/website?url=https%3A%2F%2Fexvacuum.github.io%2Fbevy_headless_render%2F&label=docs)](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
| Crate Version | Bevy Version |
|--- |--- |
| 0.1 | 0.13 |
| 0.1 | 0.14 |
## Installation
### crates.io
```toml
[dependencies]
bevy_headless_render = "0.2"
```
### Using git URL in Cargo.toml
```toml
[dependencies.bevy_framebuffer_extract]
git = "https://github.com/exvacuum/bevy_framebuffer_extract.git"
[dependencies.bevy_headless_render]
git = "https://github.com/exvacuum/bevy_headless_render.git"
```
## Usage
@ -28,13 +35,13 @@ git = "https://github.com/exvacuum/bevy_framebuffer_extract.git"
In `main.rs`:
```rs
use bevy::prelude::*;
use bevy_framebuffer_extract;
use bevy_headless_render;
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
bevy_framebuffer_extract::FramebufferExtractPlugin,
bevy_headless_render::HeadlessRenderPlugin,
))
.run();
}
@ -76,11 +83,16 @@ commands.spawn((
},
..Default::default()
},
bevy_framebuffer_extract::ExtractFramebufferBundle {
source: framebuffer_extract_sources.add(FramebufferExtractSource(image_handle.clone())), // ResMut<Assets<FramebufferExtractSource>>
destination: FramebufferExtractDestination::default(),
bevy_headless_render::HeadlessRenderBundle {
source: headless_render_sources.add(HeadlessRenderSource(image_handle.clone())), // ResMut<Assets<HeadlessRenderSource>>
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.

View File

@ -2,18 +2,18 @@ use std::sync::{Arc, Mutex};
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)]
pub struct FramebufferExtractDestination(pub Arc<Mutex<Image>>);
pub struct HeadlessRenderDestination(pub Arc<Mutex<Image>>);
impl ExtractComponent for FramebufferExtractDestination {
type QueryData = (&'static Self, &'static Handle<FramebufferExtractSource>);
impl ExtractComponent for HeadlessRenderDestination {
type QueryData = (&'static Self, &'static Handle<HeadlessRenderSource>);
type QueryFilter = ();
type Out = (Self, Handle<FramebufferExtractSource>);
type Out = (Self, Handle<HeadlessRenderSource>);
fn extract_component(
(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)]
pub struct ExtractFramebufferBundle {
pub struct HeadlessRenderBundle {
/// Source
pub source: Handle<FramebufferExtractSource>,
pub source: Handle<HeadlessRenderSource>,
/// Destination
pub dest: FramebufferExtractDestination,
pub dest: HeadlessRenderDestination,
}

View File

@ -1,7 +1,6 @@
#![warn(missing_docs)]
//! Plugin for the Bevy game engine which provides the ability to extract the frambuffer image after rendering
//! to use for whatever you want.
//! Plugin for the Bevy game engine which provides the ability to render to an image headlessly.
use bevy::{
prelude::*,
@ -10,9 +9,9 @@ use bevy::{
render_asset::RenderAssetPlugin, render_graph::RenderGraph, Render, RenderApp, RenderSet,
},
};
use components::FramebufferExtractDestination;
use nodes::{FramebufferExtractLabel, FramebufferExtractNode};
use render_assets::FramebufferExtractSource;
use components::HeadlessRenderDestination;
use nodes::{HeadlessRenderCopyLabel, HeadlessRenderCopyNode};
use render_assets::{HeadlessRenderSource, GpuHeadlessRenderSource};
/// Components used by this plugin.
pub mod components;
@ -22,28 +21,28 @@ pub mod render_assets;
mod nodes;
mod systems;
/// Plugin which handles framebuffer extraction.
pub struct FramebufferExtractPlugin;
/// Plugin which handles headless rendering.
pub struct HeadlessRenderPlugin;
impl Plugin for FramebufferExtractPlugin {
impl Plugin for HeadlessRenderPlugin {
fn build(&self, app: &mut App) {
app.register_type::<FramebufferExtractSource>()
.init_asset::<FramebufferExtractSource>()
.register_asset_reflect::<FramebufferExtractSource>()
app.register_type::<HeadlessRenderSource>()
.init_asset::<HeadlessRenderSource>()
.register_asset_reflect::<HeadlessRenderSource>()
.add_plugins((
RenderAssetPlugin::<FramebufferExtractSource>::default(),
ExtractComponentPlugin::<FramebufferExtractDestination>::default(),
RenderAssetPlugin::<GpuHeadlessRenderSource>::default(),
ExtractComponentPlugin::<HeadlessRenderDestination>::default(),
));
let render_app = app.sub_app_mut(RenderApp);
render_app.add_systems(
Render,
systems::extract_framebuffers
systems::copy_buffers
.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);
let mut graph = render_app.world_mut().resource_mut::<RenderGraph>();
graph.add_node(HeadlessRenderCopyLabel, HeadlessRenderCopyNode);
graph.add_node_edge(CameraDriverLabel, HeadlessRenderCopyLabel);
}
}

View File

@ -4,19 +4,19 @@ use bevy::{
render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext, RenderLabel},
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)]
pub struct FramebufferExtractLabel;
pub struct HeadlessRenderCopyLabel;
#[derive(Default)]
pub struct FramebufferExtractNode;
pub struct HeadlessRenderCopyNode;
impl Node for FramebufferExtractNode {
impl Node for HeadlessRenderCopyNode {
fn run(
&self,
_graph: &mut RenderGraphContext,
@ -24,12 +24,12 @@ impl Node for FramebufferExtractNode {
world: &World,
) -> Result<(), NodeRunError> {
for (_, source) in world
.resource::<RenderAssets<FramebufferExtractSource>>()
.resource::<RenderAssets<GpuHeadlessRenderSource>>()
.iter()
{
let Some(gpu_image) = world
.resource::<RenderAssets<Image>>()
.get(&source.source_handle)
.resource::<RenderAssets<GpuImage>>()
.get(source.source_handle.id())
else {
return Ok(());
};

View File

@ -2,14 +2,14 @@ use bevy::{
ecs::system::{lifetimeless::SRes, SystemParamItem},
prelude::*,
render::{
render_asset::{PrepareAssetError, RenderAsset, RenderAssetUsages, RenderAssets},
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
render_resource::{Buffer, BufferDescriptor, BufferUsages, Extent3d, TextureFormat},
renderer::RenderDevice,
renderer::RenderDevice, texture::GpuImage,
},
};
/// Render-world version of FramebufferExtractSource
pub struct GpuFramebufferExtractSource {
/// Render-world version of HeadlessRenderSource
pub struct GpuHeadlessRenderSource {
pub(crate) buffer: Buffer,
pub(crate) source_handle: Handle<Image>,
pub(crate) source_size: Extent3d,
@ -18,26 +18,22 @@ pub struct GpuFramebufferExtractSource {
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.
#[derive(Asset, Reflect, Clone, Default)]
pub struct FramebufferExtractSource(pub Handle<Image>);
pub struct HeadlessRenderSource(pub Handle<Image>);
impl RenderAsset for FramebufferExtractSource {
type PreparedAsset = GpuFramebufferExtractSource;
type Param = (SRes<RenderDevice>, SRes<RenderAssets<Image>>);
fn asset_usage(&self) -> RenderAssetUsages {
RenderAssetUsages::default()
}
impl RenderAsset for GpuHeadlessRenderSource {
type SourceAsset = HeadlessRenderSource;
type Param = (SRes<RenderDevice>, SRes<RenderAssets<GpuImage>>);
fn prepare_asset(
self,
source_asset: Self::SourceAsset,
(device, images): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self>> {
let Some(gpu_image) = images.get(&self.0) else {
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
let Some(gpu_image) = images.get(source_asset.0.id()) else {
warn!("Failed to get GPU image");
return Err(PrepareAssetError::RetryNextUpdate(self));
return Err(PrepareAssetError::RetryNextUpdate(source_asset));
};
let size = gpu_image.texture.size();
@ -47,18 +43,19 @@ impl RenderAsset for FramebufferExtractSource {
let padded_bytes_per_row =
RenderDevice::align_copy_bytes_per_row(bytes_per_row as usize) as u32;
Ok(GpuFramebufferExtractSource {
Ok(GpuHeadlessRenderSource {
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_handle: source_asset.0.clone(),
source_size: size,
bytes_per_row,
padded_bytes_per_row,
format,
})
}
}

View File

@ -9,17 +9,17 @@ use bevy::{
use pollster::FutureExt;
use crate::{components::FramebufferExtractDestination, render_assets::FramebufferExtractSource};
use crate::{components::HeadlessRenderDestination, render_assets::{HeadlessRenderSource, GpuHeadlessRenderSource}};
pub fn extract_framebuffers(
mut extract_bundles: Query<(
&Handle<FramebufferExtractSource>,
&mut FramebufferExtractDestination,
pub fn copy_buffers(
mut headless_render_query: Query<(
&Handle<HeadlessRenderSource>,
&mut HeadlessRenderDestination,
)>,
sources: Res<RenderAssets<FramebufferExtractSource>>,
sources: Res<RenderAssets<GpuHeadlessRenderSource>>,
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 {
continue;
};