Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/app/externally_driven_headless_renderer.rs
9328 views
1
//! This example shows how to make an externally driven headless renderer,
2
//! pumping the update loop manually.
3
use 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
20
fn 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
// Schedule a screenshot for this frame
27
bw.screenshot(target.clone(), i);
28
// Pump the update loop once
29
bw.update();
30
}
31
// Loop a couple times more to let screenshot gpu readback and then write to disk
32
bw.update();
33
bw.update();
34
}
35
36
struct BevyWrapper(SubApps);
37
38
impl BevyWrapper {
39
fn new() -> Self {
40
let render_plugin = RenderPlugin {
41
// Make sure all shaders are loaded for the first frame
42
synchronous_pipeline_compilation: true,
43
..default()
44
};
45
// We don't have any windows, but the WindowPlugin is still needed
46
// because a lot of bevy expects it to be there. Just configure it
47
// to not have any windows and not exit automatically.
48
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 winit because we want to own the update loop ourselves.
60
.disable::<WinitPlugin>(),
61
)
62
.add_systems(Startup, spawn_test_scene)
63
.add_systems(Update, update_camera);
64
65
// We yeet the schedule runner and never call app.run(),
66
// so we have to finish and clean up ourselves
67
app.finish();
68
app.cleanup();
69
70
// We grab the sub apps cus we dont want the runner, as we'll
71
// be pumping the update loop ourselves manually.
72
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
// We're going to render to this image, mark it as such
87
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
// Run one world update and wait for rendering to finish.
105
fn update(&mut self) {
106
self.0.update();
107
// Wait for frame to finish rendering by wait polling the device
108
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
// Schedules a screenshot to be captured on the next update.
121
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
130
fn 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
154
fn 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
}
160
161