mirror of
https://github.com/bevyengine/bevy.git
synced 2026-07-01 00:05:45 -04:00
3f401d1507
# Objective Fix #19101. (Kinda, this is not the exact solution proposed in that issue, but it still shortens most of the examples) ## Solution Most of the images created in examples are for use in render targets. `Image::new_target_texture` is made for exactly that and significantly shortens the image creation process. ## Testing I tested all the examples I changed and they seem to work fine. --- btw for some reason most of the examples use `TextureFormat::Bgra8UnormSrgb` while the documentation for `Image::new_target_texture` recommends `TextureFormat::Rgba8UnormSrgb` for SDR images. What's up with that? --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com>
151 lines
4.7 KiB
Rust
151 lines
4.7 KiB
Rust
//! 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::{PollType, TextureFormat},
|
|
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_target_texture(width, height, TextureFormat::Rgba8UnormSrgb, None);
|
|
target.asset_usage = RenderAssetUsages::RENDER_WORLD;
|
|
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);
|
|
}
|
|
}
|