Render on demand. (#3397)

This commit is contained in:
Wojciech Daniło 2022-04-14 22:28:38 +02:00 committed by GitHub
parent fbe28db1d7
commit d5d5d3aac5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 163 additions and 77 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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<T: JsTypedArrayItem> PixelReadPass<T> {
}
impl<T: JsTypedArrayItem> pass::Definition for PixelReadPass<T> {
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<T: JsTypedArrayItem> pass::Definition for PixelReadPass<T> {
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()

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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<RefCell<Option<Context>>>,
pub display_object: display::object::Instance,
pub dom: Dom,
pub context: Rc<RefCell<Option<Context>>>,
pub context_lost_handler: Rc<RefCell<Option<ContextLostHandler>>>,
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<EventListenerHandle>,
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<Cell<bool>>,
extensions: Extensions,
disable_context_menu: Rc<EventListenerHandle>,
}
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<RefCell<HashMap<mouse::Button, PointerTargetId>>> = 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()
}
}
}

View File

@ -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<LayerItem>>,
) {
) -> 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

View File

@ -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();
}

View File

@ -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<bool, Error> {
let ok_progress = |_| Ok(true);
let no_progress = Ok(false);
fn run_step(&mut self) -> Result<Option<Progress>, 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() {