Improve decisions in compositor over when to draw a frame.

This patch fixes a couple of issues in the compositor:

1) Remove the delayed composition code. Previously, this would schedule
   a composite for 12ms in the future. This doesn't really make any sense
   with WR. There's no point in doing a composite unless WR has provided
   a new frame to be drawn. This fixes issues in several benchmarks where
   we were doing multiple composite / renders per rAF, which is a waste
   of CPU time. This *does* make the framerate slower in some cases (such
   as a slow rAF callback) but it's more correct - otherwise we were just
   compositing the same frame multiple times for no real benefit.

2) Inform the window of the current animation state of the compositor.
   Specifically, if an animation (or rAF) is currently active, the
   window system switches to use event polling, and does not block on
   the OS-level event loop. In the case of active animation, we just
   assume that we want to be running as the vsync interval and not
   blocking. This means the compositor thread only sleeps on vsync
   during animation, which reduces OS scheduling and results in much
   smoother animation.
This commit is contained in:
Glenn Watson 2017-06-19 11:48:07 +10:00
parent 5f10a25ead
commit c8255922a9
6 changed files with 32 additions and 154 deletions

View file

@ -6,7 +6,6 @@ use CompositionPipeline;
use SendableFrameTree; use SendableFrameTree;
use compositor_thread::{CompositorProxy, CompositorReceiver}; use compositor_thread::{CompositorProxy, CompositorReceiver};
use compositor_thread::{InitialCompositorState, Msg, RenderListener}; use compositor_thread::{InitialCompositorState, Msg, RenderListener};
use delayed_composition::DelayedCompositionTimerProxy;
use euclid::{Point2D, TypedPoint2D, TypedVector2D, TypedRect, ScaleFactor, TypedSize2D}; use euclid::{Point2D, TypedPoint2D, TypedVector2D, TypedRect, ScaleFactor, TypedSize2D};
use gfx_traits::Epoch; use gfx_traits::Epoch;
use gleam::gl; use gleam::gl;
@ -131,9 +130,6 @@ pub struct IOCompositor<Window: WindowMethods> {
channel_to_self: CompositorProxy, channel_to_self: CompositorProxy,
/// A handle to the delayed composition timer.
delayed_composition_timer: DelayedCompositionTimerProxy,
/// The type of composition to perform /// The type of composition to perform
composite_target: CompositeTarget, composite_target: CompositeTarget,
@ -207,7 +203,6 @@ struct ScrollZoomEvent {
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum CompositionRequest { enum CompositionRequest {
NoCompositingNecessary, NoCompositingNecessary,
DelayedComposite(u64),
CompositeNow(CompositingReason), CompositeNow(CompositingReason),
} }
@ -363,7 +358,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
scale: ScaleFactor::new(1.0), scale: ScaleFactor::new(1.0),
scale_factor: scale_factor, scale_factor: scale_factor,
channel_to_self: state.sender.clone_compositor_proxy(), channel_to_self: state.sender.clone_compositor_proxy(),
delayed_composition_timer: DelayedCompositionTimerProxy::new(state.sender),
composition_request: CompositionRequest::NoCompositingNecessary, composition_request: CompositionRequest::NoCompositingNecessary,
touch_handler: TouchHandler::new(), touch_handler: TouchHandler::new(),
pending_scroll_zoom_events: Vec::new(), pending_scroll_zoom_events: Vec::new(),
@ -437,8 +431,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
let _ = receiver.recv(); let _ = receiver.recv();
} }
self.delayed_composition_timer.shutdown();
self.shutdown_state = ShutdownState::FinishedShuttingDown; self.shutdown_state = ShutdownState::FinishedShuttingDown;
} }
@ -524,16 +516,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
} }
} }
(Msg::DelayedCompositionTimeout(timestamp), ShutdownState::NotShuttingDown) => {
if let CompositionRequest::DelayedComposite(this_timestamp) =
self.composition_request {
if timestamp == this_timestamp {
self.composition_request = CompositionRequest::CompositeNow(
CompositingReason::DelayedCompositeTimeout)
}
}
}
(Msg::Recomposite(reason), ShutdownState::NotShuttingDown) => { (Msg::Recomposite(reason), ShutdownState::NotShuttingDown) => {
self.composition_request = CompositionRequest::CompositeNow(reason) self.composition_request = CompositionRequest::CompositeNow(reason)
} }
@ -753,18 +735,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
} }
} }
fn schedule_delayed_composite_if_necessary(&mut self) {
match self.composition_request {
CompositionRequest::CompositeNow(_) => return,
CompositionRequest::DelayedComposite(_) |
CompositionRequest::NoCompositingNecessary => {}
}
let timestamp = precise_time_ns();
self.delayed_composition_timer.schedule_composite(timestamp);
self.composition_request = CompositionRequest::DelayedComposite(timestamp);
}
fn scroll_fragment_to_point(&mut self, id: ClipId, point: Point2D<f32>) { fn scroll_fragment_to_point(&mut self, id: ClipId, point: Point2D<f32>) {
self.webrender_api.scroll_node_with_id(LayoutPoint::from_untyped(&point), id, self.webrender_api.scroll_node_with_id(LayoutPoint::from_untyped(&point), id,
ScrollClamping::ToContentBounds); ScrollClamping::ToContentBounds);
@ -1235,13 +1205,18 @@ impl<Window: WindowMethods> IOCompositor<Window> {
pipeline_ids.push(*pipeline_id); pipeline_ids.push(*pipeline_id);
} }
} }
let animation_state = if pipeline_ids.is_empty() {
windowing::AnimationState::Idle
} else {
windowing::AnimationState::Animating
};
self.window.set_animation_state(animation_state);
for pipeline_id in &pipeline_ids { for pipeline_id in &pipeline_ids {
self.tick_animations_for_pipeline(*pipeline_id) self.tick_animations_for_pipeline(*pipeline_id)
} }
} }
fn tick_animations_for_pipeline(&mut self, pipeline_id: PipelineId) { fn tick_animations_for_pipeline(&mut self, pipeline_id: PipelineId) {
self.schedule_delayed_composite_if_necessary();
let animation_callbacks_running = self.pipeline_details(pipeline_id).animation_callbacks_running; let animation_callbacks_running = self.pipeline_details(pipeline_id).animation_callbacks_running;
if animation_callbacks_running { if animation_callbacks_running {
let msg = ConstellationMsg::TickAnimation(pipeline_id, AnimationTickType::Script); let msg = ConstellationMsg::TickAnimation(pipeline_id, AnimationTickType::Script);
@ -1635,14 +1610,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
_ => compositor_messages.push(msg), _ => compositor_messages.push(msg),
} }
} }
if found_recomposite_msg {
compositor_messages.retain(|msg| {
match *msg {
Msg::DelayedCompositionTimeout(_) => false,
_ => true,
}
})
}
for msg in compositor_messages { for msg in compositor_messages {
if !self.handle_browser_message(msg) { if !self.handle_browser_message(msg) {
break break
@ -1664,8 +1631,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
} }
match self.composition_request { match self.composition_request {
CompositionRequest::NoCompositingNecessary | CompositionRequest::NoCompositingNecessary => {}
CompositionRequest::DelayedComposite(_) => {}
CompositionRequest::CompositeNow(_) => { CompositionRequest::CompositeNow(_) => {
self.composite() self.composite()
} }

View file

@ -100,8 +100,6 @@ pub enum Msg {
HistoryChanged(Vec<LoadData>, usize), HistoryChanged(Vec<LoadData>, usize),
/// Wether or not to follow a link /// Wether or not to follow a link
AllowNavigation(ServoUrl, IpcSender<bool>), AllowNavigation(ServoUrl, IpcSender<bool>),
/// We hit the delayed composition timeout. (See `delayed_composition.rs`.)
DelayedCompositionTimeout(u64),
/// Composite. /// Composite.
Recomposite(CompositingReason), Recomposite(CompositingReason),
/// Sends an unconsumed key event back to the compositor. /// Sends an unconsumed key event back to the compositor.
@ -160,7 +158,6 @@ impl Debug for Msg {
Msg::AllowNavigation(..) => write!(f, "AllowNavigation"), Msg::AllowNavigation(..) => write!(f, "AllowNavigation"),
Msg::LoadStart => write!(f, "LoadStart"), Msg::LoadStart => write!(f, "LoadStart"),
Msg::HistoryChanged(..) => write!(f, "HistoryChanged"), Msg::HistoryChanged(..) => write!(f, "HistoryChanged"),
Msg::DelayedCompositionTimeout(..) => write!(f, "DelayedCompositionTimeout"),
Msg::Recomposite(..) => write!(f, "Recomposite"), Msg::Recomposite(..) => write!(f, "Recomposite"),
Msg::KeyEvent(..) => write!(f, "KeyEvent"), Msg::KeyEvent(..) => write!(f, "KeyEvent"),
Msg::TouchEventProcessed(..) => write!(f, "TouchEventProcessed"), Msg::TouchEventProcessed(..) => write!(f, "TouchEventProcessed"),

View file

@ -1,107 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! A timer thread that composites near the end of the frame.
//!
//! This is useful when we need to composite next frame but we want to opportunistically give the
//! painting thread time to paint if it can.
use compositor_thread::{CompositorProxy, Msg};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread::{self, Builder};
use std::time::Duration;
use std::u32;
use time;
/// The amount of time in nanoseconds that we give to the painting thread to paint. When this
/// expires, we give up and composite anyway.
static TIMEOUT: u64 = 12_000_000;
pub struct DelayedCompositionTimerProxy {
sender: Sender<ToDelayedCompositionTimerMsg>,
}
struct DelayedCompositionTimer {
compositor_proxy: CompositorProxy,
receiver: Receiver<ToDelayedCompositionTimerMsg>,
}
enum ToDelayedCompositionTimerMsg {
Exit,
ScheduleComposite(u64),
}
impl DelayedCompositionTimerProxy {
pub fn new(compositor_proxy: CompositorProxy) -> DelayedCompositionTimerProxy {
let (to_timer_sender, to_timer_receiver) = channel();
Builder::new().spawn(move || {
let mut timer = DelayedCompositionTimer {
compositor_proxy: compositor_proxy,
receiver: to_timer_receiver,
};
timer.run();
}).unwrap();
DelayedCompositionTimerProxy {
sender: to_timer_sender,
}
}
pub fn schedule_composite(&mut self, timestamp: u64) {
self.sender.send(ToDelayedCompositionTimerMsg::ScheduleComposite(timestamp)).unwrap()
}
pub fn shutdown(&mut self) {
self.sender.send(ToDelayedCompositionTimerMsg::Exit).unwrap()
}
}
impl DelayedCompositionTimer {
fn run(&mut self) {
'outer: loop {
let mut timestamp;
loop {
match self.receiver.recv() {
Ok(ToDelayedCompositionTimerMsg::ScheduleComposite(this_timestamp)) => {
timestamp = this_timestamp;
break
}
Ok(ToDelayedCompositionTimerMsg::Exit) => break 'outer,
_ => break 'outer,
}
}
// Drain all messages from the queue.
loop {
match self.receiver.try_recv() {
Ok(ToDelayedCompositionTimerMsg::ScheduleComposite(this_timestamp)) => {
timestamp = this_timestamp;
break
}
_ => break,
}
}
let target = timestamp + TIMEOUT;
let now = time::precise_time_ns();
if target > now {
let delta_ns = target - now;
thread::sleep(duration_from_nanoseconds(delta_ns));
}
self.compositor_proxy.send(Msg::DelayedCompositionTimeout(timestamp));
}
}
}
fn duration_from_nanoseconds(nanos: u64) -> Duration {
pub const NANOS_PER_SEC: u32 = 1_000_000_000;
// Get number of seconds.
let secs = nanos / NANOS_PER_SEC as u64;
// Get number of extra nanoseconds. This should always fit in a u32, but check anyway.
let subsec_nanos = nanos % NANOS_PER_SEC as u64;
assert!(subsec_nanos <= u32::MAX as u64);
Duration::new(secs, subsec_nanos as u32)
}

View file

@ -35,7 +35,6 @@ use style_traits::CSSPixel;
mod compositor; mod compositor;
pub mod compositor_thread; pub mod compositor_thread;
mod delayed_composition;
mod touch; mod touch;
pub mod windowing; pub mod windowing;

View file

@ -102,6 +102,12 @@ impl Debug for WindowEvent {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum AnimationState {
Idle,
Animating,
}
pub trait WindowMethods { pub trait WindowMethods {
/// Returns the rendering area size in hardware pixels. /// Returns the rendering area size in hardware pixels.
fn framebuffer_size(&self) -> TypedSize2D<u32, DevicePixel>; fn framebuffer_size(&self) -> TypedSize2D<u32, DevicePixel>;
@ -163,4 +169,10 @@ pub trait WindowMethods {
/// Return the GL function pointer trait. /// Return the GL function pointer trait.
fn gl(&self) -> Rc<gl::Gl>; fn gl(&self) -> Rc<gl::Gl>;
/// Set whether the application is currently animating.
/// Typically, when animations are active, the window
/// will want to avoid blocking on UI events, and just
/// run the event loop at the vsync interval.
fn set_animation_state(&self, _state: AnimationState) {}
} }

View file

@ -6,7 +6,7 @@
use NestedEventLoopListener; use NestedEventLoopListener;
use compositing::compositor_thread::EventLoopWaker; use compositing::compositor_thread::EventLoopWaker;
use compositing::windowing::{MouseWindowEvent, WindowNavigateMsg}; use compositing::windowing::{AnimationState, MouseWindowEvent, WindowNavigateMsg};
use compositing::windowing::{WindowEvent, WindowMethods}; use compositing::windowing::{WindowEvent, WindowMethods};
use euclid::{Point2D, Size2D, TypedPoint2D, TypedVector2D, TypedRect, ScaleFactor, TypedSize2D}; use euclid::{Point2D, Size2D, TypedPoint2D, TypedVector2D, TypedRect, ScaleFactor, TypedSize2D};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -196,6 +196,8 @@ pub struct Window {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
pressed_key_map: RefCell<Vec<(ScanCode, char)>>, pressed_key_map: RefCell<Vec<(ScanCode, char)>>,
animation_state: Cell<AnimationState>,
gl: Rc<gl::Gl>, gl: Rc<gl::Gl>,
} }
@ -316,6 +318,7 @@ impl Window {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
last_pressed_key: Cell::new(None), last_pressed_key: Cell::new(None),
gl: gl.clone(), gl: gl.clone(),
animation_state: Cell::new(AnimationState::Idle),
}; };
window.present(); window.present();
@ -655,10 +658,14 @@ impl Window {
let mut events = mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new()); let mut events = mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new());
let mut close_event = false; let mut close_event = false;
let poll = self.animation_state.get() == AnimationState::Animating ||
opts::get().output_file.is_some() ||
opts::get().exit_after_load ||
opts::get().headless;
// When writing to a file then exiting, use event // When writing to a file then exiting, use event
// polling so that we don't block on a GUI event // polling so that we don't block on a GUI event
// such as mouse click. // such as mouse click.
if opts::get().output_file.is_some() || opts::get().exit_after_load || opts::get().headless { if poll {
match self.kind { match self.kind {
WindowKind::Window(ref window) => { WindowKind::Window(ref window) => {
while let Some(event) = window.poll_events().next() { while let Some(event) = window.poll_events().next() {
@ -1005,6 +1012,10 @@ impl WindowMethods for Window {
} }
fn set_animation_state(&self, state: AnimationState) {
self.animation_state.set(state);
}
fn set_inner_size(&self, size: Size2D<u32>) { fn set_inner_size(&self, size: Size2D<u32>) {
match self.kind { match self.kind {
WindowKind::Window(ref window) => { WindowKind::Window(ref window) => {