Skip to content

Commit

Permalink
Take example screenshots in CI (#8488)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
mockersf authored May 1, 2023
1 parent 5288be7 commit 8070c29
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 6 deletions.
4 changes: 3 additions & 1 deletion .github/example-run/breakout.ron
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
(
exit_after: Some(900)
exit_after: Some(900),
frame_time: Some(0.03),
screenshot_frames: [200],
)
4 changes: 3 additions & 1 deletion .github/example-run/load_gltf.ron
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
(
exit_after: Some(300)
exit_after: Some(300),
frame_time: Some(0.03),
screenshot_frames: [100],
)
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,23 @@ jobs:
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"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
mv screenshot-*.png screenshots-$example_name/
fi
done
zip traces.zip trace*.json
zip -r screenshots.zip screenshots-*
- name: save traces
uses: actions/upload-artifact@v3
with:
name: example-traces.zip
path: traces.zip
- name: save screenshots
uses: actions/upload-artifact@v3
with:
name: screenshots.zip
path: screenshots.zip
- name: Save PR number
if: ${{ failure() && github.event_name == 'pull_request' }}
run: |
Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_app/src/ci_testing.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Utilities for testing in CI environments.

use crate::{app::AppExit, App, Update};
use serde::Deserialize;

Expand All @@ -13,6 +15,11 @@ use bevy_utils::tracing::info;
pub struct CiTestingConfig {
/// The number of frames after which Bevy should exit.
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(
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod plugin_group;
mod schedule_runner;

#[cfg(feature = "bevy_ci_testing")]
mod ci_testing;
pub mod ci_testing;

pub use app::*;
pub use bevy_derive::DynamicPlugin;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"]

# 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
animation = ["bevy_animation", "bevy_gltf?/bevy_animation"]
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jpeg = ["image/jpeg"]
bmp = ["image/bmp"]
webp = ["image/webp"]
dds = ["ddsfile"]
bevy_ci_testing = ["bevy_app/bevy_ci_testing"]

shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out"]
shader_format_spirv = ["wgpu/spirv", "naga/spv-in", "naga/spv-out"]
Expand Down
28 changes: 28 additions & 0 deletions crates/bevy_render/src/view/window/screenshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,35 @@ impl Plugin for ScreenshotPlugin {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
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 {
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_time/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ keywords = ["bevy"]
[features]
default = []
serialize = ["serde"]
bevy_ci_testing = ["bevy_app/bevy_ci_testing"]

[dependencies]
# bevy
Expand Down
18 changes: 16 additions & 2 deletions crates/bevy_time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ impl Plugin for TimePlugin {
.init_resource::<FixedTime>()
.add_systems(First, time_system.in_set(TimeSystem))
.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,
)));
}
}
}
}

Expand All @@ -60,7 +73,7 @@ pub enum TimeUpdateStrategy {
Automatic,
// Update [`Time`] with an exact `Instant` value
ManualInstant(Instant),
// Update [`Time`] with the current time + a specified `Duration`
// Update [`Time`] with the last update time + a specified `Duration`
ManualDuration(Duration),
}

Expand Down Expand Up @@ -107,7 +120,8 @@ fn time_system(
TimeUpdateStrategy::Automatic => time.update_with_instant(new_time),
TimeUpdateStrategy::ManualInstant(instant) => time.update_with_instant(*instant),
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);
}
}
}

0 comments on commit 8070c29

Please sign in to comment.