externally_driven_headless_renderer/
externally_driven_headless_renderer.rs1use bevy::{
4 app::SubApps,
5 asset::RenderAssetUsages,
6 camera::RenderTarget,
7 diagnostic::FrameCount,
8 image::Image,
9 prelude::*,
10 render::{
11 render_resource::{Extent3d, PollType, TextureDimension, TextureFormat, TextureUsages},
12 renderer::RenderDevice,
13 view::screenshot::{save_to_disk, Screenshot},
14 RenderPlugin,
15 },
16 window::ExitCondition,
17 winit::WinitPlugin,
18};
19
20fn main() {
21 let mut bw = BevyWrapper::new();
22
23 let target = bw.new_render_target(500, 500);
24 bw.spawn_camera(target.clone());
25 for i in 0..10 {
26 bw.screenshot(target.clone(), i);
28 bw.update();
30 }
31 bw.update();
33 bw.update();
34}
35
36struct BevyWrapper(SubApps);
37
38impl BevyWrapper {
39 fn new() -> Self {
40 let render_plugin = RenderPlugin {
41 synchronous_pipeline_compilation: true,
43 ..default()
44 };
45 let window_plugin = WindowPlugin {
49 primary_window: None,
50 exit_condition: ExitCondition::DontExit,
51 ..default()
52 };
53
54 let mut app = App::new();
55 app.add_plugins(
56 DefaultPlugins
57 .set(window_plugin)
58 .set(render_plugin)
59 .disable::<WinitPlugin>(),
61 )
62 .add_systems(Startup, spawn_test_scene)
63 .add_systems(Update, update_camera);
64
65 app.finish();
68 app.cleanup();
69
70 Self(std::mem::take(app.sub_apps_mut()))
73 }
74
75 fn new_render_target(&mut self, width: u32, height: u32) -> RenderTarget {
76 let mut target = Image::new_uninit(
77 Extent3d {
78 width,
79 height,
80 depth_or_array_layers: 1,
81 },
82 TextureDimension::D2,
83 TextureFormat::Rgba8UnormSrgb,
84 RenderAssetUsages::RENDER_WORLD,
85 );
86 target.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT;
88 self.0
89 .main
90 .world_mut()
91 .resource_mut::<Assets<Image>>()
92 .add(target)
93 .into()
94 }
95
96 fn spawn_camera(&mut self, target: RenderTarget) -> Entity {
97 self.0
98 .main
99 .world_mut()
100 .spawn((Camera3d::default(), target, Transform::IDENTITY))
101 .id()
102 }
103
104 fn update(&mut self) {
106 self.0.update();
107 self.0
109 .main
110 .world()
111 .resource::<RenderDevice>()
112 .wgpu_device()
113 .poll(PollType::Wait {
114 submission_index: None,
115 timeout: None,
116 })
117 .unwrap();
118 }
119
120 fn screenshot(&mut self, target: RenderTarget, i: u32) {
122 self.0
123 .main
124 .world_mut()
125 .spawn(Screenshot::image(target.as_image().unwrap().clone()))
126 .observe(save_to_disk(format!("test_images/screenshot{i}.png")));
127 }
128}
129
130fn spawn_test_scene(
131 mut commands: Commands,
132 mut meshes: ResMut<Assets<Mesh>>,
133 mut materials: ResMut<Assets<StandardMaterial>>,
134) {
135 commands.spawn((
136 Mesh3d(meshes.add(Circle::new(4.0))),
137 MeshMaterial3d(materials.add(Color::WHITE)),
138 Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
139 ));
140 commands.spawn((
141 Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
142 MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
143 Transform::from_xyz(0.0, 1.0, 0.0),
144 ));
145 commands.spawn((
146 PointLight {
147 shadow_maps_enabled: true,
148 ..default()
149 },
150 Transform::from_xyz(4.0, 8.0, 4.0),
151 ));
152}
153
154fn update_camera(mut camera: Query<&mut Transform, With<Camera>>, frame_count: Res<FrameCount>) {
155 for mut t in camera.iter_mut() {
156 let (s, c) = ops::sin_cos(frame_count.0 as f32 * 0.3);
157 *t = Transform::from_xyz(s * 10.0, 4.5, c * 10.0).looking_at(Vec3::ZERO, Vec3::Y);
158 }
159}