From a430ae336677c807f0bc0c870bcc40c7cbb4584f Mon Sep 17 00:00:00 2001 From: David Cristofaro Date: Sun, 12 Mar 2023 17:59:07 +1100 Subject: [PATCH 1/3] Update bevy to 0.10 --- CHANGELOG.md | 1 + Cargo.toml | 2 +- examples/minimal.rs | 13 +++++----- src/lib.rs | 63 +++++++++++++++++++-------------------------- 4 files changed, 34 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c456cb7..bc7a8dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed +- Updated `bevy` to 0.10. - Updated `pixels` to 0.12. ## [0.8.0] - 2022-12-20 diff --git a/Cargo.toml b/Cargo.toml index 1b532a5..0f82888 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ wayland = ["bevy/wayland"] x11 = ["bevy/x11"] [dependencies] -bevy = { version = "0.9", default_features = false, features = ["bevy_winit"] } +bevy = { version = "0.10", default_features = false, features = ["bevy_winit"] } pixels = "0.12" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/minimal.rs b/examples/minimal.rs index a90ac48..9735392 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -2,7 +2,7 @@ use bevy::{ app::AppExit, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, - window::WindowResizeConstraints, + window::{WindowResizeConstraints, WindowResolution}, }; use bevy_pixels::prelude::*; use rand::prelude::*; @@ -42,10 +42,9 @@ struct Color(u8, u8, u8, u8); fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { - window: WindowDescriptor { + primary_window: Some(Window { title: "Hello Bevy Pixels".to_string(), - width: WIDTH as f32, - height: HEIGHT as f32, + resolution: WindowResolution::new(WIDTH as f32, HEIGHT as f32), resize_constraints: WindowResizeConstraints { min_width: WIDTH as f32, min_height: HEIGHT as f32, @@ -53,7 +52,7 @@ fn main() { }, fit_canvas_to_parent: true, ..default() - }, + }), ..default() })) .add_plugin(PixelsPlugin { @@ -67,8 +66,8 @@ fn main() { .add_system(bounce) .add_system(movement.after(bounce)) .add_system(exit_on_escape) - .add_system_to_stage(PixelsStage::Draw, draw_background) - .add_system_to_stage(PixelsStage::Draw, draw_objects.after(draw_background)) + .add_system(draw_background.in_set(PixelsSet::Draw)) + .add_system(draw_objects.in_set(PixelsSet::Draw).after(draw_background)) .run(); } diff --git a/src/lib.rs b/src/lib.rs index f6397ab..c76f94a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ pub mod prelude { - pub use crate::{PixelsPlugin, PixelsResource, PixelsStage}; + pub use crate::{PixelsPlugin, PixelsResource, PixelsSet}; } pub use pixels; @@ -7,7 +7,7 @@ pub use pixels; use bevy::{ diagnostic::{Diagnostic, DiagnosticId, Diagnostics}, prelude::*, - window::{WindowBackendScaleFactorChanged, WindowId, WindowResized}, + window::{WindowBackendScaleFactorChanged, WindowResized}, winit::WinitWindows, }; use pixels::{Pixels, SurfaceTexture}; @@ -16,8 +16,8 @@ use pollster::FutureExt as _; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -pub enum PixelsStage { +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub enum PixelsSet { Draw, Render, PostRender, @@ -26,7 +26,7 @@ pub enum PixelsStage { #[derive(Resource)] pub struct PixelsResource { pub pixels: Pixels, - pub window_id: WindowId, + pub window: Entity, } // Internal configuration resource for use in `setup` system. Users should set values on @@ -60,25 +60,15 @@ impl Plugin for PixelsPlugin { width: self.width, height: self.height, }) - .add_stage_after( - CoreStage::PostUpdate, - PixelsStage::Draw, - SystemStage::parallel(), + .configure_set( + PixelsSet::Draw + .before(PixelsSet::Render) + .before(PixelsSet::PostRender), // (PixelsSet::Draw, PixelsSet::Render, PixelsSet::PostRender).chain() ) - .add_stage_after( - PixelsStage::Draw, - PixelsStage::Render, - SystemStage::parallel(), - ) - .add_stage_after( - PixelsStage::Render, - PixelsStage::PostRender, - SystemStage::parallel(), - ) - .add_startup_system_to_stage(StartupStage::PreStartup, Self::setup) + .add_startup_system(Self::setup) .add_system(Self::window_resize) .add_system(Self::window_change) - .add_system_to_stage(PixelsStage::Render, Self::render); + .add_system(Self::render.in_set(PixelsSet::Render)); } } @@ -90,18 +80,15 @@ impl PixelsPlugin { mut commands: Commands, mut diagnostics: ResMut, options: Res, - windows: Res, + windows: Query<(Entity, &Window)>, winit_windows: NonSend, ) { diagnostics.add(Diagnostic::new(Self::RENDER_TIME, "render_time", 20).with_suffix("s")); - let window_id = windows - .get_primary() - .expect("primary window not found") - .id(); + let (window, _) = windows.get_single().expect("primary window not found"); let winit_window = winit_windows - .get_window(window_id) + .get_window(window) .expect("failed to get primary winit window"); let window_size = winit_window.inner_size(); @@ -121,17 +108,19 @@ impl PixelsPlugin { } .expect("failed to create pixels"); - commands.insert_resource(PixelsResource { pixels, window_id }); + commands.insert_resource(PixelsResource { pixels, window }); } pub fn window_resize( mut window_resized_events: EventReader, mut resource: ResMut, - windows: Res, + windows: Query<&Window>, ) { for event in window_resized_events.iter() { - if event.id == resource.window_id { - Self::resize_surface_to_window(&mut resource, &windows); + if event.window == resource.window { + if let Ok(window) = windows.get(event.window) { + Self::resize_surface_to_window(&mut resource, window); + } } } } @@ -141,18 +130,18 @@ impl PixelsPlugin { WindowBackendScaleFactorChanged, >, mut resource: ResMut, - windows: Res, + windows: Query<&Window>, ) { for event in window_backend_scale_factor_changed_events.iter() { - if event.id == resource.window_id { - Self::resize_surface_to_window(&mut resource, &windows); + if event.window == resource.window { + if let Ok(window) = windows.get(event.window) { + Self::resize_surface_to_window(&mut resource, window); + } } } } - fn resize_surface_to_window(resource: &mut ResMut, windows: &Res) { - let window = windows.get(resource.window_id).unwrap(); - + fn resize_surface_to_window(resource: &mut ResMut, window: &Window) { let _ = resource .pixels .resize_surface(window.physical_width(), window.physical_height()); From 22f847446ebc34b3671f2dc2b5df8ecb93e24914 Mon Sep 17 00:00:00 2001 From: David Cristofaro Date: Sat, 25 Mar 2023 15:27:43 +1100 Subject: [PATCH 2/3] Tweak privacy of internal structs and functions --- src/lib.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c76f94a..b26230e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,11 +29,8 @@ pub struct PixelsResource { pub window: Entity, } -// Internal configuration resource for use in `setup` system. Users should set values on -// `PixelsPlugin` instead of inserting this resource directly. Ideally we just read the plugin -// configuration directly within `setup` system, but this is not currently possible. #[derive(Resource)] -pub struct PixelsOptions { +struct PixelsOptions { width: u32, height: u32, } @@ -76,7 +73,7 @@ impl PixelsPlugin { pub const RENDER_TIME: DiagnosticId = DiagnosticId::from_u128(1187582084072339577959028643519383692); - pub fn setup( + fn setup( mut commands: Commands, mut diagnostics: ResMut, options: Res, @@ -111,7 +108,7 @@ impl PixelsPlugin { commands.insert_resource(PixelsResource { pixels, window }); } - pub fn window_resize( + fn window_resize( mut window_resized_events: EventReader, mut resource: ResMut, windows: Query<&Window>, @@ -125,7 +122,7 @@ impl PixelsPlugin { } } - pub fn window_change( + fn window_change( mut window_backend_scale_factor_changed_events: EventReader< WindowBackendScaleFactorChanged, >, @@ -148,7 +145,7 @@ impl PixelsPlugin { } #[cfg(not(target_arch = "wasm32"))] - pub fn render(resource: Res, mut diagnostics: ResMut) { + fn render(resource: Res, mut diagnostics: ResMut) { let start = Instant::now(); resource.pixels.render().expect("failed to render pixels"); @@ -159,7 +156,7 @@ impl PixelsPlugin { } #[cfg(target_arch = "wasm32")] - pub fn render(resource: Res) { + fn render(resource: Res) { resource.pixels.render().expect("failed to render pixels"); } } From b8ac2b7b298821487648e3d11ab1faa40c15d4dd Mon Sep 17 00:00:00 2001 From: David Cristofaro Date: Wed, 29 Mar 2023 16:08:58 +1100 Subject: [PATCH 3/3] Major bevy 0.10 changes - Adds support support for multiple windows. Made possible by the move from `PixelsResource` to `PixelsWrapper` described below. - Adds `multiple_windows` example demonstrating support for multiple windows. - Adds `scale_factor` option to control scale factor between logical window size and buffer size when using `auto_resize_buffer`. - Adds `auto_resize_buffer` option to control automatic resizing of the buffer when the window changes. - Adds `auto_resize_surface` option to control automatic resizing of the surface when the window changes. - Configuration of buffer size has been moved from `PixelsPlugin` to `PixelsOptions`. - Primary window buffer is created by providing `Some(PixelsOptions { ... })` to the `primary_window` when creating `PixelsPlugin`. This works the same was as Bevy's own configuration of primary window in the `WindowPlugin`. - Resouce `PixelsResource` has been replaced with `PixelsWrapper` component that is automatically added to `Window` entities with the `PixelsOptions` component. - Diagnostic `PixelsPlugin::RENDER_TIME` is now recorded in miliseconds instead of seconds. - Updated `minimal` example to demonstrate `auto_resize_buffer` feature. --- CHANGELOG.md | 20 ++++ examples/minimal.rs | 97 ++++++++++------ examples/multiple_windows.rs | 27 +++++ src/lib.rs | 215 +++++++++++++++++++++++------------ 4 files changed, 251 insertions(+), 108 deletions(-) create mode 100644 examples/multiple_windows.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index bc7a8dd..2bbfee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,30 @@ ## [Unreleased] +### Added + +- Added support support for multiple windows. Made possible by the move from `PixelsResource` to + `PixelsWrapper` described below. +- Added `multiple_windows` example demonstrating support for multiple windows. +- Added `scale_factor` option to control scale factor between logical window size and buffer size + when using `auto_resize_buffer`. +- Added `auto_resize_buffer` option to control automatic resizing of the buffer when the window + changes. +- Added `auto_resize_surface` option to control automatic resizing of the surface when the window + changes. + ### Changed - Updated `bevy` to 0.10. - Updated `pixels` to 0.12. +- Configuration of buffer size has been moved from `PixelsPlugin` to `PixelsOptions`. +- Primary window buffer is created by providing `Some(PixelsOptions { ... })` to the + `primary_window` when creating `PixelsPlugin`. This works the same was as Bevy's own configuration + of primary window in the `WindowPlugin`. +- Resouce `PixelsResource` has been replaced with `PixelsWrapper` component that is automatically + added to `Window` entities with the `PixelsOptions` component. +- Diagnostic `PixelsPlugin::RENDER_TIME` is now recorded in miliseconds instead of seconds. +- Updated `minimal` example to demonstrate `auto_resize_buffer` feature. ## [0.8.0] - 2022-12-20 diff --git a/examples/minimal.rs b/examples/minimal.rs index 9735392..f513e04 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -1,5 +1,4 @@ use bevy::{ - app::AppExit, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, window::{WindowResizeConstraints, WindowResolution}, @@ -7,8 +6,9 @@ use bevy::{ use bevy_pixels::prelude::*; use rand::prelude::*; -const WIDTH: u32 = 320; -const HEIGHT: u32 = 240; +const INITIAL_WIDTH: u32 = 320; +const INITIAL_HEIGHT: u32 = 240; +const SCALE_FACTOR: f32 = 2.0; #[derive(Bundle, Debug)] struct ObjectBundle { @@ -44,10 +44,13 @@ fn main() { .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "Hello Bevy Pixels".to_string(), - resolution: WindowResolution::new(WIDTH as f32, HEIGHT as f32), + resolution: WindowResolution::new( + INITIAL_WIDTH as f32 * SCALE_FACTOR, + INITIAL_HEIGHT as f32 * SCALE_FACTOR, + ), resize_constraints: WindowResizeConstraints { - min_width: WIDTH as f32, - min_height: HEIGHT as f32, + min_width: INITIAL_WIDTH as f32 * SCALE_FACTOR, + min_height: INITIAL_HEIGHT as f32 * SCALE_FACTOR, ..default() }, fit_canvas_to_parent: true, @@ -56,21 +59,27 @@ fn main() { ..default() })) .add_plugin(PixelsPlugin { - width: WIDTH, - height: HEIGHT, - ..default() + primary_window: Some(PixelsOptions { + width: INITIAL_WIDTH, + height: INITIAL_HEIGHT, + scale_factor: SCALE_FACTOR, + ..default() + }), }) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) .add_startup_system(setup) - .add_system(bounce) - .add_system(movement.after(bounce)) - .add_system(exit_on_escape) - .add_system(draw_background.in_set(PixelsSet::Draw)) - .add_system(draw_objects.in_set(PixelsSet::Draw).after(draw_background)) + .add_system(bevy::window::close_on_esc) + .add_systems((bounce, movement).chain().in_set(PixelsSet::Update)) + .add_systems( + (draw_background, draw_objects) + .chain() + .in_set(PixelsSet::Draw), + ) .run(); } +/// Spawn object. fn setup(mut commands: Commands) { let box_object = ObjectBundle { position: Position { x: 24, y: 16 }, @@ -84,14 +93,28 @@ fn setup(mut commands: Commands) { commands.spawn(box_object); } -fn bounce(mut query: Query<(&Position, &mut Velocity, &Size, &mut Color)>) { - for (position, mut velocity, size, mut color) in query.iter_mut() { +/// Bounce object off edges of buffer. +fn bounce( + options_query: Query<&PixelsOptions>, + mut query: Query<(&Position, &mut Velocity, &Size, &mut Color)>, +) { + let Ok(options) = options_query.get_single() else { return }; + + for (position, mut velocity, size, mut color) in &mut query { let mut bounce = false; - if position.x == 0 || position.x + size.width > WIDTH { + if position.x == 0 && velocity.x < 0 { + velocity.x *= -1; + bounce = true; + } + if position.x + size.width == options.width && velocity.x > 0 { velocity.x *= -1; bounce = true; } - if position.y == 0 || position.y + size.height > HEIGHT { + if position.y == 0 && velocity.y < 0 { + velocity.y *= -1; + bounce = true; + } + if position.y + size.height == options.height && velocity.y > 0 { velocity.y *= -1; bounce = true; } @@ -103,37 +126,43 @@ fn bounce(mut query: Query<(&Position, &mut Velocity, &Size, &mut Color)>) { } } -fn movement(mut query: Query<(&mut Position, &Velocity)>) { - for (mut position, velocity) in query.iter_mut() { - position.x = (position.x as i16 + velocity.x) as u32; - position.y = (position.y as i16 + velocity.y) as u32; - } -} +/// Move object based on current velocity. +fn movement( + options_query: Query<&PixelsOptions>, + mut query: Query<(&mut Position, &Velocity, &Size)>, +) { + let Ok(options) = options_query.get_single() else { return }; -fn exit_on_escape(keyboard_input: Res>, mut app_exit_events: EventWriter) { - if keyboard_input.just_pressed(KeyCode::Escape) { - app_exit_events.send(AppExit); + for (mut position, velocity, size) in &mut query { + position.x = ((position.x as i16 + velocity.x) as u32).clamp(0, options.width - size.width); + position.y = + ((position.y as i16 + velocity.y) as u32).clamp(0, options.height - size.height); } } -fn draw_background(mut pixels_resource: ResMut) { - let frame = pixels_resource.pixels.frame_mut(); +/// Draw solid background to buffer. +fn draw_background(mut wrapper_query: Query<&mut PixelsWrapper>) { + let Ok(mut wrapper) = wrapper_query.get_single_mut() else { return }; + let frame = wrapper.pixels.frame_mut(); + frame.copy_from_slice(&[0x48, 0xb2, 0xe8, 0xff].repeat(frame.len() / 4)); } +/// Draw objects to buffer. fn draw_objects( - mut pixels_resource: ResMut, + mut wrapper_query: Query<(&mut PixelsWrapper, &PixelsOptions)>, query: Query<(&Position, &Size, &Color)>, ) { - let frame = pixels_resource.pixels.frame_mut(); - let frame_width_bytes = (WIDTH * 4) as usize; + let Ok((mut wrapper, options)) = wrapper_query.get_single_mut() else { return }; + let frame = wrapper.pixels.frame_mut(); + let frame_width_bytes = (options.width * 4) as usize; - for (position, size, color) in query.iter() { + for (position, size, color) in &query { let x_offset = (position.x * 4) as usize; let width_bytes = (size.width * 4) as usize; let object_row = &[color.0, color.1, color.2, color.3].repeat(size.width as usize); - for y in position.y..(position.y + size.height - 1) { + for y in position.y..(position.y + size.height) { let y_offset = y as usize * frame_width_bytes; let i = y_offset + x_offset; let j = i + width_bytes; diff --git a/examples/multiple_windows.rs b/examples/multiple_windows.rs new file mode 100644 index 0000000..ee8c639 --- /dev/null +++ b/examples/multiple_windows.rs @@ -0,0 +1,27 @@ +use bevy::prelude::*; +use bevy_pixels::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(PixelsPlugin::default()) + .add_startup_system(setup) + .add_system(bevy::window::close_on_esc) + .add_system(draw.in_set(PixelsSet::Draw)) + .run(); +} + +/// Spawn two more windows in addition to the primary window that comes by default. +fn setup(mut commands: Commands) { + commands.spawn((Window::default(), PixelsOptions::default())); + commands.spawn((Window::default(), PixelsOptions::default())); +} + +/// Draw solid background to each window's buffer. +fn draw(mut wrapper_query: Query<&mut PixelsWrapper>) { + for mut wrapper in &mut wrapper_query { + let frame = wrapper.pixels.frame_mut(); + + frame.copy_from_slice(&[0x48, 0xb2, 0xe8, 0xff].repeat(frame.len() / 4)); + } +} diff --git a/src/lib.rs b/src/lib.rs index b26230e..953fef0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,14 @@ pub mod prelude { - pub use crate::{PixelsPlugin, PixelsResource, PixelsSet}; + pub use crate::{PixelsOptions, PixelsPlugin, PixelsSet, PixelsWrapper}; } pub use pixels; use bevy::{ diagnostic::{Diagnostic, DiagnosticId, Diagnostics}, + ecs::system::SystemState, prelude::*, - window::{WindowBackendScaleFactorChanged, WindowResized}, + window::{PrimaryWindow, RawHandleWrapper, WindowBackendScaleFactorChanged, WindowResized}, winit::WinitWindows, }; use pixels::{Pixels, SurfaceTexture}; @@ -18,54 +19,96 @@ use std::time::Instant; #[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] pub enum PixelsSet { + /// Runs in [`CoreSet::Update`] base set. Use this set for simulation logic before draw. + Update, + /// Runs in [`CoreSet::PostUpdate`] base set. Use this set for logic that draws to the buffer. Draw, + /// Runs in [`CoreSet::Last`] base set. This set is used internally for rendering the buffer to + /// the surface. Render, - PostRender, } -#[derive(Resource)] -pub struct PixelsResource { - pub pixels: Pixels, - pub window: Entity, +/// Defines the sizing and behavior of the pixel buffer and surface texture. +#[derive(Component, Debug, Copy, Clone)] +pub struct PixelsOptions { + /// Width of the pixel buffer. Changing this after initialization will resize the buffer. + pub width: u32, + /// Height of the pixel buffer. Changing this after initialization will resize the buffer. + pub height: u32, + /// Scale factor between logical window size and buffer size. Only used when + /// `auto_resize_buffer` is enabled. + pub scale_factor: f32, + /// Should the buffer automatically be resized when the window changes? + pub auto_resize_buffer: bool, + /// Should the surface texture automatically be resized when the window changes? + pub auto_resize_surface: bool, } -#[derive(Resource)] -struct PixelsOptions { - width: u32, - height: u32, +impl Default for PixelsOptions { + fn default() -> Self { + PixelsOptions { + width: 640, + height: 360, + scale_factor: 2.0, + auto_resize_buffer: true, + auto_resize_surface: true, + } + } +} + +/// Wrapper component for underlying [`Pixels`] struct. +#[derive(Component, Debug)] +pub struct PixelsWrapper { + pub pixels: Pixels, } +/// A [`Plugin`] that defines an integration between Bevy and the [`pixels`](https://github.com/parasyte/pixels) +/// crate. Should be added to app after [`DefaultPlugins`]. pub struct PixelsPlugin { - /// Width of the pixel buffer - pub width: u32, - /// Height of the pixel buffer - pub height: u32, + /// Configuration for the primary window pixel buffer. This will automatically create a + /// [`PixelsWrapper`] component (using the provided options) for the primary window entity. + pub primary_window: Option, } impl Default for PixelsPlugin { fn default() -> Self { PixelsPlugin { - width: 180, - height: 120, + primary_window: Some(PixelsOptions::default()), } } } impl Plugin for PixelsPlugin { fn build(&self, app: &mut App) { - app.insert_resource(PixelsOptions { - width: self.width, - height: self.height, - }) - .configure_set( - PixelsSet::Draw - .before(PixelsSet::Render) - .before(PixelsSet::PostRender), // (PixelsSet::Draw, PixelsSet::Render, PixelsSet::PostRender).chain() - ) + app.configure_sets(( + PixelsSet::Update.in_base_set(CoreSet::Update), + PixelsSet::Draw.in_base_set(CoreSet::PostUpdate), + PixelsSet::Render.in_base_set(CoreSet::Last), + )) .add_startup_system(Self::setup) - .add_system(Self::window_resize) - .add_system(Self::window_change) + .add_system(Self::create_pixels.in_base_set(CoreSet::First)) + .add_systems( + ( + Self::window_change, + Self::window_resize, + Self::resize_buffer.after(Self::window_resize), + ) + .in_base_set(CoreSet::PreUpdate), + ) .add_system(Self::render.in_set(PixelsSet::Render)); + + // If supplied, attach the primary window [`PixelsOptions`] component to the [`Window`] + // entity with the [`PrimaryWindow`] marker component (if it exists). This will trigger + // [`create_pixels`] system for this entity which will initialize the [`Pixels`] buffer. + if let Some(options) = &self.primary_window { + let mut system_state: SystemState>> = + SystemState::new(&mut app.world); + let query = system_state.get(&app.world); + + if let Ok(entity) = query.get_single() { + app.world.entity_mut(entity).insert(*options); + }; + } } } @@ -73,90 +116,114 @@ impl PixelsPlugin { pub const RENDER_TIME: DiagnosticId = DiagnosticId::from_u128(1187582084072339577959028643519383692); - fn setup( + /// Setup diagnostics. + fn setup(mut diagnostics: ResMut) { + diagnostics.add(Diagnostic::new(Self::RENDER_TIME, "render_time", 20).with_suffix("ms")); + } + + /// Create [`PixelsWrapper`] (and underlying [`Pixels`] buffer) for all suitable [`Window`] with + /// a [`PixelsOptions`] component. + fn create_pixels( mut commands: Commands, - mut diagnostics: ResMut, - options: Res, - windows: Query<(Entity, &Window)>, + query: Query<(Entity, &PixelsOptions), (With, Without)>, winit_windows: NonSend, ) { - diagnostics.add(Diagnostic::new(Self::RENDER_TIME, "render_time", 20).with_suffix("s")); - - let (window, _) = windows.get_single().expect("primary window not found"); - - let winit_window = winit_windows - .get_window(window) - .expect("failed to get primary winit window"); - - let window_size = winit_window.inner_size(); - let surface_texture = - SurfaceTexture::new(window_size.width, window_size.height, winit_window); - - let pixels = { - #[cfg(not(target_arch = "wasm32"))] - { - Pixels::new(options.width, options.height, surface_texture) - } - #[cfg(target_arch = "wasm32")] - { - // TODO: Find a way to asynchronously load pixels on web - Pixels::new_async(options.width, options.height, surface_texture).block_on() + for (entity, options) in &query { + let winit_window = winit_windows + .get_window(entity) + .expect("failed to get winit window"); + + let window_size = winit_window.inner_size(); + let surface_texture = + SurfaceTexture::new(window_size.width, window_size.height, winit_window); + + let pixels = { + #[cfg(not(target_arch = "wasm32"))] + { + Pixels::new(options.width, options.height, surface_texture) + } + #[cfg(target_arch = "wasm32")] + { + // TODO: Find a way to asynchronously load pixels on web. + Pixels::new_async(options.width, options.height, surface_texture).block_on() + } } - } - .expect("failed to create pixels"); + .expect("failed to create pixels"); - commands.insert_resource(PixelsResource { pixels, window }); + commands.entity(entity).insert(PixelsWrapper { pixels }); + } } + /// Resize buffer and surface to window when it is resized. fn window_resize( mut window_resized_events: EventReader, - mut resource: ResMut, - windows: Query<&Window>, + mut query: Query<(&mut PixelsWrapper, &mut PixelsOptions, &Window)>, ) { for event in window_resized_events.iter() { - if event.window == resource.window { - if let Ok(window) = windows.get(event.window) { - Self::resize_surface_to_window(&mut resource, window); + if let Ok((mut wrapper, mut options, window)) = query.get_mut(event.window) { + if options.auto_resize_buffer { + options.width = (window.width() / options.scale_factor).floor() as u32; + options.height = (window.height() / options.scale_factor).floor() as u32; + } + + if options.auto_resize_surface { + Self::resize_surface_to_window(&mut wrapper, window); } } } } + /// Resize surface to window when scale factor changes. fn window_change( mut window_backend_scale_factor_changed_events: EventReader< WindowBackendScaleFactorChanged, >, - mut resource: ResMut, - windows: Query<&Window>, + mut query: Query<(&mut PixelsWrapper, &PixelsOptions, &Window)>, ) { for event in window_backend_scale_factor_changed_events.iter() { - if event.window == resource.window { - if let Ok(window) = windows.get(event.window) { - Self::resize_surface_to_window(&mut resource, window); + if let Ok((mut wrapper, options, window)) = query.get_mut(event.window) { + if options.auto_resize_surface { + Self::resize_surface_to_window(&mut wrapper, window); } } } } - fn resize_surface_to_window(resource: &mut ResMut, window: &Window) { - let _ = resource + fn resize_surface_to_window(wrapper: &mut PixelsWrapper, window: &Window) { + let _ = wrapper .pixels .resize_surface(window.physical_width(), window.physical_height()); } + /// Resize buffer when width and height change. + fn resize_buffer( + mut query: Query<(&mut PixelsWrapper, &PixelsOptions), Changed>, + ) { + for (mut wrapper, options) in &mut query { + if options.auto_resize_buffer { + let _ = wrapper.pixels.resize_buffer(options.width, options.height); + } + } + } + + /// Render buffer to surface. #[cfg(not(target_arch = "wasm32"))] - fn render(resource: Res, mut diagnostics: ResMut) { + fn render(mut diagnostics: ResMut, query: Query<&PixelsWrapper>) { let start = Instant::now(); - resource.pixels.render().expect("failed to render pixels"); + for wrapper in &query { + wrapper.pixels.render().expect("failed to render pixels"); + } let end = Instant::now(); - let render_time = end.duration_since(start); - diagnostics.add_measurement(Self::RENDER_TIME, || render_time.as_secs_f64()); + let render_time_seconds = end.duration_since(start).as_secs_f64(); + diagnostics.add_measurement(Self::RENDER_TIME, || render_time_seconds * 1000.0); } #[cfg(target_arch = "wasm32")] - fn render(resource: Res) { - resource.pixels.render().expect("failed to render pixels"); + fn render(query: Query<&PixelsWrapper>) { + for wrapper in &query { + wrapper.pixels.render().expect("failed to render pixels"); + } } }