From d5d5d3aac5e25a298e0526b18517353c8a830766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Dani=C5=82o?= Date: Thu, 14 Apr 2022 22:28:38 +0200 Subject: [PATCH] Render on demand. (#3397) --- .../component-group/src/lib.rs | 2 +- app/gui/view/component-browser/src/lib.rs | 3 + .../core/src/display/render/composer.rs | 9 +- .../ensogl/core/src/display/render/pass.rs | 3 +- .../src/display/render/passes/pixel_read.rs | 6 +- .../core/src/display/render/passes/screen.rs | 7 +- .../core/src/display/render/passes/symbols.rs | 31 ++--- lib/rust/ensogl/core/src/display/scene.rs | 116 ++++++++++++------ .../ensogl/core/src/display/scene/layer.rs | 17 ++- lib/rust/ensogl/core/src/display/world.rs | 4 +- .../core/src/system/gpu/shader/compiler.rs | 42 +++++-- 11 files changed, 163 insertions(+), 77 deletions(-) diff --git a/app/gui/view/component-browser/component-group/src/lib.rs b/app/gui/view/component-browser/component-group/src/lib.rs index c362fb5402..c029dd0b69 100644 --- a/app/gui/view/component-browser/component-group/src/lib.rs +++ b/app/gui/view/component-browser/component-group/src/lib.rs @@ -18,13 +18,13 @@ #![warn(unused_import_braces)] #![warn(unused_qualifications)] +use ensogl_core::display::shape::*; use ensogl_core::prelude::*; use enso_frp as frp; use ensogl_core::application::Application; use ensogl_core::data::color::Rgba; use ensogl_core::display; -use ensogl_core::display::shape::*; use ensogl_gui_component::component; use ensogl_hardcoded_theme::application::component_browser::component_group as theme; use ensogl_list_view as list_view; diff --git a/app/gui/view/component-browser/src/lib.rs b/app/gui/view/component-browser/src/lib.rs index 07745a29ef..f3e2754857 100644 --- a/app/gui/view/component-browser/src/lib.rs +++ b/app/gui/view/component-browser/src/lib.rs @@ -1,6 +1,7 @@ // === Standard Linter Configuration === #![deny(non_ascii_idents)] #![warn(unsafe_code)] + // === Non-Standard Linter Configuration === #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] @@ -17,3 +18,5 @@ pub use ide_view_component_group as component_group; + + diff --git a/lib/rust/ensogl/core/src/display/render/composer.rs b/lib/rust/ensogl/core/src/display/render/composer.rs index 52fb04ea3e..1981e7093a 100644 --- a/lib/rust/ensogl/core/src/display/render/composer.rs +++ b/lib/rust/ensogl/core/src/display/render/composer.rs @@ -5,6 +5,7 @@ use crate::prelude::*; use crate::system::gpu::*; use crate::display::render::pass; +use crate::display::scene::UpdateStatus; @@ -67,9 +68,9 @@ impl { } /// Run all the registered passes in this composer. - pub fn run(&mut self) { + pub fn run(&mut self, update_status: UpdateStatus) { for pass in &mut self.passes { - pass.run(); + pass.run(update_status); } } }} @@ -118,7 +119,7 @@ impl ComposerPass { } /// Run the pass. - pub fn run(&mut self) { - self.pass.run(&self.instance); + pub fn run(&mut self, update_status: UpdateStatus) { + self.pass.run(&self.instance, update_status); } } diff --git a/lib/rust/ensogl/core/src/display/render/pass.rs b/lib/rust/ensogl/core/src/display/render/pass.rs index 0567f71c08..0082e8e001 100644 --- a/lib/rust/ensogl/core/src/display/render/pass.rs +++ b/lib/rust/ensogl/core/src/display/render/pass.rs @@ -3,6 +3,7 @@ use crate::prelude::*; use crate::system::gpu::*; +use crate::display::scene::UpdateStatus; use crate::system::gpu::data::texture::class::TextureOps; @@ -18,7 +19,7 @@ use crate::system::gpu::data::texture::class::TextureOps; #[allow(missing_docs)] pub trait Definition: CloneBoxedForDefinition + Debug + 'static { fn initialize(&mut self, _instance: &Instance) {} - fn run(&mut self, _instance: &Instance); + fn run(&mut self, _instance: &Instance, update_status: UpdateStatus); } clone_boxed!(Definition); diff --git a/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs b/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs index ce71525481..a047f3cafe 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs @@ -5,6 +5,7 @@ use crate::system::gpu::*; use crate::system::js::*; use crate::display::render::pass; +use crate::display::scene::UpdateStatus; use crate::system::gpu::data::texture::class::TextureOps; use web_sys::WebGlBuffer; @@ -180,7 +181,7 @@ impl PixelReadPass { } impl pass::Definition for PixelReadPass { - fn run(&mut self, instance: &pass::Instance) { + fn run(&mut self, instance: &pass::Instance, update_status: UpdateStatus) { if self.to_next_read > 0 { self.to_next_read -= 1; } else { @@ -189,7 +190,8 @@ impl pass::Definition for PixelReadPass { if let Some(sync) = self.sync.clone() { self.check_and_handle_sync(&instance.context, &sync); } - if self.sync.is_none() { + let need_sync = update_status.scene_was_dirty || update_status.pointer_position_changed; + if need_sync && self.sync.is_none() { self.run_not_synced(&instance.context); if let Some(callback) = &self.sync_callback { callback() diff --git a/lib/rust/ensogl/core/src/display/render/passes/screen.rs b/lib/rust/ensogl/core/src/display/render/passes/screen.rs index a0a06b0cd2..5cdee57652 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/screen.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/screen.rs @@ -4,6 +4,7 @@ use crate::prelude::*; use crate::display::render::pass; use crate::display::scene::Scene; +use crate::display::scene::UpdateStatus; use crate::display::symbol::Screen; @@ -27,7 +28,9 @@ impl ScreenRenderPass { } impl pass::Definition for ScreenRenderPass { - fn run(&mut self, _: &pass::Instance) { - self.screen.render(); + fn run(&mut self, _: &pass::Instance, update_status: UpdateStatus) { + if update_status.scene_was_dirty { + self.screen.render(); + } } } diff --git a/lib/rust/ensogl/core/src/display/render/passes/symbols.rs b/lib/rust/ensogl/core/src/display/render/passes/symbols.rs index 39367a8566..cfa15f019b 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/symbols.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/symbols.rs @@ -7,6 +7,7 @@ use crate::display::render::pass; use crate::display::scene; use crate::display::scene::layer; use crate::display::scene::Scene; +use crate::display::scene::UpdateStatus; use crate::display::symbol::registry::SymbolRegistry; use crate::display::symbol::MaskComposer; @@ -90,25 +91,27 @@ impl pass::Definition for SymbolsRenderPass { self.framebuffers = Some(Framebuffers::new(composed_fb, mask_fb, layer_fb)); } - fn run(&mut self, instance: &pass::Instance) { - let framebuffers = self.framebuffers.as_ref().unwrap(); + fn run(&mut self, instance: &pass::Instance, update_status: UpdateStatus) { + if update_status.scene_was_dirty { + let framebuffers = self.framebuffers.as_ref().unwrap(); - framebuffers.composed.bind(); + framebuffers.composed.bind(); - let arr = vec![0.0, 0.0, 0.0, 0.0]; - instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &arr); - instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &arr); + let arr = vec![0.0, 0.0, 0.0, 0.0]; + instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &arr); + instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &arr); - let mut scissor_stack = default(); - self.render_layer(instance, &self.layers.root.clone(), &mut scissor_stack, false); - if !scissor_stack.is_empty() { - warning!( - &self.logger, - "The scissor stack was not cleaned properly. \ + let mut scissor_stack = default(); + self.render_layer(instance, &self.layers.root.clone(), &mut scissor_stack, false); + if !scissor_stack.is_empty() { + warning!( + &self.logger, + "The scissor stack was not cleaned properly. \ This is an internal bug that may lead to visual artifacts. Please report it." - ); + ); + } + instance.context.bind_framebuffer(*Context::FRAMEBUFFER, None); } - instance.context.bind_framebuffer(*Context::FRAMEBUFFER, None); } } diff --git a/lib/rust/ensogl/core/src/display/scene.rs b/lib/rust/ensogl/core/src/display/scene.rs index fabf146ed8..52964072dd 100644 --- a/lib/rust/ensogl/core/src/display/scene.rs +++ b/lib/rust/ensogl/core/src/display/scene.rs @@ -522,10 +522,10 @@ impl Renderer { } /// Run the renderer. - pub fn run(&self) { + pub fn run(&self, update_status: UpdateStatus) { if let Some(composer) = &mut *self.composer.borrow_mut() { debug!(self.logger, "Running.", || { - composer.run(); + composer.run(update_status); }) } } @@ -705,35 +705,51 @@ impl Extensions { +// ==================== +// === UpdateStatus === +// ==================== + +/// Scene update status. Used to tell the renderer what is the minimal amount of per-frame +/// processing. For example, if scene was not dirty (no animations), but pointer position changed, +/// the scene should not be re-rendered, but the id-texture pixel under the mouse should be read. +#[derive(Clone, Copy, Debug, Default)] +pub struct UpdateStatus { + pub scene_was_dirty: bool, + pub pointer_position_changed: bool, +} + + + // ================= // === SceneData === // ================= #[derive(Clone, CloneRef, Debug)] pub struct SceneData { - pub display_object: display::object::Instance, - pub dom: Dom, - pub context: Rc>>, + pub display_object: display::object::Instance, + pub dom: Dom, + pub context: Rc>>, pub context_lost_handler: Rc>>, - pub symbols: SymbolRegistry, - pub variables: UniformScope, - pub current_js_event: CurrentJsEvent, - pub mouse: Mouse, - pub keyboard: Keyboard, - pub uniforms: Uniforms, - pub background: PointerTarget, - pub shapes: ShapeRegistry, - pub stats: Stats, - pub dirty: Dirty, - pub logger: Logger, - pub renderer: Renderer, - pub layers: HardcodedLayers, - pub style_sheet: style::Sheet, - pub bg_color_var: style::Var, - pub bg_color_change: callback::Handle, - pub frp: Frp, - extensions: Extensions, - disable_context_menu: Rc, + pub symbols: SymbolRegistry, + pub variables: UniformScope, + pub current_js_event: CurrentJsEvent, + pub mouse: Mouse, + pub keyboard: Keyboard, + pub uniforms: Uniforms, + pub background: PointerTarget, + pub shapes: ShapeRegistry, + pub stats: Stats, + pub dirty: Dirty, + pub logger: Logger, + pub renderer: Renderer, + pub layers: HardcodedLayers, + pub style_sheet: style::Sheet, + pub bg_color_var: style::Var, + pub bg_color_change: callback::Handle, + pub frp: Frp, + pub pointer_position_changed: Rc>, + extensions: Extensions, + disable_context_menu: Rc, } impl SceneData { @@ -784,6 +800,7 @@ impl SceneData { uniforms.pixel_ratio.set(dom.shape().pixel_ratio); let context = default(); let context_lost_handler = default(); + let pointer_position_changed = default(); Self { display_object, dom, @@ -806,6 +823,7 @@ impl SceneData { bg_color_var, bg_color_change, frp, + pointer_position_changed, extensions, disable_context_menu, } @@ -843,7 +861,7 @@ impl SceneData { &self.symbols } - fn update_shape(&self) { + fn update_shape(&self) -> bool { if self.dirty.shape.check_all() { let screen = self.dom.shape(); self.resize_canvas(screen); @@ -852,17 +870,24 @@ impl SceneData { }); self.renderer.resize_composer(); self.dirty.shape.unset_all(); + true + } else { + false } } - fn update_symbols(&self) { + fn update_symbols(&self) -> bool { if self.dirty.symbols.check_all() { self.symbols.update(); self.dirty.symbols.unset_all(); + true + } else { + false } } - fn update_camera(&self, scene: &Scene) { + fn update_camera(&self, scene: &Scene) -> bool { + let mut was_dirty = false; // Updating camera for DOM layers. Please note that DOM layers cannot use multi-camera // setups now, so we are using here the main camera only. let camera = self.camera(); @@ -871,6 +896,7 @@ impl SceneData { let welcome_screen_camera = self.layers.panel.camera(); let changed = camera.update(scene); if changed { + was_dirty = true; self.frp.camera_changed_source.emit(()); self.symbols.set_camera(&camera); self.dom.layers.front.update_view_projection(&camera); @@ -878,19 +904,26 @@ impl SceneData { } let fs_vis_camera_changed = fullscreen_vis_camera.update(scene); if fs_vis_camera_changed { + was_dirty = true; self.dom.layers.fullscreen_vis.update_view_projection(&fullscreen_vis_camera); self.dom.layers.welcome_screen.update_view_projection(&welcome_screen_camera); } let node_searcher_camera = self.layers.node_searcher.camera(); let node_searcher_camera_changed = node_searcher_camera.update(scene); if node_searcher_camera_changed { + was_dirty = true; self.dom.layers.node_searcher.update_view_projection(&node_searcher_camera); } // Updating all other cameras (the main camera was already updated, so it will be skipped). + let sublayer_was_dirty = Rc::new(Cell::new(false)); self.layers.iter_sublayers_and_masks_nested(|layer| { - layer.camera().update(scene); + let dirty = layer.camera().update(scene); + sublayer_was_dirty.set(sublayer_was_dirty.get() || dirty); }); + was_dirty = was_dirty || sublayer_was_dirty.get(); + + was_dirty } /// Resize the underlying canvas. This function should rather not be called @@ -911,8 +944,8 @@ impl SceneData { }); } - pub fn render(&self) { - self.renderer.run(); + pub fn render(&self, update_status: UpdateStatus) { + self.renderer.run(update_status); // WebGL `flush` should be called when expecting results such as queries, or at completion // of a rendering frame. Flush tells the implementation to push all pending commands out // for execution, flushing them out of the queue, instead of waiting for more commands to @@ -963,6 +996,7 @@ impl SceneData { let network = &self.frp.network; let shapes = &self.shapes; let target = &self.mouse.target; + let pointer_position_changed = &self.pointer_position_changed; let pressed: Rc>> = default(); frp::extend! { network @@ -971,6 +1005,7 @@ impl SceneData { pressed.borrow_mut().insert(*button,current_target); shapes.with_mouse_target(current_target, |t| t.mouse_down.emit(button)); }); + eval self.mouse.frp.up ([shapes,target,pressed](button) { let current_target = target.get(); if let Some(last_target) = pressed.borrow_mut().remove(button) { @@ -978,6 +1013,8 @@ impl SceneData { } shapes.with_mouse_target(current_target, |t| t.mouse_up.emit(button)); }); + + eval_ self.mouse.frp.position (pointer_position_changed.set(true)); } } @@ -1087,20 +1124,27 @@ impl Deref for Scene { impl Scene { #[profile(Debug)] - pub fn update(&self, time: animation::TimeInfo) { + pub fn update(&self, time: animation::TimeInfo) -> UpdateStatus { if let Some(context) = &*self.context.borrow() { debug!(self.logger, "Updating.", || { + let mut scene_was_dirty = false; self.frp.frame_time_source.emit(time.since_animation_loop_started.unchecked_raw()); // Please note that `update_camera` is called first as it may trigger FRP events // which may change display objects layout. - self.update_camera(self); + scene_was_dirty = self.update_camera(self) || scene_was_dirty; self.display_object.update(self); - self.layers.update(); - self.update_shape(); - self.update_symbols(); + scene_was_dirty = self.layers.update() || scene_was_dirty; + scene_was_dirty = self.update_shape() || scene_was_dirty; + scene_was_dirty = self.update_symbols() || scene_was_dirty; self.handle_mouse_over_and_out_events(); - context.shader_compiler.run(time); + scene_was_dirty = context.shader_compiler.run(time) || scene_was_dirty; + + let pointer_position_changed = self.pointer_position_changed.get(); + self.pointer_position_changed.set(false); + UpdateStatus { scene_was_dirty, pointer_position_changed } }) + } else { + default() } } } diff --git a/lib/rust/ensogl/core/src/display/scene/layer.rs b/lib/rust/ensogl/core/src/display/scene/layer.rs index 4226edadfe..e9be996a8a 100644 --- a/lib/rust/ensogl/core/src/display/scene/layer.rs +++ b/lib/rust/ensogl/core/src/display/scene/layer.rs @@ -525,8 +525,9 @@ impl LayerModel { } } - /// Consume all dirty flags and update the ordering of elements if needed. - pub fn update(&self) { + /// Consume all dirty flags and update the ordering of elements if needed. Returns [`true`] if + /// the layer or its sub-layers were modified during this call. + pub fn update(&self) -> bool { self.update_internal(None) } @@ -534,23 +535,29 @@ impl LayerModel { pub(crate) fn update_internal( &self, global_element_depth_order: Option<&DependencyGraph>, - ) { + ) -> bool { + let mut was_dirty = false; + if self.depth_order_dirty.check() { + was_dirty = true; self.depth_order_dirty.unset(); self.depth_sort(global_element_depth_order); } if self.sublayers.element_depth_order_dirty.check() { + was_dirty = true; self.sublayers.element_depth_order_dirty.unset(); for layer in self.sublayers() { - layer.update_internal(Some(&*self.global_element_depth_order.borrow())) + layer.update_internal(Some(&*self.global_element_depth_order.borrow())); } if let Some(layer) = &*self.mask.borrow() { if let Some(layer) = layer.upgrade() { - layer.update_internal(Some(&*self.global_element_depth_order.borrow())) + layer.update_internal(Some(&*self.global_element_depth_order.borrow())); } } } + + was_dirty } /// Compute a combined [`DependencyGraph`] for the layer taking into consideration the global diff --git a/lib/rust/ensogl/core/src/display/world.rs b/lib/rust/ensogl/core/src/display/world.rs index f43d20e302..1a3cd72118 100644 --- a/lib/rust/ensogl/core/src/display/world.rs +++ b/lib/rust/ensogl/core/src/display/world.rs @@ -299,9 +299,9 @@ impl WorldData { self.on.before_frame.run_all(time); self.uniforms.time.set(time.since_animation_loop_started.unchecked_raw()); self.scene_dirty.unset_all(); - self.default_scene.update(time); + let update_status = self.default_scene.update(time); self.garbage_collector.mouse_events_handled(); - self.default_scene.render(); + self.default_scene.render(update_status); self.on.after_frame.run_all(time); self.stats.end_frame(); } diff --git a/lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs b/lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs index 066690793d..d0c32bbfd3 100644 --- a/lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs +++ b/lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs @@ -131,6 +131,20 @@ struct KhrProgram { +// ================ +// === Progress === +// ================ + +/// Shader compiler progress status. +#[derive(Clone, Copy, Debug)] +#[allow(missing_docs)] +pub enum Progress { + StepProgress, + NewShaderReady, +} + + + // ================ // === Compiler === // ================ @@ -195,8 +209,9 @@ impl Compiler { self.rc.borrow_mut().submit(input, on_ready) } - /// Run the compiler. This should be run on every frame. - pub fn run(&self, time: animation::TimeInfo) { + /// Run the compiler. This should be run on every frame. Returns [`true`] if any new shaders + /// finished the compilation process during this call. + pub fn run(&self, time: animation::TimeInfo) -> bool { self.rc.borrow_mut().run(time) } } @@ -226,14 +241,17 @@ impl CompilerData { } #[profile(Debug)] - fn run(&mut self, time: animation::TimeInfo) { + fn run(&mut self, time: animation::TimeInfo) -> bool { + let mut any_new_shaders_ready = false; if self.dirty { self.run_khr_completion_check_jobs(); while self.dirty { match self.run_step() { - Ok(made_progress) => { - if !made_progress { - break; + Ok(progress) => { + match progress { + None => break, + Some(Progress::NewShaderReady) => any_new_shaders_ready = true, + Some(Progress::StepProgress) => {} } let now = (self.performance.now() as f32).ms(); let current_frame_time = @@ -256,21 +274,25 @@ impl CompilerData { } } } + any_new_shaders_ready } /// Runs the next compiler job if there is any left and if it will not cause too many jobs being /// run in parallel. The result [`bool`] indicates if the call to this function did any /// progress. #[profile(Detail)] - fn run_step(&mut self) -> Result { - let ok_progress = |_| Ok(true); - let no_progress = Ok(false); + fn run_step(&mut self) -> Result, Error> { + let ok_progress = |_| Ok(Some(Progress::StepProgress)); + let no_progress = Ok(None); let jobs = &self.jobs; let max_jobs = self.current_parallel_job_count() >= MAX_PARALLEL_COMPILE_JOBS; match () { _ if !max_jobs && !jobs.compile.is_empty() => ok_progress(self.run_next_compile_job()?), _ if !jobs.link.is_empty() => ok_progress(self.run_next_link_job()?), - _ if !jobs.link_check.is_empty() => ok_progress(self.run_next_link_check_job()?), + _ if !jobs.link_check.is_empty() => { + self.run_next_link_check_job()?; + Ok(Some(Progress::NewShaderReady)) + } _ => { if max_jobs { if !jobs.compile.is_empty() {