Path: blob/main/examples/app/externally_driven_headless_renderer.rs
9328 views
//! This example shows how to make an externally driven headless renderer,1//! pumping the update loop manually.2use bevy::{3app::SubApps,4asset::RenderAssetUsages,5camera::RenderTarget,6diagnostic::FrameCount,7image::Image,8prelude::*,9render::{10render_resource::{Extent3d, PollType, TextureDimension, TextureFormat, TextureUsages},11renderer::RenderDevice,12view::screenshot::{save_to_disk, Screenshot},13RenderPlugin,14},15window::ExitCondition,16winit::WinitPlugin,17};1819fn main() {20let mut bw = BevyWrapper::new();2122let target = bw.new_render_target(500, 500);23bw.spawn_camera(target.clone());24for i in 0..10 {25// Schedule a screenshot for this frame26bw.screenshot(target.clone(), i);27// Pump the update loop once28bw.update();29}30// Loop a couple times more to let screenshot gpu readback and then write to disk31bw.update();32bw.update();33}3435struct BevyWrapper(SubApps);3637impl BevyWrapper {38fn new() -> Self {39let render_plugin = RenderPlugin {40// Make sure all shaders are loaded for the first frame41synchronous_pipeline_compilation: true,42..default()43};44// We don't have any windows, but the WindowPlugin is still needed45// because a lot of bevy expects it to be there. Just configure it46// to not have any windows and not exit automatically.47let window_plugin = WindowPlugin {48primary_window: None,49exit_condition: ExitCondition::DontExit,50..default()51};5253let mut app = App::new();54app.add_plugins(55DefaultPlugins56.set(window_plugin)57.set(render_plugin)58// Disable winit because we want to own the update loop ourselves.59.disable::<WinitPlugin>(),60)61.add_systems(Startup, spawn_test_scene)62.add_systems(Update, update_camera);6364// We yeet the schedule runner and never call app.run(),65// so we have to finish and clean up ourselves66app.finish();67app.cleanup();6869// We grab the sub apps cus we dont want the runner, as we'll70// be pumping the update loop ourselves manually.71Self(std::mem::take(app.sub_apps_mut()))72}7374fn new_render_target(&mut self, width: u32, height: u32) -> RenderTarget {75let mut target = Image::new_uninit(76Extent3d {77width,78height,79depth_or_array_layers: 1,80},81TextureDimension::D2,82TextureFormat::Rgba8UnormSrgb,83RenderAssetUsages::RENDER_WORLD,84);85// We're going to render to this image, mark it as such86target.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT;87self.088.main89.world_mut()90.resource_mut::<Assets<Image>>()91.add(target)92.into()93}9495fn spawn_camera(&mut self, target: RenderTarget) -> Entity {96self.097.main98.world_mut()99.spawn((Camera3d::default(), target, Transform::IDENTITY))100.id()101}102103// Run one world update and wait for rendering to finish.104fn update(&mut self) {105self.0.update();106// Wait for frame to finish rendering by wait polling the device107self.0108.main109.world()110.resource::<RenderDevice>()111.wgpu_device()112.poll(PollType::Wait {113submission_index: None,114timeout: None,115})116.unwrap();117}118119// Schedules a screenshot to be captured on the next update.120fn screenshot(&mut self, target: RenderTarget, i: u32) {121self.0122.main123.world_mut()124.spawn(Screenshot::image(target.as_image().unwrap().clone()))125.observe(save_to_disk(format!("test_images/screenshot{i}.png")));126}127}128129fn spawn_test_scene(130mut commands: Commands,131mut meshes: ResMut<Assets<Mesh>>,132mut materials: ResMut<Assets<StandardMaterial>>,133) {134commands.spawn((135Mesh3d(meshes.add(Circle::new(4.0))),136MeshMaterial3d(materials.add(Color::WHITE)),137Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),138));139commands.spawn((140Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),141MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),142Transform::from_xyz(0.0, 1.0, 0.0),143));144commands.spawn((145PointLight {146shadow_maps_enabled: true,147..default()148},149Transform::from_xyz(4.0, 8.0, 4.0),150));151}152153fn update_camera(mut camera: Query<&mut Transform, With<Camera>>, frame_count: Res<FrameCount>) {154for mut t in camera.iter_mut() {155let (s, c) = ops::sin_cos(frame_count.0 as f32 * 0.3);156*t = Transform::from_xyz(s * 10.0, 4.5, c * 10.0).looking_at(Vec3::ZERO, Vec3::Y);157}158}159160161