diff --git a/Cargo.toml b/Cargo.toml index 24729c9e..824e51ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,9 @@ required-features = ["render"] name = "simple" required-features = ["render"] [[example]] +name = "simple_multipass" +required-features = ["render"] +[[example]] name = "two_windows" required-features = ["render"] [[example]] @@ -46,10 +49,10 @@ required-features = ["render"] [dependencies] bevy = { version = "0.14.0", default-features = false, features = [ - "bevy_asset", - "bevy_winit", + "bevy_asset", + "bevy_winit", ] } -egui = { version = "0.28", default-features = false, features = ["bytemuck"] } +egui = { version = "0.29", default-features = false, features = ["bytemuck"] } bytemuck = "1" webbrowser = { version = "1.0.1", optional = true } wgpu-types = "0.20" @@ -61,25 +64,25 @@ thread_local = { version = "1.1.0", optional = true } [dev-dependencies] version-sync = "0.9.4" bevy = { version = "0.14.0", default-features = false, features = [ - "x11", - "png", - "bevy_pbr", - "bevy_core_pipeline", - "tonemapping_luts", - "webgl2", + "x11", + "png", + "bevy_pbr", + "bevy_core_pipeline", + "tonemapping_luts", + "webgl2", ] } -egui = { version = "0.28", default-features = false, features = ["bytemuck"] } +egui = { version = "0.29", default-features = false, features = ["bytemuck"] } [target.'cfg(target_arch = "wasm32")'.dependencies] winit = "0.30" web-sys = { version = "0.3.63", features = [ - "Clipboard", - "ClipboardEvent", - "DataTransfer", - 'Document', - 'EventTarget', - "Window", - "Navigator", + "Clipboard", + "ClipboardEvent", + "DataTransfer", + 'Document', + 'EventTarget', + "Window", + "Navigator", ] } js-sys = "0.3.63" wasm-bindgen = "0.2.84" diff --git a/examples/paint_callback.rs b/examples/paint_callback.rs index 8bb2b217..48b9f1a1 100644 --- a/examples/paint_callback.rs +++ b/examples/paint_callback.rs @@ -20,7 +20,7 @@ use wgpu_types::{Extent3d, TextureUsages}; fn main() { App::new() - .add_plugins((DefaultPlugins, EguiPlugin, CustomPipelinePlugin)) + .add_plugins((DefaultPlugins, EguiPlugin::default(), CustomPipelinePlugin)) .add_systems(Startup, setup_worldspace) .add_systems( Update, diff --git a/examples/render_egui_to_texture.rs b/examples/render_egui_to_texture.rs index 953ee584..e22fa350 100644 --- a/examples/render_egui_to_texture.rs +++ b/examples/render_egui_to_texture.rs @@ -5,7 +5,7 @@ use wgpu_types::{Extent3d, TextureUsages}; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); - app.add_plugins(EguiPlugin); + app.add_plugins(EguiPlugin::default()); app.add_systems(Startup, setup_worldspace); app.add_systems(Update, (update_screenspace, update_worldspace)); app.run(); diff --git a/examples/render_to_image_widget.rs b/examples/render_to_image_widget.rs index c782b689..709a0697 100644 --- a/examples/render_to_image_widget.rs +++ b/examples/render_to_image_widget.rs @@ -13,7 +13,7 @@ use bevy_egui::{egui::Widget, EguiContexts, EguiPlugin, EguiUserTextures}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .add_systems(Startup, setup) .add_systems(Update, rotator_system) .add_systems(Update, render_to_image_example_system) diff --git a/examples/side_panel.rs b/examples/side_panel.rs index 14e9c56e..f778dbd3 100644 --- a/examples/side_panel.rs +++ b/examples/side_panel.rs @@ -18,7 +18,7 @@ fn main() { App::new() .insert_resource(WinitSettings::desktop_app()) .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .init_resource::() .add_systems(Startup, setup_system) .add_systems(Update, ui_example_system) diff --git a/examples/simple.rs b/examples/simple.rs index 47d532f5..62d1f51f 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -4,7 +4,7 @@ use bevy_egui::{EguiContexts, EguiPlugin}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) // Systems that create Egui widgets should be run during the `CoreSet::Update` set, // or after the `EguiSet::BeginFrame` system (which belongs to the `CoreSet::PreUpdate` set). .add_systems(Update, ui_example_system) diff --git a/examples/simple_multipass.rs b/examples/simple_multipass.rs new file mode 100644 index 00000000..3fbf8038 --- /dev/null +++ b/examples/simple_multipass.rs @@ -0,0 +1,36 @@ +use std::num::NonZero; + +use bevy::prelude::*; +use bevy_egui::{EguiContext, EguiFullOutput, EguiInput, EguiPlugin}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(EguiPlugin { manual_run: true }) + // Systems that create Egui widgets should be run during the `CoreSet::Update` set, + // or after the `EguiSet::BeginFrame` system (which belongs to the `CoreSet::PreUpdate` set). + .add_systems(Update, ui_example_system) + .run(); +} + +fn ui_example_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput, &mut EguiFullOutput)>) { + for (mut ctx, mut egui_input, mut egui_full_output) in contexts.iter_mut() { + let ui = |ctx: &egui::Context| { + egui::Window::new("Hello").show(ctx, |ui| { + let passes = ui + .ctx() + .viewport(|viewport| viewport.output.num_completed_passes) + + 1; + ui.label(format!("Passes: {}", passes)); + ui.ctx().request_discard("Trying to reach max limit"); + }); + }; + + let ctx = ctx.get_mut(); + ctx.memory_mut(|memory| { + memory.options.max_passes = NonZero::new(5).unwrap(); + }); + + **egui_full_output = Some(ctx.run(egui_input.take(), ui)); + } +} diff --git a/examples/two_windows.rs b/examples/two_windows.rs index b072950f..1a1b6533 100644 --- a/examples/two_windows.rs +++ b/examples/two_windows.rs @@ -13,7 +13,7 @@ struct Images { fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .init_resource::() .add_systems(Startup, load_assets_system) .add_systems(Startup, create_new_window_system) diff --git a/examples/ui.rs b/examples/ui.rs index b7622a0f..e8a7057c 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -32,7 +32,7 @@ fn main() { }), ..default() })) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .add_systems(Startup, configure_visuals_system) .add_systems(Startup, configure_ui_state_system) .add_systems(Update, update_ui_scale_factor_system) diff --git a/src/lib.rs b/src/lib.rs index 60c7c9cd..9ca73420 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,13 @@ use bevy::{ use std::cell::{RefCell, RefMut}; /// Adds all Egui resources and render graph nodes. -pub struct EguiPlugin; +#[derive(Default)] +pub struct EguiPlugin { + /// Controls if egui must be run manually + /// + /// using `egui::context::Context` object `run` or `begin_pass` and `end_pass` function calls. + pub manual_run: bool, +} /// A resource for storing global UI settings. #[derive(Clone, Debug, Resource, Reflect)] @@ -183,6 +189,10 @@ impl Default for EguiSettings { #[derive(Component, Clone, Debug, Default, Deref, DerefMut)] pub struct EguiInput(pub egui::RawInput); +/// Is used to store Egui context output. +#[derive(Component, Clone, Default, Deref, DerefMut)] +pub struct EguiFullOutput(pub Option); + /// A resource for accessing clipboard. /// /// The resource is available only if `manage_clipboard` feature is enabled. @@ -714,12 +724,17 @@ impl Plugin for EguiPlugin { .after(InputSystem) .after(EguiSet::InitContexts), ); - app.add_systems( - PreUpdate, - begin_frame_system - .in_set(EguiSet::BeginFrame) - .after(EguiSet::ProcessInput), - ); + + if !self.manual_run { + app.add_systems( + PreUpdate, + begin_pass_system + .in_set(EguiSet::BeginFrame) + .after(EguiSet::ProcessInput), + ); + app.add_systems(PostUpdate, end_pass_system.before(EguiSet::ProcessOutput)); + } + app.add_systems( PostUpdate, process_output_system.in_set(EguiSet::ProcessOutput), @@ -789,6 +804,8 @@ pub struct EguiContextQuery { pub ctx: &'static mut EguiContext, /// Encapsulates [`egui::RawInput`]. pub egui_input: &'static mut EguiInput, + /// Encapsulates [`egui::FullOutput`]. + pub egui_full_output: &'static mut EguiFullOutput, /// Egui shapes and textures delta. pub render_output: &'static mut EguiRenderOutput, /// Encapsulates [`egui::PlatformOutput`]. @@ -826,6 +843,7 @@ pub fn setup_new_windows_system( EguiContext::default(), EguiRenderOutput::default(), EguiInput::default(), + EguiFullOutput::default(), EguiOutput::default(), RenderTargetSize::default(), )); @@ -848,6 +866,7 @@ pub fn setup_render_to_texture_handles_system( EguiContext::default(), EguiRenderOutput::default(), EguiInput::default(), + EguiFullOutput::default(), EguiOutput::default(), RenderTargetSize::default(), )); @@ -978,7 +997,7 @@ mod tests { .build() .disable::(), ) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .update(); } } diff --git a/src/systems.rs b/src/systems.rs index 07e1d85c..8ca0fbe7 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,7 +1,8 @@ #[cfg(feature = "render")] use crate::EguiRenderToTextureHandle; use crate::{ - EguiContext, EguiContextQuery, EguiContextQueryItem, EguiInput, EguiSettings, RenderTargetSize, + EguiContext, EguiContextQuery, EguiContextQueryItem, EguiFullOutput, EguiInput, EguiSettings, + RenderTargetSize, }; #[cfg(feature = "render")] @@ -491,9 +492,16 @@ pub fn update_contexts_system( } /// Marks frame start for Egui. -pub fn begin_frame_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput)>) { +pub fn begin_pass_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput)>) { for (mut ctx, mut egui_input) in contexts.iter_mut() { - ctx.get_mut().begin_frame(egui_input.take()); + ctx.get_mut().begin_pass(egui_input.take()); + } +} + +/// Marks frame end for Egui. +pub fn end_pass_system(mut contexts: Query<(&mut EguiContext, &mut EguiFullOutput)>) { + for (mut ctx, mut full_output) in contexts.iter_mut() { + **full_output = Some(ctx.get_mut().end_pass()); } } @@ -513,7 +521,10 @@ pub fn process_output_system( for mut context in contexts.iter_mut() { let ctx = context.ctx.get_mut(); - let full_output = ctx.end_frame(); + let Some(full_output) = context.egui_full_output.0.take() else { + log::error!("bevy_egui frame output has not been prepared!"); + continue; + }; let egui::FullOutput { platform_output, shapes,