From c385b3c9737c17d59cb02e520c3b68b232cb6497 Mon Sep 17 00:00:00 2001 From: Mukilan Thiyagarajan Date: Thu, 14 Sep 2023 15:00:42 +0530 Subject: Revert "Upgrade WebRender to e491e1ae637b2eed1e7195855d88357e5eb3ddf9 (#30323)" This reverts commit a9d37cb85ac2c55fc630fccffe1ba60ff00f555b. --- third_party/webrender/webrender_api/Cargo.toml | 1 - third_party/webrender/webrender_api/src/api.rs | 2121 ++++++++++++++++++++ third_party/webrender/webrender_api/src/channel.rs | 63 +- .../webrender/webrender_api/src/display_item.rs | 183 +- .../webrender/webrender_api/src/display_list.rs | 130 +- third_party/webrender/webrender_api/src/font.rs | 5 +- .../webrender_api/src/gradient_builder.rs | 4 +- third_party/webrender/webrender_api/src/image.rs | 31 +- .../webrender/webrender_api/src/image_tiling.rs | 815 ++++++++ third_party/webrender/webrender_api/src/lib.rs | 636 +----- .../webrender/webrender_api/src/resources.rs | 327 +++ third_party/webrender/webrender_api/src/units.rs | 3 +- 12 files changed, 3350 insertions(+), 969 deletions(-) create mode 100644 third_party/webrender/webrender_api/src/api.rs create mode 100644 third_party/webrender/webrender_api/src/image_tiling.rs create mode 100644 third_party/webrender/webrender_api/src/resources.rs (limited to 'third_party/webrender/webrender_api') diff --git a/third_party/webrender/webrender_api/Cargo.toml b/third_party/webrender/webrender_api/Cargo.toml index 2d045bb7548..1a48083a10b 100644 --- a/third_party/webrender/webrender_api/Cargo.toml +++ b/third_party/webrender/webrender_api/Cargo.toml @@ -26,7 +26,6 @@ serde_bytes = "0.11" time = "0.1" malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of", package = "wr_malloc_size_of" } peek-poke = { version = "0.2", path = "../peek-poke", features = ["extras"] } -crossbeam-channel = "0.5" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9" diff --git a/third_party/webrender/webrender_api/src/api.rs b/third_party/webrender/webrender_api/src/api.rs new file mode 100644 index 00000000000..94962ad7128 --- /dev/null +++ b/third_party/webrender/webrender_api/src/api.rs @@ -0,0 +1,2121 @@ +/* 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/. */ + +#![deny(missing_docs)] + +use peek_poke::PeekPoke; +use std::cell::Cell; +use std::fmt; +use std::marker::PhantomData; +use std::os::raw::c_void; +use std::path::PathBuf; +use std::sync::Arc; +use std::u32; +use std::sync::mpsc::{Sender, Receiver, channel}; +use time::precise_time_ns; +// local imports +use crate::{display_item as di, font}; +use crate::color::{ColorU, ColorF}; +use crate::display_list::BuiltDisplayList; +use crate::font::SharedFontInstanceMap; +use crate::image::{BlobImageData, BlobImageKey, ImageData, ImageDescriptor, ImageKey}; +use crate::image::{BlobImageParams, BlobImageRequest, BlobImageResult, AsyncBlobImageRasterizer, BlobImageHandler}; +use crate::image::DEFAULT_TILE_SIZE; +use crate::resources::ApiResources; +use crate::units::*; + +/// Width and height in device pixels of image tiles. +pub type TileSize = u16; + +/// Documents are rendered in the ascending order of their associated layer values. +pub type DocumentLayer = i8; + +/// Various settings that the caller can select based on desired tradeoffs +/// between rendering quality and performance / power usage. +#[derive(Copy, Clone, Deserialize, Serialize)] +pub struct QualitySettings { + /// If true, disable creating separate picture cache slices when the + /// scroll root changes. This gives maximum opportunity to find an + /// opaque background, which enables subpixel AA. However, it is + /// usually significantly more expensive to render when scrolling. + pub force_subpixel_aa_where_possible: bool, +} + +impl Default for QualitySettings { + fn default() -> Self { + QualitySettings { + // Prefer performance over maximum subpixel AA quality, since WR + // already enables subpixel AA in more situations than other browsers. + force_subpixel_aa_where_possible: false, + } + } +} + +/// Update of a persistent resource in WebRender. +/// +/// ResourceUpdate changes keep theirs effect across display list changes. +#[derive(Clone, Deserialize, Serialize)] +pub enum ResourceUpdate { + /// See `AddImage`. + AddImage(AddImage), + /// See `UpdateImage`. + UpdateImage(UpdateImage), + /// Delete an existing image resource. + /// + /// It is invalid to continue referring to the image key in any display list + /// in the transaction that contains the `DeleteImage` message and subsequent + /// transactions. + DeleteImage(ImageKey), + /// See `AddBlobImage`. + AddBlobImage(AddBlobImage), + /// See `UpdateBlobImage`. + UpdateBlobImage(UpdateBlobImage), + /// Delete existing blob image resource. + DeleteBlobImage(BlobImageKey), + /// See `AddBlobImage::visible_area`. + SetBlobImageVisibleArea(BlobImageKey, DeviceIntRect), + /// See `AddFont`. + AddFont(AddFont), + /// Deletes an already existing font resource. + /// + /// It is invalid to continue referring to the font key in any display list + /// in the transaction that contains the `DeleteImage` message and subsequent + /// transactions. + DeleteFont(font::FontKey), + /// See `AddFontInstance`. + AddFontInstance(AddFontInstance), + /// Deletes an already existing font instance resource. + /// + /// It is invalid to continue referring to the font instance in any display + /// list in the transaction that contains the `DeleteImage` message and + /// subsequent transactions. + DeleteFontInstance(font::FontInstanceKey), +} + +impl fmt::Debug for ResourceUpdate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ResourceUpdate::AddImage(ref i) => f.write_fmt(format_args!( + "ResourceUpdate::AddImage size({:?})", + &i.descriptor.size + )), + ResourceUpdate::UpdateImage(ref i) => f.write_fmt(format_args!( + "ResourceUpdate::UpdateImage size({:?})", + &i.descriptor.size + )), + ResourceUpdate::AddBlobImage(ref i) => f.write_fmt(format_args!( + "ResourceUFpdate::AddBlobImage size({:?})", + &i.descriptor.size + )), + ResourceUpdate::UpdateBlobImage(i) => f.write_fmt(format_args!( + "ResourceUpdate::UpdateBlobImage size({:?})", + &i.descriptor.size + )), + ResourceUpdate::DeleteImage(..) => f.write_str("ResourceUpdate::DeleteImage"), + ResourceUpdate::DeleteBlobImage(..) => f.write_str("ResourceUpdate::DeleteBlobImage"), + ResourceUpdate::SetBlobImageVisibleArea(..) => f.write_str("ResourceUpdate::SetBlobImageVisibleArea"), + ResourceUpdate::AddFont(..) => f.write_str("ResourceUpdate::AddFont"), + ResourceUpdate::DeleteFont(..) => f.write_str("ResourceUpdate::DeleteFont"), + ResourceUpdate::AddFontInstance(..) => f.write_str("ResourceUpdate::AddFontInstance"), + ResourceUpdate::DeleteFontInstance(..) => f.write_str("ResourceUpdate::DeleteFontInstance"), + } + } +} + +/// A Transaction is a group of commands to apply atomically to a document. +/// +/// This mechanism ensures that: +/// - no other message can be interleaved between two commands that need to be applied together. +/// - no redundant work is performed if two commands in the same transaction cause the scene or +/// the frame to be rebuilt. +pub struct Transaction { + /// Operations affecting the scene (applied before scene building). + scene_ops: Vec, + /// Operations affecting the generation of frames (applied after scene building). + frame_ops: Vec, + + notifications: Vec, + + /// Persistent resource updates to apply as part of this transaction. + pub resource_updates: Vec, + + /// If true the transaction is piped through the scene building thread, if false + /// it will be applied directly on the render backend. + use_scene_builder_thread: bool, + + generate_frame: bool, + + /// Set to true in order to force re-rendering even if WebRender can't internally + /// detect that something has changed. + pub invalidate_rendered_frame: bool, + + low_priority: bool, +} + +impl Transaction { + /// Constructor. + pub fn new() -> Self { + Transaction { + scene_ops: Vec::new(), + frame_ops: Vec::new(), + resource_updates: Vec::new(), + notifications: Vec::new(), + use_scene_builder_thread: true, + generate_frame: false, + invalidate_rendered_frame: false, + low_priority: false, + } + } + + /// Marks this transaction to allow it to skip going through the scene builder + /// thread. + /// + /// This is useful to avoid jank in transaction associated with animated + /// property updates, panning and zooming. + /// + /// Note that transactions that skip the scene builder thread can race ahead of + /// transactions that don't skip it. + pub fn skip_scene_builder(&mut self) { + self.use_scene_builder_thread = false; + } + + /// Marks this transaction to enforce going through the scene builder thread. + pub fn use_scene_builder_thread(&mut self) { + self.use_scene_builder_thread = true; + } + + /// Returns true if the transaction has no effect. + pub fn is_empty(&self) -> bool { + !self.generate_frame && + !self.invalidate_rendered_frame && + self.scene_ops.is_empty() && + self.frame_ops.is_empty() && + self.resource_updates.is_empty() && + self.notifications.is_empty() + } + + /// Update a pipeline's epoch. + pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) { + // We track epochs before and after scene building. + // This one will be applied to the pending scene right away: + self.scene_ops.push(SceneMsg::UpdateEpoch(pipeline_id, epoch)); + // And this one will be applied to the currently built scene at the end + // of the transaction (potentially long after the scene_ops one). + self.frame_ops.push(FrameMsg::UpdateEpoch(pipeline_id, epoch)); + // We could avoid the duplication here by storing the epoch updates in a + // separate array and let the render backend schedule the updates at the + // proper times, but it wouldn't make things simpler. + } + + /// Sets the root pipeline. + /// + /// # Examples + /// + /// ``` + /// # use webrender_api::{PipelineId, RenderApiSender, Transaction}; + /// # use webrender_api::units::{DeviceIntSize}; + /// # fn example() { + /// let pipeline_id = PipelineId(0, 0); + /// let mut txn = Transaction::new(); + /// txn.set_root_pipeline(pipeline_id); + /// # } + /// ``` + pub fn set_root_pipeline(&mut self, pipeline_id: PipelineId) { + self.scene_ops.push(SceneMsg::SetRootPipeline(pipeline_id)); + } + + /// Removes data associated with a pipeline from the internal data structures. + /// If the specified `pipeline_id` is for the root pipeline, the root pipeline + /// is reset back to `None`. + pub fn remove_pipeline(&mut self, pipeline_id: PipelineId) { + self.scene_ops.push(SceneMsg::RemovePipeline(pipeline_id)); + } + + /// Supplies a new frame to WebRender. + /// + /// Non-blocking, it notifies a worker process which processes the display list. + /// + /// Note: Scrolling doesn't require an own Frame. + /// + /// Arguments: + /// + /// * `epoch`: The unique Frame ID, monotonically increasing. + /// * `background`: The background color of this pipeline. + /// * `viewport_size`: The size of the viewport for this frame. + /// * `pipeline_id`: The ID of the pipeline that is supplying this display list. + /// * `content_size`: The total screen space size of this display list's display items. + /// * `display_list`: The root Display list used in this frame. + /// * `preserve_frame_state`: If a previous frame exists which matches this pipeline + /// id, this setting determines if frame state (such as scrolling + /// position) should be preserved for this new display list. + pub fn set_display_list( + &mut self, + epoch: Epoch, + background: Option, + viewport_size: LayoutSize, + (pipeline_id, content_size, mut display_list): (PipelineId, LayoutSize, BuiltDisplayList), + preserve_frame_state: bool, + ) { + display_list.set_send_time_ns(precise_time_ns()); + self.scene_ops.push( + SceneMsg::SetDisplayList { + display_list, + epoch, + pipeline_id, + background, + viewport_size, + content_size, + preserve_frame_state, + } + ); + } + + /// Add a set of persistent resource updates to apply as part of this transaction. + pub fn update_resources(&mut self, mut resources: Vec) { + self.resource_updates.append(&mut 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); + } + + /// Setup the output region in the framebuffer for a given document. + pub fn set_document_view( + &mut self, + device_rect: DeviceIntRect, + device_pixel_ratio: f32, + ) { + self.scene_ops.push( + SceneMsg::SetDocumentView { + device_rect, + device_pixel_ratio, + }, + ); + } + + /// Enable copying of the output of this pipeline id to + /// an external texture for callers to consume. + pub fn enable_frame_output(&mut self, pipeline_id: PipelineId, enable: bool) { + self.scene_ops.push(SceneMsg::EnableFrameOutput(pipeline_id, enable)); + } + + /// Scrolls the scrolling layer under the `cursor` + /// + /// WebRender looks for the layer closest to the user + /// which has `ScrollPolicy::Scrollable` set. + pub fn scroll(&mut self, scroll_location: ScrollLocation, cursor: WorldPoint) { + self.frame_ops.push(FrameMsg::Scroll(scroll_location, cursor)); + } + + /// + pub fn scroll_node_with_id( + &mut self, + origin: LayoutPoint, + id: di::ExternalScrollId, + clamp: ScrollClamping, + ) { + self.frame_ops.push(FrameMsg::ScrollNodeWithId(origin, id, clamp)); + } + + /// Set the current quality / performance settings for this document. + pub fn set_quality_settings(&mut self, settings: QualitySettings) { + self.scene_ops.push(SceneMsg::SetQualitySettings { settings }); + } + + /// + pub fn set_page_zoom(&mut self, page_zoom: ZoomFactor) { + self.scene_ops.push(SceneMsg::SetPageZoom(page_zoom)); + } + + /// + pub fn set_pinch_zoom(&mut self, pinch_zoom: ZoomFactor) { + self.frame_ops.push(FrameMsg::SetPinchZoom(pinch_zoom)); + } + + /// + pub fn set_is_transform_async_zooming(&mut self, is_zooming: bool, animation_id: PropertyBindingId) { + self.frame_ops.push(FrameMsg::SetIsTransformAsyncZooming(is_zooming, animation_id)); + } + + /// + pub fn set_pan(&mut self, pan: DeviceIntPoint) { + self.frame_ops.push(FrameMsg::SetPan(pan)); + } + + /// Generate a new frame. When it's done and a RenderNotifier has been set + /// 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 when happened. + /// + /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready + pub fn generate_frame(&mut self) { + self.generate_frame = true; + } + + /// Invalidate rendered frame. It ensure that frame will be rendered during + /// next frame generation. WebRender could skip frame rendering if there + /// is no update. + /// But there are cases that needs to force rendering. + /// - Content of image is updated by reusing same ExternalImageId. + /// - Platform requests it if pixels become stale (like wakeup from standby). + pub fn invalidate_rendered_frame(&mut self) { + self.invalidate_rendered_frame = true; + } + + /// Supply a list of animated property bindings that should be used to resolve + /// bindings in the current display list. + pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) { + self.frame_ops.push(FrameMsg::UpdateDynamicProperties(properties)); + } + + /// Add to the list of animated property bindings that should be used to + /// resolve bindings in the current display list. This is a convenience method + /// so the caller doesn't have to figure out all the dynamic properties before + /// setting them on the transaction but can do them incrementally. + pub fn append_dynamic_transform_properties(&mut self, transforms: Vec>) { + self.frame_ops.push(FrameMsg::AppendDynamicTransformProperties(transforms)); + } + + /// Consumes this object and just returns the frame ops. + pub fn get_frame_ops(self) -> Vec { + self.frame_ops + } + + fn finalize(self, document_id: DocumentId) -> Box { + Box::new(TransactionMsg { + document_id, + 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, + invalidate_rendered_frame: self.invalidate_rendered_frame, + low_priority: self.low_priority, + blob_rasterizer: None, + blob_requests: Vec::new(), + rasterized_blobs: Vec::new(), + }) + } + + /// See `ResourceUpdate::AddImage`. + pub fn add_image( + &mut self, + key: ImageKey, + descriptor: ImageDescriptor, + data: ImageData, + tiling: Option, + ) { + self.resource_updates.push(ResourceUpdate::AddImage(AddImage { + key, + descriptor, + data, + tiling, + })); + } + + /// See `ResourceUpdate::UpdateImage`. + pub fn update_image( + &mut self, + key: ImageKey, + descriptor: ImageDescriptor, + data: ImageData, + dirty_rect: &ImageDirtyRect, + ) { + self.resource_updates.push(ResourceUpdate::UpdateImage(UpdateImage { + key, + descriptor, + data, + dirty_rect: *dirty_rect, + })); + } + + /// See `ResourceUpdate::DeleteImage`. + pub fn delete_image(&mut self, key: ImageKey) { + self.resource_updates.push(ResourceUpdate::DeleteImage(key)); + } + + /// See `ResourceUpdate::AddBlobImage`. + pub fn add_blob_image( + &mut self, + key: BlobImageKey, + descriptor: ImageDescriptor, + data: Arc, + visible_rect: DeviceIntRect, + tile_size: Option, + ) { + self.resource_updates.push( + ResourceUpdate::AddBlobImage(AddBlobImage { + key, + descriptor, + data, + visible_rect, + tile_size: tile_size.unwrap_or(DEFAULT_TILE_SIZE), + }) + ); + } + + /// See `ResourceUpdate::UpdateBlobImage`. + pub fn update_blob_image( + &mut self, + key: BlobImageKey, + descriptor: ImageDescriptor, + data: Arc, + visible_rect: DeviceIntRect, + dirty_rect: &BlobDirtyRect, + ) { + self.resource_updates.push( + ResourceUpdate::UpdateBlobImage(UpdateBlobImage { + key, + descriptor, + data, + visible_rect, + dirty_rect: *dirty_rect, + }) + ); + } + + /// See `ResourceUpdate::DeleteBlobImage`. + pub fn delete_blob_image(&mut self, key: BlobImageKey) { + self.resource_updates.push(ResourceUpdate::DeleteBlobImage(key)); + } + + /// See `ResourceUpdate::SetBlobImageVisibleArea`. + pub fn set_blob_image_visible_area(&mut self, key: BlobImageKey, area: DeviceIntRect) { + self.resource_updates.push(ResourceUpdate::SetBlobImageVisibleArea(key, area)) + } + + /// See `ResourceUpdate::AddFont`. + pub fn add_raw_font(&mut self, key: font::FontKey, bytes: Vec, index: u32) { + self.resource_updates + .push(ResourceUpdate::AddFont(AddFont::Raw(key, Arc::new(bytes), index))); + } + + /// See `ResourceUpdate::AddFont`. + pub fn add_native_font(&mut self, key: font::FontKey, native_handle: font::NativeFontHandle) { + self.resource_updates + .push(ResourceUpdate::AddFont(AddFont::Native(key, native_handle))); + } + + /// See `ResourceUpdate::DeleteFont`. + pub fn delete_font(&mut self, key: font::FontKey) { + self.resource_updates.push(ResourceUpdate::DeleteFont(key)); + } + + /// See `ResourceUpdate::AddFontInstance`. + pub fn add_font_instance( + &mut self, + key: font::FontInstanceKey, + font_key: font::FontKey, + glyph_size: f32, + options: Option, + platform_options: Option, + variations: Vec, + ) { + self.resource_updates + .push(ResourceUpdate::AddFontInstance(AddFontInstance { + key, + font_key, + glyph_size, + options, + platform_options, + variations, + })); + } + + /// See `ResourceUpdate::DeleteFontInstance`. + pub fn delete_font_instance(&mut self, key: font::FontInstanceKey) { + self.resource_updates.push(ResourceUpdate::DeleteFontInstance(key)); + } + + /// A hint that this transaction can be processed at a lower priority. High- + /// priority transactions can jump ahead of regular-priority transactions, + /// but both high- and regular-priority transactions are processed in order + /// relative to other transactions of the same priority. + pub fn set_low_priority(&mut self, low_priority: bool) { + self.low_priority = low_priority; + } + + /// Returns whether this transaction is marked as low priority. + pub fn is_low_priority(&self) -> bool { + self.low_priority + } +} + +/// +pub struct DocumentTransaction { + /// + pub document_id: DocumentId, + /// + pub transaction: Transaction, +} + +/// Represents a transaction in the format sent through the channel. +pub struct TransactionMsg { + /// + pub document_id: DocumentId, + /// Changes that require re-building the scene. + pub scene_ops: Vec, + /// Changes to animated properties that do not require re-building the scene. + pub frame_ops: Vec, + /// Updates to resources that persist across display lists. + pub resource_updates: Vec, + /// Whether to trigger frame building and rendering if something has changed. + pub generate_frame: bool, + /// Whether to force frame building and rendering even if no changes are internally + /// observed. + pub invalidate_rendered_frame: bool, + /// Whether to enforce that this transaction go through the scene builder. + pub use_scene_builder_thread: bool, + /// + pub low_priority: bool, + + /// Handlers to notify at certain points of the pipeline. + pub notifications: Vec, + /// + pub blob_rasterizer: Option>, + /// + pub blob_requests: Vec, + /// + pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, +} + +impl fmt::Debug for TransactionMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "threaded={}, genframe={}, invalidate={}, low_priority={}", + self.use_scene_builder_thread, + self.generate_frame, + self.invalidate_rendered_frame, + self.low_priority, + ).unwrap(); + for scene_op in &self.scene_ops { + writeln!(f, "\t\t{:?}", scene_op).unwrap(); + } + + for frame_op in &self.frame_ops { + writeln!(f, "\t\t{:?}", frame_op).unwrap(); + } + + for resource_update in &self.resource_updates { + writeln!(f, "\t\t{:?}", resource_update).unwrap(); + } + Ok(()) + } +} + +impl TransactionMsg { + /// Returns true if this transaction has no effect. + pub fn is_empty(&self) -> bool { + !self.generate_frame && + !self.invalidate_rendered_frame && + self.scene_ops.is_empty() && + self.frame_ops.is_empty() && + self.resource_updates.is_empty() && + self.notifications.is_empty() + } +} + +/// Creates an image resource with provided parameters. +/// +/// Must be matched with a `DeleteImage` at some point to prevent memory leaks. +#[derive(Clone, Deserialize, Serialize)] +pub struct AddImage { + /// A key to identify the image resource. + pub key: ImageKey, + /// Properties of the image. + pub descriptor: ImageDescriptor, + /// The pixels of the image. + pub data: ImageData, + /// An optional tiling scheme to apply when storing the image's data + /// on the GPU. Applies to both width and heights of the tiles. + /// + /// Note that WebRender may internally chose to tile large images + /// even if this member is set to `None`. + pub tiling: Option, +} + +/// Updates an already existing image resource. +#[derive(Clone, Deserialize, Serialize)] +pub struct UpdateImage { + /// The key identfying the image resource to update. + pub key: ImageKey, + /// Properties of the image. + pub descriptor: ImageDescriptor, + /// The pixels of the image. + pub data: ImageData, + /// An optional dirty rect that lets WebRender optimize the amount of + /// data to transfer to the GPU. + /// + /// The data provided must still represent the entire image. + pub dirty_rect: ImageDirtyRect, +} + +/// Creates a blob-image resource with provided parameters. +/// +/// Must be matched with a `DeleteImage` at some point to prevent memory leaks. +#[derive(Clone, Deserialize, Serialize)] +pub struct AddBlobImage { + /// A key to identify the blob-image resource. + pub key: BlobImageKey, + /// Properties of the image. + pub descriptor: ImageDescriptor, + /// The blob-image's serialized commands. + pub data: Arc, + /// The portion of the plane in the blob-image's internal coordinate + /// system that is stretched to fill the image display item. + /// + /// Unlike regular images, blob images are not limited in size. The + /// top-left corner of their internal coordinate system is also not + /// necessary at (0, 0). + /// This means that blob images can be updated to insert/remove content + /// in any direction to support panning and zooming. + pub visible_rect: DeviceIntRect, + /// The blob image's tile size to apply when rasterizing the blob-image + /// and when storing its rasterized data on the GPU. + /// Applies to both width and heights of the tiles. + /// + /// All blob images are tiled. + pub tile_size: TileSize, +} + +/// Updates an already existing blob-image resource. +#[derive(Clone, Deserialize, Serialize)] +pub struct UpdateBlobImage { + /// The key identfying the blob-image resource to update. + pub key: BlobImageKey, + /// Properties of the image. + pub descriptor: ImageDescriptor, + /// The blob-image's serialized commands. + pub data: Arc, + /// See `AddBlobImage::visible_rect`. + pub visible_rect: DeviceIntRect, + /// An optional dirty rect that lets WebRender optimize the amount of + /// data to to rasterize and transfer to the GPU. + pub dirty_rect: BlobDirtyRect, +} + +/// Creates a font resource. +/// +/// Must be matched with a corresponding `ResourceUpdate::DeleteFont` at some point to prevent +/// memory leaks. +#[derive(Clone, Deserialize, Serialize)] +pub enum AddFont { + /// + Raw(font::FontKey, Arc>, u32), + /// + Native(font::FontKey, font::NativeFontHandle), +} + +/// Describe an item that matched a hit-test query. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct HitTestItem { + /// The pipeline that the display item that was hit belongs to. + pub pipeline: PipelineId, + + /// The tag of the hit display item. + pub tag: di::ItemTag, + + /// The hit point in the coordinate space of the "viewport" of the display item. The + /// viewport is the scroll node formed by the root reference frame of the display item's + /// pipeline. + pub point_in_viewport: LayoutPoint, + + /// The coordinates of the original hit test point relative to the origin of this item. + /// This is useful for calculating things like text offsets in the client. + pub point_relative_to_item: LayoutPoint, +} + +/// Returned by `RenderApi::hit_test`. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct HitTestResult { + /// List of items that are match the hit-test query. + pub items: Vec, +} + +bitflags! { + #[derive(Deserialize, MallocSizeOf, Serialize)] + /// + pub struct HitTestFlags: u8 { + /// + const FIND_ALL = 0b00000001; + /// + const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010; + } +} + +/// Creates a font instance resource. +/// +/// Must be matched with a corresponding `DeleteFontInstance` at some point +/// to prevent memory leaks. +#[derive(Clone, Deserialize, Serialize)] +pub struct AddFontInstance { + /// A key to identify the font instance. + pub key: font::FontInstanceKey, + /// The font resource's key. + pub font_key: font::FontKey, + /// Glyph size in app units. + pub glyph_size: f32, + /// + pub options: Option, + /// + pub platform_options: Option, + /// + pub variations: Vec, +} + +/// Frame messages affect building the scene. +pub enum SceneMsg { + /// + UpdateEpoch(PipelineId, Epoch), + /// + SetPageZoom(ZoomFactor), + /// + SetRootPipeline(PipelineId), + /// + RemovePipeline(PipelineId), + /// + EnableFrameOutput(PipelineId, bool), + /// + SetDisplayList { + /// + display_list: BuiltDisplayList, + /// + epoch: Epoch, + /// + pipeline_id: PipelineId, + /// + background: Option, + /// + viewport_size: LayoutSize, + /// + content_size: LayoutSize, + /// + preserve_frame_state: bool, + }, + /// + SetDocumentView { + /// + device_rect: DeviceIntRect, + /// + device_pixel_ratio: f32, + }, + /// Set the current quality / performance configuration for this document. + SetQualitySettings { + /// The set of available quality / performance config values. + settings: QualitySettings, + }, +} + +/// Frame messages affect frame generation (applied after building the scene). +pub enum FrameMsg { + /// + UpdateEpoch(PipelineId, Epoch), + /// + HitTest(Option, WorldPoint, HitTestFlags, Sender), + /// + RequestHitTester(Sender>), + /// + SetPan(DeviceIntPoint), + /// + Scroll(ScrollLocation, WorldPoint), + /// + ScrollNodeWithId(LayoutPoint, di::ExternalScrollId, ScrollClamping), + /// + GetScrollNodeState(Sender>), + /// + UpdateDynamicProperties(DynamicProperties), + /// + AppendDynamicTransformProperties(Vec>), + /// + SetPinchZoom(ZoomFactor), + /// + SetIsTransformAsyncZooming(bool, PropertyBindingId), +} + +impl fmt::Debug for SceneMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch", + SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList", + SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom", + SceneMsg::RemovePipeline(..) => "SceneMsg::RemovePipeline", + SceneMsg::EnableFrameOutput(..) => "SceneMsg::EnableFrameOutput", + SceneMsg::SetDocumentView { .. } => "SceneMsg::SetDocumentView", + SceneMsg::SetRootPipeline(..) => "SceneMsg::SetRootPipeline", + SceneMsg::SetQualitySettings { .. } => "SceneMsg::SetQualitySettings", + }) + } +} + +impl fmt::Debug for FrameMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + FrameMsg::UpdateEpoch(..) => "FrameMsg::UpdateEpoch", + FrameMsg::HitTest(..) => "FrameMsg::HitTest", + FrameMsg::RequestHitTester(..) => "FrameMsg::RequestHitTester", + FrameMsg::SetPan(..) => "FrameMsg::SetPan", + FrameMsg::Scroll(..) => "FrameMsg::Scroll", + FrameMsg::ScrollNodeWithId(..) => "FrameMsg::ScrollNodeWithId", + FrameMsg::GetScrollNodeState(..) => "FrameMsg::GetScrollNodeState", + FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties", + FrameMsg::AppendDynamicTransformProperties(..) => "FrameMsg::AppendDynamicTransformProperties", + FrameMsg::SetPinchZoom(..) => "FrameMsg::SetPinchZoom", + FrameMsg::SetIsTransformAsyncZooming(..) => "FrameMsg::SetIsTransformAsyncZooming", + }) + } +} + +bitflags!{ + /// Bit flags for WR stages to store in a capture. + // Note: capturing `FRAME` without `SCENE` is not currently supported. + pub struct CaptureBits: u8 { + /// + const SCENE = 0x1; + /// + const FRAME = 0x2; + /// + const TILE_CACHE = 0x4; + /// + const EXTERNAL_RESOURCES = 0x8; + } +} + +bitflags!{ + /// Mask for clearing caches in debug commands. + pub struct ClearCache: u8 { + /// + const IMAGES = 0b1; + /// + const GLYPHS = 0b01; + /// + const GLYPH_DIMENSIONS = 0b001; + /// + const RENDER_TASKS = 0b0001; + /// + const TEXTURE_CACHE = 0b00001; + } +} + +/// Information about a loaded capture of each document +/// that is returned by `RenderBackend`. +#[derive(Clone, Debug)] +pub struct CapturedDocument { + /// + pub document_id: DocumentId, + /// + pub root_pipeline_id: Option, +} + +/// Update of the state of built-in debugging facilities. +#[derive(Clone)] +pub enum DebugCommand { + /// Sets the provided debug flags. + SetFlags(DebugFlags), + /// Configure if dual-source blending is used, if available. + EnableDualSourceBlending(bool), + /// Fetch current documents and display lists. + FetchDocuments, + /// Fetch current passes and batches. + FetchPasses, + // TODO: This should be called FetchClipScrollTree. However, that requires making + // changes to webrender's web debugger ui, touching a 4Mb minified file that + // is too big to submit through the conventional means. + /// Fetch the spatial tree. + FetchClipScrollTree, + /// Fetch render tasks. + FetchRenderTasks, + /// Fetch screenshot. + FetchScreenshot, + /// Save a capture of all the documents state. + SaveCapture(PathBuf, CaptureBits), + /// Load a capture of all the documents state. + LoadCapture(PathBuf, Option<(u32, u32)>, Sender), + /// Start capturing a sequence of scene/frame changes. + StartCaptureSequence(PathBuf, CaptureBits), + /// Stop capturing a sequence of scene/frame changes. + StopCaptureSequence, + /// Clear cached resources, forcing them to be re-uploaded from templates. + ClearCaches(ClearCache), + /// Enable/disable native compositor usage + EnableNativeCompositor(bool), + /// Enable/disable parallel job execution with rayon. + EnableMultithreading(bool), + /// Sets the maximum amount of existing batches to visit before creating a new one. + SetBatchingLookback(u32), + /// Invalidate GPU cache, forcing the update from the CPU mirror. + InvalidateGpuCache, + /// Causes the scene builder to pause for a given amount of milliseconds each time it + /// processes a transaction. + SimulateLongSceneBuild(u32), + /// Causes the low priority scene builder to pause for a given amount of milliseconds + /// each time it processes a transaction. + SimulateLongLowPrioritySceneBuild(u32), + /// Set an override tile size to use for picture caches + SetPictureTileSize(Option), +} + +/// Message sent by the `RenderApi` to the render backend thread. +pub enum ApiMsg { + /// Gets the glyph dimensions + GetGlyphDimensions(font::GlyphDimensionRequest), + /// Gets the glyph indices from a string + GetGlyphIndices(font::GlyphIndexRequest), + /// Adds a new document namespace. + CloneApi(Sender), + /// Adds a new document namespace. + CloneApiByClient(IdNamespace), + /// Adds a new document with given initial size. + AddDocument(DocumentId, DeviceIntSize, DocumentLayer), + /// A message targeted at a particular document. + UpdateDocuments(Vec>), + /// Deletes an existing document. + DeleteDocument(DocumentId), + /// An opaque handle that must be passed to the render notifier. It is used by Gecko + /// to forward gecko-specific messages to the render thread preserving the ordering + /// within the other messages. + ExternalEvent(ExternalEvent), + /// Removes all resources associated with a namespace. + ClearNamespace(IdNamespace), + /// Flush from the caches anything that isn't necessary, to free some memory. + MemoryPressure, + /// Collects a memory report. + ReportMemory(Sender>), + /// Change debugging options. + DebugCommand(DebugCommand), + /// Wakes the render backend's event loop up. Needed when an event is communicated + /// through another channel. + WakeUp, + /// See `RenderApi::wake_scene_builder`. + WakeSceneBuilder, + /// Block until a round-trip to the scene builder thread has completed. This + /// ensures that any transactions (including ones deferred to the scene + /// builder thread) have been processed. + FlushSceneBuilder(Sender<()>), + /// Shut the WebRender instance down. + ShutDown(Option>), +} + +impl fmt::Debug for ApiMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions", + ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices", + ApiMsg::CloneApi(..) => "ApiMsg::CloneApi", + ApiMsg::CloneApiByClient(..) => "ApiMsg::CloneApiByClient", + ApiMsg::AddDocument(..) => "ApiMsg::AddDocument", + ApiMsg::UpdateDocuments(..) => "ApiMsg::UpdateDocuments", + ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument", + ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent", + ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace", + ApiMsg::MemoryPressure => "ApiMsg::MemoryPressure", + ApiMsg::ReportMemory(..) => "ApiMsg::ReportMemory", + ApiMsg::DebugCommand(..) => "ApiMsg::DebugCommand", + ApiMsg::ShutDown(..) => "ApiMsg::ShutDown", + ApiMsg::WakeUp => "ApiMsg::WakeUp", + ApiMsg::WakeSceneBuilder => "ApiMsg::WakeSceneBuilder", + ApiMsg::FlushSceneBuilder(..) => "ApiMsg::FlushSceneBuilder", + }) + } +} + +/// An epoch identifies the state of a pipeline in time. +/// +/// This is mostly used as a synchronization mechanism to observe how/when particular pipeline +/// updates propagate through WebRender and are applied at various stages. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Epoch(pub u32); + +impl Epoch { + /// Magic invalid epoch value. + pub fn invalid() -> Epoch { + Epoch(u32::MAX) + } +} + +/// ID namespaces uniquely identify different users of WebRender's API. +/// +/// For example in Gecko each content process uses a separate id namespace. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Eq, MallocSizeOf, PartialEq, Hash, Ord, PartialOrd, PeekPoke)] +#[derive(Deserialize, Serialize)] +pub struct IdNamespace(pub u32); + +/// A key uniquely identifying a WebRender document. +/// +/// Instances can manage one or several documents (using the same render backend thread). +/// Each document will internally correspond to a single scene, and scenes are made of +/// one or several pipelines. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct DocumentId { + /// + pub namespace_id: IdNamespace, + /// + pub id: u32, +} + +impl DocumentId { + /// + pub fn new(namespace_id: IdNamespace, id: u32) -> Self { + DocumentId { + namespace_id, + id, + } + } + + /// + pub const INVALID: DocumentId = DocumentId { namespace_id: IdNamespace(0), id: 0 }; +} + +/// This type carries no valuable semantics for WR. However, it reflects the fact that +/// clients (Servo) may generate pipelines by different semi-independent sources. +/// These pipelines still belong to the same `IdNamespace` and the same `DocumentId`. +/// Having this extra Id field enables them to generate `PipelineId` without collision. +pub type PipelineSourceId = u32; + +/// From the point of view of WR, `PipelineId` is completely opaque and generic as long as +/// it's clonable, serializable, comparable, and hashable. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct PipelineId(pub PipelineSourceId, pub u32); + +impl Default for PipelineId { + fn default() -> Self { + PipelineId::dummy() + } +} + +impl PipelineId { + /// + pub fn dummy() -> Self { + PipelineId(0, 0) + } +} + +/// +#[derive(Copy, Clone, Debug, MallocSizeOf, Serialize, Deserialize)] +pub enum ClipIntern {} + +/// +#[derive(Copy, Clone, Debug, MallocSizeOf, Serialize, Deserialize)] +pub enum FilterDataIntern {} + +/// Information specific to a primitive type that +/// uniquely identifies a primitive template by key. +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash, Serialize, Deserialize)] +pub enum PrimitiveKeyKind { + /// Clear an existing rect, used for special effects on some platforms. + Clear, + /// + Rectangle { + /// + color: PropertyBinding, + }, +} + +/// Meta-macro to enumerate the various interner identifiers and types. +/// +/// IMPORTANT: Keep this synchronized with the list in mozilla-central located at +/// gfx/webrender_bindings/webrender_ffi.h +/// +/// Note that this could be a lot less verbose if concat_idents! were stable. :-( +#[macro_export] +macro_rules! enumerate_interners { + ($macro_name: ident) => { + $macro_name! { + clip: ClipIntern, + prim: PrimitiveKeyKind, + normal_border: NormalBorderPrim, + image_border: ImageBorder, + image: Image, + yuv_image: YuvImage, + line_decoration: LineDecoration, + linear_grad: LinearGradient, + radial_grad: RadialGradient, + conic_grad: ConicGradient, + picture: Picture, + text_run: TextRun, + filter_data: FilterDataIntern, + backdrop: Backdrop, + } + } +} + +macro_rules! declare_interning_memory_report { + ( $( $name:ident: $ty:ident, )+ ) => { + /// + #[repr(C)] + #[derive(AddAssign, Clone, Debug, Default)] + pub struct InternerSubReport { + $( + /// + pub $name: usize, + )+ + } + } +} + +enumerate_interners!(declare_interning_memory_report); + +/// Memory report for interning-related data structures. +/// cbindgen:derive-eq=false +#[repr(C)] +#[derive(Clone, Debug, Default)] +pub struct InterningMemoryReport { + /// + pub interners: InternerSubReport, + /// + pub data_stores: InternerSubReport, +} + +impl ::std::ops::AddAssign for InterningMemoryReport { + fn add_assign(&mut self, other: InterningMemoryReport) { + self.interners += other.interners; + self.data_stores += other.data_stores; + } +} + +/// Collection of heap sizes, in bytes. +/// cbindgen:derive-eq=false +#[repr(C)] +#[allow(missing_docs)] +#[derive(AddAssign, Clone, Debug, Default)] +pub struct MemoryReport { + // + // CPU Memory. + // + pub clip_stores: usize, + pub gpu_cache_metadata: usize, + pub gpu_cache_cpu_mirror: usize, + pub render_tasks: usize, + pub hit_testers: usize, + pub fonts: usize, + pub images: usize, + pub rasterized_blobs: usize, + pub shader_cache: usize, + pub interning: InterningMemoryReport, + pub display_list: usize, + + // + // GPU memory. + // + pub gpu_cache_textures: usize, + pub vertex_data_textures: usize, + pub render_target_textures: usize, + pub texture_cache_textures: usize, + pub depth_target_textures: usize, + pub swap_chain: usize, +} + +/// A C function that takes a pointer to a heap allocation and returns its size. +/// +/// This is borrowed from the malloc_size_of crate, upon which we want to avoid +/// a dependency from WebRender. +pub type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +struct ResourceId(pub u32); + +/// An opaque pointer-sized value. +#[repr(C)] +#[derive(Clone)] +pub struct ExternalEvent { + raw: usize, +} + +unsafe impl Send for ExternalEvent {} + +impl ExternalEvent { + /// Creates the event from an opaque pointer-sized value. + pub fn from_raw(raw: usize) -> Self { + ExternalEvent { raw } + } + /// Consumes self to make it obvious that the event should be forwarded only once. + pub fn unwrap(self) -> usize { + self.raw + } +} + +/// Describe whether or not scrolling should be clamped by the content bounds. +#[derive(Clone, Deserialize, Serialize)] +pub enum ScrollClamping { + /// + ToContentBounds, + /// + NoClamping, +} + +/// Allows the API to communicate with WebRender. +/// +/// This object is created along with the `Renderer` and it's main use from a +/// user perspective is to create one or several `RenderApi` objects. +pub struct RenderApiSender { + api_sender: Sender, + blob_image_handler: Option>, + shared_font_instances: SharedFontInstanceMap, +} + +impl RenderApiSender { + /// Used internally by the `Renderer`. + pub fn new( + api_sender: Sender, + blob_image_handler: Option>, + shared_font_instances: SharedFontInstanceMap, + ) -> Self { + RenderApiSender { + api_sender, + blob_image_handler, + shared_font_instances, + } + } + + /// Creates a new resource API object with a dedicated namespace. + pub fn create_api(&self) -> RenderApi { + let (sync_tx, sync_rx) = channel(); + let msg = ApiMsg::CloneApi(sync_tx); + self.api_sender.send(msg).expect("Failed to send CloneApi message"); + let namespace_id = match sync_rx.recv() { + Ok(id) => id, + Err(e) => { + // This is used to discover the underlying cause of https://github.com/servo/servo/issues/13480. + let webrender_is_alive = self.api_sender.send(ApiMsg::WakeUp); + if webrender_is_alive.is_err() { + panic!("WebRender was shut down before processing CloneApi: {}", e); + } else { + panic!("CloneApi message response was dropped while WebRender was still alive: {}", e); + } + } + }; + RenderApi { + api_sender: self.api_sender.clone(), + namespace_id, + next_id: Cell::new(ResourceId(0)), + resources: ApiResources::new( + self.blob_image_handler.as_ref().map(|handler| handler.create_similar()), + self.shared_font_instances.clone(), + ), + } + } + + /// Creates a new resource API object with a dedicated namespace. + /// Namespace id is allocated by client. + /// + /// The function could be used only when RendererOptions::namespace_alloc_by_client is true. + /// When the option is true, create_api() could not be used to prevent namespace id conflict. + pub fn create_api_by_client(&self, namespace_id: IdNamespace) -> RenderApi { + let msg = ApiMsg::CloneApiByClient(namespace_id); + self.api_sender.send(msg).expect("Failed to send CloneApiByClient message"); + RenderApi { + api_sender: self.api_sender.clone(), + namespace_id, + next_id: Cell::new(ResourceId(0)), + resources: ApiResources::new( + self.blob_image_handler.as_ref().map(|handler| handler.create_similar()), + self.shared_font_instances.clone(), + ), + } + } +} + +bitflags! { + /// Flags to enable/disable various builtin debugging tools. + #[repr(C)] + #[derive(Default, Deserialize, MallocSizeOf, Serialize)] + pub struct DebugFlags: u32 { + /// Display the frame profiler on screen. + const PROFILER_DBG = 1 << 0; + /// Display intermediate render targets on screen. + const RENDER_TARGET_DBG = 1 << 1; + /// Display all texture cache pages on screen. + const TEXTURE_CACHE_DBG = 1 << 2; + /// Display GPU timing results. + const GPU_TIME_QUERIES = 1 << 3; + /// Query the number of pixels that pass the depth test divided and show it + /// in the profiler as a percentage of the number of pixels in the screen + /// (window width times height). + const GPU_SAMPLE_QUERIES = 1 << 4; + /// Render each quad with their own draw call. + /// + /// Terrible for performance but can help with understanding the drawing + /// order when inspecting renderdoc or apitrace recordings. + const DISABLE_BATCHING = 1 << 5; + /// Display the pipeline epochs. + const EPOCHS = 1 << 6; + /// Reduce the amount of information displayed by the profiler so that + /// it occupies less screen real-estate. + const COMPACT_PROFILER = 1 << 7; + /// Print driver messages to stdout. + const ECHO_DRIVER_MESSAGES = 1 << 8; + /// Show an indicator that moves every time a frame is rendered. + const NEW_FRAME_INDICATOR = 1 << 9; + /// Show an indicator that moves every time a scene is built. + const NEW_SCENE_INDICATOR = 1 << 10; + /// Show an overlay displaying overdraw amount. + const SHOW_OVERDRAW = 1 << 11; + /// Display the contents of GPU cache. + const GPU_CACHE_DBG = 1 << 12; + /// Show a red bar that moves each time a slow frame is detected. + const SLOW_FRAME_INDICATOR = 1 << 13; + /// Clear evicted parts of the texture cache for debugging purposes. + const TEXTURE_CACHE_DBG_CLEAR_EVICTED = 1 << 14; + /// Show picture caching debug overlay + const PICTURE_CACHING_DBG = 1 << 15; + /// Highlight all primitives with colors based on kind. + const PRIMITIVE_DBG = 1 << 16; + /// Draw a zoom widget showing part of the framebuffer zoomed in. + const ZOOM_DBG = 1 << 17; + /// Scale the debug renderer down for a smaller screen. This will disrupt + /// any mapping between debug display items and page content, so shouldn't + /// be used with overlays like the picture caching or primitive display. + const SMALL_SCREEN = 1 << 18; + /// Disable various bits of the WebRender pipeline, to help narrow + /// down where slowness might be coming from. + const DISABLE_OPAQUE_PASS = 1 << 19; + /// + const DISABLE_ALPHA_PASS = 1 << 20; + /// + const DISABLE_CLIP_MASKS = 1 << 21; + /// + const DISABLE_TEXT_PRIMS = 1 << 22; + /// + const DISABLE_GRADIENT_PRIMS = 1 << 23; + /// + const OBSCURE_IMAGES = 1 << 24; + /// Taint the transparent area of the glyphs with a random opacity to easily + /// see when glyphs are re-rasterized. + const GLYPH_FLASHING = 1 << 25; + /// The profiler only displays information that is out of the ordinary. + const SMART_PROFILER = 1 << 26; + /// Dynamically control whether picture caching is enabled. + const DISABLE_PICTURE_CACHING = 1 << 27; + /// If set, dump picture cache invalidation debug to console. + const INVALIDATION_DBG = 1 << 28; + /// Log tile cache to memory for later saving as part of wr-capture + const TILE_CACHE_LOGGING_DBG = 1 << 29; + /// For debugging, force-disable automatic scaling of establishes_raster_root + /// pictures that are too large (ie go back to old behavior that prevents those + /// large pictures from establishing a raster root). + const DISABLE_RASTER_ROOT_SCALING = 1 << 30; + } +} + +/// The main entry point to interact with WebRender. +pub struct RenderApi { + api_sender: Sender, + namespace_id: IdNamespace, + next_id: Cell, + resources: ApiResources, +} + +impl RenderApi { + /// Returns the namespace ID used by this API object. + pub fn get_namespace_id(&self) -> IdNamespace { + self.namespace_id + } + + /// + pub fn create_sender(&self) -> RenderApiSender { + RenderApiSender::new( + self.api_sender.clone(), + self.resources.blob_image_handler.as_ref().map(|handler| handler.create_similar()), + self.resources.get_shared_font_instances(), + ) + } + + /// Add a document to the WebRender instance. + /// + /// Instances can manage one or several documents (using the same render backend thread). + /// Each document will internally correspond to a single scene, and scenes are made of + /// one or several pipelines. + pub fn add_document(&self, initial_size: DeviceIntSize, layer: DocumentLayer) -> DocumentId { + let new_id = self.next_unique_id(); + self.add_document_with_id(initial_size, layer, new_id) + } + + /// See `add_document` + pub fn add_document_with_id(&self, + initial_size: DeviceIntSize, + layer: DocumentLayer, + id: u32) -> DocumentId { + let document_id = DocumentId::new(self.namespace_id, id); + + let msg = ApiMsg::AddDocument(document_id, initial_size, layer); + self.api_sender.send(msg).unwrap(); + + document_id + } + + /// Delete a document. + pub fn delete_document(&self, document_id: DocumentId) { + let msg = ApiMsg::DeleteDocument(document_id); + self.api_sender.send(msg).unwrap(); + } + + /// Generate a new font key + pub fn generate_font_key(&self) -> font::FontKey { + let new_id = self.next_unique_id(); + font::FontKey::new(self.namespace_id, new_id) + } + + /// Generate a new font instance key + pub fn generate_font_instance_key(&self) -> font::FontInstanceKey { + let new_id = self.next_unique_id(); + font::FontInstanceKey::new(self.namespace_id, new_id) + } + + /// Gets the dimensions for the supplied glyph keys + /// + /// Note: Internally, the internal texture cache doesn't store + /// 'empty' textures (height or width = 0) + /// This means that glyph dimensions e.g. for spaces (' ') will mostly be None. + pub fn get_glyph_dimensions( + &self, + key: font::FontInstanceKey, + glyph_indices: Vec, + ) -> Vec> { + let (sender, rx) = channel(); + let msg = ApiMsg::GetGlyphDimensions(font::GlyphDimensionRequest { + key, + glyph_indices, + sender + }); + self.api_sender.send(msg).unwrap(); + rx.recv().unwrap() + } + + /// Gets the glyph indices for the supplied string. These + /// can be used to construct GlyphKeys. + pub fn get_glyph_indices(&self, key: font::FontKey, text: &str) -> Vec> { + let (sender, rx) = channel(); + let msg = ApiMsg::GetGlyphIndices(font::GlyphIndexRequest { + key, + text: text.to_string(), + sender, + }); + self.api_sender.send(msg).unwrap(); + rx.recv().unwrap() + } + + /// Creates an `ImageKey`. + pub fn generate_image_key(&self) -> ImageKey { + let new_id = self.next_unique_id(); + ImageKey::new(self.namespace_id, new_id) + } + + /// Creates a `BlobImageKey`. + pub fn generate_blob_image_key(&self) -> BlobImageKey { + BlobImageKey(self.generate_image_key()) + } + + /// A Gecko-specific notification mechanism to get some code executed on the + /// `Renderer`'s thread, mostly replaced by `NotificationHandler`. You should + /// probably use the latter instead. + pub fn send_external_event(&self, evt: ExternalEvent) { + let msg = ApiMsg::ExternalEvent(evt); + self.api_sender.send(msg).unwrap(); + } + + /// Notify WebRender that now is a good time to flush caches and release + /// as much memory as possible. + pub fn notify_memory_pressure(&self) { + self.api_sender.send(ApiMsg::MemoryPressure).unwrap(); + } + + /// Synchronously requests memory report. + pub fn report_memory(&self) -> MemoryReport { + let (tx, rx) = channel(); + self.api_sender.send(ApiMsg::ReportMemory(tx)).unwrap(); + *rx.recv().unwrap() + } + + /// Update debugging flags. + pub fn set_debug_flags(&self, flags: DebugFlags) { + let cmd = DebugCommand::SetFlags(flags); + self.api_sender.send(ApiMsg::DebugCommand(cmd)).unwrap(); + } + + /// Shut the WebRender instance down. + pub fn shut_down(&self, synchronously: bool) { + if synchronously { + let (tx, rx) = channel(); + self.api_sender.send(ApiMsg::ShutDown(Some(tx))).unwrap(); + rx.recv().unwrap(); + } else { + self.api_sender.send(ApiMsg::ShutDown(None)).unwrap(); + } + } + + /// Create a new unique key that can be used for + /// animated property bindings. + pub fn generate_property_binding_key(&self) -> PropertyBindingKey { + let new_id = self.next_unique_id(); + PropertyBindingKey { + id: PropertyBindingId { + namespace: self.namespace_id, + uid: new_id, + }, + _phantom: PhantomData, + } + } + + #[inline] + fn next_unique_id(&self) -> u32 { + let ResourceId(id) = self.next_id.get(); + self.next_id.set(ResourceId(id + 1)); + id + } + + // For use in Wrench only + #[doc(hidden)] + pub fn send_message(&self, msg: ApiMsg) { + self.api_sender.send(msg).unwrap(); + } + + /// Creates a transaction message from a single frame message. + fn frame_message(&self, msg: FrameMsg, document_id: DocumentId) -> Box { + Box::new(TransactionMsg { + document_id, + scene_ops: Vec::new(), + frame_ops: vec![msg], + resource_updates: Vec::new(), + notifications: Vec::new(), + generate_frame: false, + invalidate_rendered_frame: false, + use_scene_builder_thread: false, + low_priority: false, + blob_rasterizer: None, + blob_requests: Vec::new(), + rasterized_blobs: Vec::new(), + }) + } + + /// Creates a transaction message from a single scene message. + fn scene_message(&self, msg: SceneMsg, document_id: DocumentId) -> Box { + Box::new(TransactionMsg { + document_id, + scene_ops: vec![msg], + frame_ops: Vec::new(), + resource_updates: Vec::new(), + notifications: Vec::new(), + generate_frame: false, + invalidate_rendered_frame: false, + use_scene_builder_thread: false, + low_priority: false, + blob_rasterizer: None, + blob_requests: Vec::new(), + rasterized_blobs: Vec::new(), + }) + } + + /// A helper method to send document messages. + fn send_scene_msg(&self, document_id: DocumentId, msg: SceneMsg) { + // This assertion fails on Servo use-cases, because it creates different + // `RenderApi` instances for layout and compositor. + //assert_eq!(document_id.0, self.namespace_id); + self.api_sender + .send(ApiMsg::UpdateDocuments(vec![self.scene_message(msg, document_id)])) + .unwrap() + } + + /// A helper method to send document messages. + fn send_frame_msg(&self, document_id: DocumentId, msg: FrameMsg) { + // This assertion fails on Servo use-cases, because it creates different + // `RenderApi` instances for layout and compositor. + //assert_eq!(document_id.0, self.namespace_id); + self.api_sender + .send(ApiMsg::UpdateDocuments(vec![self.frame_message(msg, document_id)])) + .unwrap() + } + + /// Send a transaction to WebRender. + pub fn send_transaction(&mut self, document_id: DocumentId, transaction: Transaction) { + let mut transaction = transaction.finalize(document_id); + + self.resources.update(&mut transaction); + + self.api_sender.send(ApiMsg::UpdateDocuments(vec![transaction])).unwrap(); + } + + /// Send multiple transactions. + pub fn send_transactions(&mut self, document_ids: Vec, mut transactions: Vec) { + debug_assert!(document_ids.len() == transactions.len()); + let msgs = transactions.drain(..).zip(document_ids) + .map(|(txn, id)| { + let mut txn = txn.finalize(id); + self.resources.update(&mut txn); + + txn + }) + .collect(); + + self.api_sender.send(ApiMsg::UpdateDocuments(msgs)).unwrap(); + } + + /// Does a hit test on display items in the specified document, at the given + /// point. If a pipeline_id is specified, it is used to further restrict the + /// hit results so that only items inside that pipeline are matched. If the + /// HitTestFlags argument contains the FIND_ALL flag, then the vector of hit + /// results will contain all display items that match, ordered from front + /// to back. + pub fn hit_test(&self, + document_id: DocumentId, + pipeline_id: Option, + point: WorldPoint, + flags: HitTestFlags) + -> HitTestResult { + let (tx, rx) = channel(); + + self.send_frame_msg( + document_id, + FrameMsg::HitTest(pipeline_id, point, flags, tx) + ); + rx.recv().unwrap() + } + + /// Synchronously request an object that can perform fast hit testing queries. + pub fn request_hit_tester(&self, document_id: DocumentId) -> HitTesterRequest { + let (tx, rx) = channel(); + self.send_frame_msg( + document_id, + FrameMsg::RequestHitTester(tx) + ); + + HitTesterRequest { rx } + } + + /// Setup the output region in the framebuffer for a given document. + pub fn set_document_view( + &self, + document_id: DocumentId, + device_rect: DeviceIntRect, + device_pixel_ratio: f32, + ) { + self.send_scene_msg( + document_id, + SceneMsg::SetDocumentView { device_rect, device_pixel_ratio }, + ); + } + + /// Setup the output region in the framebuffer for a given document. + /// Enable copying of the output of this pipeline id to + /// an external texture for callers to consume. + pub fn enable_frame_output( + &self, + document_id: DocumentId, + pipeline_id: PipelineId, + enable: bool, + ) { + self.send_scene_msg( + document_id, + SceneMsg::EnableFrameOutput(pipeline_id, enable), + ); + } + + /// + pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec { + let (tx, rx) = channel(); + self.send_frame_msg(document_id, FrameMsg::GetScrollNodeState(tx)); + rx.recv().unwrap() + } + + // Some internal scheduling magic that leaked into the API. + // Buckle up and see APZUpdater.cpp for more info about what this is about. + #[doc(hidden)] + pub fn wake_scene_builder(&self) { + self.send_message(ApiMsg::WakeSceneBuilder); + } + + /// Block until a round-trip to the scene builder thread has completed. This + /// ensures that any transactions (including ones deferred to the scene + /// builder thread) have been processed. + pub fn flush_scene_builder(&self) { + let (tx, rx) = channel(); + self.send_message(ApiMsg::FlushSceneBuilder(tx)); + rx.recv().unwrap(); // block until done + } + + /// Save a capture of the current frame state for debugging. + pub fn save_capture(&self, path: PathBuf, bits: CaptureBits) { + let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path, bits)); + self.send_message(msg); + } + + /// Load a capture of the current frame state for debugging. + pub fn load_capture(&self, path: PathBuf, ids: Option<(u32, u32)>) -> Vec { + // First flush the scene builder otherwise async scenes might clobber + // the capture we are about to load. + self.flush_scene_builder(); + + let (tx, rx) = channel(); + let msg = ApiMsg::DebugCommand(DebugCommand::LoadCapture(path, ids, tx)); + self.send_message(msg); + + let mut documents = Vec::new(); + while let Ok(captured_doc) = rx.recv() { + documents.push(captured_doc); + } + documents + } + + /// Start capturing a sequence of frames. + pub fn start_capture_sequence(&self, path: PathBuf, bits: CaptureBits) { + let msg = ApiMsg::DebugCommand(DebugCommand::StartCaptureSequence(path, bits)); + self.send_message(msg); + } + + /// Stop capturing sequences of frames. + pub fn stop_capture_sequence(&self) { + let msg = ApiMsg::DebugCommand(DebugCommand::StopCaptureSequence); + self.send_message(msg); + } + + /// Update the state of builtin debugging facilities. + pub fn send_debug_cmd(&mut self, cmd: DebugCommand) { + if let DebugCommand::EnableMultithreading(enable) = cmd { + // TODO(nical) we should enable it for all RenderApis. + self.resources.enable_multithreading(enable); + } + let msg = ApiMsg::DebugCommand(cmd); + self.send_message(msg); + } +} + +impl Drop for RenderApi { + fn drop(&mut self) { + let msg = ApiMsg::ClearNamespace(self.namespace_id); + let _ = self.api_sender.send(msg); + } +} + +/// A hit tester requested to the render backend thread but not necessarily ready yet. +/// +/// The request should be resolved as late as possible to reduce the likelihood of blocking. +pub struct HitTesterRequest { + rx: Receiver>, +} + +impl HitTesterRequest { + /// Block until the hit tester is available and return it, consuming teh request. + pub fn resolve(self) -> Arc { + self.rx.recv().unwrap() + } +} + +/// +#[derive(Clone)] +pub struct ScrollNodeState { + /// + pub id: di::ExternalScrollId, + /// + pub scroll_offset: LayoutVector2D, +} + +/// +#[derive(Clone, Copy, Debug)] +pub enum ScrollLocation { + /// Scroll by a certain amount. + Delta(LayoutVector2D), + /// Scroll to very top of element. + Start, + /// Scroll to very bottom of element. + End, +} + +/// Represents a zoom factor. +#[derive(Clone, Copy, Debug)] +pub struct ZoomFactor(f32); + +impl ZoomFactor { + /// Construct a new zoom factor. + pub fn new(scale: f32) -> Self { + ZoomFactor(scale) + } + + /// Get the zoom factor as an untyped float. + pub fn get(self) -> f32 { + self.0 + } +} + +/// A key to identify an animated property binding. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)] +pub struct PropertyBindingId { + namespace: IdNamespace, + uid: u32, +} + +impl PropertyBindingId { + /// Constructor. + pub fn new(value: u64) -> Self { + PropertyBindingId { + namespace: IdNamespace((value >> 32) as u32), + uid: value as u32, + } + } +} + +/// A unique key that is used for connecting animated property +/// values to bindings in the display list. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct PropertyBindingKey { + /// + pub id: PropertyBindingId, + _phantom: PhantomData, +} + +/// Construct a property value from a given key and value. +impl PropertyBindingKey { + /// + pub fn with(self, value: T) -> PropertyValue { + PropertyValue { key: self, value } + } +} + +impl PropertyBindingKey { + /// Constructor. + pub fn new(value: u64) -> Self { + PropertyBindingKey { + id: PropertyBindingId::new(value), + _phantom: PhantomData, + } + } +} + +/// A binding property can either be a specific value +/// (the normal, non-animated case) or point to a binding location +/// to fetch the current value from. +/// Note that Binding has also a non-animated value, the value is +/// used for the case where the animation is still in-delay phase +/// (i.e. the animation doesn't produce any animation values). +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum PropertyBinding { + /// Non-animated value. + Value(T), + /// Animated binding. + Binding(PropertyBindingKey, T), +} + +impl Default for PropertyBinding { + fn default() -> Self { + PropertyBinding::Value(Default::default()) + } +} + +impl From for PropertyBinding { + fn from(value: T) -> PropertyBinding { + PropertyBinding::Value(value) + } +} + +impl From> for PropertyBindingKey { + fn from(key: PropertyBindingKey) -> PropertyBindingKey { + PropertyBindingKey { + id: key.id.clone(), + _phantom: PhantomData, + } + } +} + +impl From> for PropertyBindingKey { + fn from(key: PropertyBindingKey) -> PropertyBindingKey { + PropertyBindingKey { + id: key.id.clone(), + _phantom: PhantomData, + } + } +} + +impl From> for PropertyBinding { + fn from(value: PropertyBinding) -> PropertyBinding { + match value { + PropertyBinding::Value(value) => PropertyBinding::Value(value.into()), + PropertyBinding::Binding(k, v) => { + PropertyBinding::Binding(k.into(), v.into()) + } + } + } +} + +impl From> for PropertyBinding { + fn from(value: PropertyBinding) -> PropertyBinding { + match value { + PropertyBinding::Value(value) => PropertyBinding::Value(value.into()), + PropertyBinding::Binding(k, v) => { + PropertyBinding::Binding(k.into(), v.into()) + } + } + } +} + +/// The current value of an animated property. This is +/// supplied by the calling code. +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] +pub struct PropertyValue { + /// + pub key: PropertyBindingKey, + /// + pub value: T, +} + +/// When using `generate_frame()`, a list of `PropertyValue` structures +/// can optionally be supplied to provide the current value of any +/// animated properties. +#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Default)] +pub struct DynamicProperties { + /// + pub transforms: Vec>, + /// opacity + pub floats: Vec>, + /// background color + pub colors: Vec>, +} + +/// A handler to integrate WebRender with the thread that contains the `Renderer`. +pub trait RenderNotifier: Send { + /// + fn clone(&self) -> Box; + /// Wake the thread containing the `Renderer` up (after updates have been put + /// in the renderer's queue). + fn wake_up(&self); + /// Notify the thread containing the `Renderer` that a new frame is ready. + fn new_frame_ready(&self, _: DocumentId, scrolled: bool, composite_needed: bool, render_time_ns: Option); + /// A Gecko-specific notification mechanism to get some code executed on the + /// `Renderer`'s thread, mostly replaced by `NotificationHandler`. You should + /// probably use the latter instead. + fn external_event(&self, _evt: ExternalEvent) { + unimplemented!() + } + /// Notify the thread containing the `Renderer` that the render backend has been + /// shut down. + fn shut_down(&self) {} +} + +/// A stage of the rendering pipeline. +#[repr(u32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Checkpoint { + /// + SceneBuilt, + /// + FrameBuilt, + /// + FrameTexturesUpdated, + /// + FrameRendered, + /// 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, +} + +/// A handler to notify when a transaction reaches certain stages of the rendering +/// pipeline. +pub trait NotificationHandler : Send + Sync { + /// Entry point of the handler to implement. Invoked by WebRender. + fn notify(&self, when: Checkpoint); +} + +/// A request to notify a handler when the transaction reaches certain stages of the +/// rendering pipeline. +/// +/// The request is guaranteed to be notified once and only once, even if the transaction +/// is dropped before the requested check-point. +pub struct NotificationRequest { + handler: Option>, + when: Checkpoint, +} + +impl NotificationRequest { + /// Constructor. + pub fn new(when: Checkpoint, handler: Box) -> Self { + NotificationRequest { + handler: Some(handler), + when, + } + } + + /// The specified stage at which point the handler should be notified. + pub fn when(&self) -> Checkpoint { self.when } + + /// Called by WebRender at specified stages to notify the registered handler. + pub fn notify(mut self) { + if let Some(handler) = self.handler.take() { + handler.notify(self.when); + } + } +} + +/// An object that can perform hit-testing without doing synchronous queries to +/// the RenderBackendThread. +pub trait ApiHitTester: Send + Sync { + /// Does a hit test on display items in the specified document, at the given + /// point. If a pipeline_id is specified, it is used to further restrict the + /// hit results so that only items inside that pipeline are matched. If the + /// HitTestFlags argument contains the FIND_ALL flag, then the vector of hit + /// results will contain all display items that match, ordered from front + /// to back. + fn hit_test(&self, pipeline_id: Option, point: WorldPoint, flags: HitTestFlags) -> HitTestResult; +} + +impl Drop for NotificationRequest { + fn drop(&mut self) { + if let Some(ref mut handler) = self.handler { + handler.notify(Checkpoint::TransactionDropped); + } + } +} + +// This Clone impl yields an "empty" request because we don't want the requests +// to be notified twice so the request is owned by only one of the API messages +// (the original one) after the clone. +// This works in practice because the notifications requests are used for +// synchronization so we don't need to include them in the recording mechanism +// in wrench that clones the messages. +impl Clone for NotificationRequest { + fn clone(&self) -> Self { + NotificationRequest { + when: self.when, + handler: None, + } + } +} + + +bitflags! { + /// Each bit of the edge AA mask is: + /// 0, when the edge of the primitive needs to be considered for AA + /// 1, when the edge of the segment needs to be considered for AA + /// + /// *Note*: the bit values have to match the shader logic in + /// `write_transform_vertex()` function. + #[cfg_attr(feature = "serialize", derive(Serialize))] + #[cfg_attr(feature = "deserialize", derive(Deserialize))] + #[derive(MallocSizeOf)] + pub struct EdgeAaSegmentMask: u8 { + /// + const LEFT = 0x1; + /// + const TOP = 0x2; + /// + const RIGHT = 0x4; + /// + const BOTTOM = 0x8; + } +} diff --git a/third_party/webrender/webrender_api/src/channel.rs b/third_party/webrender/webrender_api/src/channel.rs index 7d21c6e4339..2bc4a5f16b8 100644 --- a/third_party/webrender/webrender_api/src/channel.rs +++ b/third_party/webrender/webrender_api/src/channel.rs @@ -2,19 +2,12 @@ * 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/. */ -use crate::{Epoch, PipelineId}; +use crate::api::{Epoch, PipelineId}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::io::{self, Cursor, Error, ErrorKind, Read}; use std::mem; - -pub use crossbeam_channel as crossbeam; - -#[cfg(not(target_os = "windows"))] -pub use crossbeam_channel::{Sender, Receiver}; - -#[cfg(target_os = "windows")] -pub use std::sync::mpsc::{Sender, Receiver}; +use std::sync::mpsc; #[derive(Clone)] pub struct Payload { @@ -81,7 +74,7 @@ pub type PayloadSender = MsgSender; pub type PayloadReceiver = MsgReceiver; pub struct MsgReceiver { - rx: Receiver, + rx: mpsc::Receiver, } impl MsgReceiver { @@ -89,14 +82,14 @@ impl MsgReceiver { self.rx.recv().map_err(|e| io::Error::new(ErrorKind::Other, e.to_string())) } - pub fn to_crossbeam_receiver(self) -> Receiver { + pub fn to_mpsc_receiver(self) -> mpsc::Receiver { self.rx } } #[derive(Clone)] pub struct MsgSender { - tx: Sender, + tx: mpsc::Sender, } impl MsgSender { @@ -106,12 +99,12 @@ impl MsgSender { } pub fn payload_channel() -> Result<(PayloadSender, PayloadReceiver), Error> { - let (tx, rx) = unbounded_channel(); + let (tx, rx) = mpsc::channel(); Ok((PayloadSender { tx }, PayloadReceiver { rx })) } pub fn msg_channel() -> Result<(MsgSender, MsgReceiver), Error> { - let (tx, rx) = unbounded_channel(); + let (tx, rx) = mpsc::channel(); Ok((MsgSender { tx }, MsgReceiver { rx })) } @@ -136,45 +129,3 @@ impl<'de, T> Deserialize<'de> for MsgSender { unreachable!(); } } - -/// A create a channel intended for one-shot uses, for example the channels -/// created to block on a synchronous query and then discarded, -#[cfg(not(target_os = "windows"))] -pub fn single_msg_channel() -> (Sender, Receiver) { - crossbeam_channel::bounded(1) -} - -/// A fast MPMC message channel that can hold a fixed number of messages. -/// -/// If the channel is full, the sender will block upon sending extra messages -/// until the receiver has consumed some messages. -/// The capacity parameter should be chosen either: -/// - high enough to avoid blocking on the common cases, -/// - or, on the contrary, using the blocking behavior as a means to prevent -/// fast producers from building up work faster than it is consumed. -#[cfg(not(target_os = "windows"))] -pub fn fast_channel(capacity: usize) -> (Sender, Receiver) { - crossbeam_channel::bounded(capacity) -} - -/// Creates an MPMC channel that is a bit slower than the fast_channel but doesn't -/// have a limit on the number of messages held at a given time and therefore -/// doesn't block when sending. -#[cfg(not(target_os = "windows"))] -pub use crossbeam_channel::unbounded as unbounded_channel; - - -#[cfg(target_os = "windows")] -pub fn fast_channel(_cap: usize) -> (Sender, Receiver) { - std::sync::mpsc::channel() -} - -#[cfg(target_os = "windows")] -pub fn unbounded_channel() -> (Sender, Receiver) { - std::sync::mpsc::channel() -} - -#[cfg(target_os = "windows")] -pub fn single_msg_channel() -> (Sender, Receiver) { - std::sync::mpsc::channel() -} diff --git a/third_party/webrender/webrender_api/src/display_item.rs b/third_party/webrender/webrender_api/src/display_item.rs index 5c425b9ed17..e6b8e036e46 100644 --- a/third_party/webrender/webrender_api/src/display_item.rs +++ b/third_party/webrender/webrender_api/src/display_item.rs @@ -2,16 +2,15 @@ * 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/. */ -use euclid::{SideOffsets2D, Angle}; +use euclid::SideOffsets2D; use peek_poke::PeekPoke; use std::ops::Not; // local imports use crate::font; -use crate::{PipelineId, PropertyBinding}; +use crate::api::{PipelineId, PropertyBinding}; use crate::color::ColorF; use crate::image::{ColorDepth, ImageKey}; use crate::units::*; -use std::hash::{Hash, Hasher}; // ****************************************************************** // * NOTE: some of these structs have an "IMPLICIT" comment. * @@ -48,10 +47,6 @@ bitflags! { /// compositor surface under certain (implementation specific) conditions. This /// is typically used for large videos, and canvas elements. const PREFER_COMPOSITOR_SURFACE = 1 << 3; - /// If set, this primitive can be passed directly to the compositor via its - /// ExternalImageId, and the compositor will use the native image directly. - /// Used as a further extension on top of PREFER_COMPOSITOR_SURFACE. - const SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE = 1 << 4; } } @@ -73,6 +68,10 @@ pub struct CommonItemProperties { pub clip_id: ClipId, /// The coordinate-space the item is in (yes, it can be really granular) pub spatial_id: SpatialId, + /// Opaque bits for our clients to use for hit-testing. This is the most + /// dubious "common" field, but because it's an Option, it usually only + /// wastes a single byte (for None). + pub hit_info: Option, /// Various flags describing properties of this primitive. pub flags: PrimitiveFlags, } @@ -87,6 +86,7 @@ impl CommonItemProperties { clip_rect, spatial_id: space_and_clip.spatial_id, clip_id: space_and_clip.clip_id, + hit_info: None, flags: PrimitiveFlags::default(), } } @@ -155,7 +155,6 @@ pub enum DisplayItem { SetFilterOps, SetFilterData, SetFilterPrimitives, - SetPoints, // These marker items terminate a scope introduced by a previous item. PopReferenceFrame, @@ -204,7 +203,6 @@ pub enum DebugDisplayItem { SetFilterOps(Vec), SetFilterData(FilterData), SetFilterPrimitives(Vec), - SetPoints(Vec), PopReferenceFrame, PopStackingContext, @@ -216,8 +214,7 @@ pub struct ImageMaskClipDisplayItem { pub id: ClipId, pub parent_space_and_clip: SpaceAndClipInfo, pub image_mask: ImageMask, - pub fill_rule: FillRule, -} // IMPLICIT points: Vec +} #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] pub struct RectClipDisplayItem { @@ -309,7 +306,7 @@ pub struct ScrollFrameDisplayItem { pub content_rect: LayoutRect, pub clip_rect: LayoutRect, pub parent_space_and_clip: SpaceAndClipInfo, - pub external_id: ExternalScrollId, + pub external_id: Option, pub scroll_sensitivity: ScrollSensitivity, /// The amount this scrollframe has already been scrolled by, in the caller. /// This means that all the display items that are inside the scrollframe @@ -341,7 +338,6 @@ pub struct ClearRectangleDisplayItem { #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] pub struct HitTestDisplayItem { pub common: CommonItemProperties, - pub tag: ItemTag, } #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] @@ -627,15 +623,6 @@ pub struct Gradient { pub extend_mode: ExtendMode, } // IMPLICIT: stops: Vec -impl Gradient { - pub fn is_valid(&self) -> bool { - self.start_point.x.is_finite() && - self.start_point.y.is_finite() && - self.end_point.x.is_finite() && - self.end_point.y.is_finite() - } -} - /// The area #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] pub struct GradientDisplayItem { @@ -668,15 +655,6 @@ pub struct RadialGradient { pub extend_mode: ExtendMode, } // IMPLICIT stops: Vec -impl RadialGradient { - pub fn is_valid(&self) -> bool { - self.center.x.is_finite() && - self.center.y.is_finite() && - self.start_offset.is_finite() && - self.end_offset.is_finite() - } -} - #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] pub struct ConicGradient { pub center: LayoutPoint, @@ -686,16 +664,6 @@ pub struct ConicGradient { pub extend_mode: ExtendMode, } // IMPLICIT stops: Vec -impl ConicGradient { - pub fn is_valid(&self) -> bool { - self.center.x.is_finite() && - self.center.y.is_finite() && - self.angle.is_finite() && - self.start_offset.is_finite() && - self.end_offset.is_finite() - } -} - /// Just an abstraction for bundling up a bunch of clips into a "super clip". #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] pub struct ClipChainItem { @@ -743,87 +711,23 @@ pub struct ReferenceFrameDisplayListItem { #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] pub enum ReferenceFrameKind { + /// Zoom reference frames must be a scale + translation only + Zoom, /// A normal transform matrix, may contain perspective (the CSS transform property) - Transform { - /// Optionally marks the transform as only ever having a simple 2D scale or translation, - /// allowing for optimizations. - is_2d_scale_translation: bool, - /// Marks that the transform should be snapped. Used for transforms which animate in - /// response to scrolling, eg for zooming or dynamic toolbar fixed-positioning. - should_snap: bool, - }, + Transform, /// A perspective transform, that optionally scrolls relative to a specific scroll node Perspective { scrolling_relative_to: Option, } } -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] -pub enum Rotation { - Degree0, - Degree90, - Degree180, - Degree270, -} - -impl Rotation { - pub fn to_matrix( - &self, - size: LayoutSize, - ) -> LayoutTransform { - let (shift_center_to_origin, angle) = match self { - Rotation::Degree0 => { - (LayoutTransform::translation(-size.width / 2., -size.height / 2., 0.), Angle::degrees(0.)) - }, - Rotation::Degree90 => { - (LayoutTransform::translation(-size.height / 2., -size.width / 2., 0.), Angle::degrees(90.)) - }, - Rotation::Degree180 => { - (LayoutTransform::translation(-size.width / 2., -size.height / 2., 0.), Angle::degrees(180.)) - }, - Rotation::Degree270 => { - (LayoutTransform::translation(-size.height / 2., -size.width / 2., 0.), Angle::degrees(270.)) - }, - }; - let shift_origin_to_center = LayoutTransform::translation(size.width / 2., size.height / 2., 0.); - - shift_center_to_origin - .then(&LayoutTransform::rotation(0., 0., 1.0, angle)) - .then(&shift_origin_to_center) - } -} - -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] -pub enum ReferenceTransformBinding { - /// Standard reference frame which contains a precomputed transform. - Static { - binding: PropertyBinding, - }, - /// Computed reference frame which dynamically calculates the transform - /// based on the given parameters. The reference is the content size of - /// the parent iframe, which is affected by snapping. - Computed { - scale_from: Option, - vertical_flip: bool, - rotation: Rotation, - }, -} - -impl Default for ReferenceTransformBinding { - fn default() -> Self { - ReferenceTransformBinding::Static { - binding: Default::default(), - } - } -} - #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] pub struct ReferenceFrame { pub kind: ReferenceFrameKind, pub transform_style: TransformStyle, /// The transform matrix, either the perspective matrix or the transform /// matrix. - pub transform: ReferenceTransformBinding, + pub transform: PropertyBinding, pub id: SpatialId, } @@ -858,7 +762,7 @@ pub enum TransformStyle { /// when we want to cache the output, and performance is /// important. Note that this is a performance hint only, /// which WR may choose to ignore. -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, MallocSizeOf, Serialize, PeekPoke)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] #[repr(u8)] pub enum RasterSpace { // Rasterize in local-space, applying supplied scale to primitives. @@ -881,23 +785,6 @@ impl RasterSpace { } } -impl Eq for RasterSpace {} - -impl Hash for RasterSpace { - fn hash(&self, state: &mut H) { - match self { - RasterSpace::Screen => { - 0.hash(state); - } - RasterSpace::Local(scale) => { - // Note: this is inconsistent with the Eq impl for -0.0 (don't care). - 1.hash(state); - scale.to_bits().hash(state); - } - } - } -} - bitflags! { #[repr(C)] #[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)] @@ -1024,8 +911,7 @@ impl FloodPrimitive { #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] pub struct BlurPrimitive { pub input: FilterPrimitiveInput, - pub width: f32, - pub height: f32, + pub radius: f32, } #[repr(C)] @@ -1152,7 +1038,7 @@ pub enum FilterOp { /// Filter that does no transformation of the colors, needed for /// debug purposes only. Identity, - Blur(f32, f32), + Blur(f32), Brightness(f32), Contrast(f32), Grayscale(f32), @@ -1344,7 +1230,6 @@ pub enum YuvColorSpace { Rec601 = 0, Rec709 = 1, Rec2020 = 2, - Identity = 3, // aka RGB as per ISO/IEC 23091-2:2019 } #[repr(u8)] @@ -1519,34 +1404,6 @@ impl ComplexClipRegion { } } -pub const POLYGON_CLIP_VERTEX_MAX: usize = 16; - -#[repr(u8)] -#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)] -pub enum FillRule { - Nonzero = 0x1, // Behaves as the SVG fill-rule definition for nonzero. - Evenodd = 0x2, // Behaves as the SVG fill-rule definition for evenodd. -} - -impl From for FillRule { - fn from(fill_rule: u8) -> Self { - match fill_rule { - 0x1 => FillRule::Nonzero, - 0x2 => FillRule::Evenodd, - _ => panic!("Unexpected FillRule value."), - } - } -} - -impl From for u8 { - fn from(fill_rule: FillRule) -> Self { - match fill_rule { - FillRule::Nonzero => 0x1, - FillRule::Evenodd => 0x2, - } - } -} - #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)] pub struct ClipChainId(pub u64, pub PipelineId); @@ -1674,7 +1531,6 @@ impl DisplayItem { DisplayItem::SetFilterOps => "set_filter_ops", DisplayItem::SetFilterData => "set_filter_data", DisplayItem::SetFilterPrimitives => "set_filter_primitives", - DisplayItem::SetPoints => "set_points", DisplayItem::RadialGradient(..) => "radial_gradient", DisplayItem::Rectangle(..) => "rectangle", DisplayItem::ScrollFrame(..) => "scroll_frame", @@ -1716,13 +1572,8 @@ impl_default_for_enums! { FilterOp => Identity, ComponentTransferFuncType => Identity, ClipMode => Clip, - FillRule => Nonzero, ClipId => ClipId::invalid(), - ReferenceFrameKind => Transform { - is_2d_scale_translation: false, - should_snap: false, - }, - Rotation => Degree0, + ReferenceFrameKind => Transform, TransformStyle => Flat, RasterSpace => Local(f32::default()), MixBlendMode => Normal, diff --git a/third_party/webrender/webrender_api/src/display_list.rs b/third_party/webrender/webrender_api/src/display_list.rs index e2498c5c194..0680a9875d3 100644 --- a/third_party/webrender/webrender_api/src/display_list.rs +++ b/third_party/webrender/webrender_api/src/display_list.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use euclid::SideOffsets2D; -use peek_poke::{ensure_red_zone, peek_from_slice, poke_extend_vec, strip_red_zone}; +use peek_poke::{ensure_red_zone, peek_from_slice, poke_extend_vec}; use peek_poke::{poke_inplace_slice, poke_into_vec, Poke}; #[cfg(feature = "deserialize")] use serde::de::Deserializer; @@ -20,7 +20,7 @@ use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; // local imports use crate::display_item as di; use crate::display_item_cache::*; -use crate::{PipelineId, PropertyBinding}; +use crate::api::{PipelineId, PropertyBinding}; use crate::gradient_builder::GradientBuilder; use crate::color::ColorF; use crate::font::{FontInstanceKey, GlyphInstance, GlyphOptions}; @@ -115,18 +115,6 @@ pub struct BuiltDisplayList { descriptor: BuiltDisplayListDescriptor, } -#[repr(C)] -#[derive(Copy, Clone, Deserialize, Serialize)] -pub enum GeckoDisplayListType { - None, - Partial(f64), - Full(f64), -} - -impl Default for GeckoDisplayListType { - fn default() -> Self { GeckoDisplayListType::None } -} - /// Describes the memory layout of a display list. /// /// A display list consists of some number of display list items, followed by a number of display @@ -134,8 +122,6 @@ impl Default for GeckoDisplayListType { #[repr(C)] #[derive(Copy, Clone, Default, Deserialize, Serialize)] pub struct BuiltDisplayListDescriptor { - /// Gecko specific information about the display list. - gecko_display_list_type: GeckoDisplayListType, /// The first IPC time stamp: before any work has been done builder_start_time: u64, /// The second IPC time stamp: after serialization @@ -182,10 +168,6 @@ impl DisplayListWithCache { self.display_list.descriptor() } - pub fn times(&self) -> (u64, u64, u64) { - self.display_list.times() - } - pub fn data(&self) -> &[u8] { self.display_list.data() } @@ -239,7 +221,6 @@ pub struct BuiltDisplayListIter<'a> { cur_filter_primitives: ItemRange<'a, di::FilterPrimitive>, cur_clip_chain_items: ItemRange<'a, di::ClipId>, cur_complex_clip: ItemRange<'a, di::ComplexClipRegion>, - cur_points: ItemRange<'a, LayoutPoint>, peeking: Peek, /// Should just be initialized but never populated in release builds debug_stats: DebugStats, @@ -282,7 +263,7 @@ impl DebugStats { /// Logs the stats for the given serialized slice #[cfg(feature = "display_list_stats")] - fn log_slice( + fn log_slice( &mut self, slice_name: &'static str, range: &ItemRange, @@ -337,10 +318,6 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> { self.iter.cur_complex_clip } - pub fn points(&self) -> ItemRange { - self.iter.cur_points - } - pub fn glyphs(&self) -> ItemRange { self.iter.glyphs() } @@ -414,14 +391,6 @@ impl BuiltDisplayList { ) } - pub fn gecko_display_list_stats(&self) -> (f64, bool) { - match self.descriptor.gecko_display_list_type { - GeckoDisplayListType::Full(duration) => (duration, true), - GeckoDisplayListType::Partial(duration) => (duration, false), - _ => (0.0, false) - } - } - pub fn total_clip_nodes(&self) -> usize { self.descriptor.total_clip_nodes } @@ -503,9 +472,6 @@ impl BuiltDisplayList { Real::SetGradientStops => Debug::SetGradientStops( item.iter.cur_stops.iter().collect() ), - Real::SetPoints => Debug::SetPoints( - item.iter.cur_points.iter().collect() - ), Real::RectClip(v) => Debug::RectClip(v), Real::RoundedRectClip(v) => Debug::RoundedRectClip(v), Real::ImageMaskClip(v) => Debug::ImageMaskClip(v), @@ -575,7 +541,6 @@ impl<'a> BuiltDisplayListIter<'a> { cur_filter_primitives: ItemRange::default(), cur_clip_chain_items: ItemRange::default(), cur_complex_clip: ItemRange::default(), - cur_points: ItemRange::default(), peeking: Peek::NotPeeking, debug_stats: DebugStats { last_addr: data.as_ptr() as usize, @@ -644,7 +609,6 @@ impl<'a> BuiltDisplayListIter<'a> { self.cur_stops = ItemRange::default(); self.cur_complex_clip = ItemRange::default(); self.cur_clip_chain_items = ItemRange::default(); - self.cur_points = ItemRange::default(); self.cur_filters = ItemRange::default(); self.cur_filter_primitives = ItemRange::default(); self.cur_filter_data.clear(); @@ -655,8 +619,7 @@ impl<'a> BuiltDisplayListIter<'a> { SetGradientStops | SetFilterOps | SetFilterData | - SetFilterPrimitives | - SetPoints => { + SetFilterPrimitives => { // These are marker items for populating other display items, don't yield them. continue; } @@ -718,10 +681,6 @@ impl<'a> BuiltDisplayListIter<'a> { self.cur_filter_primitives = skip_slice::(&mut self.data); self.debug_stats.log_slice("set_filter_primitives.primitives", &self.cur_filter_primitives); } - SetPoints => { - self.cur_points = skip_slice::(&mut self.data); - self.debug_stats.log_slice("set_points.points", &self.cur_points); - } ClipChain(_) => { self.cur_clip_chain_items = skip_slice::(&mut self.data); self.debug_stats.log_slice("clip_chain.clip_ids", &self.cur_clip_chain_items); @@ -935,10 +894,6 @@ impl<'de> Deserialize<'de> for BuiltDisplayList { DisplayListBuilder::push_iter_impl(&mut temp, stops); Real::SetGradientStops }, - Debug::SetPoints(points) => { - DisplayListBuilder::push_iter_impl(&mut temp, points); - Real::SetPoints - }, Debug::RectClip(v) => Real::RectClip(v), Debug::RoundedRectClip(v) => Real::RoundedRectClip(v), Debug::ImageMaskClip(v) => Real::ImageMaskClip(v), @@ -976,7 +931,6 @@ impl<'de> Deserialize<'de> for BuiltDisplayList { Ok(BuiltDisplayList { data, descriptor: BuiltDisplayListDescriptor { - gecko_display_list_type: GeckoDisplayListType::None, builder_start_time: 0, builder_finish_time: 1, send_start_time: 1, @@ -1022,6 +976,9 @@ pub struct DisplayListBuilder { next_clip_chain_id: u64, builder_start_time: u64, + /// The size of the content of this display list. This is used to allow scrolling + /// outside the bounds of the display list items themselves. + content_size: LayoutSize, save_state: Option, cache_size: usize, @@ -1029,12 +986,13 @@ pub struct DisplayListBuilder { } impl DisplayListBuilder { - pub fn new(pipeline_id: PipelineId) -> Self { - Self::with_capacity(pipeline_id, 0) + pub fn new(pipeline_id: PipelineId, content_size: LayoutSize) -> Self { + Self::with_capacity(pipeline_id, content_size, 0) } pub fn with_capacity( pipeline_id: PipelineId, + content_size: LayoutSize, capacity: usize, ) -> Self { let start_time = precise_time_ns(); @@ -1051,12 +1009,18 @@ impl DisplayListBuilder { next_spatial_index: FIRST_SPATIAL_NODE_INDEX, next_clip_chain_id: 0, builder_start_time: start_time, + content_size, save_state: None, cache_size: 0, serialized_content_buffer: None, } } + /// Return the content size for this display list + pub fn content_size(&self) -> LayoutSize { + self.content_size + } + /// Saves the current display list state, so it may be `restore()`'d. /// /// # Conditions: @@ -1111,15 +1075,11 @@ impl DisplayListBuilder { W: Write { let mut temp = BuiltDisplayList::default(); - ensure_red_zone::(&mut self.data); - temp.descriptor.extra_data_offset = self.data.len(); mem::swap(&mut temp.data, &mut self.data); let mut index: usize = 0; { - let mut cache = DisplayItemCache::new(); - cache.update(&temp); - let mut iter = temp.iter_with_cache(&cache); + let mut iter = temp.iter(); while let Some(item) = iter.next_raw() { if index >= range.start.unwrap_or(0) && range.end.map_or(true, |e| index < e) { writeln!(sink, "{}{:?}", " ".repeat(indent), item.item()).unwrap(); @@ -1129,7 +1089,6 @@ impl DisplayListBuilder { } self.data = temp.data; - strip_red_zone::(&mut self.data); index } @@ -1276,11 +1235,9 @@ impl DisplayListBuilder { pub fn push_hit_test( &mut self, common: &di::CommonItemProperties, - tag: di::ItemTag, ) { let item = di::DisplayItem::HitTest(di::HitTestDisplayItem { common: *common, - tag, }); self.push_item(&item); } @@ -1577,9 +1534,7 @@ impl DisplayListBuilder { origin, reference_frame: di::ReferenceFrame { transform_style, - transform: di::ReferenceTransformBinding::Static { - binding: transform, - }, + transform, kind, id, }, @@ -1589,38 +1544,6 @@ impl DisplayListBuilder { id } - pub fn push_computed_frame( - &mut self, - origin: LayoutPoint, - parent_spatial_id: di::SpatialId, - scale_from: Option, - vertical_flip: bool, - rotation: di::Rotation, - ) -> di::SpatialId { - let id = self.generate_spatial_index(); - - let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem { - parent_spatial_id, - origin, - reference_frame: di::ReferenceFrame { - transform_style: di::TransformStyle::Flat, - transform: di::ReferenceTransformBinding::Computed { - scale_from, - vertical_flip, - rotation, - }, - kind: di::ReferenceFrameKind::Transform { - is_2d_scale_translation: false, - should_snap: false, - }, - id, - }, - }); - - self.push_item(&item); - id - } - pub fn pop_reference_frame(&mut self) { self.push_item(&di::DisplayItem::PopReferenceFrame); } @@ -1773,7 +1696,7 @@ impl DisplayListBuilder { pub fn define_scroll_frame( &mut self, parent_space_and_clip: &di::SpaceAndClipInfo, - external_id: di::ExternalScrollId, + external_id: Option, content_rect: LayoutRect, clip_rect: LayoutRect, scroll_sensitivity: di::ScrollSensitivity, @@ -1819,25 +1742,14 @@ impl DisplayListBuilder { &mut self, parent_space_and_clip: &di::SpaceAndClipInfo, image_mask: di::ImageMask, - points: &[LayoutPoint], - fill_rule: di::FillRule, ) -> di::ClipId { let id = self.generate_clip_index(); let item = di::DisplayItem::ImageMaskClip(di::ImageMaskClipDisplayItem { id, parent_space_and_clip: *parent_space_and_clip, image_mask, - fill_rule, }); - // We only need to supply points if there are at least 3, which is the - // minimum to specify a polygon. BuiltDisplayListIter.next ensures that points - // are cleared between processing other display items, so we'll correctly get - // zero points when no SetPoints item has been pushed. - if points.len() >= 3 { - self.push_item(&di::DisplayItem::SetPoints); - self.push_iter(points); - } self.push_item(&item); id } @@ -2016,7 +1928,7 @@ impl DisplayListBuilder { self.cache_size = cache_size; } - pub fn finalize(mut self) -> (PipelineId, BuiltDisplayList) { + pub fn finalize(mut self) -> (PipelineId, LayoutSize, BuiltDisplayList) { assert!(self.save_state.is_none(), "Finalized DisplayListBuilder with a pending save"); if let Some(content) = self.serialized_content_buffer.take() { @@ -2039,9 +1951,9 @@ impl DisplayListBuilder { let end_time = precise_time_ns(); ( self.pipeline_id, + self.content_size, BuiltDisplayList { descriptor: BuiltDisplayListDescriptor { - gecko_display_list_type: GeckoDisplayListType::None, builder_start_time: self.builder_start_time, builder_finish_time: end_time, send_start_time: end_time, diff --git a/third_party/webrender/webrender_api/src/font.rs b/third_party/webrender/webrender_api/src/font.rs index 3052db8f9e1..7f24736f09e 100644 --- a/third_party/webrender/webrender_api/src/font.rs +++ b/third_party/webrender/webrender_api/src/font.rs @@ -15,11 +15,10 @@ use std::cmp::Ordering; use std::hash::{Hash, Hasher}; #[cfg(not(target_os = "macos"))] use std::path::PathBuf; -use std::sync::{Arc, RwLock, RwLockReadGuard}; +use std::sync::{Arc, RwLock, RwLockReadGuard, mpsc::Sender}; use std::collections::HashMap; // local imports -use crate::IdNamespace; -use crate::channel::Sender; +use crate::api::IdNamespace; use crate::color::ColorU; use crate::units::LayoutPoint; diff --git a/third_party/webrender/webrender_api/src/gradient_builder.rs b/third_party/webrender/webrender_api/src/gradient_builder.rs index 6347396f791..883acbafa38 100644 --- a/third_party/webrender/webrender_api/src/gradient_builder.rs +++ b/third_party/webrender/webrender_api/src/gradient_builder.rs @@ -132,9 +132,7 @@ impl GradientBuilder { let first = *stops.first().unwrap(); let last = *stops.last().unwrap(); - // Express the assertion so that if one of the offsets is NaN, we don't panic - // and instead take the branch that handles degenerate gradients. - assert!(!(first.offset > last.offset)); + assert!(first.offset <= last.offset); let stops_delta = last.offset - first.offset; diff --git a/third_party/webrender/webrender_api/src/image.rs b/third_party/webrender/webrender_api/src/image.rs index 23c660b647e..4a664bddcfd 100644 --- a/third_party/webrender/webrender_api/src/image.rs +++ b/third_party/webrender/webrender_api/src/image.rs @@ -9,7 +9,7 @@ use peek_poke::PeekPoke; use std::ops::{Add, Sub}; use std::sync::Arc; // local imports -use crate::{IdNamespace, TileSize}; +use crate::api::{IdNamespace, PipelineId, TileSize}; use crate::display_item::ImageRendering; use crate::font::{FontInstanceKey, FontInstanceData, FontKey, FontTemplate}; use crate::units::*; @@ -100,24 +100,38 @@ pub trait ExternalImageHandler { fn unlock(&mut self, key: ExternalImageId, channel_index: u8); } +/// Allows callers to receive a texture with the contents of a specific +/// pipeline copied to it. +pub trait OutputImageHandler { + /// Return the native texture handle and the size of the texture. + fn lock(&mut self, pipeline_id: PipelineId) -> Option<(u32, FramebufferIntSize)>; + /// Unlock will only be called if the lock() call succeeds, when WR has issued + /// the GL commands to copy the output to the texture handle. + fn unlock(&mut self, pipeline_id: PipelineId); +} + /// Specifies the type of texture target in driver terms. #[repr(u8)] -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub enum ImageBufferKind { +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum TextureTarget { /// Standard texture. This maps to GL_TEXTURE_2D in OpenGL. - Texture2D = 0, + Default = 0, + /// Array texture. This maps to GL_TEXTURE_2D_ARRAY in OpenGL. See + /// https://www.khronos.org/opengl/wiki/Array_Texture for background + /// on Array textures. + Array = 1, /// Rectangle texture. This maps to GL_TEXTURE_RECTANGLE in OpenGL. This /// is similar to a standard texture, with a few subtle differences /// (no mipmaps, non-power-of-two dimensions, different coordinate space) /// that make it useful for representing the kinds of textures we use /// in WebRender. See https://www.khronos.org/opengl/wiki/Rectangle_Texture /// for background on Rectangle textures. - TextureRect = 1, + Rect = 2, /// External texture. This maps to GL_TEXTURE_EXTERNAL_OES in OpenGL, which /// is an extension. This is used for image formats that OpenGL doesn't /// understand, particularly YUV. See /// https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external.txt - TextureExternal = 2, + External = 3, } /// Storage format identifier for externally-managed images. @@ -125,7 +139,7 @@ pub enum ImageBufferKind { #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum ExternalImageType { /// The image is texture-backed. - TextureHandle(ImageBufferKind), + TextureHandle(TextureTarget), /// The image is heap-allocated by the embedding. Buffer, } @@ -326,9 +340,10 @@ pub enum ImageData { } mod serde_image_data_raw { - use serde::{Deserializer, Serializer}; use serde_bytes; + use std::sync::Arc; + use serde::{Deserializer, Serializer}; pub fn serialize(bytes: &Arc>, serializer: S) -> Result { serde_bytes::serialize(bytes.as_slice(), serializer) diff --git a/third_party/webrender/webrender_api/src/image_tiling.rs b/third_party/webrender/webrender_api/src/image_tiling.rs new file mode 100644 index 00000000000..8fdc82ef24d --- /dev/null +++ b/third_party/webrender/webrender_api/src/image_tiling.rs @@ -0,0 +1,815 @@ +/* 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/. */ + +use crate::{TileSize, EdgeAaSegmentMask}; +use crate::units::*; +use euclid::{point2, size2}; +use std::i32; +use std::ops::Range; + +/// If repetitions are far enough apart that only one is within +/// the primitive rect, then we can simplify the parameters and +/// treat the primitive as not repeated. +/// This can let us avoid unnecessary work later to handle some +/// of the parameters. +pub fn simplify_repeated_primitive( + stretch_size: &LayoutSize, + tile_spacing: &mut LayoutSize, + prim_rect: &mut LayoutRect, +) { + let stride = *stretch_size + *tile_spacing; + + if stride.width >= prim_rect.size.width { + tile_spacing.width = 0.0; + prim_rect.size.width = f32::min(prim_rect.size.width, stretch_size.width); + } + if stride.height >= prim_rect.size.height { + tile_spacing.height = 0.0; + prim_rect.size.height = f32::min(prim_rect.size.height, stretch_size.height); + } +} + +pub struct Repetition { + pub origin: LayoutPoint, + pub edge_flags: EdgeAaSegmentMask, +} + +pub struct RepetitionIterator { + current_x: i32, + x_count: i32, + current_y: i32, + y_count: i32, + row_flags: EdgeAaSegmentMask, + current_origin: LayoutPoint, + initial_origin: LayoutPoint, + stride: LayoutSize, +} + +impl Iterator for RepetitionIterator { + type Item = Repetition; + + fn next(&mut self) -> Option { + if self.current_x == self.x_count { + self.current_y += 1; + if self.current_y >= self.y_count { + return None; + } + self.current_x = 0; + + self.row_flags = EdgeAaSegmentMask::empty(); + if self.current_y == self.y_count - 1 { + self.row_flags |= EdgeAaSegmentMask::BOTTOM; + } + + self.current_origin.x = self.initial_origin.x; + self.current_origin.y += self.stride.height; + } + + let mut edge_flags = self.row_flags; + if self.current_x == 0 { + edge_flags |= EdgeAaSegmentMask::LEFT; + } + + if self.current_x == self.x_count - 1 { + edge_flags |= EdgeAaSegmentMask::RIGHT; + } + + let repetition = Repetition { + origin: self.current_origin, + edge_flags, + }; + + self.current_origin.x += self.stride.width; + self.current_x += 1; + + Some(repetition) + } +} + +pub fn repetitions( + prim_rect: &LayoutRect, + visible_rect: &LayoutRect, + stride: LayoutSize, +) -> RepetitionIterator { + assert!(stride.width > 0.0); + assert!(stride.height > 0.0); + + let visible_rect = match prim_rect.intersection(&visible_rect) { + Some(rect) => rect, + None => { + return RepetitionIterator { + current_origin: LayoutPoint::zero(), + initial_origin: LayoutPoint::zero(), + current_x: 0, + current_y: 0, + x_count: 0, + y_count: 0, + stride, + row_flags: EdgeAaSegmentMask::empty(), + } + } + }; + + let nx = if visible_rect.origin.x > prim_rect.origin.x { + f32::floor((visible_rect.origin.x - prim_rect.origin.x) / stride.width) + } else { + 0.0 + }; + + let ny = if visible_rect.origin.y > prim_rect.origin.y { + f32::floor((visible_rect.origin.y - prim_rect.origin.y) / stride.height) + } else { + 0.0 + }; + + let x0 = prim_rect.origin.x + nx * stride.width; + let y0 = prim_rect.origin.y + ny * stride.height; + + let x_most = visible_rect.max_x(); + let y_most = visible_rect.max_y(); + + let x_count = f32::ceil((x_most - x0) / stride.width) as i32; + let y_count = f32::ceil((y_most - y0) / stride.height) as i32; + + let mut row_flags = EdgeAaSegmentMask::TOP; + if y_count == 1 { + row_flags |= EdgeAaSegmentMask::BOTTOM; + } + + RepetitionIterator { + current_origin: LayoutPoint::new(x0, y0), + initial_origin: LayoutPoint::new(x0, y0), + current_x: 0, + current_y: 0, + x_count, + y_count, + row_flags, + stride, + } +} + +#[derive(Debug)] +pub struct Tile { + pub rect: LayoutRect, + pub offset: TileOffset, + pub edge_flags: EdgeAaSegmentMask, +} + +#[derive(Debug)] +pub struct TileIteratorExtent { + /// Range of visible tiles to iterate over in number of tiles. + tile_range: Range, + /// Range of tiles of the full image including tiles that are culled out. + image_tiles: Range, + /// Size of the first tile in layout space. + first_tile_layout_size: f32, + /// Size of the last tile in layout space. + last_tile_layout_size: f32, + /// Position of blob point (0, 0) in layout space. + layout_tiling_origin: f32, + /// Position of the top-left corner of the primitive rect in layout space. + layout_prim_start: f32, +} + +#[derive(Debug)] +pub struct TileIterator { + current_tile: TileOffset, + x: TileIteratorExtent, + y: TileIteratorExtent, + regular_tile_size: LayoutSize, +} + +impl Iterator for TileIterator { + type Item = Tile; + + fn next(&mut self) -> Option { + // If we reach the end of a row, reset to the beginning of the next row. + if self.current_tile.x >= self.x.tile_range.end { + self.current_tile.y += 1; + self.current_tile.x = self.x.tile_range.start; + } + + // Stop iterating if we reach the last tile. We may start here if there + // were no tiles to iterate over. + if self.current_tile.x >= self.x.tile_range.end || self.current_tile.y >= self.y.tile_range.end { + return None; + } + + let tile_offset = self.current_tile; + + let mut segment_rect = LayoutRect { + origin: LayoutPoint::new( + self.x.layout_tiling_origin + tile_offset.x as f32 * self.regular_tile_size.width, + self.y.layout_tiling_origin + tile_offset.y as f32 * self.regular_tile_size.height, + ), + size: self.regular_tile_size, + }; + + let mut edge_flags = EdgeAaSegmentMask::empty(); + + if tile_offset.x == self.x.image_tiles.start { + edge_flags |= EdgeAaSegmentMask::LEFT; + segment_rect.size.width = self.x.first_tile_layout_size; + segment_rect.origin.x = self.x.layout_prim_start; + } + if tile_offset.x == self.x.image_tiles.end - 1 { + edge_flags |= EdgeAaSegmentMask::RIGHT; + segment_rect.size.width = self.x.last_tile_layout_size; + } + + if tile_offset.y == self.y.image_tiles.start { + segment_rect.size.height = self.y.first_tile_layout_size; + segment_rect.origin.y = self.y.layout_prim_start; + edge_flags |= EdgeAaSegmentMask::TOP; + } + if tile_offset.y == self.y.image_tiles.end - 1 { + segment_rect.size.height = self.y.last_tile_layout_size; + edge_flags |= EdgeAaSegmentMask::BOTTOM; + } + + assert!(tile_offset.y < self.y.tile_range.end); + let tile = Tile { + rect: segment_rect, + offset: tile_offset, + edge_flags, + }; + + self.current_tile.x += 1; + + Some(tile) + } +} + +pub fn tiles( + prim_rect: &LayoutRect, + visible_rect: &LayoutRect, + image_rect: &DeviceIntRect, + device_tile_size: i32, +) -> TileIterator { + // The image resource is tiled. We have to generate an image primitive + // for each tile. + // We need to do this because the image is broken up into smaller tiles in the texture + // cache and the image shader is not able to work with this type of sparse representation. + + // The tiling logic works as follows: + // + // +-#################-+ -+ + // | #//| | |//# | | image size + // | #//| | |//# | | + // +-#--+----+----+--#-+ | -+ + // | #//| | |//# | | | regular tile size + // | #//| | |//# | | | + // +-#--+----+----+--#-+ | -+-+ + // | #//|////|////|//# | | | "leftover" height + // | ################# | -+ ---+ + // +----+----+----+----+ + // + // In the ascii diagram above, a large image is split into tiles of almost regular size. + // The tiles on the edges (hatched in the diagram) can be smaller than the regular tiles + // and are handled separately in the code (we'll call them boundary tiles). + // + // Each generated segment corresponds to a tile in the texture cache, with the + // assumption that the boundary tiles are sized to fit their own irregular size in the + // texture cache. + // + // Because we can have very large virtual images we iterate over the visible portion of + // the image in layer space instead of iterating over all device tiles. + + let visible_rect = match prim_rect.intersection(&visible_rect) { + Some(rect) => rect, + None => { + return TileIterator { + current_tile: TileOffset::zero(), + x: TileIteratorExtent { + tile_range: 0..0, + image_tiles: 0..0, + first_tile_layout_size: 0.0, + last_tile_layout_size: 0.0, + layout_tiling_origin: 0.0, + layout_prim_start: prim_rect.origin.x, + }, + y: TileIteratorExtent { + tile_range: 0..0, + image_tiles: 0..0, + first_tile_layout_size: 0.0, + last_tile_layout_size: 0.0, + layout_tiling_origin: 0.0, + layout_prim_start: prim_rect.origin.y, + }, + regular_tile_size: LayoutSize::zero(), + } + } + }; + + // Size of regular tiles in layout space. + let layout_tile_size = LayoutSize::new( + device_tile_size as f32 / image_rect.size.width as f32 * prim_rect.size.width, + device_tile_size as f32 / image_rect.size.height as f32 * prim_rect.size.height, + ); + + // The decomposition logic is exactly the same on each axis so we reduce + // this to a 1-dimensional problem in an attempt to make the code simpler. + + let x_extent = tiles_1d( + layout_tile_size.width, + visible_rect.x_range(), + prim_rect.min_x(), + image_rect.x_range(), + device_tile_size, + ); + + let y_extent = tiles_1d( + layout_tile_size.height, + visible_rect.y_range(), + prim_rect.min_y(), + image_rect.y_range(), + device_tile_size, + ); + + TileIterator { + current_tile: point2( + x_extent.tile_range.start, + y_extent.tile_range.start, + ), + x: x_extent, + y: y_extent, + regular_tile_size: layout_tile_size, + } +} + +/// Decompose tiles along an arbitrary axis. +/// +/// This does most of the heavy lifting needed for `tiles` but in a single dimension for +/// the sake of simplicity since the problem is independent on the x and y axes. +fn tiles_1d( + layout_tile_size: f32, + layout_visible_range: Range, + layout_prim_start: f32, + device_image_range: Range, + device_tile_size: i32, +) -> TileIteratorExtent { + // A few sanity checks. + debug_assert!(layout_tile_size > 0.0); + debug_assert!(layout_visible_range.end >= layout_visible_range.start); + debug_assert!(device_image_range.end > device_image_range.start); + debug_assert!(device_tile_size > 0); + + // Sizes of the boundary tiles in pixels. + let first_tile_device_size = first_tile_size_1d(&device_image_range, device_tile_size); + let last_tile_device_size = last_tile_size_1d(&device_image_range, device_tile_size); + + // [start..end[ Range of tiles of this row/column (in number of tiles) without + // taking culling into account. + let image_tiles = tile_range_1d(&device_image_range, device_tile_size); + + // Layout offset of tile (0, 0) with respect to the top-left corner of the display item. + let layout_offset = device_image_range.start as f32 * layout_tile_size / device_tile_size as f32; + // Position in layout space of tile (0, 0). + let layout_tiling_origin = layout_prim_start - layout_offset; + + // [start..end[ Range of the visible tiles (because of culling). + let visible_tiles_start = f32::floor((layout_visible_range.start - layout_tiling_origin) / layout_tile_size) as i32; + let visible_tiles_end = f32::ceil((layout_visible_range.end - layout_tiling_origin) / layout_tile_size) as i32; + + // Combine the above two to get the tiles in the image that are visible this frame. + let mut tiles_start = i32::max(image_tiles.start, visible_tiles_start); + let tiles_end = i32::min(image_tiles.end, visible_tiles_end); + if tiles_start > tiles_end { + tiles_start = tiles_end; + } + + // The size in layout space of the boundary tiles. + let first_tile_layout_size = if tiles_start == image_tiles.start { + first_tile_device_size as f32 * layout_tile_size / device_tile_size as f32 + } else { + // boundary tile was culled out, so the new first tile is a regularly sized tile. + layout_tile_size + }; + + // Same here. + let last_tile_layout_size = if tiles_end == image_tiles.end { + last_tile_device_size as f32 * layout_tile_size / device_tile_size as f32 + } else { + layout_tile_size + }; + + TileIteratorExtent { + tile_range: tiles_start..tiles_end, + image_tiles, + first_tile_layout_size, + last_tile_layout_size, + layout_tiling_origin, + layout_prim_start, + } +} + +/// Compute the range of tiles (in number of tiles) that intersect the provided +/// image range (in pixels) in an arbitrary dimension. +/// +/// ```ignore +/// +/// 0 +/// : +/// #-+---+---+---+---+---+--# +/// # | | | | | | # +/// #-+---+---+---+---+---+--# +/// ^ : ^ +/// +/// +------------------------+ image_range +/// +---+ regular_tile_size +/// +/// ``` +fn tile_range_1d( + image_range: &Range, + regular_tile_size: i32, +) -> Range { + // Integer division truncates towards zero so with negative values if the first/last + // tile isn't a full tile we can get offset by one which we account for here. + + let mut start = image_range.start / regular_tile_size; + if image_range.start % regular_tile_size < 0 { + start -= 1; + } + + let mut end = image_range.end / regular_tile_size; + if image_range.end % regular_tile_size > 0 { + end += 1; + } + + start..end +} + +// Sizes of the first boundary tile in pixels. +// +// It can be smaller than the regular tile size if the image is not a multiple +// of the regular tile size. +fn first_tile_size_1d( + image_range: &Range, + regular_tile_size: i32, +) -> i32 { + // We have to account for how the % operation behaves for negative values. + let image_size = image_range.end - image_range.start; + i32::min( + match image_range.start % regular_tile_size { + // . #------+------+ . + // . #//////| | . + 0 => regular_tile_size, + // (zero) -> 0 . #--+------+ . + // . . #//| | . + // %(m): ~~> + m if m > 0 => regular_tile_size - m, + // . . #--+------+ 0 <- (zero) + // . . #//| | . + // %(m): <~~ + m => -m, + }, + image_size + ) +} + +// Sizes of the last boundary tile in pixels. +// +// It can be smaller than the regular tile size if the image is not a multiple +// of the regular tile size. +fn last_tile_size_1d( + image_range: &Range, + regular_tile_size: i32, +) -> i32 { + // We have to account for how the modulo operation behaves for negative values. + let image_size = image_range.end - image_range.start; + i32::min( + match image_range.end % regular_tile_size { + // +------+------# . + // tiles: . | |//////# . + 0 => regular_tile_size, + // . +------+--# . 0 <- (zero) + // . | |//# . . + // modulo (m): <~~ + m if m < 0 => regular_tile_size + m, + // (zero) -> 0 +------+--# . . + // . | |//# . . + // modulo (m): ~~> + m => m, + }, + image_size, + ) +} + +pub fn compute_tile_rect( + image_rect: &DeviceIntRect, + regular_tile_size: TileSize, + tile: TileOffset, +) -> DeviceIntRect { + let regular_tile_size = regular_tile_size as i32; + DeviceIntRect { + origin: point2( + compute_tile_origin_1d(image_rect.x_range(), regular_tile_size, tile.x as i32), + compute_tile_origin_1d(image_rect.y_range(), regular_tile_size, tile.y as i32), + ), + size: size2( + compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32), + compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32), + ), + } +} + +fn compute_tile_origin_1d( + img_range: Range, + regular_tile_size: i32, + tile_offset: i32, +) -> i32 { + let tile_range = tile_range_1d(&img_range, regular_tile_size); + if tile_offset == tile_range.start { + img_range.start + } else { + tile_offset * regular_tile_size + } +} + +// Compute the width and height in pixels of a tile depending on its position in the image. +pub fn compute_tile_size( + image_rect: &DeviceIntRect, + regular_tile_size: TileSize, + tile: TileOffset, +) -> DeviceIntSize { + let regular_tile_size = regular_tile_size as i32; + size2( + compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32), + compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32), + ) +} + +fn compute_tile_size_1d( + img_range: Range, + regular_tile_size: i32, + tile_offset: i32, +) -> i32 { + let tile_range = tile_range_1d(&img_range, regular_tile_size); + + // Most tiles are going to have base_size as width and height, + // except for tiles around the edges that are shrunk to fit the image data. + let actual_size = if tile_offset == tile_range.start { + first_tile_size_1d(&img_range, regular_tile_size) + } else if tile_offset == tile_range.end - 1 { + last_tile_size_1d(&img_range, regular_tile_size) + } else { + regular_tile_size + }; + + assert!(actual_size > 0); + + actual_size +} + +pub fn compute_tile_range( + visible_area: &DeviceIntRect, + tile_size: u16, +) -> TileRange { + let tile_size = tile_size as i32; + let x_range = tile_range_1d(&visible_area.x_range(), tile_size); + let y_range = tile_range_1d(&visible_area.y_range(), tile_size); + + TileRange { + origin: point2(x_range.start, y_range.start), + size: size2(x_range.end - x_range.start, y_range.end - y_range.start), + } +} + +pub fn for_each_tile_in_range( + range: &TileRange, + mut callback: impl FnMut(TileOffset), +) { + for y in range.y_range() { + for x in range.x_range() { + callback(point2(x, y)); + } + } +} + +pub fn compute_valid_tiles_if_bounds_change( + prev_rect: &DeviceIntRect, + new_rect: &DeviceIntRect, + tile_size: u16, +) -> Option { + let intersection = match prev_rect.intersection(new_rect) { + Some(rect) => rect, + None => { + return Some(TileRange::zero()); + } + }; + + let left = prev_rect.min_x() != new_rect.min_x(); + let right = prev_rect.max_x() != new_rect.max_x(); + let top = prev_rect.min_y() != new_rect.min_y(); + let bottom = prev_rect.max_y() != new_rect.max_y(); + + if !left && !right && !top && !bottom { + // Bounds have not changed. + return None; + } + + let tw = 1.0 / (tile_size as f32); + let th = 1.0 / (tile_size as f32); + + let tiles = intersection + .cast::() + .scale(tw, th); + + let min_x = if left { f32::ceil(tiles.min_x()) } else { f32::floor(tiles.min_x()) }; + let min_y = if top { f32::ceil(tiles.min_y()) } else { f32::floor(tiles.min_y()) }; + let max_x = if right { f32::floor(tiles.max_x()) } else { f32::ceil(tiles.max_x()) }; + let max_y = if bottom { f32::floor(tiles.max_y()) } else { f32::ceil(tiles.max_y()) }; + + Some(TileRange { + origin: point2(min_x as i32, min_y as i32), + size: size2((max_x - min_x) as i32, (max_y - min_y) as i32), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + use euclid::rect; + + // this checks some additional invariants + fn checked_for_each_tile( + prim_rect: &LayoutRect, + visible_rect: &LayoutRect, + device_image_rect: &DeviceIntRect, + device_tile_size: i32, + callback: &mut dyn FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask), + ) { + let mut coverage = LayoutRect::zero(); + let mut seen_tiles = HashSet::new(); + for tile in tiles( + prim_rect, + visible_rect, + device_image_rect, + device_tile_size, + ) { + // make sure we don't get sent duplicate tiles + assert!(!seen_tiles.contains(&tile.offset)); + seen_tiles.insert(tile.offset); + coverage = coverage.union(&tile.rect); + assert!(prim_rect.contains_rect(&tile.rect)); + callback(&tile.rect, tile.offset, tile.edge_flags); + } + assert!(prim_rect.contains_rect(&coverage)); + assert!(coverage.contains_rect(&visible_rect.intersection(&prim_rect).unwrap_or(LayoutRect::zero()))); + } + + #[test] + fn basic() { + let mut count = 0; + checked_for_each_tile(&rect(0., 0., 1000., 1000.), + &rect(75., 75., 400., 400.), + &rect(0, 0, 400, 400), + 36, + &mut |_tile_rect, _tile_offset, _tile_flags| { + count += 1; + }, + ); + assert_eq!(count, 36); + } + + #[test] + fn empty() { + let mut count = 0; + checked_for_each_tile(&rect(0., 0., 74., 74.), + &rect(75., 75., 400., 400.), + &rect(0, 0, 400, 400), + 36, + &mut |_tile_rect, _tile_offset, _tile_flags| { + count += 1; + }, + ); + assert_eq!(count, 0); + } + + #[test] + fn test_tiles_1d() { + // Exactly one full tile at positive offset. + let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 0..64, 64); + assert_eq!(result.tile_range.start, 0); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // Exactly one full tile at negative offset. + let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..0, 64); + assert_eq!(result.tile_range.start, -1); + assert_eq!(result.tile_range.end, 0); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // Two full tiles at negative and positive offsets. + let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..64, 64); + assert_eq!(result.tile_range.start, -1); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // One partial tile at positive offset, non-zero origin, culled out. + let result = tiles_1d(64.0, -100.0..10.0, 64.0, 64..310, 64); + assert_eq!(result.tile_range.start, result.tile_range.end); + + // Two tiles at negative and positive offsets, one of which is culled out. + // The remaining tile is partially culled but it should still generate a full tile. + let result = tiles_1d(64.0, 10.0..10000.0, -64.0, -64..64, 64); + assert_eq!(result.tile_range.start, 0); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + let result = tiles_1d(64.0, -10000.0..-10.0, -64.0, -64..64, 64); + assert_eq!(result.tile_range.start, -1); + assert_eq!(result.tile_range.end, 0); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // Stretched tile in layout space device tile size is 64 and layout tile size is 128. + // So the resulting tile sizes in layout space should be multiplied by two. + let result = tiles_1d(128.0, -10000.0..10000.0, -64.0, -64..32, 64); + assert_eq!(result.tile_range.start, -1); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 128.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // Two visible tiles (the rest is culled out). + let result = tiles_1d(10.0, 0.0..20.0, 0.0, 0..64, 64); + assert_eq!(result.tile_range.start, 0); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 10.0); + assert_eq!(result.last_tile_layout_size, 10.0); + + // Two visible tiles at negative layout offsets (the rest is culled out). + let result = tiles_1d(10.0, -20.0..0.0, -20.0, 0..64, 64); + assert_eq!(result.tile_range.start, 0); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 10.0); + assert_eq!(result.last_tile_layout_size, 10.0); + } + + #[test] + fn test_tile_range_1d() { + assert_eq!(tile_range_1d(&(0..256), 256), 0..1); + assert_eq!(tile_range_1d(&(0..257), 256), 0..2); + assert_eq!(tile_range_1d(&(-1..257), 256), -1..2); + assert_eq!(tile_range_1d(&(-256..256), 256), -1..1); + assert_eq!(tile_range_1d(&(-20..-10), 6), -4..-1); + assert_eq!(tile_range_1d(&(20..100), 256), 0..1); + } + + #[test] + fn test_first_last_tile_size_1d() { + assert_eq!(first_tile_size_1d(&(0..10), 64), 10); + assert_eq!(first_tile_size_1d(&(-20..0), 64), 20); + + assert_eq!(last_tile_size_1d(&(0..10), 64), 10); + assert_eq!(last_tile_size_1d(&(-20..0), 64), 20); + } + + #[test] + fn doubly_partial_tiles() { + // In the following tests the image is a single tile and none of the sides of the tile + // align with the tile grid. + // This can only happen when we have a single non-aligned partial tile and no regular + // tiles. + assert_eq!(first_tile_size_1d(&(300..310), 64), 10); + assert_eq!(first_tile_size_1d(&(-20..-10), 64), 10); + + assert_eq!(last_tile_size_1d(&(300..310), 64), 10); + assert_eq!(last_tile_size_1d(&(-20..-10), 64), 10); + + + // One partial tile at positve offset, non-zero origin. + let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 300..310, 64); + assert_eq!(result.tile_range.start, 4); + assert_eq!(result.tile_range.end, 5); + assert_eq!(result.first_tile_layout_size, 10.0); + assert_eq!(result.last_tile_layout_size, 10.0); + } + + #[test] + fn smaller_than_tile_size_at_origin() { + let r = compute_tile_rect( + &rect(0, 0, 80, 80), + 256, + point2(0, 0), + ); + + assert_eq!(r, rect(0, 0, 80, 80)); + } + + #[test] + fn smaller_than_tile_size_with_offset() { + let r = compute_tile_rect( + &rect(20, 20, 80, 80), + 256, + point2(0, 0), + ); + + assert_eq!(r, rect(20, 20, 80, 80)); + } +} diff --git a/third_party/webrender/webrender_api/src/lib.rs b/third_party/webrender/webrender_api/src/lib.rs index f5cda1fbf65..848f4740c90 100644 --- a/third_party/webrender/webrender_api/src/lib.rs +++ b/third_party/webrender/webrender_api/src/lib.rs @@ -15,21 +15,25 @@ #![cfg_attr(feature = "cargo-clippy", allow(clippy::float_cmp, clippy::too_many_arguments))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::unreadable_literal, clippy::new_without_default))] -pub use crossbeam_channel; -pub use euclid; - #[macro_use] extern crate bitflags; #[cfg(feature = "nightly")] -extern crate core; +use core; +#[cfg(target_os = "macos")] +use core_foundation; +#[cfg(target_os = "macos")] +use core_graphics; +#[macro_use] +extern crate derive_more; +pub use euclid; #[macro_use] extern crate malloc_size_of_derive; #[macro_use] extern crate serde_derive; use malloc_size_of; -use peek_poke; +mod api; pub mod channel; mod color; mod display_item; @@ -38,8 +42,13 @@ mod display_list; mod font; mod gradient_builder; mod image; +mod resources; pub mod units; +#[doc(hidden)] +pub mod image_tiling; + +pub use crate::api::*; pub use crate::color::*; pub use crate::display_item::*; pub use crate::display_item_cache::DisplayItemCache; @@ -47,619 +56,4 @@ pub use crate::display_list::*; pub use crate::font::*; pub use crate::gradient_builder::*; pub use crate::image::*; - -use crate::units::*; -use crate::channel::Receiver; -use std::marker::PhantomData; -use std::sync::Arc; -use std::os::raw::c_void; -use peek_poke::PeekPoke; - -/// Width and height in device pixels of image tiles. -pub type TileSize = u16; - -/// Various settings that the caller can select based on desired tradeoffs -/// between rendering quality and performance / power usage. -#[derive(Copy, Clone, Deserialize, Serialize)] -pub struct QualitySettings { - /// If true, disable creating separate picture cache slices when the - /// scroll root changes. This gives maximum opportunity to find an - /// opaque background, which enables subpixel AA. However, it is - /// usually significantly more expensive to render when scrolling. - pub force_subpixel_aa_where_possible: bool, -} - -impl Default for QualitySettings { - fn default() -> Self { - QualitySettings { - // Prefer performance over maximum subpixel AA quality, since WR - // already enables subpixel AA in more situations than other browsers. - force_subpixel_aa_where_possible: false, - } - } -} - -/// An epoch identifies the state of a pipeline in time. -/// -/// This is mostly used as a synchronization mechanism to observe how/when particular pipeline -/// updates propagate through WebRender and are applied at various stages. -#[repr(C)] -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] -pub struct Epoch(pub u32); - -impl Epoch { - /// Magic invalid epoch value. - pub fn invalid() -> Epoch { - Epoch(u32::MAX) - } -} - -/// ID namespaces uniquely identify different users of WebRender's API. -/// -/// For example in Gecko each content process uses a separate id namespace. -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, Eq, MallocSizeOf, PartialEq, Hash, Ord, PartialOrd, PeekPoke)] -#[derive(Deserialize, Serialize)] -pub struct IdNamespace(pub u32); - -/// A key uniquely identifying a WebRender document. -/// -/// Instances can manage one or several documents (using the same render backend thread). -/// Each document will internally correspond to a single scene, and scenes are made of -/// one or several pipelines. -#[repr(C)] -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] -pub struct DocumentId { - /// - pub namespace_id: IdNamespace, - /// - pub id: u32, -} - -impl DocumentId { - /// - pub fn new(namespace_id: IdNamespace, id: u32) -> Self { - DocumentId { - namespace_id, - id, - } - } - - /// - pub const INVALID: DocumentId = DocumentId { namespace_id: IdNamespace(0), id: 0 }; -} - -/// This type carries no valuable semantics for WR. However, it reflects the fact that -/// clients (Servo) may generate pipelines by different semi-independent sources. -/// These pipelines still belong to the same `IdNamespace` and the same `DocumentId`. -/// Having this extra Id field enables them to generate `PipelineId` without collision. -pub type PipelineSourceId = u32; - -/// From the point of view of WR, `PipelineId` is completely opaque and generic as long as -/// it's clonable, serializable, comparable, and hashable. -#[repr(C)] -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] -pub struct PipelineId(pub PipelineSourceId, pub u32); - -impl Default for PipelineId { - fn default() -> Self { - PipelineId::dummy() - } -} - -impl PipelineId { - /// - pub fn dummy() -> Self { - PipelineId(!0, !0) - } -} - - -/// An opaque pointer-sized value. -#[repr(C)] -#[derive(Clone)] -pub struct ExternalEvent { - raw: usize, -} - -unsafe impl Send for ExternalEvent {} - -impl ExternalEvent { - /// Creates the event from an opaque pointer-sized value. - pub fn from_raw(raw: usize) -> Self { - ExternalEvent { raw } - } - /// Consumes self to make it obvious that the event should be forwarded only once. - pub fn unwrap(self) -> usize { - self.raw - } -} - -/// Describe whether or not scrolling should be clamped by the content bounds. -#[derive(Clone, Deserialize, Serialize)] -pub enum ScrollClamping { - /// - ToContentBounds, - /// - NoClamping, -} - -/// A handler to integrate WebRender with the thread that contains the `Renderer`. -pub trait RenderNotifier: Send { - /// - fn clone(&self) -> Box; - /// Wake the thread containing the `Renderer` up (after updates have been put - /// in the renderer's queue). - fn wake_up( - &self, - composite_needed: bool, - ); - /// Notify the thread containing the `Renderer` that a new frame is ready. - fn new_frame_ready(&self, _: DocumentId, scrolled: bool, composite_needed: bool, render_time_ns: Option); - /// A Gecko-specific notification mechanism to get some code executed on the - /// `Renderer`'s thread, mostly replaced by `NotificationHandler`. You should - /// probably use the latter instead. - fn external_event(&self, _evt: ExternalEvent) { - unimplemented!() - } - /// Notify the thread containing the `Renderer` that the render backend has been - /// shut down. - fn shut_down(&self) {} -} - -/// A stage of the rendering pipeline. -#[repr(u32)] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Checkpoint { - /// - SceneBuilt, - /// - FrameBuilt, - /// - FrameTexturesUpdated, - /// - FrameRendered, - /// 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, -} - -/// A handler to notify when a transaction reaches certain stages of the rendering -/// pipeline. -pub trait NotificationHandler : Send + Sync { - /// Entry point of the handler to implement. Invoked by WebRender. - fn notify(&self, when: Checkpoint); -} - -/// A request to notify a handler when the transaction reaches certain stages of the -/// rendering pipeline. -/// -/// The request is guaranteed to be notified once and only once, even if the transaction -/// is dropped before the requested check-point. -pub struct NotificationRequest { - handler: Option>, - when: Checkpoint, -} - -impl NotificationRequest { - /// Constructor. - pub fn new(when: Checkpoint, handler: Box) -> Self { - NotificationRequest { - handler: Some(handler), - when, - } - } - - /// The specified stage at which point the handler should be notified. - pub fn when(&self) -> Checkpoint { self.when } - - /// Called by WebRender at specified stages to notify the registered handler. - pub fn notify(mut self) { - if let Some(handler) = self.handler.take() { - handler.notify(self.when); - } - } -} - -/// An object that can perform hit-testing without doing synchronous queries to -/// the RenderBackendThread. -pub trait ApiHitTester: Send + Sync { - /// Does a hit test on display items in the specified document, at the given - /// point. If a pipeline_id is specified, it is used to further restrict the - /// hit results so that only items inside that pipeline are matched. The vector - /// of hit results will contain all display items that match, ordered from - /// front to back. - fn hit_test(&self, pipeline_id: Option, point: WorldPoint, flags: HitTestFlags) -> HitTestResult; -} - -/// A hit tester requested to the render backend thread but not necessarily ready yet. -/// -/// The request should be resolved as late as possible to reduce the likelihood of blocking. -pub struct HitTesterRequest { - #[doc(hidden)] - pub rx: Receiver>, -} - -impl HitTesterRequest { - /// Block until the hit tester is available and return it, consuming teh request. - pub fn resolve(self) -> Arc { - self.rx.recv().unwrap() - } -} - -/// Describe an item that matched a hit-test query. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct HitTestItem { - /// The pipeline that the display item that was hit belongs to. - pub pipeline: PipelineId, - - /// The tag of the hit display item. - pub tag: ItemTag, - - /// The hit point in the coordinate space of the "viewport" of the display item. The - /// viewport is the scroll node formed by the root reference frame of the display item's - /// pipeline. - pub point_in_viewport: LayoutPoint, - - /// The coordinates of the original hit test point relative to the origin of this item. - /// This is useful for calculating things like text offsets in the client. - pub point_relative_to_item: LayoutPoint, -} - -/// Returned by `RenderApi::hit_test`. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct HitTestResult { - /// List of items that are match the hit-test query. - pub items: Vec, -} - -bitflags! { - #[derive(Deserialize, MallocSizeOf, Serialize)] - /// - pub struct HitTestFlags: u8 { - /// - const FIND_ALL = 0b00000001; - /// - const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010; - } -} - -impl Drop for NotificationRequest { - fn drop(&mut self) { - if let Some(ref mut handler) = self.handler { - handler.notify(Checkpoint::TransactionDropped); - } - } -} - -// This Clone impl yields an "empty" request because we don't want the requests -// to be notified twice so the request is owned by only one of the API messages -// (the original one) after the clone. -// This works in practice because the notifications requests are used for -// synchronization so we don't need to include them in the recording mechanism -// in wrench that clones the messages. -impl Clone for NotificationRequest { - fn clone(&self) -> Self { - NotificationRequest { - when: self.when, - handler: None, - } - } -} - - -/// A key to identify an animated property binding. -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)] -pub struct PropertyBindingId { - pub namespace: IdNamespace, - pub uid: u32, -} - -impl PropertyBindingId { - /// Constructor. - pub fn new(value: u64) -> Self { - PropertyBindingId { - namespace: IdNamespace((value >> 32) as u32), - uid: value as u32, - } - } -} - -/// A unique key that is used for connecting animated property -/// values to bindings in the display list. -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] -pub struct PropertyBindingKey { - /// - pub id: PropertyBindingId, - #[doc(hidden)] - pub _phantom: PhantomData, -} - -/// Construct a property value from a given key and value. -impl PropertyBindingKey { - /// - pub fn with(self, value: T) -> PropertyValue { - PropertyValue { key: self, value } - } -} - -impl PropertyBindingKey { - /// Constructor. - pub fn new(value: u64) -> Self { - PropertyBindingKey { - id: PropertyBindingId::new(value), - _phantom: PhantomData, - } - } -} - -/// A binding property can either be a specific value -/// (the normal, non-animated case) or point to a binding location -/// to fetch the current value from. -/// Note that Binding has also a non-animated value, the value is -/// used for the case where the animation is still in-delay phase -/// (i.e. the animation doesn't produce any animation values). -#[repr(C)] -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] -pub enum PropertyBinding { - /// Non-animated value. - Value(T), - /// Animated binding. - Binding(PropertyBindingKey, T), -} - -impl Default for PropertyBinding { - fn default() -> Self { - PropertyBinding::Value(Default::default()) - } -} - -impl From for PropertyBinding { - fn from(value: T) -> PropertyBinding { - PropertyBinding::Value(value) - } -} - -impl From> for PropertyBindingKey { - fn from(key: PropertyBindingKey) -> PropertyBindingKey { - PropertyBindingKey { - id: key.id.clone(), - _phantom: PhantomData, - } - } -} - -impl From> for PropertyBindingKey { - fn from(key: PropertyBindingKey) -> PropertyBindingKey { - PropertyBindingKey { - id: key.id.clone(), - _phantom: PhantomData, - } - } -} - -impl From> for PropertyBinding { - fn from(value: PropertyBinding) -> PropertyBinding { - match value { - PropertyBinding::Value(value) => PropertyBinding::Value(value.into()), - PropertyBinding::Binding(k, v) => { - PropertyBinding::Binding(k.into(), v.into()) - } - } - } -} - -impl From> for PropertyBinding { - fn from(value: PropertyBinding) -> PropertyBinding { - match value { - PropertyBinding::Value(value) => PropertyBinding::Value(value.into()), - PropertyBinding::Binding(k, v) => { - PropertyBinding::Binding(k.into(), v.into()) - } - } - } -} - -/// The current value of an animated property. This is -/// supplied by the calling code. -#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] -pub struct PropertyValue { - /// - pub key: PropertyBindingKey, - /// - pub value: T, -} - -/// When using `generate_frame()`, a list of `PropertyValue` structures -/// can optionally be supplied to provide the current value of any -/// animated properties. -#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Default)] -pub struct DynamicProperties { - /// - pub transforms: Vec>, - /// opacity - pub floats: Vec>, - /// background color - pub colors: Vec>, -} - -/// A C function that takes a pointer to a heap allocation and returns its size. -/// -/// This is borrowed from the malloc_size_of crate, upon which we want to avoid -/// a dependency from WebRender. -pub type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; - -bitflags! { - /// Flags to enable/disable various builtin debugging tools. - #[repr(C)] - #[derive(Default, Deserialize, MallocSizeOf, Serialize)] - pub struct DebugFlags: u32 { - /// Display the frame profiler on screen. - const PROFILER_DBG = 1 << 0; - /// Display intermediate render targets on screen. - const RENDER_TARGET_DBG = 1 << 1; - /// Display all texture cache pages on screen. - const TEXTURE_CACHE_DBG = 1 << 2; - /// Display GPU timing results. - const GPU_TIME_QUERIES = 1 << 3; - /// Query the number of pixels that pass the depth test divided and show it - /// in the profiler as a percentage of the number of pixels in the screen - /// (window width times height). - const GPU_SAMPLE_QUERIES = 1 << 4; - /// Render each quad with their own draw call. - /// - /// Terrible for performance but can help with understanding the drawing - /// order when inspecting renderdoc or apitrace recordings. - const DISABLE_BATCHING = 1 << 5; - /// Display the pipeline epochs. - const EPOCHS = 1 << 6; - /// Print driver messages to stdout. - const ECHO_DRIVER_MESSAGES = 1 << 7; - /// Show an overlay displaying overdraw amount. - const SHOW_OVERDRAW = 1 << 8; - /// Display the contents of GPU cache. - const GPU_CACHE_DBG = 1 << 9; - /// Clear evicted parts of the texture cache for debugging purposes. - const TEXTURE_CACHE_DBG_CLEAR_EVICTED = 1 << 10; - /// Show picture caching debug overlay - const PICTURE_CACHING_DBG = 1 << 11; - /// Highlight all primitives with colors based on kind. - const PRIMITIVE_DBG = 1 << 12; - /// Draw a zoom widget showing part of the framebuffer zoomed in. - const ZOOM_DBG = 1 << 13; - /// Scale the debug renderer down for a smaller screen. This will disrupt - /// any mapping between debug display items and page content, so shouldn't - /// be used with overlays like the picture caching or primitive display. - const SMALL_SCREEN = 1 << 14; - /// Disable various bits of the WebRender pipeline, to help narrow - /// down where slowness might be coming from. - const DISABLE_OPAQUE_PASS = 1 << 15; - /// - const DISABLE_ALPHA_PASS = 1 << 16; - /// - const DISABLE_CLIP_MASKS = 1 << 17; - /// - const DISABLE_TEXT_PRIMS = 1 << 18; - /// - const DISABLE_GRADIENT_PRIMS = 1 << 19; - /// - const OBSCURE_IMAGES = 1 << 20; - /// Taint the transparent area of the glyphs with a random opacity to easily - /// see when glyphs are re-rasterized. - const GLYPH_FLASHING = 1 << 21; - /// The profiler only displays information that is out of the ordinary. - const SMART_PROFILER = 1 << 22; - /// If set, dump picture cache invalidation debug to console. - const INVALIDATION_DBG = 1 << 23; - /// Log tile cache to memory for later saving as part of wr-capture - const TILE_CACHE_LOGGING_DBG = 1 << 24; - /// Collect and dump profiler statistics to captures. - const PROFILER_CAPTURE = (1 as u32) << 25; // need "as u32" until we have cbindgen#556 - /// Invalidate picture tiles every frames (useful when inspecting GPU work in external tools). - const FORCE_PICTURE_INVALIDATION = (1 as u32) << 26; - const USE_BATCHED_TEXTURE_UPLOADS = (1 as u32) << 27; - const USE_DRAW_CALLS_FOR_TEXTURE_COPY = (1 as u32) << 28; - } -} - -/// Information specific to a primitive type that -/// uniquely identifies a primitive template by key. -#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash, Serialize, Deserialize)] -pub enum PrimitiveKeyKind { - /// Clear an existing rect, used for special effects on some platforms. - Clear, - /// - Rectangle { - /// - color: PropertyBinding, - }, -} - -/// -#[derive(Clone)] -pub struct ScrollNodeState { - /// - pub id: ExternalScrollId, - /// - pub scroll_offset: LayoutVector2D, -} - -/// -#[derive(Clone, Copy, Debug)] -pub enum ScrollLocation { - /// Scroll by a certain amount. - Delta(LayoutVector2D), - /// Scroll to very top of element. - Start, - /// Scroll to very bottom of element. - End, -} - -/// Represents a zoom factor. -#[derive(Clone, Copy, Debug)] -pub struct ZoomFactor(f32); - -impl ZoomFactor { - /// Construct a new zoom factor. - pub fn new(scale: f32) -> Self { - ZoomFactor(scale) - } - - /// Get the zoom factor as an untyped float. - pub fn get(self) -> f32 { - self.0 - } -} - -/// Crash annotations included in crash reports. -#[repr(C)] -#[derive(Clone, Copy)] -pub enum CrashAnnotation { - CompileShader = 0, - DrawShader = 1, -} - -/// Handler to expose support for annotating crash reports. -pub trait CrashAnnotator : Send { - fn set(&self, annotation: CrashAnnotation, value: &std::ffi::CStr); - fn clear(&self, annotation: CrashAnnotation); - fn box_clone(&self) -> Box; -} - -impl Clone for Box { - fn clone(&self) -> Box { - self.box_clone() - } -} - -/// Guard to add a crash annotation at creation, and clear it at destruction. -pub struct CrashAnnotatorGuard<'a> { - annotator: &'a Option>, - annotation: CrashAnnotation, -} - -impl<'a> CrashAnnotatorGuard<'a> { - pub fn new( - annotator: &'a Option>, - annotation: CrashAnnotation, - value: &std::ffi::CStr, - ) -> Self { - if let Some(ref annotator) = annotator { - annotator.set(annotation, value); - } - Self { - annotator, - annotation, - } - } -} - -impl<'a> Drop for CrashAnnotatorGuard<'a> { - fn drop(&mut self) { - if let Some(ref annotator) = self.annotator { - annotator.clear(self.annotation); - } - } -} +pub use crate::resources::DEFAULT_TILE_SIZE; diff --git a/third_party/webrender/webrender_api/src/resources.rs b/third_party/webrender/webrender_api/src/resources.rs new file mode 100644 index 00000000000..c41fdc3009e --- /dev/null +++ b/third_party/webrender/webrender_api/src/resources.rs @@ -0,0 +1,327 @@ +/* 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/. */ + +use crate::{BlobImageKey, ImageDescriptor, DirtyRect, TileSize, ResourceUpdate}; +use crate::{BlobImageHandler, AsyncBlobImageRasterizer, BlobImageData, BlobImageParams}; +use crate::{BlobImageRequest, BlobImageDescriptor, BlobImageResources, TransactionMsg}; +use crate::{FontKey, FontTemplate, FontInstanceData, FontInstanceKey, AddFont}; +use crate::image_tiling::*; +use crate::units::*; +use crate::font::SharedFontInstanceMap; +use crate::euclid::{point2, size2}; + +pub const DEFAULT_TILE_SIZE: TileSize = 512; + +use std::collections::HashMap; +use std::sync::Arc; + +/// We use this to generate the async blob rendering requests. +struct BlobImageTemplate { + descriptor: ImageDescriptor, + tile_size: TileSize, + dirty_rect: BlobDirtyRect, + /// See ImageResource::visible_rect. + visible_rect: DeviceIntRect, + // If the active rect of the blob changes, this represents the + // range of tiles that remain valid. This must be taken into + // account in addition to the valid rect when submitting blob + // rasterization requests. + // `None` means the bounds have not changed (tiles are still valid). + // `Some(TileRange::zero())` means all of the tiles are invalid. + valid_tiles_after_bounds_change: Option, +} + +struct FontResources { + templates: HashMap, + instances: SharedFontInstanceMap, +} + +pub struct ApiResources { + blob_image_templates: HashMap, + pub blob_image_handler: Option>, + fonts: FontResources, +} + +impl BlobImageResources for FontResources { + fn get_font_data(&self, key: FontKey) -> &FontTemplate { + self.templates.get(&key).unwrap() + } + fn get_font_instance_data(&self, key: FontInstanceKey) -> Option { + self.instances.get_font_instance_data(key) + } +} + +impl ApiResources { + pub fn new( + blob_image_handler: Option>, + instances: SharedFontInstanceMap, + ) -> Self { + ApiResources { + blob_image_templates: HashMap::new(), + blob_image_handler, + fonts: FontResources { + templates: HashMap::new(), + instances, + } + } + } + + pub fn get_shared_font_instances(&self) -> SharedFontInstanceMap { + self.fonts.instances.clone() + } + + pub fn update(&mut self, transaction: &mut TransactionMsg) { + let mut blobs_to_rasterize = Vec::new(); + for update in &transaction.resource_updates { + match *update { + ResourceUpdate::AddBlobImage(ref img) => { + self.blob_image_handler + .as_mut() + .unwrap() + .add(img.key, Arc::clone(&img.data), &img.visible_rect, img.tile_size); + + self.blob_image_templates.insert( + img.key, + BlobImageTemplate { + descriptor: img.descriptor, + tile_size: img.tile_size, + dirty_rect: DirtyRect::All, + valid_tiles_after_bounds_change: None, + visible_rect: img.visible_rect, + }, + ); + blobs_to_rasterize.push(img.key); + } + ResourceUpdate::UpdateBlobImage(ref img) => { + debug_assert_eq!(img.visible_rect.size, img.descriptor.size); + self.update_blob_image( + img.key, + Some(&img.descriptor), + Some(&img.dirty_rect), + Some(Arc::clone(&img.data)), + &img.visible_rect, + ); + blobs_to_rasterize.push(img.key); + } + ResourceUpdate::DeleteBlobImage(key) => { + self.blob_image_templates.remove(&key); + } + ResourceUpdate::SetBlobImageVisibleArea(ref key, ref area) => { + self.update_blob_image(*key, None, None, None, area); + blobs_to_rasterize.push(*key); + } + ResourceUpdate::AddFont(ref font) => { + match font { + AddFont::Raw(key, bytes, index) => { + self.fonts.templates.insert( + *key, + FontTemplate::Raw(Arc::clone(bytes), *index), + ); + } + AddFont::Native(key, native_font_handle) => { + self.fonts.templates.insert( + *key, + FontTemplate::Native(native_font_handle.clone()), + ); + } + } + } + ResourceUpdate::AddFontInstance(ref instance) => { + // TODO(nical): Don't clone these. + self.fonts.instances.add_font_instance( + instance.key, + instance.font_key, + instance.glyph_size, + instance.options.clone(), + instance.platform_options.clone(), + instance.variations.clone(), + ); + } + ResourceUpdate::DeleteFont(key) => { + self.fonts.templates.remove(&key); + if let Some(ref mut handler) = self.blob_image_handler { + handler.delete_font(key); + } + } + ResourceUpdate::DeleteFontInstance(key) => { + // We will delete from the shared font instance map in the resource cache + // after scene swap. + + if let Some(ref mut r) = self.blob_image_handler { + r.delete_font_instance(key); + } + } + _ => {} + } + } + + let (rasterizer, requests) = self.create_blob_scene_builder_requests(&blobs_to_rasterize); + transaction.blob_rasterizer = rasterizer; + transaction.blob_requests = requests; + } + + pub fn enable_multithreading(&mut self, enable: bool) { + if let Some(ref mut handler) = self.blob_image_handler { + handler.enable_multithreading(enable); + } + } + + fn update_blob_image( + &mut self, + key: BlobImageKey, + descriptor: Option<&ImageDescriptor>, + dirty_rect: Option<&BlobDirtyRect>, + data: Option>, + visible_rect: &DeviceIntRect, + ) { + if let Some(data) = data { + let dirty_rect = dirty_rect.unwrap(); + self.blob_image_handler.as_mut().unwrap().update(key, data, visible_rect, dirty_rect); + } + + let image = self.blob_image_templates + .get_mut(&key) + .expect("Attempt to update non-existent blob image"); + + let mut valid_tiles_after_bounds_change = compute_valid_tiles_if_bounds_change( + &image.visible_rect, + visible_rect, + image.tile_size, + ); + + match (image.valid_tiles_after_bounds_change, valid_tiles_after_bounds_change) { + (Some(old), Some(ref mut new)) => { + *new = new.intersection(&old).unwrap_or_else(TileRange::zero); + } + (Some(old), None) => { + valid_tiles_after_bounds_change = Some(old); + } + _ => {} + } + + let blob_size = visible_rect.size; + + if let Some(descriptor) = descriptor { + image.descriptor = *descriptor; + } else { + // make sure the descriptor size matches the visible rect. + // This might not be necessary but let's stay on the safe side. + image.descriptor.size = blob_size; + } + + if let Some(dirty_rect) = dirty_rect { + image.dirty_rect = image.dirty_rect.union(dirty_rect); + } + + image.valid_tiles_after_bounds_change = valid_tiles_after_bounds_change; + image.visible_rect = *visible_rect; + } + + pub fn create_blob_scene_builder_requests( + &mut self, + keys: &[BlobImageKey] + ) -> (Option>, Vec) { + if self.blob_image_handler.is_none() || keys.is_empty() { + return (None, Vec::new()); + } + + let mut blob_request_params = Vec::new(); + for key in keys { + let template = self.blob_image_templates.get_mut(key).unwrap(); + + // If we know that only a portion of the blob image is in the viewport, + // only request these visible tiles since blob images can be huge. + let tiles = compute_tile_range( + &template.visible_rect, + template.tile_size, + ); + + // Don't request tiles that weren't invalidated. + let dirty_tiles = match template.dirty_rect { + DirtyRect::Partial(dirty_rect) => { + compute_tile_range( + &dirty_rect.cast_unit(), + template.tile_size, + ) + } + DirtyRect::All => tiles, + }; + + for_each_tile_in_range(&tiles, |tile| { + let still_valid = template.valid_tiles_after_bounds_change + .map(|valid_tiles| valid_tiles.contains(tile)) + .unwrap_or(true); + + if still_valid && !dirty_tiles.contains(tile) { + return; + } + + let descriptor = BlobImageDescriptor { + rect: compute_tile_rect( + &template.visible_rect, + template.tile_size, + tile, + ).cast_unit(), + format: template.descriptor.format, + }; + + assert!(descriptor.rect.size.width > 0 && descriptor.rect.size.height > 0); + blob_request_params.push( + BlobImageParams { + request: BlobImageRequest { key: *key, tile }, + descriptor, + dirty_rect: DirtyRect::All, + } + ); + }); + + template.dirty_rect = DirtyRect::empty(); + template.valid_tiles_after_bounds_change = None; + } + + let handler = self.blob_image_handler.as_mut().unwrap(); + handler.prepare_resources(&self.fonts, &blob_request_params); + (Some(handler.create_blob_rasterizer()), blob_request_params) + } +} + +fn compute_valid_tiles_if_bounds_change( + prev_rect: &DeviceIntRect, + new_rect: &DeviceIntRect, + tile_size: u16, +) -> Option { + let intersection = match prev_rect.intersection(new_rect) { + Some(rect) => rect, + None => { + return Some(TileRange::zero()); + } + }; + + let left = prev_rect.min_x() != new_rect.min_x(); + let right = prev_rect.max_x() != new_rect.max_x(); + let top = prev_rect.min_y() != new_rect.min_y(); + let bottom = prev_rect.max_y() != new_rect.max_y(); + + if !left && !right && !top && !bottom { + // Bounds have not changed. + return None; + } + + let tw = 1.0 / (tile_size as f32); + let th = 1.0 / (tile_size as f32); + + let tiles = intersection + .cast::() + .scale(tw, th); + + let min_x = if left { f32::ceil(tiles.min_x()) } else { f32::floor(tiles.min_x()) }; + let min_y = if top { f32::ceil(tiles.min_y()) } else { f32::floor(tiles.min_y()) }; + let max_x = if right { f32::floor(tiles.max_x()) } else { f32::ceil(tiles.max_x()) }; + let max_y = if bottom { f32::floor(tiles.max_y()) } else { f32::ceil(tiles.max_y()) }; + + Some(TileRange { + origin: point2(min_x as i32, min_y as i32), + size: size2((max_x - min_x) as i32, (max_y - min_y) as i32), + }) +} diff --git a/third_party/webrender/webrender_api/src/units.rs b/third_party/webrender/webrender_api/src/units.rs index 5ec6a80e920..9913e4e6647 100644 --- a/third_party/webrender/webrender_api/src/units.rs +++ b/third_party/webrender/webrender_api/src/units.rs @@ -32,7 +32,6 @@ pub type DeviceIntLength = Length; pub type DeviceIntSideOffsets = SideOffsets2D; pub type DeviceRect = Rect; -pub type DeviceBox2D = Box2D; pub type DevicePoint = Point2D; pub type DeviceVector2D = Vector2D; pub type DeviceSize = Size2D; @@ -150,7 +149,7 @@ pub type BlobToDeviceTranslation = Translation2D; /// may grow. Storing them as texel coords and normalizing /// the UVs in the vertex shader means nothing needs to be /// updated on the CPU when the texture size changes. -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct TexelRect { pub uv0: DevicePoint, pub uv1: DevicePoint, -- cgit v1.2.3