From 328c26d02c50de0bc77f0d24a376f43ba89517b1 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Tue, 26 Apr 2022 22:15:24 +0000 Subject: [PATCH] Add an example to test small window sizes (#3597) # Objective We keep getting issues where things break at small window sizes, e.g #3368 (caused by #3153), #3596 ('caused' by #3545) ## Solution - Add a test that we can make small windows. Currently, this fails on my machine with some quite scary vulkan errors: ``` 2022-01-08T22:55:13.770261Z ERROR wgpu_hal::vulkan::instance: VALIDATION [VUID-VkSwapchainCreateInfoKHR-imageExtent-01274 (0x7cd0911d)] Validation Error: [ VUID-VkSwapchainCreateInfoKHR-imageExtent-01274 ] Object 0: handle = 0x1adbd410a60, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x7cd0911d | vkCreateSwapchainKHR() called with imageExtent = (225,60), which is outside the bounds returned by vkGetPhysicalDeviceSurfaceCapabilitiesKHR(): currentExtent = (225,56), minImageExtent = (225,56), maxImageExtent = (225,56). The Vulkan spec states: imageExtent must be between minImageExtent and maxImageExtent, inclusive, where minImageExtent and maxImageExtent are members of the VkSurfaceCapabilitiesKHR structure returned by vkGetPhysicalDeviceSurfaceCapabilitiesKHR for the surface (https://vulkan.lunarg.com/doc/view/1.2.198.1/windows/1.2-extensions/vkspec.html#VUID-VkSwapchainCreateInfoKHR-imageExtent-01274) 2022-01-08T22:55:13.770808Z ERROR wgpu_hal::vulkan::instance: objects: (type: DEVICE, hndl: 0x1adbd410a60, name: ?) 2022-01-08T22:55:13.787403Z ERROR wgpu_hal::vulkan::instance: VALIDATION [VUID-VkSwapchainCreateInfoKHR-imageExtent-01274 (0x7cd0911d)] Validation Error: [ VUID-VkSwapchainCreateInfoKHR-imageExtent-01274 ] Object 0: handle = 0x1adbd410a60, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x7cd0911d | vkCreateSwapchainKHR() called with imageExtent = (225,56), which is outside the bounds returned by vkGetPhysicalDeviceSurfaceCapabilitiesKHR(): currentExtent = (225,52), minImageExtent = (225,52), maxImageExtent = (225,52). The Vulkan spec states: imageExtent must be between minImageExtent and maxImageExtent, inclusive, where minImageExtent and maxImageExtent are members of the VkSurfaceCapabilitiesKHR structure returned by vkGetPhysicalDeviceSurfaceCapabilitiesKHR for the surface (https://vulkan.lunarg.com/doc/view/1.2.198.1/windows/1.2-extensions/vkspec.html#VUID-VkSwapchainCreateInfoKHR-imageExtent-01274) ``` etc. This might be a new issue here, although I'm surprised it's vulkan giving this error; wgpu should stop it if this is illegal. --- .github/example-run/minimising.ron | 3 + .github/example-run/resizing.ron | 4 + Cargo.toml | 9 ++ tests/window/minimising.rs | 75 ++++++++++++++ tests/window/resizing.rs | 153 +++++++++++++++++++++++++++++ 5 files changed, 244 insertions(+) create mode 100644 .github/example-run/minimising.ron create mode 100644 .github/example-run/resizing.ron create mode 100644 tests/window/minimising.rs create mode 100644 tests/window/resizing.rs diff --git a/.github/example-run/minimising.ron b/.github/example-run/minimising.ron new file mode 100644 index 0000000000000..e8577916525db --- /dev/null +++ b/.github/example-run/minimising.ron @@ -0,0 +1,3 @@ +( + exit_after: Some(90) +) diff --git a/.github/example-run/resizing.ron b/.github/example-run/resizing.ron new file mode 100644 index 0000000000000..f4914e281bcd6 --- /dev/null +++ b/.github/example-run/resizing.ron @@ -0,0 +1,4 @@ +( + // Ensures that the full cycle will run + exit_after: Some(410) +) diff --git a/Cargo.toml b/Cargo.toml index 3f5c13b5109b7..35276aeb441be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -615,6 +615,15 @@ path = "examples/window/transparent_window.rs" name = "window_settings" path = "examples/window/window_settings.rs" +[[example]] +name = "resizing" +path = "tests/window/resizing.rs" + +[[example]] +name = "minimising" +path = "tests/window/minimising.rs" + + # Android [[example]] crate-type = ["cdylib"] diff --git a/tests/window/minimising.rs b/tests/window/minimising.rs new file mode 100644 index 0000000000000..b4fd98cb49f4d --- /dev/null +++ b/tests/window/minimising.rs @@ -0,0 +1,75 @@ +//! A test to confirm that `bevy` allows minimising the window +//! This is run in CI to ensure that this doesn't regress again. +use bevy::prelude::*; + +fn main() { + // TODO: Combine this with `resizing` once multiple_windows is simpler than + // it is currently. + App::new() + .insert_resource(WindowDescriptor { + title: "Minimising".into(), + ..Default::default() + }) + .add_plugins(DefaultPlugins) + .add_system(minimise_automatically) + .add_startup_system(setup_3d) + .add_startup_system(setup_2d) + .run(); +} + +fn minimise_automatically(mut windows: ResMut, mut frames: Local) { + if *frames == 60 { + windows.get_primary_mut().unwrap().set_minimized(true); + } else { + *frames += 1; + } +} + +/// A simple 3d scene, taken from the `3d_scene` example +fn setup_3d( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // plane + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..default() + }); + // cube + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }); + // light + commands.spawn_bundle(PointLightBundle { + point_light: PointLight { + intensity: 1500.0, + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} + +/// A simple 2d scene, taken from the `rect` example +fn setup_2d(mut commands: Commands) { + commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(SpriteBundle { + sprite: Sprite { + color: Color::rgb(0.25, 0.25, 0.75), + custom_size: Some(Vec2::new(50.0, 50.0)), + ..default() + }, + ..default() + }); +} diff --git a/tests/window/resizing.rs b/tests/window/resizing.rs new file mode 100644 index 0000000000000..9c4c422a6a21b --- /dev/null +++ b/tests/window/resizing.rs @@ -0,0 +1,153 @@ +//! A test to confirm that `bevy` allows setting the window to arbitrary small sizes +//! This is run in CI to ensure that this doesn't regress again. + +use bevy::{input::system::exit_on_esc_system, prelude::*}; + +// The smallest size reached is 1x1, as X11 doesn't support windows with a 0 dimension +// TODO: Add a check for platforms other than X11 for 0xk and kx0, despite those currently unsupported on CI. +const MAX_WIDTH: u16 = 401; +const MAX_HEIGHT: u16 = 401; +const MIN_WIDTH: u16 = 1; +const MIN_HEIGHT: u16 = 1; +const RESIZE_STEP: u16 = 4; + +struct Dimensions { + width: u16, + height: u16, +} + +fn main() { + App::new() + .insert_resource(WindowDescriptor { + width: MAX_WIDTH.try_into().unwrap(), + height: MAX_HEIGHT.try_into().unwrap(), + scale_factor_override: Some(1.), + title: "Resizing".into(), + ..Default::default() + }) + .insert_resource(Dimensions { + width: MAX_WIDTH, + height: MAX_HEIGHT, + }) + .add_plugins(DefaultPlugins) + .insert_resource(Phase::ContractingY) + .add_system(change_window_size) + .add_system(sync_dimensions) + .add_system(exit_on_esc_system) + .add_startup_system(setup_3d) + .add_startup_system(setup_2d) + .run(); +} + +enum Phase { + ContractingY, + ContractingX, + ExpandingY, + ExpandingX, +} + +use Phase::*; + +fn change_window_size( + mut windows: ResMut, + mut phase: ResMut, + mut first_complete: Local, +) { + // Put off rendering for one frame, as currently for a frame where + // resizing happens, nothing is presented. + // TODO: Debug and fix this if feasible + if !*first_complete { + *first_complete = true; + return; + } + let height = windows.height; + let width = windows.width; + match *phase { + Phase::ContractingY => { + if height <= MIN_HEIGHT { + *phase = ContractingX; + } else { + windows.height -= RESIZE_STEP; + } + } + Phase::ContractingX => { + if width <= MIN_WIDTH { + *phase = ExpandingY; + } else { + windows.width -= RESIZE_STEP; + } + } + Phase::ExpandingY => { + if height >= MAX_HEIGHT { + *phase = ExpandingX; + } else { + windows.height += RESIZE_STEP; + } + } + Phase::ExpandingX => { + if width >= MAX_WIDTH { + *phase = ContractingY; + } else { + windows.width += RESIZE_STEP; + } + } + } +} + +fn sync_dimensions(dim: Res, mut windows: ResMut) { + if dim.is_changed() { + windows.get_primary_mut().unwrap().set_resolution( + dim.width.try_into().unwrap(), + dim.height.try_into().unwrap(), + ); + } +} + +/// A simple 3d scene, taken from the `3d_scene` example +fn setup_3d( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // plane + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..default() + }); + // cube + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }); + // light + commands.spawn_bundle(PointLightBundle { + point_light: PointLight { + intensity: 1500.0, + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} + +/// A simple 2d scene, taken from the `rect` example +fn setup_2d(mut commands: Commands) { + commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(SpriteBundle { + sprite: Sprite { + color: Color::rgb(0.25, 0.25, 0.75), + custom_size: Some(Vec2::new(50.0, 50.0)), + ..default() + }, + ..default() + }); +}