mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
Take example screenshots in CI (#8488)
# Objective - I want to take screenshots of examples in CI to help with validation of changes ## Solution - Can override how much time is updated per frame - Can specify on which frame to take a screenshots - Save screenshots in CI I reused the `TimeUpdateStrategy::ManualDuration` to be able to set the time update strategy to a fixed duration every frame. Its previous meaning didn't make much sense to me. This change makes it possible to have screenshots that are exactly the same across runs. If this gets merged, I'll add visual comparison of screenshots between runs to ensure nothing gets broken ## Migration Guide * `TimeUpdateStrategy::ManualDuration` meaning has changed. Instead of setting time to `Instant::now()` plus the given duration, it sets time to last update plus the given duration.
This commit is contained in:
parent
5288be7c6e
commit
8070c29c21
10 changed files with 71 additions and 6 deletions
4
.github/example-run/breakout.ron
vendored
4
.github/example-run/breakout.ron
vendored
|
@ -1,3 +1,5 @@
|
||||||
(
|
(
|
||||||
exit_after: Some(900)
|
exit_after: Some(900),
|
||||||
|
frame_time: Some(0.03),
|
||||||
|
screenshot_frames: [200],
|
||||||
)
|
)
|
||||||
|
|
4
.github/example-run/load_gltf.ron
vendored
4
.github/example-run/load_gltf.ron
vendored
|
@ -1,3 +1,5 @@
|
||||||
(
|
(
|
||||||
exit_after: Some(300)
|
exit_after: Some(300),
|
||||||
|
frame_time: Some(0.03),
|
||||||
|
screenshot_frames: [100],
|
||||||
)
|
)
|
||||||
|
|
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
@ -199,13 +199,23 @@ jobs:
|
||||||
echo "running $example_name - "`date`
|
echo "running $example_name - "`date`
|
||||||
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
|
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
|
||||||
sleep 10
|
sleep 10
|
||||||
|
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
|
||||||
|
mkdir screenshots-$example_name
|
||||||
|
mv screenshot-*.png screenshots-$example_name/
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
zip traces.zip trace*.json
|
zip traces.zip trace*.json
|
||||||
|
zip -r screenshots.zip screenshots-*
|
||||||
- name: save traces
|
- name: save traces
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: example-traces.zip
|
name: example-traces.zip
|
||||||
path: traces.zip
|
path: traces.zip
|
||||||
|
- name: save screenshots
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: screenshots.zip
|
||||||
|
path: screenshots.zip
|
||||||
- name: Save PR number
|
- name: Save PR number
|
||||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Utilities for testing in CI environments.
|
||||||
|
|
||||||
use crate::{app::AppExit, App, Update};
|
use crate::{app::AppExit, App, Update};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -13,6 +15,11 @@ use bevy_utils::tracing::info;
|
||||||
pub struct CiTestingConfig {
|
pub struct CiTestingConfig {
|
||||||
/// The number of frames after which Bevy should exit.
|
/// The number of frames after which Bevy should exit.
|
||||||
pub exit_after: Option<u32>,
|
pub exit_after: Option<u32>,
|
||||||
|
/// The time in seconds to update for each frame.
|
||||||
|
pub frame_time: Option<f32>,
|
||||||
|
/// Frames at which to capture a screenshot.
|
||||||
|
#[serde(default)]
|
||||||
|
pub screenshot_frames: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ci_testing_exit_after(
|
fn ci_testing_exit_after(
|
||||||
|
|
|
@ -10,7 +10,7 @@ mod plugin_group;
|
||||||
mod schedule_runner;
|
mod schedule_runner;
|
||||||
|
|
||||||
#[cfg(feature = "bevy_ci_testing")]
|
#[cfg(feature = "bevy_ci_testing")]
|
||||||
mod ci_testing;
|
pub mod ci_testing;
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use bevy_derive::DynamicPlugin;
|
pub use bevy_derive::DynamicPlugin;
|
||||||
|
|
|
@ -77,7 +77,7 @@ subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
|
||||||
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"]
|
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"]
|
||||||
|
|
||||||
# enable systems that allow for automated testing on CI
|
# enable systems that allow for automated testing on CI
|
||||||
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render?/ci_limits"]
|
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_time/bevy_ci_testing", "bevy_render?/bevy_ci_testing", "bevy_render?/ci_limits"]
|
||||||
|
|
||||||
# Enable animation support, and glTF animation loading
|
# Enable animation support, and glTF animation loading
|
||||||
animation = ["bevy_animation", "bevy_gltf?/bevy_animation"]
|
animation = ["bevy_animation", "bevy_gltf?/bevy_animation"]
|
||||||
|
|
|
@ -17,6 +17,7 @@ jpeg = ["image/jpeg"]
|
||||||
bmp = ["image/bmp"]
|
bmp = ["image/bmp"]
|
||||||
webp = ["image/webp"]
|
webp = ["image/webp"]
|
||||||
dds = ["ddsfile"]
|
dds = ["ddsfile"]
|
||||||
|
bevy_ci_testing = ["bevy_app/bevy_ci_testing"]
|
||||||
|
|
||||||
shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out"]
|
shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out"]
|
||||||
shader_format_spirv = ["wgpu/spirv", "naga/spv-in", "naga/spv-out"]
|
shader_format_spirv = ["wgpu/spirv", "naga/spv-in", "naga/spv-out"]
|
||||||
|
|
|
@ -139,8 +139,36 @@ impl Plugin for ScreenshotPlugin {
|
||||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app.init_resource::<SpecializedRenderPipelines<ScreenshotToScreenPipeline>>();
|
render_app.init_resource::<SpecializedRenderPipelines<ScreenshotToScreenPipeline>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_ci_testing")]
|
||||||
|
if app
|
||||||
|
.world
|
||||||
|
.contains_resource::<bevy_app::ci_testing::CiTestingConfig>()
|
||||||
|
{
|
||||||
|
app.add_systems(bevy_app::Update, ci_testing_screenshot_at);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_ci_testing")]
|
||||||
|
fn ci_testing_screenshot_at(
|
||||||
|
mut current_frame: bevy_ecs::prelude::Local<u32>,
|
||||||
|
ci_testing_config: bevy_ecs::prelude::Res<bevy_app::ci_testing::CiTestingConfig>,
|
||||||
|
mut screenshot_manager: ResMut<ScreenshotManager>,
|
||||||
|
main_window: Query<Entity, With<bevy_window::PrimaryWindow>>,
|
||||||
|
) {
|
||||||
|
if ci_testing_config
|
||||||
|
.screenshot_frames
|
||||||
|
.contains(&*current_frame)
|
||||||
|
{
|
||||||
|
info!("Taking a screenshot at frame {}.", *current_frame);
|
||||||
|
let path = format!("./screenshot-{}.png", *current_frame);
|
||||||
|
screenshot_manager
|
||||||
|
.save_screenshot_to_disk(main_window.single(), path)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
*current_frame += 1;
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn align_byte_size(value: u32) -> u32 {
|
pub(crate) fn align_byte_size(value: u32) -> u32 {
|
||||||
value + (COPY_BYTES_PER_ROW_ALIGNMENT - (value % COPY_BYTES_PER_ROW_ALIGNMENT))
|
value + (COPY_BYTES_PER_ROW_ALIGNMENT - (value % COPY_BYTES_PER_ROW_ALIGNMENT))
|
||||||
|
|
|
@ -11,6 +11,7 @@ keywords = ["bevy"]
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
serialize = ["serde"]
|
serialize = ["serde"]
|
||||||
|
bevy_ci_testing = ["bevy_app/bevy_ci_testing"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
|
|
|
@ -47,6 +47,19 @@ impl Plugin for TimePlugin {
|
||||||
.init_resource::<FixedTime>()
|
.init_resource::<FixedTime>()
|
||||||
.add_systems(First, time_system.in_set(TimeSystem))
|
.add_systems(First, time_system.in_set(TimeSystem))
|
||||||
.add_systems(RunFixedUpdateLoop, run_fixed_update_schedule);
|
.add_systems(RunFixedUpdateLoop, run_fixed_update_schedule);
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_ci_testing")]
|
||||||
|
if let Some(ci_testing_config) = app
|
||||||
|
.world
|
||||||
|
.get_resource::<bevy_app::ci_testing::CiTestingConfig>()
|
||||||
|
{
|
||||||
|
if let Some(frame_time) = ci_testing_config.frame_time {
|
||||||
|
app.world
|
||||||
|
.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f32(
|
||||||
|
frame_time,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +73,7 @@ pub enum TimeUpdateStrategy {
|
||||||
Automatic,
|
Automatic,
|
||||||
// Update [`Time`] with an exact `Instant` value
|
// Update [`Time`] with an exact `Instant` value
|
||||||
ManualInstant(Instant),
|
ManualInstant(Instant),
|
||||||
// Update [`Time`] with the current time + a specified `Duration`
|
// Update [`Time`] with the last update time + a specified `Duration`
|
||||||
ManualDuration(Duration),
|
ManualDuration(Duration),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +120,8 @@ fn time_system(
|
||||||
TimeUpdateStrategy::Automatic => time.update_with_instant(new_time),
|
TimeUpdateStrategy::Automatic => time.update_with_instant(new_time),
|
||||||
TimeUpdateStrategy::ManualInstant(instant) => time.update_with_instant(*instant),
|
TimeUpdateStrategy::ManualInstant(instant) => time.update_with_instant(*instant),
|
||||||
TimeUpdateStrategy::ManualDuration(duration) => {
|
TimeUpdateStrategy::ManualDuration(duration) => {
|
||||||
time.update_with_instant(Instant::now() + *duration);
|
let last_update = time.last_update().unwrap_or_else(|| time.startup());
|
||||||
|
time.update_with_instant(last_update + *duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue