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::{PollType, TextureFormat},
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 =
77 Image::new_target_texture(width, height, TextureFormat::Rgba8UnormSrgb, None);
78 target.asset_usage = RenderAssetUsages::RENDER_WORLD;
79 self.0
80 .main
81 .world_mut()
82 .resource_mut::<Assets<Image>>()
83 .add(target)
84 .into()
85 }
86
87 fn spawn_camera(&mut self, target: RenderTarget) -> Entity {
88 self.0
89 .main
90 .world_mut()
91 .spawn((Camera3d::default(), target, Transform::IDENTITY))
92 .id()
93 }
94
95 fn update(&mut self) {
97 self.0.update();
98 self.0
100 .main
101 .world()
102 .resource::<RenderDevice>()
103 .wgpu_device()
104 .poll(PollType::Wait {
105 submission_index: None,
106 timeout: None,
107 })
108 .unwrap();
109 }
110
111 fn screenshot(&mut self, target: RenderTarget, i: u32) {
113 self.0
114 .main
115 .world_mut()
116 .spawn(Screenshot::image(target.as_image().unwrap().clone()))
117 .observe(save_to_disk(format!("test_images/screenshot{i}.png")));
118 }
119}
120
121fn spawn_test_scene(
122 mut commands: Commands,
123 mut meshes: ResMut<Assets<Mesh>>,
124 mut materials: ResMut<Assets<StandardMaterial>>,
125) {
126 commands.spawn((
127 Mesh3d(meshes.add(Circle::new(4.0))),
128 MeshMaterial3d(materials.add(Color::WHITE)),
129 Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
130 ));
131 commands.spawn((
132 Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
133 MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
134 Transform::from_xyz(0.0, 1.0, 0.0),
135 ));
136 commands.spawn((
137 PointLight {
138 shadow_maps_enabled: true,
139 ..default()
140 },
141 Transform::from_xyz(4.0, 8.0, 4.0),
142 ));
143}
144
145fn update_camera(mut camera: Query<&mut Transform, With<Camera>>, frame_count: Res<FrameCount>) {
146 for mut t in camera.iter_mut() {
147 let (s, c) = ops::sin_cos(frame_count.0 as f32 * 0.3);
148 *t = Transform::from_xyz(s * 10.0, 4.5, c * 10.0).looking_at(Vec3::ZERO, Vec3::Y);
149 }
150}