forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexternally_driven_headless_renderer.rs
More file actions
159 lines (146 loc) · 4.97 KB
/
externally_driven_headless_renderer.rs
File metadata and controls
159 lines (146 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! This example shows how to make an externally driven headless renderer,
//! pumping the update loop manually.
use bevy::{
app::SubApps,
asset::RenderAssetUsages,
camera::RenderTarget,
diagnostic::FrameCount,
image::Image,
prelude::*,
render::{
render_resource::{Extent3d, PollType, TextureDimension, TextureFormat, TextureUsages},
renderer::RenderDevice,
view::screenshot::{save_to_disk, Screenshot},
RenderPlugin,
},
window::ExitCondition,
winit::WinitPlugin,
};
fn main() {
let mut bw = BevyWrapper::new();
let target = bw.new_render_target(500, 500);
bw.spawn_camera(target.clone());
for i in 0..10 {
// Schedule a screenshot for this frame
bw.screenshot(target.clone(), i);
// Pump the update loop once
bw.update();
}
// Loop a couple times more to let screenshot gpu readback and then write to disk
bw.update();
bw.update();
}
struct BevyWrapper(SubApps);
impl BevyWrapper {
fn new() -> Self {
let render_plugin = RenderPlugin {
// Make sure all shaders are loaded for the first frame
synchronous_pipeline_compilation: true,
..default()
};
// We don't have any windows, but the WindowPlugin is still needed
// because a lot of bevy expects it to be there. Just configure it
// to not have any windows and not exit automatically.
let window_plugin = WindowPlugin {
primary_window: None,
exit_condition: ExitCondition::DontExit,
..default()
};
let mut app = App::new();
app.add_plugins(
DefaultPlugins
.set(window_plugin)
.set(render_plugin)
// Disable winit because we want to own the update loop ourselves.
.disable::<WinitPlugin>(),
)
.add_systems(Startup, spawn_test_scene)
.add_systems(Update, update_camera);
// We yeet the schedule runner and never call app.run(),
// so we have to finish and clean up ourselves
app.finish();
app.cleanup();
// We grab the sub apps cus we dont want the runner, as we'll
// be pumping the update loop ourselves manually.
Self(std::mem::take(app.sub_apps_mut()))
}
fn new_render_target(&mut self, width: u32, height: u32) -> RenderTarget {
let mut target = Image::new_uninit(
Extent3d {
width,
height,
depth_or_array_layers: 1,
},
TextureDimension::D2,
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
);
// We're going to render to this image, mark it as such
target.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT;
self.0
.main
.world_mut()
.resource_mut::<Assets<Image>>()
.add(target)
.into()
}
fn spawn_camera(&mut self, target: RenderTarget) -> Entity {
self.0
.main
.world_mut()
.spawn((Camera3d::default(), target, Transform::IDENTITY))
.id()
}
// Run one world update and wait for rendering to finish.
fn update(&mut self) {
self.0.update();
// Wait for frame to finish rendering by wait polling the device
self.0
.main
.world()
.resource::<RenderDevice>()
.wgpu_device()
.poll(PollType::Wait {
submission_index: None,
timeout: None,
})
.unwrap();
}
// Schedules a screenshot to be captured on the next update.
fn screenshot(&mut self, target: RenderTarget, i: u32) {
self.0
.main
.world_mut()
.spawn(Screenshot::image(target.as_image().unwrap().clone()))
.observe(save_to_disk(format!("test_images/screenshot{i}.png")));
}
}
fn spawn_test_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Mesh3d(meshes.add(Circle::new(4.0))),
MeshMaterial3d(materials.add(Color::WHITE)),
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
));
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
Transform::from_xyz(0.0, 1.0, 0.0),
));
commands.spawn((
PointLight {
shadow_maps_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
}
fn update_camera(mut camera: Query<&mut Transform, With<Camera>>, frame_count: Res<FrameCount>) {
for mut t in camera.iter_mut() {
let (s, c) = ops::sin_cos(frame_count.0 as f32 * 0.3);
*t = Transform::from_xyz(s * 10.0, 4.5, c * 10.0).looking_at(Vec3::ZERO, Vec3::Y);
}
}