diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 4762408b4d..f08acfb62e 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -9,6 +9,7 @@ use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, Dev use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult}; use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping}; use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, ImageKey}; +use api::{NotificationRequest, Checkpoint}; use api::channel::{MsgReceiver, Payload}; #[cfg(feature = "capture")] use api::CaptureBits; @@ -44,6 +45,7 @@ use std::u32; #[cfg(feature = "replay")] use tiling::Frame; use time::precise_time_ns; +use util::drain_filter; #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] @@ -574,6 +576,7 @@ impl RenderBackend { txn.document_id, replace(&mut txn.resource_updates, Vec::new()), replace(&mut txn.frame_ops, Vec::new()), + replace(&mut txn.notifications, Vec::new()), txn.build_frame, txn.render_frame, &mut frame_counter, @@ -801,6 +804,7 @@ impl RenderBackend { resource_updates: transaction_msg.resource_updates, frame_ops: transaction_msg.frame_ops, rasterized_blobs: Vec::new(), + notifications: transaction_msg.notifications, set_root_pipeline: None, build_frame: transaction_msg.generate_frame, render_frame: transaction_msg.generate_frame, @@ -836,6 +840,7 @@ impl RenderBackend { txn.document_id, replace(&mut txn.resource_updates, Vec::new()), replace(&mut txn.frame_ops, Vec::new()), + replace(&mut txn.notifications, Vec::new()), txn.build_frame, txn.render_frame, frame_counter, @@ -872,6 +877,7 @@ impl RenderBackend { document_id: DocumentId, resource_updates: Vec, mut frame_ops: Vec, + mut notifications: Vec, mut build_frame: bool, mut render_frame: bool, frame_counter: &mut u32, @@ -981,6 +987,12 @@ impl RenderBackend { self.result_tx.send(msg).unwrap(); } + drain_filter( + &mut notifications, + |n| { n.when() == Checkpoint::FrameBuilt }, + |n| { n.notify(); }, + ); + // Always forward the transaction to the renderer if a frame was requested, // otherwise gecko can get into a state where it waits (forever) for the // transaction to complete before sending new work. diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index 3170b794cb..3f24f7c607 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -42,6 +42,7 @@ use std::path::PathBuf; use std::sync::{Arc, RwLock}; use texture_cache::{TextureCache, TextureCacheHandle, Eviction}; use tiling::SpecialRenderPasses; +use util::drain_filter; const DEFAULT_TILE_SIZE: TileSize = 512; @@ -518,9 +519,8 @@ impl ResourceCache { updates: &mut Vec, profile_counters: &mut ResourceProfileCounters, ) { - let mut new_updates = Vec::with_capacity(updates.len()); - for update in mem::replace(updates, Vec::new()) { - match update { + for update in updates.iter() { + match *update { ResourceUpdate::AddImage(ref img) => { if let ImageData::Blob(ref blob_data) = img.data { self.add_blob_image( @@ -541,7 +541,7 @@ impl ResourceCache { ); } } - ResourceUpdate::SetImageVisibleArea(key, area) => { + ResourceUpdate::SetImageVisibleArea(ref key, ref area) => { if let Some(template) = self.blob_image_templates.get_mut(&key) { if let Some(tile_size) = template.tiling { template.viewport_tiles = Some(compute_tile_range( @@ -554,8 +554,17 @@ impl ResourceCache { } _ => {} } + } - match update { + drain_filter( + updates, + |update| match *update { + ResourceUpdate::AddFont(_) | + ResourceUpdate::AddFontInstance(_) => true, + _ => false, + }, + // Updates that were moved out of the array: + |update: ResourceUpdate| match update { ResourceUpdate::AddFont(font) => { match font { AddFont::Raw(id, bytes, index) => { @@ -567,7 +576,7 @@ impl ResourceCache { } } } - ResourceUpdate::AddFontInstance(mut instance) => { + ResourceUpdate::AddFontInstance(instance) => { self.add_font_instance( instance.key, instance.font_key, @@ -577,13 +586,9 @@ impl ResourceCache { instance.variations, ); } - other => { - new_updates.push(other); - } + _ => { unreachable!(); } } - } - - *updates = new_updates; + ); } pub fn set_blob_rasterizer(&mut self, rasterizer: Box) { diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs index 830e9cfac8..6b23c31dfc 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -4,7 +4,7 @@ use api::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImageResult}; use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, Epoch}; -use api::{BuiltDisplayList, ColorF, LayoutSize}; +use api::{BuiltDisplayList, ColorF, LayoutSize, NotificationRequest, Checkpoint}; use api::channel::MsgSender; use frame_builder::{FrameBuilderConfig, FrameBuilder}; use clip_scroll_tree::ClipScrollTree; @@ -17,6 +17,7 @@ use scene::Scene; use std::sync::mpsc::{channel, Receiver, Sender}; use std::mem::replace; use time::precise_time_ns; +use util::drain_filter; /// Represents the work associated to a transaction before scene building. pub struct Transaction { @@ -30,6 +31,7 @@ pub struct Transaction { pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, pub resource_updates: Vec, pub frame_ops: Vec, + pub notifications: Vec, pub set_root_pipeline: Option, pub build_frame: bool, pub render_frame: bool, @@ -61,6 +63,7 @@ pub struct BuiltTransaction { pub blob_rasterizer: Option>, pub frame_ops: Vec, pub removed_pipelines: Vec, + pub notifications: Vec, pub scene_build_start_time: u64, pub scene_build_end_time: u64, pub build_frame: bool, @@ -251,6 +254,7 @@ impl SceneBuilder { blob_rasterizer: None, frame_ops: Vec::new(), removed_pipelines: Vec::new(), + notifications: Vec::new(), scene_build_start_time, scene_build_end_time: precise_time_ns(), }); @@ -322,6 +326,12 @@ impl SceneBuilder { ); rasterized_blobs.append(&mut txn.rasterized_blobs); + drain_filter( + &mut txn.notifications, + |n| { n.when() == Checkpoint::SceneBuilt }, + |n| { n.notify(); }, + ); + Box::new(BuiltTransaction { document_id: txn.document_id, build_frame: txn.build_frame || built_scene.is_some(), @@ -332,6 +342,7 @@ impl SceneBuilder { blob_rasterizer: replace(&mut txn.blob_rasterizer, None), frame_ops: replace(&mut txn.frame_ops, Vec::new()), removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()), + notifications: replace(&mut txn.notifications, Vec::new()), scene_build_start_time, scene_build_end_time: precise_time_ns(), }) diff --git a/webrender/src/util.rs b/webrender/src/util.rs index 846895497a..1f01b5e168 100644 --- a/webrender/src/util.rs +++ b/webrender/src/util.rs @@ -508,3 +508,63 @@ pub fn world_rect_to_device_pixels( let device_rect = rect * device_pixel_scale; device_rect.round_out() } + +/// Run the first callback over all elements in the array. If the callback returns true, +/// the element is removed from the array and moved to a second callback. +/// +/// This is a simple implementation waiting for Vec::drain_filter to be stable. +/// When that happens, code like: +/// +/// let filter = |op| { +/// match *op { +/// Enum::Foo | Enum::Bar => true, +/// Enum::Baz => false, +/// } +/// }; +/// drain_filter( +/// &mut ops, +/// filter, +/// |op| { +/// match op { +/// Enum::Foo => { foo(); } +/// Enum::Bar => { bar(); } +/// Enum::Baz => { unreachable!(); } +/// } +/// }, +/// ); +/// +/// Can be rewritten as: +/// +/// let filter = |op| { +/// match *op { +/// Enum::Foo | Enum::Bar => true, +/// Enum::Baz => false, +/// } +/// }; +/// for op in ops.drain_filter(filter) { +/// match op { +/// Enum::Foo => { foo(); } +/// Enum::Bar => { bar(); } +/// Enum::Baz => { unreachable!(); } +/// } +/// } +/// +/// See https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter +pub fn drain_filter( + vec: &mut Vec, + mut filter: Filter, + mut action: Action, +) +where + Filter: FnMut(&mut T) -> bool, + Action: FnMut(T) +{ + let mut i = 0; + while i != vec.len() { + if filter(&mut vec[i]) { + action(vec.remove(i)); + } else { + i += 1; + } + } +} diff --git a/webrender_api/src/api.rs b/webrender_api/src/api.rs index 8cd4b160e2..6591069250 100644 --- a/webrender_api/src/api.rs +++ b/webrender_api/src/api.rs @@ -10,6 +10,7 @@ use std::cell::Cell; use std::fmt; use std::marker::PhantomData; use std::path::PathBuf; +use std::sync::Arc; use std::u32; use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, DeviceUintRect}; use {DeviceUintSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions}; @@ -48,6 +49,8 @@ pub struct Transaction { // Additional display list data. payloads: Vec, + notifications: Vec, + // Resource updates are applied after scene building. pub resource_updates: Vec, @@ -67,6 +70,7 @@ impl Transaction { frame_ops: Vec::new(), resource_updates: Vec::new(), payloads: Vec::new(), + notifications: Vec::new(), use_scene_builder_thread: true, generate_frame: false, low_priority: false, @@ -88,7 +92,8 @@ impl Transaction { !self.generate_frame && self.scene_ops.is_empty() && self.frame_ops.is_empty() && - self.resource_updates.is_empty() + self.resource_updates.is_empty() && + self.notifications.is_empty() } pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) { @@ -171,6 +176,21 @@ impl Transaction { self.merge(resources); } + // Note: Gecko uses this to get notified when a transaction that contains + // potentially long blob rasterization or scene build is ready to be rendered. + // so that the tab-switching integration can react adequately when tab + // switching takes too long. For this use case when matters is that the + // notification doesn't fire before scene building and blob rasterization. + + /// Trigger a notification at a certain stage of the rendering pipeline. + /// + /// Not that notification requests are skipped during serialization, so is is + /// best to use them for synchronization purposes and not for things that could + /// affect the WebRender's state. + pub fn notify(&mut self, event: NotificationRequest) { + self.notifications.push(event); + } + pub fn set_window_parameters( &mut self, window_size: DeviceUintSize, @@ -219,7 +239,7 @@ impl Transaction { /// in `webrender::Renderer`, [new_frame_ready()][notifier] gets called. /// Note that the notifier is called even if the frame generation was a /// no-op; the arguments passed to `new_frame_ready` will provide information - /// as to what happened. + /// as to when happened. /// /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready pub fn generate_frame(&mut self) { @@ -257,6 +277,7 @@ impl Transaction { scene_ops: self.scene_ops, frame_ops: self.frame_ops, resource_updates: self.resource_updates, + notifications: self.notifications, use_scene_builder_thread: self.use_scene_builder_thread, generate_frame: self.generate_frame, low_priority: self.low_priority, @@ -371,6 +392,9 @@ pub struct TransactionMsg { pub generate_frame: bool, pub use_scene_builder_thread: bool, pub low_priority: bool, + + #[serde(skip)] + pub notifications: Vec, } impl TransactionMsg { @@ -378,7 +402,8 @@ impl TransactionMsg { !self.generate_frame && self.scene_ops.is_empty() && self.frame_ops.is_empty() && - self.resource_updates.is_empty() + self.resource_updates.is_empty() && + self.notifications.is_empty() } // TODO: We only need this for a few RenderApi methods which we should remove. @@ -387,6 +412,7 @@ impl TransactionMsg { scene_ops: Vec::new(), frame_ops: vec![msg], resource_updates: Vec::new(), + notifications: Vec::new(), generate_frame: false, use_scene_builder_thread: false, low_priority: false, @@ -398,6 +424,7 @@ impl TransactionMsg { scene_ops: vec![msg], frame_ops: Vec::new(), resource_updates: Vec::new(), + notifications: Vec::new(), generate_frame: false, use_scene_builder_thread: false, low_priority: false, @@ -1140,3 +1167,48 @@ pub trait RenderNotifier: Send { } fn shut_down(&self) {} } + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Checkpoint { + SceneBuilt, + FrameBuilt, + /// NotificationRequests get notified with this if they get dropped without having been + /// notified. This provides the guarantee that if a request is created it will get notified. + TransactionDropped, +} + +pub trait NotificationHandler : Send + Sync { + fn notify(&self, when: Checkpoint); +} + +#[derive(Clone)] +pub struct NotificationRequest { + handler: Arc, + when: Checkpoint, + done: bool, +} + +impl NotificationRequest { + pub fn new(when: Checkpoint, handler: Arc) -> Self { + NotificationRequest { + handler, + when, + done: false, + } + } + + pub fn when(&self) -> Checkpoint { self.when } + + pub fn notify(mut self) { + self.handler.notify(self.when); + self.done = true; + } +} + +impl Drop for NotificationRequest { + fn drop(&mut self) { + if !self.done { + self.handler.notify(Checkpoint::TransactionDropped); + } + } +}