#![deny(rust_2018_idioms)]
#![allow(elided_lifetimes_in_paths)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
mod render_world;
pub mod encoder;
use bevy::{
prelude::*,
render::{
camera::RenderTarget,
render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
texture::BevyDefault,
},
utils::all_tuples,
};
use std::sync::Mutex;
#[doc(inline)]
pub use encoder::Encoder;
type BoxedEncoder = Box<dyn Encoder + Send + Sync + 'static>;
pub struct CapturePlugin;
impl Plugin for CapturePlugin {
fn build(&self, app: &mut App) {
app.add_plugins(render_world::CaptureRenderWorldPlugin);
}
}
#[derive(Default, Bundle)]
pub struct CaptureBundle {
pub capture: Capture,
pub camera_source: CaptureSource,
}
#[derive(Default, Component)]
pub struct Capture {
state: CaptureState,
}
impl Capture {
pub fn start(&mut self, encoders: impl IntoEncoders) {
self.state = CaptureState::Capturing {
encoders: Mutex::new(Some(Encoders(encoders.into_encoders()))),
paused: false,
};
}
pub fn pause(&mut self) {
if let CaptureState::Capturing { paused, .. } = &mut self.state {
*paused = true;
}
}
pub fn resume(&mut self) {
if let CaptureState::Capturing { paused, .. } = &mut self.state {
*paused = false;
}
}
pub fn stop(&mut self) {
self.state = CaptureState::Idle;
}
pub fn is_capturing(&self) -> bool {
matches!(&self.state, CaptureState::Capturing { .. })
}
pub fn is_paused(&self) -> bool {
matches!(&self.state, CaptureState::Capturing { paused: true, .. })
}
}
#[derive(Default)]
enum CaptureState {
#[default]
Idle,
Capturing {
encoders: Mutex<Option<Encoders>>,
paused: bool,
},
}
struct Encoders(Vec<BoxedEncoder>);
impl Drop for Encoders {
fn drop(&mut self) {
for encoder in self.0.drain(..) {
encoder.finish();
}
}
}
#[derive(Default, Clone, Copy, Component)]
#[non_exhaustive] pub enum CaptureSource {
#[default]
ThisCamera,
Camera(Entity),
}
pub trait CameraTargetHeadless {
fn target_headless(self, width: u32, height: u32, images: &mut Assets<Image>) -> Self;
}
impl CameraTargetHeadless for Camera {
fn target_headless(mut self, width: u32, height: u32, images: &mut Assets<Image>) -> Self {
let mut image = Image::new_fill(
Extent3d {
width,
height,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&[0; 4],
TextureFormat::bevy_default(),
RenderAssetUsages::default(),
);
image.texture_descriptor.usage |= TextureUsages::COPY_SRC
| TextureUsages::RENDER_ATTACHMENT
| TextureUsages::TEXTURE_BINDING;
self.target = RenderTarget::Image(images.add(image));
self
}
}
impl CameraTargetHeadless for Camera2dBundle {
fn target_headless(mut self, width: u32, height: u32, images: &mut Assets<Image>) -> Self {
self.camera = self.camera.target_headless(width, height, images);
self
}
}
impl CameraTargetHeadless for Camera3dBundle {
fn target_headless(mut self, width: u32, height: u32, images: &mut Assets<Image>) -> Self {
self.camera = self.camera.target_headless(width, height, images);
self
}
}
pub trait IntoEncoders {
fn into_encoders(self) -> Vec<BoxedEncoder>;
}
impl IntoEncoders for BoxedEncoder {
fn into_encoders(self) -> Vec<BoxedEncoder> {
vec![self]
}
}
impl IntoEncoders for Vec<BoxedEncoder> {
fn into_encoders(self) -> Vec<BoxedEncoder> {
self
}
}
impl<E> IntoEncoders for E
where
E: Encoder + Send + Sync + 'static,
{
fn into_encoders(self) -> Vec<BoxedEncoder> {
vec![Box::new(self)]
}
}
macro_rules! impl_into_encoders {
($(($E:ident, $e:ident)),*) => {
impl<$($E),*> IntoEncoders for ($($E,)*)
where
$($E: Encoder + Send + Sync + 'static,)*
{
fn into_encoders(self) -> Vec<BoxedEncoder> {
let ($($e,)*) = self;
vec![$(Box::new($e),)*]
}
}
};
}
all_tuples!(impl_into_encoders, 0, 15, E, e);