diff options
Diffstat (limited to 'components/compositing/compositor.rs')
-rw-r--r-- | components/compositing/compositor.rs | 185 |
1 files changed, 103 insertions, 82 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 27d1e8f93e1..a4afeb01b3c 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -9,7 +9,7 @@ use std::fs::create_dir_all; use std::iter::once; use std::rc::Rc; use std::sync::Arc; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use std::time::{SystemTime, UNIX_EPOCH}; use base::cross_process_instant::CrossProcessInstant; use base::id::{PipelineId, WebViewId}; @@ -53,6 +53,7 @@ use webrender_api::{ }; use crate::InitialCompositorState; +use crate::refresh_driver::RefreshDriver; use crate::webview_manager::WebViewManager; use crate::webview_renderer::{PinchZoomResult, UnknownWebView, WebViewRenderer}; @@ -86,6 +87,9 @@ pub enum WebRenderDebugOption { } /// Data that is shared by all WebView renderers. pub struct ServoRenderer { + /// The [`RefreshDriver`] which manages the rythym of painting. + refresh_driver: RefreshDriver, + /// This is a temporary map between [`PipelineId`]s and their associated [`WebViewId`]. Once /// all renderer operations become per-`WebView` this map can be removed, but we still sometimes /// need to work backwards to figure out what `WebView` is associated with a `Pipeline`. @@ -151,18 +155,14 @@ pub struct IOCompositor { /// The number of frames pending to receive from WebRender. pending_frames: usize, - /// The [`Instant`] of the last animation tick, used to avoid flooding the Constellation and - /// ScriptThread with a deluge of animation ticks. - last_animation_tick: Instant, - /// A handle to the memory profiler which will automatically unregister /// when it's dropped. _mem_profiler_registration: ProfilerRegistration, } /// Why we need to be repainted. This is used for debugging. -#[derive(Clone, Copy, Default)] -struct RepaintReason(u8); +#[derive(Clone, Copy, Default, PartialEq)] +pub(crate) struct RepaintReason(u8); bitflags! { impl RepaintReason: u8 { @@ -281,6 +281,11 @@ impl PipelineDetails { } } +pub enum HitTestError { + EpochMismatch, + Others, +} + impl ServoRenderer { pub fn shutdown_state(&self) -> ShutdownState { self.shutdown_state.get() @@ -290,15 +295,19 @@ impl ServoRenderer { &self, point: DevicePoint, details_for_pipeline: impl Fn(PipelineId) -> Option<&'a PipelineDetails>, - ) -> Option<CompositorHitTestResult> { - self.hit_test_at_point_with_flags_and_pipeline( + ) -> Result<CompositorHitTestResult, HitTestError> { + match self.hit_test_at_point_with_flags_and_pipeline( point, HitTestFlags::empty(), None, details_for_pipeline, - ) - .first() - .cloned() + ) { + Ok(hit_test_results) => hit_test_results + .first() + .cloned() + .ok_or(HitTestError::Others), + Err(error) => Err(error), + } } // TODO: split this into first half (global) and second half (one for whole compositor, one for webview) @@ -308,14 +317,15 @@ impl ServoRenderer { flags: HitTestFlags, pipeline_id: Option<WebRenderPipelineId>, details_for_pipeline: impl Fn(PipelineId) -> Option<&'a PipelineDetails>, - ) -> Vec<CompositorHitTestResult> { + ) -> Result<Vec<CompositorHitTestResult>, HitTestError> { // DevicePoint and WorldPoint are the same for us. let world_point = WorldPoint::from_untyped(point.to_untyped()); let results = self.webrender_api .hit_test(self.webrender_document, pipeline_id, world_point, flags); - results + let mut epoch_mismatch = false; + let results = results .items .iter() .filter_map(|item| { @@ -323,10 +333,16 @@ impl ServoRenderer { let details = details_for_pipeline(pipeline_id)?; // If the epoch in the tag does not match the current epoch of the pipeline, - // then the hit test is against an old version of the display list and we - // should ignore this hit test for now. + // then the hit test is against an old version of the display list. match details.most_recent_display_list_epoch { - Some(epoch) if epoch.as_u16() == item.tag.1 => {}, + Some(epoch) => { + if epoch.as_u16() != item.tag.1 { + // It's too early to hit test for now. + // New scene building is in progress. + epoch_mismatch = true; + return None; + } + }, _ => return None, } @@ -340,7 +356,13 @@ impl ServoRenderer { scroll_tree_node: info.scroll_tree_node, }) }) - .collect() + .collect(); + + if epoch_mismatch { + return Err(HitTestError::EpochMismatch); + } + + Ok(results) } pub(crate) fn send_transaction(&mut self, transaction: Transaction) { @@ -386,6 +408,10 @@ impl IOCompositor { ); let compositor = IOCompositor { global: Rc::new(RefCell::new(ServoRenderer { + refresh_driver: RefreshDriver::new( + state.constellation_chan.clone(), + state.event_loop_waker, + ), shutdown_state: state.shutdown_state, pipeline_to_webview_map: Default::default(), compositor_receiver: state.receiver, @@ -406,7 +432,6 @@ impl IOCompositor { webrender: Some(state.webrender), rendering_context: state.rendering_context, pending_frames: 0, - last_animation_tick: Instant::now(), _mem_profiler_registration: registration, }; @@ -450,7 +475,16 @@ impl IOCompositor { } pub fn needs_repaint(&self) -> bool { - !self.needs_repaint.get().is_empty() + let repaint_reason = self.needs_repaint.get(); + if repaint_reason.is_empty() { + return false; + } + + !self + .global + .borrow() + .refresh_driver + .wait_to_paint(repaint_reason) } pub fn finish_shutting_down(&mut self) { @@ -519,15 +553,17 @@ impl IOCompositor { pipeline_id, animation_state, ) => { - if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { - if webview_renderer - .change_pipeline_running_animations_state(pipeline_id, animation_state) && - webview_renderer.animating() - { - // These operations should eventually happen per-WebView, but they are - // global now as rendering is still global to all WebViews. - self.process_animations(true); - } + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { + return; + }; + + if webview_renderer + .change_pipeline_running_animations_state(pipeline_id, animation_state) + { + self.global + .borrow() + .refresh_driver + .notify_animation_state_changed(webview_renderer); } }, @@ -572,14 +608,15 @@ impl IOCompositor { }, CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => { - if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { - if webview_renderer.set_throttled(pipeline_id, throttled) && - webview_renderer.animating() - { - // These operations should eventually happen per-WebView, but they are - // global now as rendering is still global to all WebViews. - self.process_animations(true); - } + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { + return; + }; + + if webview_renderer.set_throttled(pipeline_id, throttled) { + self.global + .borrow() + .refresh_driver + .notify_animation_state_changed(webview_renderer); } }, @@ -604,7 +641,7 @@ impl IOCompositor { .global .borrow() .hit_test_at_point(point, details_for_pipeline); - if let Some(result) = result { + if let Ok(result) = result { self.global.borrow_mut().update_cursor(point, &result); } } @@ -634,7 +671,7 @@ impl IOCompositor { }; let dppx = webview_renderer.device_pixels_per_page_pixel(); let point = dppx.transform_point(Point2D::new(x, y)); - webview_renderer.dispatch_input_event( + webview_renderer.dispatch_point_input_event( InputEvent::MouseButton(MouseButtonEvent::new(action, button, point)) .with_webdriver_message_id(Some(message_id)), ); @@ -647,7 +684,7 @@ impl IOCompositor { }; let dppx = webview_renderer.device_pixels_per_page_pixel(); let point = dppx.transform_point(Point2D::new(x, y)); - webview_renderer.dispatch_input_event( + webview_renderer.dispatch_point_input_event( InputEvent::MouseMove(MouseMoveEvent::new(point)) .with_webdriver_message_id(Some(message_id)), ); @@ -669,7 +706,7 @@ impl IOCompositor { let scroll_delta = dppx.transform_vector(Vector2D::new(delta_x as f32, delta_y as f32)); webview_renderer - .dispatch_input_event(InputEvent::Wheel(WheelEvent { delta, point })); + .dispatch_point_input_event(InputEvent::Wheel(WheelEvent { delta, point })); webview_renderer.on_webdriver_wheel_action(scroll_delta, point); }, @@ -777,6 +814,8 @@ impl IOCompositor { let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { return warn!("Could not find WebView for incoming display list"); }; + // WebRender is not ready until we receive "NewWebRenderFrameReady" + webview_renderer.webrender_frame_ready.set(false); let pipeline_id = display_list_info.pipeline_id; let details = webview_renderer.ensure_pipeline_details(pipeline_id.into()); @@ -826,7 +865,8 @@ impl IOCompositor { flags, pipeline, details_for_pipeline, - ); + ) + .unwrap_or_default(); let _ = sender.send(result); }, @@ -933,6 +973,11 @@ impl IOCompositor { warn!("Sending response to get screen size failed ({error:?})."); } }, + CompositorMsg::Viewport(webview_id, viewport_description) => { + if let Some(webview) = self.webview_renderers.get_mut(webview_id) { + webview.set_viewport_description(viewport_description); + } + }, } } @@ -1271,39 +1316,6 @@ impl IOCompositor { self.set_needs_repaint(RepaintReason::Resize); } - /// If there are any animations running, dispatches appropriate messages to the constellation. - fn process_animations(&mut self, force: bool) { - // When running animations in order to dump a screenshot (not after a full composite), don't send - // animation ticks faster than about 60Hz. - // - // TODO: This should be based on the refresh rate of the screen and also apply to all - // animation ticks, not just ones sent while waiting to dump screenshots. This requires - // something like a refresh driver concept though. - if !force && (Instant::now() - self.last_animation_tick) < Duration::from_millis(16) { - return; - } - self.last_animation_tick = Instant::now(); - - let animating_webviews: Vec<_> = self - .webview_renderers - .iter() - .filter_map(|webview_renderer| { - if webview_renderer.animating() { - Some(webview_renderer.id) - } else { - None - } - }) - .collect(); - if !animating_webviews.is_empty() { - if let Err(error) = self.global.borrow().constellation_sender.send( - EmbedderToConstellationMessage::TickAnimation(animating_webviews), - ) { - warn!("Sending tick to constellation failed ({error:?})."); - } - } - } - pub fn on_zoom_reset_window_event(&mut self, webview_id: WebViewId) { if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { return; @@ -1410,6 +1422,11 @@ impl IOCompositor { /// Render the WebRender scene to the active `RenderingContext`. If successful, trigger /// the next round of animations. pub fn render(&mut self) -> bool { + self.global + .borrow() + .refresh_driver + .notify_will_paint(self.webview_renderers.iter()); + if let Err(error) = self.render_inner() { warn!("Unable to render: {error:?}"); return false; @@ -1419,9 +1436,6 @@ impl IOCompositor { // the scene no longer needs to be repainted. self.needs_repaint.set(RepaintReason::empty()); - // Queue up any subsequent paints for animations. - self.process_animations(true); - true } @@ -1492,10 +1506,8 @@ impl IOCompositor { if opts::get().wait_for_stable_image { // The current image may be ready to output. However, if there are animations active, - // tick those instead and continue waiting for the image output to be stable AND - // all active animations to complete. + // continue waiting for the image output to be stable AND all active animations to complete. if self.animations_or_animation_callbacks_running() { - self.process_animations(false); return Err(UnableToComposite::NotReadyToPaintImage( NotReadyToPaint::AnimationsActive, )); @@ -1647,11 +1659,20 @@ impl IOCompositor { }, CompositorMsg::NewWebRenderFrameReady(..) => { found_recomposite_msg = true; - compositor_messages.push(msg) + compositor_messages.push(msg); }, _ => compositor_messages.push(msg), } } + + if found_recomposite_msg { + // Process all pending events + self.webview_renderers.iter().for_each(|webview| { + webview.dispatch_pending_point_input_events(); + webview.webrender_frame_ready.set(true); + }); + } + for msg in compositor_messages { self.handle_browser_message(msg); |