aboutsummaryrefslogtreecommitdiffstats
path: root/third_party/webrender/webrender_api/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/webrender/webrender_api/src')
-rw-r--r--third_party/webrender/webrender_api/src/api.rs2123
-rw-r--r--third_party/webrender/webrender_api/src/channel.rs131
-rw-r--r--third_party/webrender/webrender_api/src/color.rs160
-rw-r--r--third_party/webrender/webrender_api/src/display_item.rs1589
-rw-r--r--third_party/webrender/webrender_api/src/display_item_cache.rs115
-rw-r--r--third_party/webrender/webrender_api/src/display_list.rs1969
-rw-r--r--third_party/webrender/webrender_api/src/font.rs604
-rw-r--r--third_party/webrender/webrender_api/src/gradient_builder.rs178
-rw-r--r--third_party/webrender/webrender_api/src/image.rs595
-rw-r--r--third_party/webrender/webrender_api/src/image_tiling.rs815
-rw-r--r--third_party/webrender/webrender_api/src/lib.rs64
-rw-r--r--third_party/webrender/webrender_api/src/resources.rs327
-rw-r--r--third_party/webrender/webrender_api/src/units.rs324
13 files changed, 8994 insertions, 0 deletions
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..3e8a99e9215
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/api.rs
@@ -0,0 +1,2123 @@
+/* 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)]
+
+extern crate serde_bytes;
+
+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<SceneMsg>,
+ /// Operations affecting the generation of frames (applied after scene building).
+ frame_ops: Vec<FrameMsg>,
+
+ notifications: Vec<NotificationRequest>,
+
+ /// Persistent resource updates to apply as part of this transaction.
+ pub resource_updates: Vec<ResourceUpdate>,
+
+ /// 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<ColorF>,
+ 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<ResourceUpdate>) {
+ 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<PropertyValue<LayoutTransform>>) {
+ self.frame_ops.push(FrameMsg::AppendDynamicTransformProperties(transforms));
+ }
+
+ /// Consumes this object and just returns the frame ops.
+ pub fn get_frame_ops(self) -> Vec<FrameMsg> {
+ self.frame_ops
+ }
+
+ fn finalize(self, document_id: DocumentId) -> Box<TransactionMsg> {
+ 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<TileSize>,
+ ) {
+ 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<BlobImageData>,
+ visible_rect: DeviceIntRect,
+ tile_size: Option<TileSize>,
+ ) {
+ 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<BlobImageData>,
+ 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<u8>, 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<font::FontInstanceOptions>,
+ platform_options: Option<font::FontInstancePlatformOptions>,
+ variations: Vec<font::FontVariation>,
+ ) {
+ 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<SceneMsg>,
+ /// Changes to animated properties that do not require re-building the scene.
+ pub frame_ops: Vec<FrameMsg>,
+ /// Updates to resources that persist across display lists.
+ pub resource_updates: Vec<ResourceUpdate>,
+ /// 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<NotificationRequest>,
+ ///
+ pub blob_rasterizer: Option<Box<dyn AsyncBlobImageRasterizer>>,
+ ///
+ pub blob_requests: Vec<BlobImageParams>,
+ ///
+ 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<TileSize>,
+}
+
+/// 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<BlobImageData>,
+ /// 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<BlobImageData>,
+ /// 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<Vec<u8>>, 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<HitTestItem>,
+}
+
+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<font::FontInstanceOptions>,
+ ///
+ pub platform_options: Option<font::FontInstancePlatformOptions>,
+ ///
+ pub variations: Vec<font::FontVariation>,
+}
+
+/// 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<ColorF>,
+ ///
+ 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<PipelineId>, WorldPoint, HitTestFlags, Sender<HitTestResult>),
+ ///
+ RequestHitTester(Sender<Arc<dyn ApiHitTester>>),
+ ///
+ SetPan(DeviceIntPoint),
+ ///
+ Scroll(ScrollLocation, WorldPoint),
+ ///
+ ScrollNodeWithId(LayoutPoint, di::ExternalScrollId, ScrollClamping),
+ ///
+ GetScrollNodeState(Sender<Vec<ScrollNodeState>>),
+ ///
+ UpdateDynamicProperties(DynamicProperties),
+ ///
+ AppendDynamicTransformProperties(Vec<PropertyValue<LayoutTransform>>),
+ ///
+ 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<PipelineId>,
+}
+
+/// 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<CapturedDocument>),
+ /// 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<DeviceIntSize>),
+}
+
+/// 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<IdNamespace>),
+ /// 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<Box<TransactionMsg>>),
+ /// 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<Box<MemoryReport>>),
+ /// 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<Sender<()>>),
+}
+
+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<ColorU>,
+ },
+}
+
+/// 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<ApiMsg>,
+ blob_image_handler: Option<Box<dyn BlobImageHandler>>,
+ shared_font_instances: SharedFontInstanceMap,
+}
+
+impl RenderApiSender {
+ /// Used internally by the `Renderer`.
+ pub fn new(
+ api_sender: Sender<ApiMsg>,
+ blob_image_handler: Option<Box<dyn BlobImageHandler>>,
+ 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<ApiMsg>,
+ namespace_id: IdNamespace,
+ next_id: Cell<ResourceId>,
+ 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<font::GlyphIndex>,
+ ) -> Vec<Option<font::GlyphDimensions>> {
+ 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<Option<u32>> {
+ 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<T: Copy>(&self) -> PropertyBindingKey<T> {
+ 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<TransactionMsg> {
+ 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<TransactionMsg> {
+ 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<DocumentId>, mut transactions: Vec<Transaction>) {
+ 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<PipelineId>,
+ 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<ScrollNodeState> {
+ 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<CapturedDocument> {
+ // 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<Arc<dyn ApiHitTester>>,
+}
+
+impl HitTesterRequest {
+ /// Block until the hit tester is available and return it, consuming teh request.
+ pub fn resolve(self) -> Arc<dyn ApiHitTester> {
+ 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<T> {
+ ///
+ pub id: PropertyBindingId,
+ _phantom: PhantomData<T>,
+}
+
+/// Construct a property value from a given key and value.
+impl<T: Copy> PropertyBindingKey<T> {
+ ///
+ pub fn with(self, value: T) -> PropertyValue<T> {
+ PropertyValue { key: self, value }
+ }
+}
+
+impl<T> PropertyBindingKey<T> {
+ /// 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<T> {
+ /// Non-animated value.
+ Value(T),
+ /// Animated binding.
+ Binding(PropertyBindingKey<T>, T),
+}
+
+impl<T: Default> Default for PropertyBinding<T> {
+ fn default() -> Self {
+ PropertyBinding::Value(Default::default())
+ }
+}
+
+impl<T> From<T> for PropertyBinding<T> {
+ fn from(value: T) -> PropertyBinding<T> {
+ PropertyBinding::Value(value)
+ }
+}
+
+impl From<PropertyBindingKey<ColorF>> for PropertyBindingKey<ColorU> {
+ fn from(key: PropertyBindingKey<ColorF>) -> PropertyBindingKey<ColorU> {
+ PropertyBindingKey {
+ id: key.id.clone(),
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl From<PropertyBindingKey<ColorU>> for PropertyBindingKey<ColorF> {
+ fn from(key: PropertyBindingKey<ColorU>) -> PropertyBindingKey<ColorF> {
+ PropertyBindingKey {
+ id: key.id.clone(),
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl From<PropertyBinding<ColorF>> for PropertyBinding<ColorU> {
+ fn from(value: PropertyBinding<ColorF>) -> PropertyBinding<ColorU> {
+ match value {
+ PropertyBinding::Value(value) => PropertyBinding::Value(value.into()),
+ PropertyBinding::Binding(k, v) => {
+ PropertyBinding::Binding(k.into(), v.into())
+ }
+ }
+ }
+}
+
+impl From<PropertyBinding<ColorU>> for PropertyBinding<ColorF> {
+ fn from(value: PropertyBinding<ColorU>) -> PropertyBinding<ColorF> {
+ 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<T> {
+ ///
+ pub key: PropertyBindingKey<T>,
+ ///
+ 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<PropertyValue<LayoutTransform>>,
+ /// opacity
+ pub floats: Vec<PropertyValue<f32>>,
+ /// background color
+ pub colors: Vec<PropertyValue<ColorF>>,
+}
+
+/// A handler to integrate WebRender with the thread that contains the `Renderer`.
+pub trait RenderNotifier: Send {
+ ///
+ fn clone(&self) -> Box<dyn RenderNotifier>;
+ /// 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<u64>);
+ /// 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<Box<dyn NotificationHandler>>,
+ when: Checkpoint,
+}
+
+impl NotificationRequest {
+ /// Constructor.
+ pub fn new(when: Checkpoint, handler: Box<dyn NotificationHandler>) -> 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<PipelineId>, 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
new file mode 100644
index 00000000000..2bc4a5f16b8
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/channel.rs
@@ -0,0 +1,131 @@
+/* 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::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;
+use std::sync::mpsc;
+
+#[derive(Clone)]
+pub struct Payload {
+ /// An epoch used to get the proper payload for a pipeline id frame request.
+ ///
+ /// TODO(emilio): Is this still relevant? We send the messages for the same
+ /// pipeline in order, so we shouldn't need it. Seems like this was only
+ /// wallpapering (in most cases) the underlying problem in #991.
+ pub epoch: Epoch,
+ /// A pipeline id to key the payload with, along with the epoch.
+ pub pipeline_id: PipelineId,
+ pub display_list_data: Vec<u8>,
+}
+
+impl Payload {
+ /// Convert the payload to a raw byte vector, in order for it to be
+ /// efficiently shared via shmem, for example.
+ /// This is a helper static method working on a slice.
+ pub fn construct_data(epoch: Epoch, pipeline_id: PipelineId, dl_data: &[u8]) -> Vec<u8> {
+ let mut data = Vec::with_capacity(
+ mem::size_of::<u32>() + 2 * mem::size_of::<u32>() + mem::size_of::<u64>() + dl_data.len(),
+ );
+ data.write_u32::<LittleEndian>(epoch.0).unwrap();
+ data.write_u32::<LittleEndian>(pipeline_id.0).unwrap();
+ data.write_u32::<LittleEndian>(pipeline_id.1).unwrap();
+ data.write_u64::<LittleEndian>(dl_data.len() as u64)
+ .unwrap();
+ data.extend_from_slice(dl_data);
+ data
+ }
+ /// Convert the payload to a raw byte vector, in order for it to be
+ /// efficiently shared via shmem, for example.
+ pub fn to_data(&self) -> Vec<u8> {
+ Self::construct_data(self.epoch, self.pipeline_id, &self.display_list_data)
+ }
+
+ /// Deserializes the given payload from a raw byte vector.
+ pub fn from_data(data: &[u8]) -> Payload {
+ let mut payload_reader = Cursor::new(data);
+ let epoch = Epoch(payload_reader.read_u32::<LittleEndian>().unwrap());
+ let pipeline_id = PipelineId(
+ payload_reader.read_u32::<LittleEndian>().unwrap(),
+ payload_reader.read_u32::<LittleEndian>().unwrap(),
+ );
+
+ let dl_size = payload_reader.read_u64::<LittleEndian>().unwrap() as usize;
+ let mut built_display_list_data = vec![0; dl_size];
+ payload_reader
+ .read_exact(&mut built_display_list_data[..])
+ .unwrap();
+
+ assert_eq!(payload_reader.position(), data.len() as u64);
+
+ Payload {
+ epoch,
+ pipeline_id,
+ display_list_data: built_display_list_data,
+ }
+ }
+}
+
+pub type PayloadSender = MsgSender<Payload>;
+
+pub type PayloadReceiver = MsgReceiver<Payload>;
+
+pub struct MsgReceiver<T> {
+ rx: mpsc::Receiver<T>,
+}
+
+impl<T> MsgReceiver<T> {
+ pub fn recv(&self) -> Result<T, Error> {
+ self.rx.recv().map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))
+ }
+
+ pub fn to_mpsc_receiver(self) -> mpsc::Receiver<T> {
+ self.rx
+ }
+}
+
+#[derive(Clone)]
+pub struct MsgSender<T> {
+ tx: mpsc::Sender<T>,
+}
+
+impl<T> MsgSender<T> {
+ pub fn send(&self, data: T) -> Result<(), Error> {
+ self.tx.send(data).map_err(|_| Error::new(ErrorKind::Other, "cannot send on closed channel"))
+ }
+}
+
+pub fn payload_channel() -> Result<(PayloadSender, PayloadReceiver), Error> {
+ let (tx, rx) = mpsc::channel();
+ Ok((PayloadSender { tx }, PayloadReceiver { rx }))
+}
+
+pub fn msg_channel<T>() -> Result<(MsgSender<T>, MsgReceiver<T>), Error> {
+ let (tx, rx) = mpsc::channel();
+ Ok((MsgSender { tx }, MsgReceiver { rx }))
+}
+
+///
+/// These serialize methods are needed to satisfy the compiler
+/// which uses these implementations for the recording tool.
+/// The recording tool only outputs messages that don't contain
+/// Senders or Receivers, so in theory these should never be
+/// called in the in-process config. If they are called,
+/// there may be a bug in the messages that the replay tool is writing.
+///
+
+impl<T> Serialize for MsgSender<T> {
+ fn serialize<S: Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
+ unreachable!();
+ }
+}
+
+impl<'de, T> Deserialize<'de> for MsgSender<T> {
+ fn deserialize<D>(_: D) -> Result<MsgSender<T>, D::Error>
+ where D: Deserializer<'de> {
+ unreachable!();
+ }
+}
diff --git a/third_party/webrender/webrender_api/src/color.rs b/third_party/webrender/webrender_api/src/color.rs
new file mode 100644
index 00000000000..f19f83bb3d0
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/color.rs
@@ -0,0 +1,160 @@
+/* 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 peek_poke::PeekPoke;
+use std::cmp;
+use std::hash::{Hash, Hasher};
+
+/// Represents pre-multiplied RGBA colors with floating point numbers.
+///
+/// All components must be between 0.0 and 1.0.
+/// An alpha value of 1.0 is opaque while 0.0 is fully transparent.
+///
+/// In premultiplied colors transitions to transparent always look "nice"
+/// therefore they are used in CSS gradients.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
+pub struct PremultipliedColorF {
+ pub r: f32,
+ pub g: f32,
+ pub b: f32,
+ pub a: f32,
+}
+
+#[allow(missing_docs)]
+impl PremultipliedColorF {
+ pub const BLACK: PremultipliedColorF = PremultipliedColorF { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
+ pub const TRANSPARENT: PremultipliedColorF = PremultipliedColorF { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
+ pub const WHITE: PremultipliedColorF = PremultipliedColorF { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
+
+ pub fn to_array(&self) -> [f32; 4] {
+ [self.r, self.g, self.b, self.a]
+ }
+}
+
+/// Represents RGBA screen colors with floating point numbers.
+///
+/// All components must be between 0.0 and 1.0.
+/// An alpha value of 1.0 is opaque while 0.0 is fully transparent.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub struct ColorF {
+ pub r: f32,
+ pub g: f32,
+ pub b: f32,
+ pub a: f32,
+}
+
+#[allow(missing_docs)]
+impl ColorF {
+ pub const BLACK: ColorF = ColorF { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
+ pub const TRANSPARENT: ColorF = ColorF { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
+ pub const WHITE: ColorF = ColorF { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
+
+ /// Constructs a new `ColorF` from its components.
+ pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
+ ColorF { r, g, b, a }
+ }
+
+ /// Multiply the RGB channels (but not alpha) with a given factor.
+ pub fn scale_rgb(&self, scale: f32) -> Self {
+ ColorF {
+ r: self.r * scale,
+ g: self.g * scale,
+ b: self.b * scale,
+ a: self.a,
+ }
+ }
+
+ // Scale the alpha by a given factor.
+ pub fn scale_alpha(&self, scale: f32) -> Self {
+ ColorF {
+ r: self.r,
+ g: self.g,
+ b: self.b,
+ a: self.a * scale,
+ }
+ }
+
+ pub fn to_array(&self) -> [f32; 4] {
+ [self.r, self.g, self.b, self.a]
+ }
+
+ /// Multiply the RGB components with the alpha channel.
+ pub fn premultiplied(&self) -> PremultipliedColorF {
+ let c = self.scale_rgb(self.a);
+ PremultipliedColorF { r: c.r, g: c.g, b: c.b, a: c.a }
+ }
+}
+
+// Floats don't impl Hash/Eq/Ord...
+impl Eq for PremultipliedColorF {}
+impl Ord for PremultipliedColorF {
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ self.partial_cmp(other).unwrap_or(cmp::Ordering::Equal)
+ }
+}
+
+#[cfg_attr(feature = "cargo-clippy", allow(clippy::derive_hash_xor_eq))]
+impl Hash for PremultipliedColorF {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // Note: this is inconsistent with the Eq impl for -0.0 (don't care).
+ self.r.to_bits().hash(state);
+ self.g.to_bits().hash(state);
+ self.b.to_bits().hash(state);
+ self.a.to_bits().hash(state);
+ }
+}
+
+/// Represents RGBA screen colors with one byte per channel.
+///
+/// If the alpha value `a` is 255 the color is opaque.
+#[repr(C)]
+#[derive(Clone, Copy, Hash, Eq, Debug, Deserialize, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)]
+pub struct ColorU {
+ pub r: u8,
+ pub g: u8,
+ pub b: u8,
+ pub a: u8,
+}
+
+impl ColorU {
+ /// Constructs a new additive `ColorU` from its components.
+ pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
+ ColorU { r, g, b, a }
+ }
+}
+
+fn round_to_int(x: f32) -> u8 {
+ debug_assert!((0.0 <= x) && (x <= 1.0), "{} should be between 0 and 1", x);
+ let f = (255.0 * x) + 0.5;
+ let val = f.floor();
+ debug_assert!(val <= 255.0);
+ val as u8
+}
+
+// TODO: We shouldn't really convert back to `ColorU` ever,
+// since it's lossy. One of the blockers is that all of our debug colors
+// are specified in `ColorF`. Changing it to `ColorU` would be nice.
+impl From<ColorF> for ColorU {
+ fn from(color: ColorF) -> Self {
+ ColorU {
+ r: round_to_int(color.r),
+ g: round_to_int(color.g),
+ b: round_to_int(color.b),
+ a: round_to_int(color.a),
+ }
+ }
+}
+
+impl From<ColorU> for ColorF {
+ fn from(color: ColorU) -> Self {
+ ColorF {
+ r: color.r as f32 / 255.0,
+ g: color.g as f32 / 255.0,
+ b: color.b as f32 / 255.0,
+ a: color.a as f32 / 255.0,
+ }
+ }
+}
diff --git a/third_party/webrender/webrender_api/src/display_item.rs b/third_party/webrender/webrender_api/src/display_item.rs
new file mode 100644
index 00000000000..e6b8e036e46
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/display_item.rs
@@ -0,0 +1,1589 @@
+/* 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 euclid::SideOffsets2D;
+use peek_poke::PeekPoke;
+use std::ops::Not;
+// local imports
+use crate::font;
+use crate::api::{PipelineId, PropertyBinding};
+use crate::color::ColorF;
+use crate::image::{ColorDepth, ImageKey};
+use crate::units::*;
+
+// ******************************************************************
+// * NOTE: some of these structs have an "IMPLICIT" comment. *
+// * This indicates that the BuiltDisplayList will have serialized *
+// * a list of values nearby that this item consumes. The traversal *
+// * iterator should handle finding these. DebugDisplayItem should *
+// * make them explicit. *
+// ******************************************************************
+
+/// A tag that can be used to identify items during hit testing. If the tag
+/// is missing then the item doesn't take part in hit testing at all. This
+/// is composed of two numbers. In Servo, the first is an identifier while the
+/// second is used to select the cursor that should be used during mouse
+/// movement. In Gecko, the first is a scrollframe identifier, while the second
+/// is used to store various flags that APZ needs to properly process input
+/// events.
+pub type ItemTag = (u64, u16);
+
+/// An identifier used to refer to previously sent display items. Currently it
+/// refers to individual display items, but this may change later.
+pub type ItemKey = u16;
+
+bitflags! {
+ #[repr(C)]
+ #[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)]
+ pub struct PrimitiveFlags: u8 {
+ /// The CSS backface-visibility property (yes, it can be really granular)
+ const IS_BACKFACE_VISIBLE = 1 << 0;
+ /// If set, this primitive represents a scroll bar container
+ const IS_SCROLLBAR_CONTAINER = 1 << 1;
+ /// If set, this primitive represents a scroll bar thumb
+ const IS_SCROLLBAR_THUMB = 1 << 2;
+ /// This is used as a performance hint - this primitive may be promoted to a native
+ /// compositor surface under certain (implementation specific) conditions. This
+ /// is typically used for large videos, and canvas elements.
+ const PREFER_COMPOSITOR_SURFACE = 1 << 3;
+ }
+}
+
+impl Default for PrimitiveFlags {
+ fn default() -> Self {
+ PrimitiveFlags::IS_BACKFACE_VISIBLE
+ }
+}
+
+/// A grouping of fields a lot of display items need, just to avoid
+/// repeating these over and over in this file.
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct CommonItemProperties {
+ /// Bounds of the display item to clip to. Many items are logically
+ /// infinite, and rely on this clip_rect to define their bounds
+ /// (solid colors, background-images, gradients, etc).
+ pub clip_rect: LayoutRect,
+ /// Additional clips
+ 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<ItemTag>,
+ /// Various flags describing properties of this primitive.
+ pub flags: PrimitiveFlags,
+}
+
+impl CommonItemProperties {
+ /// Convenience for tests.
+ pub fn new(
+ clip_rect: LayoutRect,
+ space_and_clip: SpaceAndClipInfo,
+ ) -> Self {
+ Self {
+ clip_rect,
+ spatial_id: space_and_clip.spatial_id,
+ clip_id: space_and_clip.clip_id,
+ hit_info: None,
+ flags: PrimitiveFlags::default(),
+ }
+ }
+}
+
+/// Per-primitive information about the nodes in the clip tree and
+/// the spatial tree that the primitive belongs to.
+///
+/// Note: this is a separate struct from `PrimitiveInfo` because
+/// it needs indirectional mapping during the DL flattening phase,
+/// turning into `ScrollNodeAndClipChain`.
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct SpaceAndClipInfo {
+ pub spatial_id: SpatialId,
+ pub clip_id: ClipId,
+}
+
+impl SpaceAndClipInfo {
+ /// Create a new space/clip info associated with the root
+ /// scroll frame.
+ pub fn root_scroll(pipeline_id: PipelineId) -> Self {
+ SpaceAndClipInfo {
+ spatial_id: SpatialId::root_scroll_node(pipeline_id),
+ clip_id: ClipId::root(pipeline_id),
+ }
+ }
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub enum DisplayItem {
+ // These are the "real content" display items
+ Rectangle(RectangleDisplayItem),
+ ClearRectangle(ClearRectangleDisplayItem),
+ HitTest(HitTestDisplayItem),
+ Text(TextDisplayItem),
+ Line(LineDisplayItem),
+ Border(BorderDisplayItem),
+ BoxShadow(BoxShadowDisplayItem),
+ PushShadow(PushShadowDisplayItem),
+ Gradient(GradientDisplayItem),
+ RadialGradient(RadialGradientDisplayItem),
+ ConicGradient(ConicGradientDisplayItem),
+ Image(ImageDisplayItem),
+ RepeatingImage(RepeatingImageDisplayItem),
+ YuvImage(YuvImageDisplayItem),
+ BackdropFilter(BackdropFilterDisplayItem),
+
+ // Clips
+ RectClip(RectClipDisplayItem),
+ RoundedRectClip(RoundedRectClipDisplayItem),
+ ImageMaskClip(ImageMaskClipDisplayItem),
+ Clip(ClipDisplayItem),
+ ClipChain(ClipChainItem),
+
+ // Spaces and Frames that content can be scoped under.
+ ScrollFrame(ScrollFrameDisplayItem),
+ StickyFrame(StickyFrameDisplayItem),
+ Iframe(IframeDisplayItem),
+ PushReferenceFrame(ReferenceFrameDisplayListItem),
+ PushStackingContext(PushStackingContextDisplayItem),
+
+ // These marker items indicate an array of data follows, to be used for the
+ // next non-marker item.
+ SetGradientStops,
+ SetFilterOps,
+ SetFilterData,
+ SetFilterPrimitives,
+
+ // These marker items terminate a scope introduced by a previous item.
+ PopReferenceFrame,
+ PopStackingContext,
+ PopAllShadows,
+
+ ReuseItems(ItemKey),
+ RetainedItems(ItemKey),
+}
+
+/// This is a "complete" version of the DisplayItem, with all implicit trailing
+/// arrays included, for debug serialization (captures).
+#[cfg(any(feature = "serialize", feature = "deserialize"))]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "deserialize", derive(Deserialize))]
+pub enum DebugDisplayItem {
+ Rectangle(RectangleDisplayItem),
+ ClearRectangle(ClearRectangleDisplayItem),
+ HitTest(HitTestDisplayItem),
+ Text(TextDisplayItem, Vec<font::GlyphInstance>),
+ Line(LineDisplayItem),
+ Border(BorderDisplayItem),
+ BoxShadow(BoxShadowDisplayItem),
+ PushShadow(PushShadowDisplayItem),
+ Gradient(GradientDisplayItem),
+ RadialGradient(RadialGradientDisplayItem),
+ ConicGradient(ConicGradientDisplayItem),
+ Image(ImageDisplayItem),
+ RepeatingImage(RepeatingImageDisplayItem),
+ YuvImage(YuvImageDisplayItem),
+ BackdropFilter(BackdropFilterDisplayItem),
+
+ ImageMaskClip(ImageMaskClipDisplayItem),
+ RoundedRectClip(RoundedRectClipDisplayItem),
+ RectClip(RectClipDisplayItem),
+ Clip(ClipDisplayItem, Vec<ComplexClipRegion>),
+ ClipChain(ClipChainItem, Vec<ClipId>),
+
+ ScrollFrame(ScrollFrameDisplayItem),
+ StickyFrame(StickyFrameDisplayItem),
+ Iframe(IframeDisplayItem),
+ PushReferenceFrame(ReferenceFrameDisplayListItem),
+ PushStackingContext(PushStackingContextDisplayItem),
+
+ SetGradientStops(Vec<GradientStop>),
+ SetFilterOps(Vec<FilterOp>),
+ SetFilterData(FilterData),
+ SetFilterPrimitives(Vec<FilterPrimitive>),
+
+ PopReferenceFrame,
+ PopStackingContext,
+ PopAllShadows,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ImageMaskClipDisplayItem {
+ pub id: ClipId,
+ pub parent_space_and_clip: SpaceAndClipInfo,
+ pub image_mask: ImageMask,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct RectClipDisplayItem {
+ pub id: ClipId,
+ pub parent_space_and_clip: SpaceAndClipInfo,
+ pub clip_rect: LayoutRect,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct RoundedRectClipDisplayItem {
+ pub id: ClipId,
+ pub parent_space_and_clip: SpaceAndClipInfo,
+ pub clip: ComplexClipRegion,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ClipDisplayItem {
+ pub id: ClipId,
+ pub parent_space_and_clip: SpaceAndClipInfo,
+ pub clip_rect: LayoutRect,
+} // IMPLICIT: complex_clips: Vec<ComplexClipRegion>
+
+/// The minimum and maximum allowable offset for a sticky frame in a single dimension.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct StickyOffsetBounds {
+ /// The minimum offset for this frame, typically a negative value, which specifies how
+ /// far in the negative direction the sticky frame can offset its contents in this
+ /// dimension.
+ pub min: f32,
+
+ /// The maximum offset for this frame, typically a positive value, which specifies how
+ /// far in the positive direction the sticky frame can offset its contents in this
+ /// dimension.
+ pub max: f32,
+}
+
+impl StickyOffsetBounds {
+ pub fn new(min: f32, max: f32) -> StickyOffsetBounds {
+ StickyOffsetBounds { min, max }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct StickyFrameDisplayItem {
+ pub id: SpatialId,
+ pub parent_spatial_id: SpatialId,
+ pub bounds: LayoutRect,
+
+ /// The margins that should be maintained between the edge of the parent viewport and this
+ /// sticky frame. A margin of None indicates that the sticky frame should not stick at all
+ /// to that particular edge of the viewport.
+ pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
+
+ /// The minimum and maximum vertical offsets for this sticky frame. Ignoring these constraints,
+ /// the sticky frame will continue to stick to the edge of the viewport as its original
+ /// position is scrolled out of view. Constraints specify a maximum and minimum offset from the
+ /// original position relative to non-sticky content within the same scrolling frame.
+ pub vertical_offset_bounds: StickyOffsetBounds,
+
+ /// The minimum and maximum horizontal offsets for this sticky frame. Ignoring these constraints,
+ /// the sticky frame will continue to stick to the edge of the viewport as its original
+ /// position is scrolled out of view. Constraints specify a maximum and minimum offset from the
+ /// original position relative to non-sticky content within the same scrolling frame.
+ pub horizontal_offset_bounds: StickyOffsetBounds,
+
+ /// The amount of offset that has already been applied to the sticky frame. A positive y
+ /// component this field means that a top-sticky item was in a scrollframe that has been
+ /// scrolled down, such that the sticky item's position needed to be offset downwards by
+ /// `previously_applied_offset.y`. A negative y component corresponds to the upward offset
+ /// applied due to bottom-stickiness. The x-axis works analogously.
+ pub previously_applied_offset: LayoutVector2D,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub enum ScrollSensitivity {
+ ScriptAndInputEvents,
+ Script,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ScrollFrameDisplayItem {
+ /// The id of the clip this scroll frame creates
+ pub clip_id: ClipId,
+ /// The id of the space this scroll frame creates
+ pub scroll_frame_id: SpatialId,
+ /// The size of the contents this contains (so the backend knows how far it can scroll).
+ // FIXME: this can *probably* just be a size? Origin seems to just get thrown out.
+ pub content_rect: LayoutRect,
+ pub clip_rect: LayoutRect,
+ pub parent_space_and_clip: SpaceAndClipInfo,
+ pub external_id: Option<ExternalScrollId>,
+ 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
+ /// will have their coordinates shifted by this amount, and this offset
+ /// should be added to those display item coordinates in order to get a
+ /// normalized value that is consistent across display lists.
+ pub external_scroll_offset: LayoutVector2D,
+}
+
+/// A solid or an animating color to draw (may not actually be a rectangle due to complex clips)
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct RectangleDisplayItem {
+ pub common: CommonItemProperties,
+ pub bounds: LayoutRect,
+ pub color: PropertyBinding<ColorF>,
+}
+
+/// Clears all colors from the area, making it possible to cut holes in the window.
+/// (useful for things like the macos frosted-glass effect).
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ClearRectangleDisplayItem {
+ pub common: CommonItemProperties,
+ pub bounds: LayoutRect,
+}
+
+/// A minimal hit-testable item for the parent browser's convenience, and is
+/// slimmer than a RectangleDisplayItem (no color). The existence of this as a
+/// distinct item also makes it easier to inspect/debug display items.
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct HitTestDisplayItem {
+ pub common: CommonItemProperties,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct LineDisplayItem {
+ pub common: CommonItemProperties,
+ /// We need a separate rect from common.clip_rect to encode cute
+ /// tricks that firefox does to make a series of text-decorations seamlessly
+ /// line up -- snapping the decorations to a multiple of their period, and
+ /// then clipping them to their "proper" area. This rect is that "logical"
+ /// snapped area that may be clipped to the right size by the clip_rect.
+ pub area: LayoutRect,
+ /// Whether the rect is interpretted as vertical or horizontal
+ pub orientation: LineOrientation,
+ /// This could potentially be implied from area, but we currently prefer
+ /// that this is the responsibility of the layout engine. Value irrelevant
+ /// for non-wavy lines.
+ // FIXME: this was done before we could use tagged unions in enums, but now
+ // it should just be part of LineStyle::Wavy.
+ pub wavy_line_thickness: f32,
+ pub color: ColorF,
+ pub style: LineStyle,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)]
+pub enum LineOrientation {
+ Vertical,
+ Horizontal,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)]
+pub enum LineStyle {
+ Solid,
+ Dotted,
+ Dashed,
+ Wavy,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct TextDisplayItem {
+ pub common: CommonItemProperties,
+ /// The area all the glyphs should be found in. Strictly speaking this isn't
+ /// necessarily needed, but layout engines should already "know" this, and we
+ /// use it cull and size things quickly before glyph layout is done. Currently
+ /// the glyphs *can* be outside these bounds, but that should imply they
+ /// can be cut off.
+ // FIXME: these are currently sometimes ignored to keep some old wrench tests
+ // working, but we should really just fix the tests!
+ pub bounds: LayoutRect,
+ pub font_key: font::FontInstanceKey,
+ pub color: ColorF,
+ pub glyph_options: Option<font::GlyphOptions>,
+} // IMPLICIT: glyphs: Vec<font::GlyphInstance>
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub struct NormalBorder {
+ pub left: BorderSide,
+ pub right: BorderSide,
+ pub top: BorderSide,
+ pub bottom: BorderSide,
+ pub radius: BorderRadius,
+ /// Whether to apply anti-aliasing on the border corners.
+ ///
+ /// Note that for this to be `false` and work, this requires the borders to
+ /// be solid, and no border-radius.
+ pub do_aa: bool,
+}
+
+impl NormalBorder {
+ fn can_disable_antialiasing(&self) -> bool {
+ fn is_valid(style: BorderStyle) -> bool {
+ style == BorderStyle::Solid || style == BorderStyle::None
+ }
+
+ self.radius.is_zero() &&
+ is_valid(self.top.style) &&
+ is_valid(self.left.style) &&
+ is_valid(self.bottom.style) &&
+ is_valid(self.right.style)
+ }
+
+ /// Normalizes a border so that we don't render disallowed stuff, like inset
+ /// borders that are less than two pixels wide.
+ #[inline]
+ pub fn normalize(&mut self, widths: &LayoutSideOffsets) {
+ debug_assert!(
+ self.do_aa || self.can_disable_antialiasing(),
+ "Unexpected disabled-antialiasing in a border, likely won't work or will be ignored"
+ );
+
+ #[inline]
+ fn renders_small_border_solid(style: BorderStyle) -> bool {
+ match style {
+ BorderStyle::Groove |
+ BorderStyle::Ridge => true,
+ _ => false,
+ }
+ }
+
+ let normalize_side = |side: &mut BorderSide, width: f32| {
+ if renders_small_border_solid(side.style) && width < 2. {
+ side.style = BorderStyle::Solid;
+ }
+ };
+
+ normalize_side(&mut self.left, widths.left);
+ normalize_side(&mut self.right, widths.right);
+ normalize_side(&mut self.top, widths.top);
+ normalize_side(&mut self.bottom, widths.bottom);
+ }
+}
+
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq, Serialize, Deserialize, Eq, Hash, PeekPoke)]
+pub enum RepeatMode {
+ Stretch,
+ Repeat,
+ Round,
+ Space,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub enum NinePatchBorderSource {
+ Image(ImageKey),
+ Gradient(Gradient),
+ RadialGradient(RadialGradient),
+ ConicGradient(ConicGradient),
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct NinePatchBorder {
+ /// Describes what to use as the 9-patch source image. If this is an image,
+ /// it will be stretched to fill the size given by width x height.
+ pub source: NinePatchBorderSource,
+
+ /// The width of the 9-part image.
+ pub width: i32,
+
+ /// The height of the 9-part image.
+ pub height: i32,
+
+ /// Distances from each edge where the image should be sliced up. These
+ /// values are in 9-part-image space (the same space as width and height),
+ /// and the resulting image parts will be used to fill the corresponding
+ /// parts of the border as given by the border widths. This can lead to
+ /// stretching.
+ /// Slices can be overlapping. In that case, the same pixels from the
+ /// 9-part image will show up in multiple parts of the resulting border.
+ pub slice: DeviceIntSideOffsets,
+
+ /// Controls whether the center of the 9 patch image is rendered or
+ /// ignored. The center is never rendered if the slices are overlapping.
+ pub fill: bool,
+
+ /// Determines what happens if the horizontal side parts of the 9-part
+ /// image have a different size than the horizontal parts of the border.
+ pub repeat_horizontal: RepeatMode,
+
+ /// Determines what happens if the vertical side parts of the 9-part
+ /// image have a different size than the vertical parts of the border.
+ pub repeat_vertical: RepeatMode,
+
+ /// The outset for the border.
+ /// TODO(mrobinson): This should be removed and handled by the client.
+ pub outset: LayoutSideOffsets, // TODO: what unit is this in?
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub enum BorderDetails {
+ Normal(NormalBorder),
+ NinePatch(NinePatchBorder),
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct BorderDisplayItem {
+ pub common: CommonItemProperties,
+ pub bounds: LayoutRect,
+ pub widths: LayoutSideOffsets,
+ pub details: BorderDetails,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub enum BorderRadiusKind {
+ Uniform,
+ NonUniform,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub struct BorderRadius {
+ pub top_left: LayoutSize,
+ pub top_right: LayoutSize,
+ pub bottom_left: LayoutSize,
+ pub bottom_right: LayoutSize,
+}
+
+impl Default for BorderRadius {
+ fn default() -> Self {
+ BorderRadius {
+ top_left: LayoutSize::zero(),
+ top_right: LayoutSize::zero(),
+ bottom_left: LayoutSize::zero(),
+ bottom_right: LayoutSize::zero(),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub struct BorderSide {
+ pub color: ColorF,
+ pub style: BorderStyle,
+}
+
+#[repr(u32)]
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, Hash, Eq, PeekPoke)]
+pub enum BorderStyle {
+ None = 0,
+ Solid = 1,
+ Double = 2,
+ Dotted = 3,
+ Dashed = 4,
+ Hidden = 5,
+ Groove = 6,
+ Ridge = 7,
+ Inset = 8,
+ Outset = 9,
+}
+
+impl BorderStyle {
+ pub fn is_hidden(self) -> bool {
+ self == BorderStyle::Hidden || self == BorderStyle::None
+ }
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum BoxShadowClipMode {
+ Outset = 0,
+ Inset = 1,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct BoxShadowDisplayItem {
+ pub common: CommonItemProperties,
+ pub box_bounds: LayoutRect,
+ pub offset: LayoutVector2D,
+ pub color: ColorF,
+ pub blur_radius: f32,
+ pub spread_radius: f32,
+ pub border_radius: BorderRadius,
+ pub clip_mode: BoxShadowClipMode,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct PushShadowDisplayItem {
+ pub space_and_clip: SpaceAndClipInfo,
+ pub shadow: Shadow,
+ pub should_inflate: bool,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct Shadow {
+ pub offset: LayoutVector2D,
+ pub color: ColorF,
+ pub blur_radius: f32,
+}
+
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, Hash, Eq, MallocSizeOf, PartialEq, Serialize, Deserialize, Ord, PartialOrd, PeekPoke)]
+pub enum ExtendMode {
+ Clamp,
+ Repeat,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct Gradient {
+ pub start_point: LayoutPoint,
+ pub end_point: LayoutPoint,
+ pub extend_mode: ExtendMode,
+} // IMPLICIT: stops: Vec<GradientStop>
+
+/// The area
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct GradientDisplayItem {
+ /// NOTE: common.clip_rect is the area the gradient covers
+ pub common: CommonItemProperties,
+ /// The area to tile the gradient over (first tile starts at origin of this rect)
+ // FIXME: this should ideally just be `tile_origin` here, with the clip_rect
+ // defining the bounds of the item. Needs non-trivial backend changes.
+ pub bounds: LayoutRect,
+ /// How big a tile of the of the gradient should be (common case: bounds.size)
+ pub tile_size: LayoutSize,
+ /// The space between tiles of the gradient (common case: 0)
+ pub tile_spacing: LayoutSize,
+ pub gradient: Gradient,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub struct GradientStop {
+ pub offset: f32,
+ pub color: ColorF,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct RadialGradient {
+ pub center: LayoutPoint,
+ pub radius: LayoutSize,
+ pub start_offset: f32,
+ pub end_offset: f32,
+ pub extend_mode: ExtendMode,
+} // IMPLICIT stops: Vec<GradientStop>
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ConicGradient {
+ pub center: LayoutPoint,
+ pub angle: f32,
+ pub start_offset: f32,
+ pub end_offset: f32,
+ pub extend_mode: ExtendMode,
+} // IMPLICIT stops: Vec<GradientStop>
+
+/// 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 {
+ pub id: ClipChainId,
+ pub parent: Option<ClipChainId>,
+} // IMPLICIT clip_ids: Vec<ClipId>
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct RadialGradientDisplayItem {
+ pub common: CommonItemProperties,
+ /// The area to tile the gradient over (first tile starts at origin of this rect)
+ // FIXME: this should ideally just be `tile_origin` here, with the clip_rect
+ // defining the bounds of the item. Needs non-trivial backend changes.
+ pub bounds: LayoutRect,
+ pub gradient: RadialGradient,
+ pub tile_size: LayoutSize,
+ pub tile_spacing: LayoutSize,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ConicGradientDisplayItem {
+ pub common: CommonItemProperties,
+ /// The area to tile the gradient over (first tile starts at origin of this rect)
+ // FIXME: this should ideally just be `tile_origin` here, with the clip_rect
+ // defining the bounds of the item. Needs non-trivial backend changes.
+ pub bounds: LayoutRect,
+ pub gradient: ConicGradient,
+ pub tile_size: LayoutSize,
+ pub tile_spacing: LayoutSize,
+}
+
+/// Renders a filtered region of its backdrop
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct BackdropFilterDisplayItem {
+ pub common: CommonItemProperties,
+}
+// IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>, filter_primitives: Vec<FilterPrimitive>
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ReferenceFrameDisplayListItem {
+ pub origin: LayoutPoint,
+ pub parent_spatial_id: SpatialId,
+ pub reference_frame: ReferenceFrame,
+}
+
+#[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,
+ /// A perspective transform, that optionally scrolls relative to a specific scroll node
+ Perspective {
+ scrolling_relative_to: Option<ExternalScrollId>,
+ }
+}
+
+#[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: PropertyBinding<LayoutTransform>,
+ pub id: SpatialId,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct PushStackingContextDisplayItem {
+ pub origin: LayoutPoint,
+ pub spatial_id: SpatialId,
+ pub prim_flags: PrimitiveFlags,
+ pub stacking_context: StackingContext,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct StackingContext {
+ pub transform_style: TransformStyle,
+ pub mix_blend_mode: MixBlendMode,
+ pub clip_id: Option<ClipId>,
+ pub raster_space: RasterSpace,
+ pub flags: StackingContextFlags,
+}
+// IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>, filter_primitives: Vec<FilterPrimitive>
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)]
+pub enum TransformStyle {
+ Flat = 0,
+ Preserve3D = 1,
+}
+
+/// Configure whether the contents of a stacking context
+/// should be rasterized in local space or screen space.
+/// Local space rasterized pictures are typically used
+/// 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, Serialize, PeekPoke)]
+#[repr(u8)]
+pub enum RasterSpace {
+ // Rasterize in local-space, applying supplied scale to primitives.
+ // Best performance, but lower quality.
+ Local(f32),
+
+ // Rasterize the picture in screen-space, including rotation / skew etc in
+ // the rasterized element. Best quality, but slower performance. Note that
+ // any stacking context with a perspective transform will be rasterized
+ // in local-space, even if this is set.
+ Screen,
+}
+
+impl RasterSpace {
+ pub fn local_scale(self) -> Option<f32> {
+ match self {
+ RasterSpace::Local(scale) => Some(scale),
+ RasterSpace::Screen => None,
+ }
+ }
+}
+
+bitflags! {
+ #[repr(C)]
+ #[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)]
+ pub struct StackingContextFlags: u8 {
+ /// If true, this stacking context represents a backdrop root, per the CSS
+ /// filter-effects specification (see https://drafts.fxtf.org/filter-effects-2/#BackdropRoot).
+ const IS_BACKDROP_ROOT = 1 << 0;
+ /// If true, this stacking context is a blend container than contains
+ /// mix-blend-mode children (and should thus be isolated).
+ const IS_BLEND_CONTAINER = 1 << 1;
+ }
+}
+
+impl Default for StackingContextFlags {
+ fn default() -> Self {
+ StackingContextFlags::empty()
+ }
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum MixBlendMode {
+ Normal = 0,
+ Multiply = 1,
+ Screen = 2,
+ Overlay = 3,
+ Darken = 4,
+ Lighten = 5,
+ ColorDodge = 6,
+ ColorBurn = 7,
+ HardLight = 8,
+ SoftLight = 9,
+ Difference = 10,
+ Exclusion = 11,
+ Hue = 12,
+ Saturation = 13,
+ Color = 14,
+ Luminosity = 15,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum ColorSpace {
+ Srgb,
+ LinearRgb,
+}
+
+/// Available composite operoations for the composite filter primitive
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum CompositeOperator {
+ Over,
+ In,
+ Atop,
+ Out,
+ Xor,
+ Lighter,
+ Arithmetic([f32; 4]),
+}
+
+impl CompositeOperator {
+ // This must stay in sync with the composite operator defines in cs_svg_filter.glsl
+ pub fn as_int(&self) -> u32 {
+ match self {
+ CompositeOperator::Over => 0,
+ CompositeOperator::In => 1,
+ CompositeOperator::Out => 2,
+ CompositeOperator::Atop => 3,
+ CompositeOperator::Xor => 4,
+ CompositeOperator::Lighter => 5,
+ CompositeOperator::Arithmetic(..) => 6,
+ }
+ }
+}
+
+/// An input to a SVG filter primitive.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum FilterPrimitiveInput {
+ /// The input is the original graphic that the filter is being applied to.
+ Original,
+ /// The input is the output of the previous filter primitive in the filter primitive chain.
+ Previous,
+ /// The input is the output of the filter primitive at the given index in the filter primitive chain.
+ OutputOfPrimitiveIndex(usize),
+}
+
+impl FilterPrimitiveInput {
+ /// Gets the index of the input.
+ /// Returns `None` if the source graphic is the input.
+ pub fn to_index(self, cur_index: usize) -> Option<usize> {
+ match self {
+ FilterPrimitiveInput::Previous if cur_index > 0 => Some(cur_index - 1),
+ FilterPrimitiveInput::OutputOfPrimitiveIndex(index) => Some(index),
+ _ => None,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct BlendPrimitive {
+ pub input1: FilterPrimitiveInput,
+ pub input2: FilterPrimitiveInput,
+ pub mode: MixBlendMode,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct FloodPrimitive {
+ pub color: ColorF,
+}
+
+impl FloodPrimitive {
+ pub fn sanitize(&mut self) {
+ self.color.r = self.color.r.min(1.0).max(0.0);
+ self.color.g = self.color.g.min(1.0).max(0.0);
+ self.color.b = self.color.b.min(1.0).max(0.0);
+ self.color.a = self.color.a.min(1.0).max(0.0);
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct BlurPrimitive {
+ pub input: FilterPrimitiveInput,
+ pub radius: f32,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct OpacityPrimitive {
+ pub input: FilterPrimitiveInput,
+ pub opacity: f32,
+}
+
+impl OpacityPrimitive {
+ pub fn sanitize(&mut self) {
+ self.opacity = self.opacity.min(1.0).max(0.0);
+ }
+}
+
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ColorMatrixPrimitive {
+ pub input: FilterPrimitiveInput,
+ pub matrix: [f32; 20],
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct DropShadowPrimitive {
+ pub input: FilterPrimitiveInput,
+ pub shadow: Shadow,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ComponentTransferPrimitive {
+ pub input: FilterPrimitiveInput,
+ // Component transfer data is stored in FilterData.
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct IdentityPrimitive {
+ pub input: FilterPrimitiveInput,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct OffsetPrimitive {
+ pub input: FilterPrimitiveInput,
+ pub offset: LayoutVector2D,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct CompositePrimitive {
+ pub input1: FilterPrimitiveInput,
+ pub input2: FilterPrimitiveInput,
+ pub operator: CompositeOperator,
+}
+
+/// See: https://github.com/eqrion/cbindgen/issues/9
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub enum FilterPrimitiveKind {
+ Identity(IdentityPrimitive),
+ Blend(BlendPrimitive),
+ Flood(FloodPrimitive),
+ Blur(BlurPrimitive),
+ // TODO: Support animated opacity?
+ Opacity(OpacityPrimitive),
+ /// cbindgen:derive-eq=false
+ ColorMatrix(ColorMatrixPrimitive),
+ DropShadow(DropShadowPrimitive),
+ ComponentTransfer(ComponentTransferPrimitive),
+ Offset(OffsetPrimitive),
+ Composite(CompositePrimitive),
+}
+
+impl Default for FilterPrimitiveKind {
+ fn default() -> Self {
+ FilterPrimitiveKind::Identity(IdentityPrimitive::default())
+ }
+}
+
+impl FilterPrimitiveKind {
+ pub fn sanitize(&mut self) {
+ match self {
+ FilterPrimitiveKind::Flood(flood) => flood.sanitize(),
+ FilterPrimitiveKind::Opacity(opacity) => opacity.sanitize(),
+
+ // No sanitization needed.
+ FilterPrimitiveKind::Identity(..) |
+ FilterPrimitiveKind::Blend(..) |
+ FilterPrimitiveKind::ColorMatrix(..) |
+ FilterPrimitiveKind::Offset(..) |
+ FilterPrimitiveKind::Composite(..) |
+ FilterPrimitiveKind::Blur(..) |
+ FilterPrimitiveKind::DropShadow(..) |
+ // Component transfer's filter data is sanitized separately.
+ FilterPrimitiveKind::ComponentTransfer(..) => {}
+ }
+ }
+}
+
+/// SVG Filter Primitive.
+/// See: https://github.com/eqrion/cbindgen/issues/9
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct FilterPrimitive {
+ pub kind: FilterPrimitiveKind,
+ pub color_space: ColorSpace,
+}
+
+impl FilterPrimitive {
+ pub fn sanitize(&mut self) {
+ self.kind.sanitize();
+ }
+}
+
+/// CSS filter.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, PeekPoke)]
+pub enum FilterOp {
+ /// Filter that does no transformation of the colors, needed for
+ /// debug purposes only.
+ Identity,
+ Blur(f32),
+ Brightness(f32),
+ Contrast(f32),
+ Grayscale(f32),
+ HueRotate(f32),
+ Invert(f32),
+ Opacity(PropertyBinding<f32>, f32),
+ Saturate(f32),
+ Sepia(f32),
+ DropShadow(Shadow),
+ ColorMatrix([f32; 20]),
+ SrgbToLinear,
+ LinearToSrgb,
+ ComponentTransfer,
+ Flood(ColorF),
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, PeekPoke)]
+pub enum ComponentTransferFuncType {
+ Identity = 0,
+ Table = 1,
+ Discrete = 2,
+ Linear = 3,
+ Gamma = 4,
+}
+
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct FilterData {
+ pub func_r_type: ComponentTransferFuncType,
+ pub r_values: Vec<f32>,
+ pub func_g_type: ComponentTransferFuncType,
+ pub g_values: Vec<f32>,
+ pub func_b_type: ComponentTransferFuncType,
+ pub b_values: Vec<f32>,
+ pub func_a_type: ComponentTransferFuncType,
+ pub a_values: Vec<f32>,
+}
+
+fn sanitize_func_type(
+ func_type: ComponentTransferFuncType,
+ values: &[f32],
+) -> ComponentTransferFuncType {
+ if values.is_empty() {
+ return ComponentTransferFuncType::Identity;
+ }
+ if values.len() < 2 && func_type == ComponentTransferFuncType::Linear {
+ return ComponentTransferFuncType::Identity;
+ }
+ if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma {
+ return ComponentTransferFuncType::Identity;
+ }
+ func_type
+}
+
+fn sanitize_values(
+ func_type: ComponentTransferFuncType,
+ values: &[f32],
+) -> bool {
+ if values.len() < 2 && func_type == ComponentTransferFuncType::Linear {
+ return false;
+ }
+ if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma {
+ return false;
+ }
+ true
+}
+
+impl FilterData {
+ /// Ensure that the number of values matches up with the function type.
+ pub fn sanitize(&self) -> FilterData {
+ FilterData {
+ func_r_type: sanitize_func_type(self.func_r_type, &self.r_values),
+ r_values:
+ if sanitize_values(self.func_r_type, &self.r_values) {
+ self.r_values.clone()
+ } else {
+ Vec::new()
+ },
+ func_g_type: sanitize_func_type(self.func_g_type, &self.g_values),
+ g_values:
+ if sanitize_values(self.func_g_type, &self.g_values) {
+ self.g_values.clone()
+ } else {
+ Vec::new()
+ },
+
+ func_b_type: sanitize_func_type(self.func_b_type, &self.b_values),
+ b_values:
+ if sanitize_values(self.func_b_type, &self.b_values) {
+ self.b_values.clone()
+ } else {
+ Vec::new()
+ },
+
+ func_a_type: sanitize_func_type(self.func_a_type, &self.a_values),
+ a_values:
+ if sanitize_values(self.func_a_type, &self.a_values) {
+ self.a_values.clone()
+ } else {
+ Vec::new()
+ },
+
+ }
+ }
+
+ pub fn is_identity(&self) -> bool {
+ self.func_r_type == ComponentTransferFuncType::Identity &&
+ self.func_g_type == ComponentTransferFuncType::Identity &&
+ self.func_b_type == ComponentTransferFuncType::Identity &&
+ self.func_a_type == ComponentTransferFuncType::Identity
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct IframeDisplayItem {
+ pub bounds: LayoutRect,
+ pub clip_rect: LayoutRect,
+ pub space_and_clip: SpaceAndClipInfo,
+ pub pipeline_id: PipelineId,
+ pub ignore_missing_pipeline: bool,
+}
+
+/// This describes an image that fills the specified area. It stretches or shrinks
+/// the image as necessary. While RepeatingImageDisplayItem could otherwise provide
+/// a superset of the functionality, it has been problematic inferring the desired
+/// repetition properties when snapping changes the size of the primitive.
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ImageDisplayItem {
+ pub common: CommonItemProperties,
+ /// The area to tile the image over (first tile starts at origin of this rect)
+ // FIXME: this should ideally just be `tile_origin` here, with the clip_rect
+ // defining the bounds of the item. Needs non-trivial backend changes.
+ pub bounds: LayoutRect,
+ pub image_key: ImageKey,
+ pub image_rendering: ImageRendering,
+ pub alpha_type: AlphaType,
+ /// A hack used by gecko to color a simple bitmap font used for tofu glyphs
+ pub color: ColorF,
+}
+
+/// This describes a background-image and its tiling. It repeats in a grid to fill
+/// the specified area.
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct RepeatingImageDisplayItem {
+ pub common: CommonItemProperties,
+ /// The area to tile the image over (first tile starts at origin of this rect)
+ // FIXME: this should ideally just be `tile_origin` here, with the clip_rect
+ // defining the bounds of the item. Needs non-trivial backend changes.
+ pub bounds: LayoutRect,
+ /// How large to make a single tile of the image (common case: bounds.size)
+ pub stretch_size: LayoutSize,
+ /// The space between tiles (common case: 0)
+ pub tile_spacing: LayoutSize,
+ pub image_key: ImageKey,
+ pub image_rendering: ImageRendering,
+ pub alpha_type: AlphaType,
+ /// A hack used by gecko to color a simple bitmap font used for tofu glyphs
+ pub color: ColorF,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum ImageRendering {
+ Auto = 0,
+ CrispEdges = 1,
+ Pixelated = 2,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum AlphaType {
+ Alpha = 0,
+ PremultipliedAlpha = 1,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct YuvImageDisplayItem {
+ pub common: CommonItemProperties,
+ pub bounds: LayoutRect,
+ pub yuv_data: YuvData,
+ pub color_depth: ColorDepth,
+ pub color_space: YuvColorSpace,
+ pub color_range: ColorRange,
+ pub image_rendering: ImageRendering,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum YuvColorSpace {
+ Rec601 = 0,
+ Rec709 = 1,
+ Rec2020 = 2,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum ColorRange {
+ Limited = 0,
+ Full = 1,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)]
+pub enum YuvData {
+ NV12(ImageKey, ImageKey), // (Y channel, CbCr interleaved channel)
+ PlanarYCbCr(ImageKey, ImageKey, ImageKey), // (Y channel, Cb channel, Cr Channel)
+ InterleavedYCbCr(ImageKey), // (YCbCr interleaved channel)
+}
+
+impl YuvData {
+ pub fn get_format(&self) -> YuvFormat {
+ match *self {
+ YuvData::NV12(..) => YuvFormat::NV12,
+ YuvData::PlanarYCbCr(..) => YuvFormat::PlanarYCbCr,
+ YuvData::InterleavedYCbCr(..) => YuvFormat::InterleavedYCbCr,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum YuvFormat {
+ NV12 = 0,
+ PlanarYCbCr = 1,
+ InterleavedYCbCr = 2,
+}
+
+impl YuvFormat {
+ pub fn get_plane_num(self) -> usize {
+ match self {
+ YuvFormat::NV12 => 2,
+ YuvFormat::PlanarYCbCr => 3,
+ YuvFormat::InterleavedYCbCr => 1,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ImageMask {
+ pub image: ImageKey,
+ pub rect: LayoutRect,
+ pub repeat: bool,
+}
+
+impl ImageMask {
+ /// Get a local clipping rect contributed by this mask.
+ pub fn get_local_clip_rect(&self) -> Option<LayoutRect> {
+ if self.repeat {
+ None
+ } else {
+ Some(self.rect)
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, Serialize, Deserialize, Eq, Hash, PeekPoke)]
+pub enum ClipMode {
+ Clip, // Pixels inside the region are visible.
+ ClipOut, // Pixels outside the region are visible.
+}
+
+impl Not for ClipMode {
+ type Output = ClipMode;
+
+ fn not(self) -> ClipMode {
+ match self {
+ ClipMode::Clip => ClipMode::ClipOut,
+ ClipMode::ClipOut => ClipMode::Clip,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ComplexClipRegion {
+ /// The boundaries of the rectangle.
+ pub rect: LayoutRect,
+ /// Border radii of this rectangle.
+ pub radii: BorderRadius,
+ /// Whether we are clipping inside or outside
+ /// the region.
+ pub mode: ClipMode,
+}
+
+impl BorderRadius {
+ pub fn zero() -> BorderRadius {
+ BorderRadius {
+ top_left: LayoutSize::new(0.0, 0.0),
+ top_right: LayoutSize::new(0.0, 0.0),
+ bottom_left: LayoutSize::new(0.0, 0.0),
+ bottom_right: LayoutSize::new(0.0, 0.0),
+ }
+ }
+
+ pub fn uniform(radius: f32) -> BorderRadius {
+ BorderRadius {
+ top_left: LayoutSize::new(radius, radius),
+ top_right: LayoutSize::new(radius, radius),
+ bottom_left: LayoutSize::new(radius, radius),
+ bottom_right: LayoutSize::new(radius, radius),
+ }
+ }
+
+ pub fn uniform_size(radius: LayoutSize) -> BorderRadius {
+ BorderRadius {
+ top_left: radius,
+ top_right: radius,
+ bottom_left: radius,
+ bottom_right: radius,
+ }
+ }
+
+ pub fn is_uniform(&self) -> Option<f32> {
+ match self.is_uniform_size() {
+ Some(radius) if radius.width == radius.height => Some(radius.width),
+ _ => None,
+ }
+ }
+
+ pub fn is_uniform_size(&self) -> Option<LayoutSize> {
+ let uniform_radius = self.top_left;
+ if self.top_right == uniform_radius && self.bottom_left == uniform_radius &&
+ self.bottom_right == uniform_radius
+ {
+ Some(uniform_radius)
+ } else {
+ None
+ }
+ }
+
+ /// Return whether, in each corner, the radius in *either* direction is zero.
+ /// This means that none of the corners are rounded.
+ pub fn is_zero(&self) -> bool {
+ let corner_is_zero = |corner: &LayoutSize| corner.width == 0.0 || corner.height == 0.0;
+ corner_is_zero(&self.top_left) &&
+ corner_is_zero(&self.top_right) &&
+ corner_is_zero(&self.bottom_right) &&
+ corner_is_zero(&self.bottom_left)
+ }
+}
+
+impl ComplexClipRegion {
+ /// Create a new complex clip region.
+ pub fn new(
+ rect: LayoutRect,
+ radii: BorderRadius,
+ mode: ClipMode,
+ ) -> Self {
+ ComplexClipRegion { rect, radii, mode }
+ }
+}
+
+impl ComplexClipRegion {
+ /// Get a local clipping rect contributed by this clip region.
+ pub fn get_local_clip_rect(&self) -> Option<LayoutRect> {
+ match self.mode {
+ ClipMode::Clip => {
+ Some(self.rect)
+ }
+ ClipMode::ClipOut => {
+ None
+ }
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)]
+pub struct ClipChainId(pub u64, pub PipelineId);
+
+/// A reference to a clipping node defining how an item is clipped.
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)]
+pub enum ClipId {
+ Clip(usize, PipelineId),
+ ClipChain(ClipChainId),
+}
+
+const ROOT_CLIP_ID: usize = 0;
+
+impl ClipId {
+ /// Return the root clip ID - effectively doing no clipping.
+ pub fn root(pipeline_id: PipelineId) -> Self {
+ ClipId::Clip(ROOT_CLIP_ID, pipeline_id)
+ }
+
+ /// Return an invalid clip ID - needed in places where we carry
+ /// one but need to not attempt to use it.
+ pub fn invalid() -> Self {
+ ClipId::Clip(!0, PipelineId::dummy())
+ }
+
+ pub fn pipeline_id(&self) -> PipelineId {
+ match *self {
+ ClipId::Clip(_, pipeline_id) |
+ ClipId::ClipChain(ClipChainId(_, pipeline_id)) => pipeline_id,
+ }
+ }
+
+ pub fn is_root(&self) -> bool {
+ match *self {
+ ClipId::Clip(id, _) => id == ROOT_CLIP_ID,
+ ClipId::ClipChain(_) => false,
+ }
+ }
+
+ pub fn is_valid(&self) -> bool {
+ match *self {
+ ClipId::Clip(id, _) => id != !0,
+ _ => true,
+ }
+ }
+}
+
+/// A reference to a spatial node defining item positioning.
+#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)]
+pub struct SpatialId(pub usize, PipelineId);
+
+const ROOT_REFERENCE_FRAME_SPATIAL_ID: usize = 0;
+const ROOT_SCROLL_NODE_SPATIAL_ID: usize = 1;
+
+impl SpatialId {
+ pub fn new(spatial_node_index: usize, pipeline_id: PipelineId) -> Self {
+ SpatialId(spatial_node_index, pipeline_id)
+ }
+
+ pub fn root_reference_frame(pipeline_id: PipelineId) -> Self {
+ SpatialId(ROOT_REFERENCE_FRAME_SPATIAL_ID, pipeline_id)
+ }
+
+ pub fn root_scroll_node(pipeline_id: PipelineId) -> Self {
+ SpatialId(ROOT_SCROLL_NODE_SPATIAL_ID, pipeline_id)
+ }
+
+ pub fn pipeline_id(&self) -> PipelineId {
+ self.1
+ }
+
+ pub fn is_root_reference_frame(&self) -> bool {
+ self.0 == ROOT_REFERENCE_FRAME_SPATIAL_ID
+ }
+
+ pub fn is_root_scroll_node(&self) -> bool {
+ self.0 == ROOT_SCROLL_NODE_SPATIAL_ID
+ }
+}
+
+/// An external identifier that uniquely identifies a scroll frame independent of its ClipId, which
+/// may change from frame to frame. This should be unique within a pipeline. WebRender makes no
+/// attempt to ensure uniqueness. The zero value is reserved for use by the root scroll node of
+/// every pipeline, which always has an external id.
+///
+/// When setting display lists with the `preserve_frame_state` this id is used to preserve scroll
+/// offsets between different sets of SpatialNodes which are ScrollFrames.
+#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)]
+#[repr(C)]
+pub struct ExternalScrollId(pub u64, pub PipelineId);
+
+impl ExternalScrollId {
+ pub fn pipeline_id(&self) -> PipelineId {
+ self.1
+ }
+
+ pub fn is_root(&self) -> bool {
+ self.0 == 0
+ }
+}
+
+impl DisplayItem {
+ pub fn debug_name(&self) -> &'static str {
+ match *self {
+ DisplayItem::Border(..) => "border",
+ DisplayItem::BoxShadow(..) => "box_shadow",
+ DisplayItem::ClearRectangle(..) => "clear_rectangle",
+ DisplayItem::HitTest(..) => "hit_test",
+ DisplayItem::RectClip(..) => "rect_clip",
+ DisplayItem::RoundedRectClip(..) => "rounded_rect_clip",
+ DisplayItem::ImageMaskClip(..) => "image_mask_clip",
+ DisplayItem::Clip(..) => "clip",
+ DisplayItem::ClipChain(..) => "clip_chain",
+ DisplayItem::ConicGradient(..) => "conic_gradient",
+ DisplayItem::Gradient(..) => "gradient",
+ DisplayItem::Iframe(..) => "iframe",
+ DisplayItem::Image(..) => "image",
+ DisplayItem::RepeatingImage(..) => "repeating_image",
+ DisplayItem::Line(..) => "line",
+ DisplayItem::PopAllShadows => "pop_all_shadows",
+ DisplayItem::PopReferenceFrame => "pop_reference_frame",
+ DisplayItem::PopStackingContext => "pop_stacking_context",
+ DisplayItem::PushShadow(..) => "push_shadow",
+ DisplayItem::PushReferenceFrame(..) => "push_reference_frame",
+ DisplayItem::PushStackingContext(..) => "push_stacking_context",
+ DisplayItem::SetFilterOps => "set_filter_ops",
+ DisplayItem::SetFilterData => "set_filter_data",
+ DisplayItem::SetFilterPrimitives => "set_filter_primitives",
+ DisplayItem::RadialGradient(..) => "radial_gradient",
+ DisplayItem::Rectangle(..) => "rectangle",
+ DisplayItem::ScrollFrame(..) => "scroll_frame",
+ DisplayItem::SetGradientStops => "set_gradient_stops",
+ DisplayItem::ReuseItems(..) => "reuse_item",
+ DisplayItem::RetainedItems(..) => "retained_items",
+ DisplayItem::StickyFrame(..) => "sticky_frame",
+ DisplayItem::Text(..) => "text",
+ DisplayItem::YuvImage(..) => "yuv_image",
+ DisplayItem::BackdropFilter(..) => "backdrop_filter",
+ }
+ }
+}
+
+macro_rules! impl_default_for_enums {
+ ($($enum:ident => $init:expr ),+) => {
+ $(impl Default for $enum {
+ #[allow(unused_imports)]
+ fn default() -> Self {
+ use $enum::*;
+ $init
+ }
+ })*
+ }
+}
+
+impl_default_for_enums! {
+ DisplayItem => PopStackingContext,
+ ScrollSensitivity => ScriptAndInputEvents,
+ LineOrientation => Vertical,
+ LineStyle => Solid,
+ RepeatMode => Stretch,
+ NinePatchBorderSource => Image(ImageKey::default()),
+ BorderDetails => Normal(NormalBorder::default()),
+ BorderRadiusKind => Uniform,
+ BorderStyle => None,
+ BoxShadowClipMode => Outset,
+ ExtendMode => Clamp,
+ FilterOp => Identity,
+ ComponentTransferFuncType => Identity,
+ ClipMode => Clip,
+ ClipId => ClipId::invalid(),
+ ReferenceFrameKind => Transform,
+ TransformStyle => Flat,
+ RasterSpace => Local(f32::default()),
+ MixBlendMode => Normal,
+ ImageRendering => Auto,
+ AlphaType => Alpha,
+ YuvColorSpace => Rec601,
+ ColorRange => Limited,
+ YuvData => NV12(ImageKey::default(), ImageKey::default()),
+ YuvFormat => NV12,
+ FilterPrimitiveInput => Original,
+ ColorSpace => Srgb,
+ CompositeOperator => Over
+}
diff --git a/third_party/webrender/webrender_api/src/display_item_cache.rs b/third_party/webrender/webrender_api/src/display_item_cache.rs
new file mode 100644
index 00000000000..169e54797a9
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/display_item_cache.rs
@@ -0,0 +1,115 @@
+/* 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::display_item::*;
+use crate::display_list::*;
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct CachedDisplayItem {
+ item: DisplayItem,
+ data: Vec<u8>,
+}
+
+impl CachedDisplayItem {
+ pub fn display_item(&self) -> &DisplayItem {
+ &self.item
+ }
+
+ pub fn data_as_item_range<T>(&self) -> ItemRange<T> {
+ ItemRange::new(&self.data)
+ }
+}
+
+impl MallocSizeOf for CachedDisplayItem {
+ fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+ self.data.size_of(ops)
+ }
+}
+
+impl From<DisplayItemRef<'_, '_>> for CachedDisplayItem {
+ fn from(item_ref: DisplayItemRef) -> Self {
+ let item = item_ref.item();
+
+ match item {
+ DisplayItem::Text(..) => CachedDisplayItem {
+ item: *item,
+ data: item_ref.glyphs().bytes().to_vec(),
+ },
+ _ => CachedDisplayItem {
+ item: *item,
+ data: Vec::new(),
+ },
+ }
+ }
+}
+
+#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
+struct CacheEntry {
+ items: Vec<CachedDisplayItem>,
+ occupied: bool,
+}
+
+#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
+pub struct DisplayItemCache {
+ entries: Vec<CacheEntry>,
+}
+
+impl DisplayItemCache {
+ fn add_item(&mut self, key: ItemKey, item: CachedDisplayItem) {
+ let mut entry = &mut self.entries[key as usize];
+ entry.items.push(item);
+ entry.occupied = true;
+ }
+
+ fn clear_entry(&mut self, key: ItemKey) {
+ let mut entry = &mut self.entries[key as usize];
+ entry.items.clear();
+ entry.occupied = false;
+ }
+
+ fn grow_if_needed(&mut self, capacity: usize) {
+ if capacity > self.entries.len() {
+ self.entries.resize_with(capacity, || CacheEntry {
+ items: Vec::new(),
+ occupied: false,
+ });
+ }
+ }
+
+ pub fn get_items(&self, key: ItemKey) -> &[CachedDisplayItem] {
+ let entry = &self.entries[key as usize];
+ debug_assert!(entry.occupied);
+ entry.items.as_slice()
+ }
+
+ pub fn new() -> Self {
+ Self {
+ entries: Vec::new(),
+ }
+ }
+
+ pub fn update(&mut self, display_list: &BuiltDisplayList) {
+ self.grow_if_needed(display_list.cache_size());
+
+ let mut iter = display_list.extra_data_iter();
+ let mut current_key: Option<ItemKey> = None;
+ loop {
+ let item = match iter.next() {
+ Some(item) => item,
+ None => break,
+ };
+
+ if let DisplayItem::RetainedItems(key) = item.item() {
+ current_key = Some(*key);
+ self.clear_entry(*key);
+ continue;
+ }
+
+ let key = current_key.expect("Missing RetainedItems marker");
+ let cached_item = CachedDisplayItem::from(item);
+ self.add_item(key, cached_item);
+ }
+ }
+}
diff --git a/third_party/webrender/webrender_api/src/display_list.rs b/third_party/webrender/webrender_api/src/display_list.rs
new file mode 100644
index 00000000000..0680a9875d3
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/display_list.rs
@@ -0,0 +1,1969 @@
+/* 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 euclid::SideOffsets2D;
+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;
+#[cfg(feature = "serialize")]
+use serde::ser::{Serializer, SerializeSeq};
+use serde::{Deserialize, Serialize};
+use std::io::Write;
+use std::marker::PhantomData;
+use std::ops::Range;
+use std::mem;
+use std::collections::HashMap;
+use time::precise_time_ns;
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+// local imports
+use crate::display_item as di;
+use crate::display_item_cache::*;
+use crate::api::{PipelineId, PropertyBinding};
+use crate::gradient_builder::GradientBuilder;
+use crate::color::ColorF;
+use crate::font::{FontInstanceKey, GlyphInstance, GlyphOptions};
+use crate::image::{ColorDepth, ImageKey};
+use crate::units::*;
+
+
+// We don't want to push a long text-run. If a text-run is too long, split it into several parts.
+// This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_TEXT_RUN) * 2
+pub const MAX_TEXT_RUN_LENGTH: usize = 2040;
+
+// See ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID
+// TODO(mrobinson): It would be a good idea to eliminate the root scroll frame which is only
+// used by Servo.
+const FIRST_SPATIAL_NODE_INDEX: usize = 2;
+
+// See ROOT_SCROLL_NODE_SPATIAL_ID
+const FIRST_CLIP_NODE_INDEX: usize = 1;
+
+#[repr(C)]
+#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct ItemRange<'a, T> {
+ bytes: &'a [u8],
+ _boo: PhantomData<T>,
+}
+
+impl<'a, T> Copy for ItemRange<'a, T> {}
+impl<'a, T> Clone for ItemRange<'a, T> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<'a, T> Default for ItemRange<'a, T> {
+ fn default() -> Self {
+ ItemRange {
+ bytes: Default::default(),
+ _boo: PhantomData,
+ }
+ }
+}
+
+impl<'a, T> ItemRange<'a, T> {
+ pub fn new(bytes: &'a [u8]) -> Self {
+ Self {
+ bytes,
+ _boo: PhantomData
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ // Nothing more than space for a length (0).
+ self.bytes.len() <= mem::size_of::<usize>()
+ }
+
+ pub fn bytes(&self) -> &[u8] {
+ &self.bytes
+ }
+}
+
+impl<'a, T: Default> ItemRange<'a, T> {
+ pub fn iter(&self) -> AuxIter<'a, T> {
+ AuxIter::new(T::default(), self.bytes)
+ }
+}
+
+impl<'a, T> IntoIterator for ItemRange<'a, T>
+where
+ T: Copy + Default + peek_poke::Peek,
+{
+ type Item = T;
+ type IntoIter = AuxIter<'a, T>;
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct TempFilterData<'a> {
+ pub func_types: ItemRange<'a, di::ComponentTransferFuncType>,
+ pub r_values: ItemRange<'a, f32>,
+ pub g_values: ItemRange<'a, f32>,
+ pub b_values: ItemRange<'a, f32>,
+ pub a_values: ItemRange<'a, f32>,
+}
+
+/// A display list.
+#[derive(Clone, Default)]
+pub struct BuiltDisplayList {
+ /// Serde encoded bytes. Mostly DisplayItems, but some mixed in slices.
+ data: Vec<u8>,
+ descriptor: BuiltDisplayListDescriptor,
+}
+
+/// 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
+/// items.
+#[repr(C)]
+#[derive(Copy, Clone, Default, Deserialize, Serialize)]
+pub struct BuiltDisplayListDescriptor {
+ /// The first IPC time stamp: before any work has been done
+ builder_start_time: u64,
+ /// The second IPC time stamp: after serialization
+ builder_finish_time: u64,
+ /// The third IPC time stamp: just before sending
+ send_start_time: u64,
+ /// The amount of clipping nodes created while building this display list.
+ total_clip_nodes: usize,
+ /// The amount of spatial nodes created while building this display list.
+ total_spatial_nodes: usize,
+ /// The size of the cache for this display list.
+ cache_size: usize,
+ /// The offset for additional display list data.
+ extra_data_offset: usize,
+}
+
+#[derive(Clone)]
+pub struct DisplayListWithCache {
+ display_list: BuiltDisplayList,
+ cache: DisplayItemCache,
+}
+
+impl DisplayListWithCache {
+ pub fn iter(&self) -> BuiltDisplayListIter {
+ self.display_list.iter_with_cache(&self.cache)
+ }
+
+ pub fn new_from_list(display_list: BuiltDisplayList) -> Self {
+ let mut cache = DisplayItemCache::new();
+ cache.update(&display_list);
+
+ DisplayListWithCache {
+ display_list,
+ cache
+ }
+ }
+
+ pub fn update(&mut self, display_list: BuiltDisplayList) {
+ self.cache.update(&display_list);
+ self.display_list = display_list;
+ }
+
+ pub fn descriptor(&self) -> &BuiltDisplayListDescriptor {
+ self.display_list.descriptor()
+ }
+
+ pub fn data(&self) -> &[u8] {
+ self.display_list.data()
+ }
+}
+
+impl MallocSizeOf for DisplayListWithCache {
+ fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+ self.display_list.data.size_of(ops) + self.cache.size_of(ops)
+ }
+}
+
+#[cfg(feature = "serialize")]
+impl Serialize for DisplayListWithCache {
+ fn serialize<S: Serializer>(
+ &self,
+ serializer: S
+ ) -> Result<S::Ok, S::Error> {
+ BuiltDisplayList::serialize_with_iterator(serializer, self.iter())
+ }
+}
+
+#[cfg(feature = "deserialize")]
+impl<'de> Deserialize<'de> for DisplayListWithCache {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let display_list = BuiltDisplayList::deserialize(deserializer)?;
+ let cache = DisplayItemCache::new();
+
+ Ok(DisplayListWithCache {
+ display_list,
+ cache,
+ })
+ }
+}
+
+impl BuiltDisplayListDescriptor {}
+
+pub struct BuiltDisplayListIter<'a> {
+ list: &'a BuiltDisplayList,
+ data: &'a [u8],
+ cache: Option<&'a DisplayItemCache>,
+ pending_items: std::slice::Iter<'a, CachedDisplayItem>,
+ cur_cached_item: Option<&'a CachedDisplayItem>,
+ cur_item: di::DisplayItem,
+ cur_stops: ItemRange<'a, di::GradientStop>,
+ cur_glyphs: ItemRange<'a, GlyphInstance>,
+ cur_filters: ItemRange<'a, di::FilterOp>,
+ cur_filter_data: Vec<TempFilterData<'a>>,
+ cur_filter_primitives: ItemRange<'a, di::FilterPrimitive>,
+ cur_clip_chain_items: ItemRange<'a, di::ClipId>,
+ cur_complex_clip: ItemRange<'a, di::ComplexClipRegion>,
+ peeking: Peek,
+ /// Should just be initialized but never populated in release builds
+ debug_stats: DebugStats,
+}
+
+/// Internal info used for more detailed analysis of serialized display lists
+#[allow(dead_code)]
+struct DebugStats {
+ /// Last address in the buffer we pointed to, for computing serialized sizes
+ last_addr: usize,
+ stats: HashMap<&'static str, ItemStats>,
+}
+
+impl DebugStats {
+ #[cfg(feature = "display_list_stats")]
+ fn _update_entry(&mut self, name: &'static str, item_count: usize, byte_count: usize) {
+ let entry = self.stats.entry(name).or_default();
+ entry.total_count += item_count;
+ entry.num_bytes += byte_count;
+ }
+
+ /// Computes the number of bytes we've processed since we last called
+ /// this method, so we can compute the serialized size of a display item.
+ #[cfg(feature = "display_list_stats")]
+ fn debug_num_bytes(&mut self, data: &[u8]) -> usize {
+ let old_addr = self.last_addr;
+ let new_addr = data.as_ptr() as usize;
+ let delta = new_addr - old_addr;
+ self.last_addr = new_addr;
+
+ delta
+ }
+
+ /// Logs stats for the last deserialized display item
+ #[cfg(feature = "display_list_stats")]
+ fn log_item(&mut self, data: &[u8], item: &di::DisplayItem) {
+ let num_bytes = self.debug_num_bytes(data);
+ self._update_entry(item.debug_name(), 1, num_bytes);
+ }
+
+ /// Logs the stats for the given serialized slice
+ #[cfg(feature = "display_list_stats")]
+ fn log_slice<T: Peek>(
+ &mut self,
+ slice_name: &'static str,
+ range: &ItemRange<T>,
+ ) {
+ // Run this so log_item_stats is accurate, but ignore its result
+ // because log_slice_stats may be called after multiple slices have been
+ // processed, and the `range` has everything we need.
+ self.last_addr = range.bytes.as_ptr() as usize + range.bytes.len();
+
+ self._update_entry(slice_name, range.iter().len(), range.bytes.len());
+ }
+
+ #[cfg(not(feature = "display_list_stats"))]
+ fn log_slice<T>(&mut self, _slice_name: &str, _range: &ItemRange<T>) {
+ /* no-op */
+ }
+}
+
+/// Stats for an individual item
+#[derive(Copy, Clone, Debug, Default)]
+pub struct ItemStats {
+ /// How many instances of this kind of item we deserialized
+ pub total_count: usize,
+ /// How many bytes we processed for this kind of item
+ pub num_bytes: usize,
+}
+
+pub struct DisplayItemRef<'a: 'b, 'b> {
+ iter: &'b BuiltDisplayListIter<'a>,
+}
+
+// Some of these might just become ItemRanges
+impl<'a, 'b> DisplayItemRef<'a, 'b> {
+ pub fn display_list(&self) -> &BuiltDisplayList {
+ self.iter.display_list()
+ }
+
+ // Creates a new iterator where this element's iterator is, to hack around borrowck.
+ pub fn sub_iter(&self) -> BuiltDisplayListIter<'a> {
+ self.iter.sub_iter()
+ }
+
+ pub fn item(&self) -> &di::DisplayItem {
+ self.iter.current_item()
+ }
+
+ pub fn clip_chain_items(&self) -> ItemRange<di::ClipId> {
+ self.iter.cur_clip_chain_items
+ }
+
+ pub fn complex_clip(&self) -> ItemRange<di::ComplexClipRegion> {
+ self.iter.cur_complex_clip
+ }
+
+ pub fn glyphs(&self) -> ItemRange<GlyphInstance> {
+ self.iter.glyphs()
+ }
+
+ pub fn gradient_stops(&self) -> ItemRange<di::GradientStop> {
+ self.iter.gradient_stops()
+ }
+
+ pub fn filters(&self) -> ItemRange<di::FilterOp> {
+ self.iter.cur_filters
+ }
+
+ pub fn filter_datas(&self) -> &Vec<TempFilterData> {
+ &self.iter.cur_filter_data
+ }
+
+ pub fn filter_primitives(&self) -> ItemRange<di::FilterPrimitive> {
+ self.iter.cur_filter_primitives
+ }
+}
+
+#[derive(PartialEq)]
+enum Peek {
+ StartPeeking,
+ IsPeeking,
+ NotPeeking,
+}
+
+#[derive(Clone)]
+pub struct AuxIter<'a, T> {
+ item: T,
+ data: &'a [u8],
+ size: usize,
+// _boo: PhantomData<T>,
+}
+
+impl BuiltDisplayList {
+ pub fn from_data(data: Vec<u8>, descriptor: BuiltDisplayListDescriptor) -> Self {
+ BuiltDisplayList { data, descriptor }
+ }
+
+ pub fn into_data(self) -> (Vec<u8>, BuiltDisplayListDescriptor) {
+ (self.data, self.descriptor)
+ }
+
+ pub fn data(&self) -> &[u8] {
+ &self.data[..]
+ }
+
+ pub fn item_slice(&self) -> &[u8] {
+ &self.data[..self.descriptor.extra_data_offset]
+ }
+
+ pub fn extra_slice(&self) -> &[u8] {
+ &self.data[self.descriptor.extra_data_offset..]
+ }
+
+ pub fn descriptor(&self) -> &BuiltDisplayListDescriptor {
+ &self.descriptor
+ }
+
+ pub fn set_send_time_ns(&mut self, time: u64) {
+ self.descriptor.send_start_time = time;
+ }
+
+ pub fn times(&self) -> (u64, u64, u64) {
+ (
+ self.descriptor.builder_start_time,
+ self.descriptor.builder_finish_time,
+ self.descriptor.send_start_time,
+ )
+ }
+
+ pub fn total_clip_nodes(&self) -> usize {
+ self.descriptor.total_clip_nodes
+ }
+
+ pub fn total_spatial_nodes(&self) -> usize {
+ self.descriptor.total_spatial_nodes
+ }
+
+ pub fn iter(&self) -> BuiltDisplayListIter {
+ BuiltDisplayListIter::new(self, self.item_slice(), None)
+ }
+
+ pub fn extra_data_iter(&self) -> BuiltDisplayListIter {
+ BuiltDisplayListIter::new(self, self.extra_slice(), None)
+ }
+
+ pub fn iter_with_cache<'a>(
+ &'a self,
+ cache: &'a DisplayItemCache
+ ) -> BuiltDisplayListIter<'a> {
+ BuiltDisplayListIter::new(self, self.item_slice(), Some(cache))
+ }
+
+ pub fn cache_size(&self) -> usize {
+ self.descriptor.cache_size
+ }
+
+ #[cfg(feature = "serialize")]
+ pub fn serialize_with_iterator<S: Serializer>(
+ serializer: S,
+ mut iterator: BuiltDisplayListIter,
+ ) -> Result<S::Ok, S::Error> {
+ use crate::display_item::DisplayItem as Real;
+ use crate::display_item::DebugDisplayItem as Debug;
+
+ let mut seq = serializer.serialize_seq(None)?;
+
+ while let Some(item) = iterator.next_raw() {
+ let serial_di = match *item.item() {
+ Real::Clip(v) => Debug::Clip(
+ v,
+ item.iter.cur_complex_clip.iter().collect()
+ ),
+ Real::ClipChain(v) => Debug::ClipChain(
+ v,
+ item.iter.cur_clip_chain_items.iter().collect()
+ ),
+ Real::ScrollFrame(v) => Debug::ScrollFrame(v),
+ Real::Text(v) => Debug::Text(
+ v,
+ item.iter.cur_glyphs.iter().collect()
+ ),
+ Real::SetFilterOps => Debug::SetFilterOps(
+ item.iter.cur_filters.iter().collect()
+ ),
+ Real::SetFilterData => {
+ debug_assert!(!item.iter.cur_filter_data.is_empty(),
+ "next_raw should have populated cur_filter_data");
+ let temp_filter_data = &item.iter.cur_filter_data[item.iter.cur_filter_data.len()-1];
+
+ let func_types: Vec<di::ComponentTransferFuncType> =
+ temp_filter_data.func_types.iter().collect();
+ debug_assert!(func_types.len() == 4,
+ "someone changed the number of filter funcs without updating this code");
+ Debug::SetFilterData(di::FilterData {
+ func_r_type: func_types[0],
+ r_values: temp_filter_data.r_values.iter().collect(),
+ func_g_type: func_types[1],
+ g_values: temp_filter_data.g_values.iter().collect(),
+ func_b_type: func_types[2],
+ b_values: temp_filter_data.b_values.iter().collect(),
+ func_a_type: func_types[3],
+ a_values: temp_filter_data.a_values.iter().collect(),
+ })
+ },
+ Real::SetFilterPrimitives => Debug::SetFilterPrimitives(
+ item.iter.cur_filter_primitives.iter().collect()
+ ),
+ Real::SetGradientStops => Debug::SetGradientStops(
+ item.iter.cur_stops.iter().collect()
+ ),
+ Real::RectClip(v) => Debug::RectClip(v),
+ Real::RoundedRectClip(v) => Debug::RoundedRectClip(v),
+ Real::ImageMaskClip(v) => Debug::ImageMaskClip(v),
+ Real::StickyFrame(v) => Debug::StickyFrame(v),
+ Real::Rectangle(v) => Debug::Rectangle(v),
+ Real::ClearRectangle(v) => Debug::ClearRectangle(v),
+ Real::HitTest(v) => Debug::HitTest(v),
+ Real::Line(v) => Debug::Line(v),
+ Real::Image(v) => Debug::Image(v),
+ Real::RepeatingImage(v) => Debug::RepeatingImage(v),
+ Real::YuvImage(v) => Debug::YuvImage(v),
+ Real::Border(v) => Debug::Border(v),
+ Real::BoxShadow(v) => Debug::BoxShadow(v),
+ Real::Gradient(v) => Debug::Gradient(v),
+ Real::RadialGradient(v) => Debug::RadialGradient(v),
+ Real::ConicGradient(v) => Debug::ConicGradient(v),
+ Real::Iframe(v) => Debug::Iframe(v),
+ Real::PushReferenceFrame(v) => Debug::PushReferenceFrame(v),
+ Real::PushStackingContext(v) => Debug::PushStackingContext(v),
+ Real::PushShadow(v) => Debug::PushShadow(v),
+ Real::BackdropFilter(v) => Debug::BackdropFilter(v),
+
+ Real::PopReferenceFrame => Debug::PopReferenceFrame,
+ Real::PopStackingContext => Debug::PopStackingContext,
+ Real::PopAllShadows => Debug::PopAllShadows,
+ Real::ReuseItems(_) |
+ Real::RetainedItems(_) => unreachable!("Unexpected item"),
+ };
+ seq.serialize_element(&serial_di)?
+ }
+ seq.end()
+ }
+}
+
+/// Returns the byte-range the slice occupied.
+fn skip_slice<'a, T: peek_poke::Peek>(data: &mut &'a [u8]) -> ItemRange<'a, T> {
+ let mut skip_offset = 0usize;
+ *data = peek_from_slice(data, &mut skip_offset);
+ let (skip, rest) = data.split_at(skip_offset);
+
+ // Adjust data pointer to skip read values
+ *data = rest;
+
+ ItemRange {
+ bytes: skip,
+ _boo: PhantomData,
+ }
+}
+
+impl<'a> BuiltDisplayListIter<'a> {
+ pub fn new(
+ list: &'a BuiltDisplayList,
+ data: &'a [u8],
+ cache: Option<&'a DisplayItemCache>,
+ ) -> Self {
+ Self {
+ list,
+ data,
+ cache,
+ pending_items: [].iter(),
+ cur_cached_item: None,
+ cur_item: di::DisplayItem::PopStackingContext,
+ cur_stops: ItemRange::default(),
+ cur_glyphs: ItemRange::default(),
+ cur_filters: ItemRange::default(),
+ cur_filter_data: Vec::new(),
+ cur_filter_primitives: ItemRange::default(),
+ cur_clip_chain_items: ItemRange::default(),
+ cur_complex_clip: ItemRange::default(),
+ peeking: Peek::NotPeeking,
+ debug_stats: DebugStats {
+ last_addr: data.as_ptr() as usize,
+ stats: HashMap::default(),
+ },
+ }
+ }
+
+ pub fn sub_iter(&self) -> Self {
+ let mut iter = BuiltDisplayListIter::new(
+ self.list, self.data, self.cache
+ );
+ iter.pending_items = self.pending_items.clone();
+ iter
+ }
+
+ pub fn display_list(&self) -> &'a BuiltDisplayList {
+ self.list
+ }
+
+ pub fn current_item(&self) -> &di::DisplayItem {
+ match self.cur_cached_item {
+ Some(cached_item) => cached_item.display_item(),
+ None => &self.cur_item
+ }
+ }
+
+ fn cached_item_range_or<T>(
+ &self,
+ data: ItemRange<'a, T>
+ ) -> ItemRange<'a, T> {
+ match self.cur_cached_item {
+ Some(cached_item) => cached_item.data_as_item_range(),
+ None => data,
+ }
+ }
+
+ pub fn glyphs(&self) -> ItemRange<GlyphInstance> {
+ self.cached_item_range_or(self.cur_glyphs)
+ }
+
+ pub fn gradient_stops(&self) -> ItemRange<di::GradientStop> {
+ self.cached_item_range_or(self.cur_stops)
+ }
+
+ fn advance_pending_items(&mut self) -> bool {
+ self.cur_cached_item = self.pending_items.next();
+ self.cur_cached_item.is_some()
+ }
+
+ pub fn next<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> {
+ use crate::DisplayItem::*;
+
+ match self.peeking {
+ Peek::IsPeeking => {
+ self.peeking = Peek::NotPeeking;
+ return Some(self.as_ref());
+ }
+ Peek::StartPeeking => {
+ self.peeking = Peek::IsPeeking;
+ }
+ Peek::NotPeeking => { /* do nothing */ }
+ }
+
+ // Don't let these bleed into another item
+ self.cur_stops = ItemRange::default();
+ self.cur_complex_clip = ItemRange::default();
+ self.cur_clip_chain_items = ItemRange::default();
+ self.cur_filters = ItemRange::default();
+ self.cur_filter_primitives = ItemRange::default();
+ self.cur_filter_data.clear();
+
+ loop {
+ self.next_raw()?;
+ match self.cur_item {
+ SetGradientStops |
+ SetFilterOps |
+ SetFilterData |
+ SetFilterPrimitives => {
+ // These are marker items for populating other display items, don't yield them.
+ continue;
+ }
+ _ => {
+ break;
+ }
+ }
+ }
+
+ Some(self.as_ref())
+ }
+
+ /// Gets the next display item, even if it's a dummy. Also doesn't handle peeking
+ /// and may leave irrelevant ranges live (so a Clip may have GradientStops if
+ /// for some reason you ask).
+ pub fn next_raw<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> {
+ use crate::DisplayItem::*;
+
+ if self.advance_pending_items() {
+ return Some(self.as_ref());
+ }
+
+ // A "red zone" of DisplayItem::max_size() bytes has been added to the
+ // end of the serialized display list. If this amount, or less, is
+ // remaining then we've reached the end of the display list.
+ if self.data.len() <= di::DisplayItem::max_size() {
+ return None;
+ }
+
+ self.data = peek_from_slice(self.data, &mut self.cur_item);
+ self.log_item_stats();
+
+ match self.cur_item {
+ SetGradientStops => {
+ self.cur_stops = skip_slice::<di::GradientStop>(&mut self.data);
+ self.debug_stats.log_slice("set_gradient_stops.stops", &self.cur_stops);
+ }
+ SetFilterOps => {
+ self.cur_filters = skip_slice::<di::FilterOp>(&mut self.data);
+ self.debug_stats.log_slice("set_filter_ops.ops", &self.cur_filters);
+ }
+ SetFilterData => {
+ self.cur_filter_data.push(TempFilterData {
+ func_types: skip_slice::<di::ComponentTransferFuncType>(&mut self.data),
+ r_values: skip_slice::<f32>(&mut self.data),
+ g_values: skip_slice::<f32>(&mut self.data),
+ b_values: skip_slice::<f32>(&mut self.data),
+ a_values: skip_slice::<f32>(&mut self.data),
+ });
+
+ let data = *self.cur_filter_data.last().unwrap();
+ self.debug_stats.log_slice("set_filter_data.func_types", &data.func_types);
+ self.debug_stats.log_slice("set_filter_data.r_values", &data.r_values);
+ self.debug_stats.log_slice("set_filter_data.g_values", &data.g_values);
+ self.debug_stats.log_slice("set_filter_data.b_values", &data.b_values);
+ self.debug_stats.log_slice("set_filter_data.a_values", &data.a_values);
+ }
+ SetFilterPrimitives => {
+ self.cur_filter_primitives = skip_slice::<di::FilterPrimitive>(&mut self.data);
+ self.debug_stats.log_slice("set_filter_primitives.primitives", &self.cur_filter_primitives);
+ }
+ ClipChain(_) => {
+ self.cur_clip_chain_items = skip_slice::<di::ClipId>(&mut self.data);
+ self.debug_stats.log_slice("clip_chain.clip_ids", &self.cur_clip_chain_items);
+ }
+ Clip(_) => {
+ self.cur_complex_clip = skip_slice::<di::ComplexClipRegion>(&mut self.data);
+ self.debug_stats.log_slice("clip.complex_clips", &self.cur_complex_clip);
+ }
+ Text(_) => {
+ self.cur_glyphs = skip_slice::<GlyphInstance>(&mut self.data);
+ self.debug_stats.log_slice("text.glyphs", &self.cur_glyphs);
+ }
+ ReuseItems(key) => {
+ match self.cache {
+ Some(cache) => {
+ self.pending_items = cache.get_items(key).iter();
+ self.advance_pending_items();
+ }
+ None => {
+ unreachable!("Cache marker without cache!");
+ }
+ }
+ }
+ _ => { /* do nothing */ }
+ }
+
+ Some(self.as_ref())
+ }
+
+ pub fn as_ref<'b>(&'b self) -> DisplayItemRef<'a, 'b> {
+ DisplayItemRef {
+ iter: self,
+ }
+ }
+
+ pub fn skip_current_stacking_context(&mut self) {
+ let mut depth = 0;
+ while let Some(item) = self.next() {
+ match *item.item() {
+ di::DisplayItem::PushStackingContext(..) => depth += 1,
+ di::DisplayItem::PopStackingContext if depth == 0 => return,
+ di::DisplayItem::PopStackingContext => depth -= 1,
+ _ => {}
+ }
+ }
+ }
+
+ pub fn current_stacking_context_empty(&mut self) -> bool {
+ match self.peek() {
+ Some(item) => *item.item() == di::DisplayItem::PopStackingContext,
+ None => true,
+ }
+ }
+
+ pub fn peek<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> {
+ if self.peeking == Peek::NotPeeking {
+ self.peeking = Peek::StartPeeking;
+ self.next()
+ } else {
+ Some(self.as_ref())
+ }
+ }
+
+ /// Get the debug stats for what this iterator has deserialized.
+ /// Should always be empty in release builds.
+ pub fn debug_stats(&mut self) -> Vec<(&'static str, ItemStats)> {
+ let mut result = self.debug_stats.stats.drain().collect::<Vec<_>>();
+ result.sort_by_key(|stats| stats.0);
+ result
+ }
+
+ /// Adds the debug stats from another to our own, assuming we are a sub-iter of the other
+ /// (so we can ignore where they were in the traversal).
+ pub fn merge_debug_stats_from(&mut self, other: &mut Self) {
+ for (key, other_entry) in other.debug_stats.stats.iter() {
+ let entry = self.debug_stats.stats.entry(key).or_default();
+
+ entry.total_count += other_entry.total_count;
+ entry.num_bytes += other_entry.num_bytes;
+ }
+ }
+
+ /// Logs stats for the last deserialized display item
+ #[cfg(feature = "display_list_stats")]
+ fn log_item_stats(&mut self) {
+ self.debug_stats.log_item(self.data, &self.cur_item);
+ }
+
+ #[cfg(not(feature = "display_list_stats"))]
+ fn log_item_stats(&mut self) { /* no-op */ }
+}
+
+impl<'a, T> AuxIter<'a, T> {
+ pub fn new(item: T, mut data: &'a [u8]) -> Self {
+ let mut size = 0usize;
+ if !data.is_empty() {
+ data = peek_from_slice(data, &mut size);
+ };
+
+ AuxIter {
+ item,
+ data,
+ size,
+// _boo: PhantomData,
+ }
+ }
+}
+
+impl<'a, T: Copy + peek_poke::Peek> Iterator for AuxIter<'a, T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.size == 0 {
+ None
+ } else {
+ self.size -= 1;
+ self.data = peek_from_slice(self.data, &mut self.item);
+ Some(self.item)
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.size, Some(self.size))
+ }
+}
+
+impl<'a, T: Copy + peek_poke::Peek> ::std::iter::ExactSizeIterator for AuxIter<'a, T> {}
+
+#[cfg(feature = "serialize")]
+impl Serialize for BuiltDisplayList {
+ fn serialize<S: Serializer>(
+ &self,
+ serializer: S
+ ) -> Result<S::Ok, S::Error> {
+ Self::serialize_with_iterator(serializer, self.iter())
+ }
+}
+
+// The purpose of this implementation is to deserialize
+// a display list from one format just to immediately
+// serialize then into a "built" `Vec<u8>`.
+
+#[cfg(feature = "deserialize")]
+impl<'de> Deserialize<'de> for BuiltDisplayList {
+ fn deserialize<D: Deserializer<'de>>(
+ deserializer: D
+ ) -> Result<Self, D::Error> {
+ use crate::display_item::DisplayItem as Real;
+ use crate::display_item::DebugDisplayItem as Debug;
+
+ let list = Vec::<Debug>::deserialize(deserializer)?;
+
+ let mut data = Vec::new();
+ let mut temp = Vec::new();
+ let mut total_clip_nodes = FIRST_CLIP_NODE_INDEX;
+ let mut total_spatial_nodes = FIRST_SPATIAL_NODE_INDEX;
+ for complete in list {
+ let item = match complete {
+ Debug::Clip(v, complex_clips) => {
+ total_clip_nodes += 1;
+ DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
+ Real::Clip(v)
+ },
+ Debug::ClipChain(v, clip_chain_ids) => {
+ DisplayListBuilder::push_iter_impl(&mut temp, clip_chain_ids);
+ Real::ClipChain(v)
+ }
+ Debug::ScrollFrame(v) => {
+ total_spatial_nodes += 1;
+ total_clip_nodes += 1;
+ Real::ScrollFrame(v)
+ }
+ Debug::StickyFrame(v) => {
+ total_spatial_nodes += 1;
+ Real::StickyFrame(v)
+ }
+ Debug::Text(v, glyphs) => {
+ DisplayListBuilder::push_iter_impl(&mut temp, glyphs);
+ Real::Text(v)
+ },
+ Debug::Iframe(v) => {
+ total_clip_nodes += 1;
+ Real::Iframe(v)
+ }
+ Debug::PushReferenceFrame(v) => {
+ total_spatial_nodes += 1;
+ Real::PushReferenceFrame(v)
+ }
+ Debug::SetFilterOps(filters) => {
+ DisplayListBuilder::push_iter_impl(&mut temp, filters);
+ Real::SetFilterOps
+ },
+ Debug::SetFilterData(filter_data) => {
+ let func_types: Vec<di::ComponentTransferFuncType> =
+ [filter_data.func_r_type,
+ filter_data.func_g_type,
+ filter_data.func_b_type,
+ filter_data.func_a_type].to_vec();
+ DisplayListBuilder::push_iter_impl(&mut temp, func_types);
+ DisplayListBuilder::push_iter_impl(&mut temp, filter_data.r_values);
+ DisplayListBuilder::push_iter_impl(&mut temp, filter_data.g_values);
+ DisplayListBuilder::push_iter_impl(&mut temp, filter_data.b_values);
+ DisplayListBuilder::push_iter_impl(&mut temp, filter_data.a_values);
+ Real::SetFilterData
+ },
+ Debug::SetFilterPrimitives(filter_primitives) => {
+ DisplayListBuilder::push_iter_impl(&mut temp, filter_primitives);
+ Real::SetFilterPrimitives
+ }
+ Debug::SetGradientStops(stops) => {
+ DisplayListBuilder::push_iter_impl(&mut temp, stops);
+ Real::SetGradientStops
+ },
+ Debug::RectClip(v) => Real::RectClip(v),
+ Debug::RoundedRectClip(v) => Real::RoundedRectClip(v),
+ Debug::ImageMaskClip(v) => Real::ImageMaskClip(v),
+ Debug::Rectangle(v) => Real::Rectangle(v),
+ Debug::ClearRectangle(v) => Real::ClearRectangle(v),
+ Debug::HitTest(v) => Real::HitTest(v),
+ Debug::Line(v) => Real::Line(v),
+ Debug::Image(v) => Real::Image(v),
+ Debug::RepeatingImage(v) => Real::RepeatingImage(v),
+ Debug::YuvImage(v) => Real::YuvImage(v),
+ Debug::Border(v) => Real::Border(v),
+ Debug::BoxShadow(v) => Real::BoxShadow(v),
+ Debug::Gradient(v) => Real::Gradient(v),
+ Debug::RadialGradient(v) => Real::RadialGradient(v),
+ Debug::ConicGradient(v) => Real::ConicGradient(v),
+ Debug::PushStackingContext(v) => Real::PushStackingContext(v),
+ Debug::PushShadow(v) => Real::PushShadow(v),
+ Debug::BackdropFilter(v) => Real::BackdropFilter(v),
+
+ Debug::PopStackingContext => Real::PopStackingContext,
+ Debug::PopReferenceFrame => Real::PopReferenceFrame,
+ Debug::PopAllShadows => Real::PopAllShadows,
+ };
+ poke_into_vec(&item, &mut data);
+ // the aux data is serialized after the item, hence the temporary
+ data.extend(temp.drain(..));
+ }
+
+ // Add `DisplayItem::max_size` zone of zeroes to the end of display list
+ // so there is at least this amount available in the display list during
+ // serialization.
+ ensure_red_zone::<di::DisplayItem>(&mut data);
+ let extra_data_offset = data.len();
+
+ Ok(BuiltDisplayList {
+ data,
+ descriptor: BuiltDisplayListDescriptor {
+ builder_start_time: 0,
+ builder_finish_time: 1,
+ send_start_time: 1,
+ total_clip_nodes,
+ total_spatial_nodes,
+ extra_data_offset,
+ cache_size: 0,
+ },
+ })
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct SaveState {
+ dl_len: usize,
+ next_clip_index: usize,
+ next_spatial_index: usize,
+ next_clip_chain_id: u64,
+}
+
+/// DisplayListSection determines the target buffer for the display items.
+pub enum DisplayListSection {
+ /// The main/default buffer: contains item data and item group markers.
+ Data,
+ /// Auxiliary buffer: contains the item data for item groups.
+ ExtraData,
+ /// Temporary buffer: contains the data for pending item group. Flushed to
+ /// one of the buffers above, after item grouping finishes.
+ Chunk,
+}
+
+#[derive(Clone)]
+pub struct DisplayListBuilder {
+ pub data: Vec<u8>,
+ pub pipeline_id: PipelineId,
+
+ extra_data: Vec<u8>,
+ pending_chunk: Vec<u8>,
+ writing_to_chunk: bool,
+
+ next_clip_index: usize,
+ next_spatial_index: usize,
+ 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<SaveState>,
+
+ cache_size: usize,
+ serialized_content_buffer: Option<String>,
+}
+
+impl DisplayListBuilder {
+ 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();
+
+ DisplayListBuilder {
+ data: Vec::with_capacity(capacity),
+ pipeline_id,
+
+ extra_data: Vec::new(),
+ pending_chunk: Vec::new(),
+ writing_to_chunk: false,
+
+ next_clip_index: FIRST_CLIP_NODE_INDEX,
+ 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:
+ ///
+ /// * Doesn't support popping clips that were pushed before the save.
+ /// * Doesn't support nested saves.
+ /// * Must call `clear_save()` if the restore becomes unnecessary.
+ pub fn save(&mut self) {
+ assert!(self.save_state.is_none(), "DisplayListBuilder doesn't support nested saves");
+
+ self.save_state = Some(SaveState {
+ dl_len: self.data.len(),
+ next_clip_index: self.next_clip_index,
+ next_spatial_index: self.next_spatial_index,
+ next_clip_chain_id: self.next_clip_chain_id,
+ });
+ }
+
+ /// Restores the state of the builder to when `save()` was last called.
+ pub fn restore(&mut self) {
+ let state = self.save_state.take().expect("No save to restore DisplayListBuilder from");
+
+ self.data.truncate(state.dl_len);
+ self.next_clip_index = state.next_clip_index;
+ self.next_spatial_index = state.next_spatial_index;
+ self.next_clip_chain_id = state.next_clip_chain_id;
+ }
+
+ /// Discards the builder's save (indicating the attempted operation was successful).
+ pub fn clear_save(&mut self) {
+ self.save_state.take().expect("No save to clear in DisplayListBuilder");
+ }
+
+ /// Emits a debug representation of display items in the list, for debugging
+ /// purposes. If the range's start parameter is specified, only display
+ /// items starting at that index (inclusive) will be printed. If the range's
+ /// end parameter is specified, only display items before that index
+ /// (exclusive) will be printed. Calling this function with end <= start is
+ /// allowed but is just a waste of CPU cycles. The function emits the
+ /// debug representation of the selected display items, one per line, with
+ /// the given indent, to the provided sink object. The return value is
+ /// the total number of items in the display list, which allows the
+ /// caller to subsequently invoke this function to only dump the newly-added
+ /// items.
+ pub fn emit_display_list<W>(
+ &mut self,
+ indent: usize,
+ range: Range<Option<usize>>,
+ mut sink: W,
+ ) -> usize
+ where
+ W: Write
+ {
+ let mut temp = BuiltDisplayList::default();
+ mem::swap(&mut temp.data, &mut self.data);
+
+ let mut index: usize = 0;
+ {
+ 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();
+ }
+ index += 1;
+ }
+ }
+
+ self.data = temp.data;
+ index
+ }
+
+ /// Print the display items in the list to stdout.
+ pub fn dump_serialized_display_list(&mut self) {
+ self.serialized_content_buffer = Some(String::new());
+ }
+
+ fn add_to_display_list_dump<T: std::fmt::Debug>(&mut self, item: T) {
+ if let Some(ref mut content) = self.serialized_content_buffer {
+ use std::fmt::Write;
+ write!(content, "{:?}\n", item).expect("DL dump write failed.");
+ }
+ }
+
+ /// Returns the default section that DisplayListBuilder will write to,
+ /// if no section is specified explicitly.
+ fn default_section(&self) -> DisplayListSection {
+ if self.writing_to_chunk {
+ DisplayListSection::Chunk
+ } else {
+ DisplayListSection::Data
+ }
+ }
+
+ fn buffer_from_section(
+ &mut self,
+ section: DisplayListSection
+ ) -> &mut Vec<u8> {
+ match section {
+ DisplayListSection::Data => &mut self.data,
+ DisplayListSection::ExtraData => &mut self.extra_data,
+ DisplayListSection::Chunk => &mut self.pending_chunk,
+ }
+ }
+
+ #[inline]
+ pub fn push_item_to_section(
+ &mut self,
+ item: &di::DisplayItem,
+ section: DisplayListSection,
+ ) {
+ poke_into_vec(item, self.buffer_from_section(section));
+ self.add_to_display_list_dump(item);
+ }
+
+ /// Add an item to the display list.
+ ///
+ /// NOTE: It is usually preferable to use the specialized methods to push
+ /// display items. Pushing unexpected or invalid items here may
+ /// result in WebRender panicking or behaving in unexpected ways.
+ #[inline]
+ pub fn push_item(&mut self, item: &di::DisplayItem) {
+ self.push_item_to_section(item, self.default_section());
+ }
+
+ fn push_iter_impl<I>(data: &mut Vec<u8>, iter_source: I)
+ where
+ I: IntoIterator,
+ I::IntoIter: ExactSizeIterator,
+ I::Item: Poke,
+ {
+ let iter = iter_source.into_iter();
+ let len = iter.len();
+ // Format:
+ // payload_byte_size: usize, item_count: usize, [I; item_count]
+
+ // Track the the location of where to write byte size with offsets
+ // instead of pointers because data may be moved in memory during
+ // `serialize_iter_fast`.
+ let byte_size_offset = data.len();
+
+ // We write a dummy value so there's room for later
+ poke_into_vec(&0usize, data);
+ poke_into_vec(&len, data);
+ let count = poke_extend_vec(iter, data);
+ debug_assert_eq!(len, count, "iterator.len() returned two different values");
+
+ // Add red zone
+ ensure_red_zone::<I::Item>(data);
+
+ // Now write the actual byte_size
+ let final_offset = data.len();
+ debug_assert!(final_offset >= (byte_size_offset + mem::size_of::<usize>()),
+ "space was never allocated for this array's byte_size");
+ let byte_size = final_offset - byte_size_offset - mem::size_of::<usize>();
+ poke_inplace_slice(&byte_size, &mut data[byte_size_offset..]);
+ }
+
+ /// Push items from an iterator to the display list.
+ ///
+ /// NOTE: Pushing unexpected or invalid items to the display list
+ /// may result in panic and confusion.
+ pub fn push_iter<I>(&mut self, iter: I)
+ where
+ I: IntoIterator,
+ I::IntoIter: ExactSizeIterator,
+ I::Item: Poke,
+ {
+ let mut buffer = self.buffer_from_section(self.default_section());
+ Self::push_iter_impl(&mut buffer, iter);
+ }
+
+ pub fn push_rect(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ color: ColorF,
+ ) {
+ let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem {
+ common: *common,
+ color: PropertyBinding::Value(color),
+ bounds,
+ });
+ self.push_item(&item);
+ }
+
+ pub fn push_rect_with_animation(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ color: PropertyBinding<ColorF>,
+ ) {
+ let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem {
+ common: *common,
+ color,
+ bounds,
+ });
+ self.push_item(&item);
+ }
+
+ pub fn push_clear_rect(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ ) {
+ let item = di::DisplayItem::ClearRectangle(di::ClearRectangleDisplayItem {
+ common: *common,
+ bounds,
+ });
+ self.push_item(&item);
+ }
+
+ pub fn push_hit_test(
+ &mut self,
+ common: &di::CommonItemProperties,
+ ) {
+ let item = di::DisplayItem::HitTest(di::HitTestDisplayItem {
+ common: *common,
+ });
+ self.push_item(&item);
+ }
+
+ pub fn push_line(
+ &mut self,
+ common: &di::CommonItemProperties,
+ area: &LayoutRect,
+ wavy_line_thickness: f32,
+ orientation: di::LineOrientation,
+ color: &ColorF,
+ style: di::LineStyle,
+ ) {
+ let item = di::DisplayItem::Line(di::LineDisplayItem {
+ common: *common,
+ area: *area,
+ wavy_line_thickness,
+ orientation,
+ color: *color,
+ style,
+ });
+
+ self.push_item(&item);
+ }
+
+ pub fn push_image(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ image_rendering: di::ImageRendering,
+ alpha_type: di::AlphaType,
+ key: ImageKey,
+ color: ColorF,
+ ) {
+ let item = di::DisplayItem::Image(di::ImageDisplayItem {
+ common: *common,
+ bounds,
+ image_key: key,
+ image_rendering,
+ alpha_type,
+ color,
+ });
+
+ self.push_item(&item);
+ }
+
+ pub fn push_repeating_image(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ stretch_size: LayoutSize,
+ tile_spacing: LayoutSize,
+ image_rendering: di::ImageRendering,
+ alpha_type: di::AlphaType,
+ key: ImageKey,
+ color: ColorF,
+ ) {
+ let item = di::DisplayItem::RepeatingImage(di::RepeatingImageDisplayItem {
+ common: *common,
+ bounds,
+ image_key: key,
+ stretch_size,
+ tile_spacing,
+ image_rendering,
+ alpha_type,
+ color,
+ });
+
+ self.push_item(&item);
+ }
+
+ /// Push a yuv image. All planar data in yuv image should use the same buffer type.
+ pub fn push_yuv_image(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ yuv_data: di::YuvData,
+ color_depth: ColorDepth,
+ color_space: di::YuvColorSpace,
+ color_range: di::ColorRange,
+ image_rendering: di::ImageRendering,
+ ) {
+ let item = di::DisplayItem::YuvImage(di::YuvImageDisplayItem {
+ common: *common,
+ bounds,
+ yuv_data,
+ color_depth,
+ color_space,
+ color_range,
+ image_rendering,
+ });
+ self.push_item(&item);
+ }
+
+ pub fn push_text(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ glyphs: &[GlyphInstance],
+ font_key: FontInstanceKey,
+ color: ColorF,
+ glyph_options: Option<GlyphOptions>,
+ ) {
+ let item = di::DisplayItem::Text(di::TextDisplayItem {
+ common: *common,
+ bounds,
+ color,
+ font_key,
+ glyph_options,
+ });
+
+ for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
+ self.push_item(&item);
+ self.push_iter(split_glyphs);
+ }
+ }
+
+ /// NOTE: gradients must be pushed in the order they're created
+ /// because create_gradient stores the stops in anticipation.
+ pub fn create_gradient(
+ &mut self,
+ start_point: LayoutPoint,
+ end_point: LayoutPoint,
+ stops: Vec<di::GradientStop>,
+ extend_mode: di::ExtendMode,
+ ) -> di::Gradient {
+ let mut builder = GradientBuilder::with_stops(stops);
+ let gradient = builder.gradient(start_point, end_point, extend_mode);
+ self.push_stops(builder.stops());
+ gradient
+ }
+
+ /// NOTE: gradients must be pushed in the order they're created
+ /// because create_gradient stores the stops in anticipation.
+ pub fn create_radial_gradient(
+ &mut self,
+ center: LayoutPoint,
+ radius: LayoutSize,
+ stops: Vec<di::GradientStop>,
+ extend_mode: di::ExtendMode,
+ ) -> di::RadialGradient {
+ let mut builder = GradientBuilder::with_stops(stops);
+ let gradient = builder.radial_gradient(center, radius, extend_mode);
+ self.push_stops(builder.stops());
+ gradient
+ }
+
+ /// NOTE: gradients must be pushed in the order they're created
+ /// because create_gradient stores the stops in anticipation.
+ pub fn create_conic_gradient(
+ &mut self,
+ center: LayoutPoint,
+ angle: f32,
+ stops: Vec<di::GradientStop>,
+ extend_mode: di::ExtendMode,
+ ) -> di::ConicGradient {
+ let mut builder = GradientBuilder::with_stops(stops);
+ let gradient = builder.conic_gradient(center, angle, extend_mode);
+ self.push_stops(builder.stops());
+ gradient
+ }
+
+ pub fn push_border(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ widths: LayoutSideOffsets,
+ details: di::BorderDetails,
+ ) {
+ let item = di::DisplayItem::Border(di::BorderDisplayItem {
+ common: *common,
+ bounds,
+ details,
+ widths,
+ });
+
+ self.push_item(&item);
+ }
+
+ pub fn push_box_shadow(
+ &mut self,
+ common: &di::CommonItemProperties,
+ box_bounds: LayoutRect,
+ offset: LayoutVector2D,
+ color: ColorF,
+ blur_radius: f32,
+ spread_radius: f32,
+ border_radius: di::BorderRadius,
+ clip_mode: di::BoxShadowClipMode,
+ ) {
+ let item = di::DisplayItem::BoxShadow(di::BoxShadowDisplayItem {
+ common: *common,
+ box_bounds,
+ offset,
+ color,
+ blur_radius,
+ spread_radius,
+ border_radius,
+ clip_mode,
+ });
+
+ self.push_item(&item);
+ }
+
+ /// Pushes a linear gradient to be displayed.
+ ///
+ /// The gradient itself is described in the
+ /// `gradient` parameter. It is drawn on
+ /// a "tile" with the dimensions from `tile_size`.
+ /// These tiles are now repeated to the right and
+ /// to the bottom infinitely. If `tile_spacing`
+ /// is not zero spacers with the given dimensions
+ /// are inserted between the tiles as seams.
+ ///
+ /// The origin of the tiles is given in `layout.rect.origin`.
+ /// If the gradient should only be displayed once limit
+ /// the `layout.rect.size` to a single tile.
+ /// The gradient is only visible within the local clip.
+ pub fn push_gradient(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ gradient: di::Gradient,
+ tile_size: LayoutSize,
+ tile_spacing: LayoutSize,
+ ) {
+ let item = di::DisplayItem::Gradient(di::GradientDisplayItem {
+ common: *common,
+ bounds,
+ gradient,
+ tile_size,
+ tile_spacing,
+ });
+
+ self.push_item(&item);
+ }
+
+ /// Pushes a radial gradient to be displayed.
+ ///
+ /// See [`push_gradient`](#method.push_gradient) for explanation.
+ pub fn push_radial_gradient(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ gradient: di::RadialGradient,
+ tile_size: LayoutSize,
+ tile_spacing: LayoutSize,
+ ) {
+ let item = di::DisplayItem::RadialGradient(di::RadialGradientDisplayItem {
+ common: *common,
+ bounds,
+ gradient,
+ tile_size,
+ tile_spacing,
+ });
+
+ self.push_item(&item);
+ }
+
+ /// Pushes a conic gradient to be displayed.
+ ///
+ /// See [`push_gradient`](#method.push_gradient) for explanation.
+ pub fn push_conic_gradient(
+ &mut self,
+ common: &di::CommonItemProperties,
+ bounds: LayoutRect,
+ gradient: di::ConicGradient,
+ tile_size: LayoutSize,
+ tile_spacing: LayoutSize,
+ ) {
+ let item = di::DisplayItem::ConicGradient(di::ConicGradientDisplayItem {
+ common: *common,
+ bounds,
+ gradient,
+ tile_size,
+ tile_spacing,
+ });
+
+ self.push_item(&item);
+ }
+
+ pub fn push_reference_frame(
+ &mut self,
+ origin: LayoutPoint,
+ parent_spatial_id: di::SpatialId,
+ transform_style: di::TransformStyle,
+ transform: PropertyBinding<LayoutTransform>,
+ kind: di::ReferenceFrameKind,
+ ) -> 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,
+ transform,
+ kind,
+ id,
+ },
+ });
+
+ self.push_item(&item);
+ id
+ }
+
+ pub fn pop_reference_frame(&mut self) {
+ self.push_item(&di::DisplayItem::PopReferenceFrame);
+ }
+
+ pub fn push_stacking_context(
+ &mut self,
+ origin: LayoutPoint,
+ spatial_id: di::SpatialId,
+ prim_flags: di::PrimitiveFlags,
+ clip_id: Option<di::ClipId>,
+ transform_style: di::TransformStyle,
+ mix_blend_mode: di::MixBlendMode,
+ filters: &[di::FilterOp],
+ filter_datas: &[di::FilterData],
+ filter_primitives: &[di::FilterPrimitive],
+ raster_space: di::RasterSpace,
+ flags: di::StackingContextFlags,
+ ) {
+ self.push_filters(filters, filter_datas, filter_primitives);
+
+ let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem {
+ origin,
+ spatial_id,
+ prim_flags,
+ stacking_context: di::StackingContext {
+ transform_style,
+ mix_blend_mode,
+ clip_id,
+ raster_space,
+ flags,
+ },
+ });
+
+ self.push_item(&item);
+ }
+
+ /// Helper for examples/ code.
+ pub fn push_simple_stacking_context(
+ &mut self,
+ origin: LayoutPoint,
+ spatial_id: di::SpatialId,
+ prim_flags: di::PrimitiveFlags,
+ ) {
+ self.push_simple_stacking_context_with_filters(
+ origin,
+ spatial_id,
+ prim_flags,
+ &[],
+ &[],
+ &[],
+ );
+ }
+
+ /// Helper for examples/ code.
+ pub fn push_simple_stacking_context_with_filters(
+ &mut self,
+ origin: LayoutPoint,
+ spatial_id: di::SpatialId,
+ prim_flags: di::PrimitiveFlags,
+ filters: &[di::FilterOp],
+ filter_datas: &[di::FilterData],
+ filter_primitives: &[di::FilterPrimitive],
+ ) {
+ self.push_stacking_context(
+ origin,
+ spatial_id,
+ prim_flags,
+ None,
+ di::TransformStyle::Flat,
+ di::MixBlendMode::Normal,
+ filters,
+ filter_datas,
+ filter_primitives,
+ di::RasterSpace::Screen,
+ di::StackingContextFlags::empty(),
+ );
+ }
+
+ pub fn pop_stacking_context(&mut self) {
+ self.push_item(&di::DisplayItem::PopStackingContext);
+ }
+
+ pub fn push_stops(&mut self, stops: &[di::GradientStop]) {
+ if stops.is_empty() {
+ return;
+ }
+ self.push_item(&di::DisplayItem::SetGradientStops);
+ self.push_iter(stops);
+ }
+
+ pub fn push_backdrop_filter(
+ &mut self,
+ common: &di::CommonItemProperties,
+ filters: &[di::FilterOp],
+ filter_datas: &[di::FilterData],
+ filter_primitives: &[di::FilterPrimitive],
+ ) {
+ self.push_filters(filters, filter_datas, filter_primitives);
+
+ let item = di::DisplayItem::BackdropFilter(di::BackdropFilterDisplayItem {
+ common: *common,
+ });
+ self.push_item(&item);
+ }
+
+ pub fn push_filters(
+ &mut self,
+ filters: &[di::FilterOp],
+ filter_datas: &[di::FilterData],
+ filter_primitives: &[di::FilterPrimitive],
+ ) {
+ if !filters.is_empty() {
+ self.push_item(&di::DisplayItem::SetFilterOps);
+ self.push_iter(filters);
+ }
+
+ for filter_data in filter_datas {
+ let func_types = [
+ filter_data.func_r_type, filter_data.func_g_type,
+ filter_data.func_b_type, filter_data.func_a_type];
+ self.push_item(&di::DisplayItem::SetFilterData);
+ self.push_iter(&func_types);
+ self.push_iter(&filter_data.r_values);
+ self.push_iter(&filter_data.g_values);
+ self.push_iter(&filter_data.b_values);
+ self.push_iter(&filter_data.a_values);
+ }
+
+ if !filter_primitives.is_empty() {
+ self.push_item(&di::DisplayItem::SetFilterPrimitives);
+ self.push_iter(filter_primitives);
+ }
+ }
+
+ fn generate_clip_index(&mut self) -> di::ClipId {
+ self.next_clip_index += 1;
+ di::ClipId::Clip(self.next_clip_index - 1, self.pipeline_id)
+ }
+
+ fn generate_spatial_index(&mut self) -> di::SpatialId {
+ self.next_spatial_index += 1;
+ di::SpatialId::new(self.next_spatial_index - 1, self.pipeline_id)
+ }
+
+ fn generate_clip_chain_id(&mut self) -> di::ClipChainId {
+ self.next_clip_chain_id += 1;
+ di::ClipChainId(self.next_clip_chain_id - 1, self.pipeline_id)
+ }
+
+ pub fn define_scroll_frame(
+ &mut self,
+ parent_space_and_clip: &di::SpaceAndClipInfo,
+ external_id: Option<di::ExternalScrollId>,
+ content_rect: LayoutRect,
+ clip_rect: LayoutRect,
+ scroll_sensitivity: di::ScrollSensitivity,
+ external_scroll_offset: LayoutVector2D,
+ ) -> di::SpaceAndClipInfo {
+ let clip_id = self.generate_clip_index();
+ let scroll_frame_id = self.generate_spatial_index();
+ let item = di::DisplayItem::ScrollFrame(di::ScrollFrameDisplayItem {
+ content_rect,
+ clip_rect,
+ parent_space_and_clip: *parent_space_and_clip,
+ clip_id,
+ scroll_frame_id,
+ external_id,
+ scroll_sensitivity,
+ external_scroll_offset,
+ });
+
+ self.push_item(&item);
+
+ di::SpaceAndClipInfo {
+ spatial_id: scroll_frame_id,
+ clip_id,
+ }
+ }
+
+ pub fn define_clip_chain<I>(
+ &mut self,
+ parent: Option<di::ClipChainId>,
+ clips: I,
+ ) -> di::ClipChainId
+ where
+ I: IntoIterator<Item = di::ClipId>,
+ I::IntoIter: ExactSizeIterator + Clone,
+ {
+ let id = self.generate_clip_chain_id();
+ self.push_item(&di::DisplayItem::ClipChain(di::ClipChainItem { id, parent }));
+ self.push_iter(clips);
+ id
+ }
+
+ pub fn define_clip_image_mask(
+ &mut self,
+ parent_space_and_clip: &di::SpaceAndClipInfo,
+ image_mask: di::ImageMask,
+ ) -> 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,
+ });
+
+ self.push_item(&item);
+ id
+ }
+
+ pub fn define_clip_rect(
+ &mut self,
+ parent_space_and_clip: &di::SpaceAndClipInfo,
+ clip_rect: LayoutRect,
+ ) -> di::ClipId {
+ let id = self.generate_clip_index();
+ let item = di::DisplayItem::RectClip(di::RectClipDisplayItem {
+ id,
+ parent_space_and_clip: *parent_space_and_clip,
+ clip_rect,
+ });
+
+ self.push_item(&item);
+ id
+ }
+
+ pub fn define_clip_rounded_rect(
+ &mut self,
+ parent_space_and_clip: &di::SpaceAndClipInfo,
+ clip: di::ComplexClipRegion,
+ ) -> di::ClipId {
+ let id = self.generate_clip_index();
+ let item = di::DisplayItem::RoundedRectClip(di::RoundedRectClipDisplayItem {
+ id,
+ parent_space_and_clip: *parent_space_and_clip,
+ clip,
+ });
+
+ self.push_item(&item);
+ id
+ }
+
+ pub fn define_clip<I>(
+ &mut self,
+ parent_space_and_clip: &di::SpaceAndClipInfo,
+ clip_rect: LayoutRect,
+ complex_clips: I,
+ ) -> di::ClipId
+ where
+ I: IntoIterator<Item = di::ComplexClipRegion>,
+ I::IntoIter: ExactSizeIterator + Clone,
+ {
+ let id = self.generate_clip_index();
+ let item = di::DisplayItem::Clip(di::ClipDisplayItem {
+ id,
+ parent_space_and_clip: *parent_space_and_clip,
+ clip_rect,
+ });
+
+ self.push_item(&item);
+ self.push_iter(complex_clips);
+ id
+ }
+
+ pub fn define_sticky_frame(
+ &mut self,
+ parent_spatial_id: di::SpatialId,
+ frame_rect: LayoutRect,
+ margins: SideOffsets2D<Option<f32>, LayoutPixel>,
+ vertical_offset_bounds: di::StickyOffsetBounds,
+ horizontal_offset_bounds: di::StickyOffsetBounds,
+ previously_applied_offset: LayoutVector2D,
+ ) -> di::SpatialId {
+ let id = self.generate_spatial_index();
+ let item = di::DisplayItem::StickyFrame(di::StickyFrameDisplayItem {
+ parent_spatial_id,
+ id,
+ bounds: frame_rect,
+ margins,
+ vertical_offset_bounds,
+ horizontal_offset_bounds,
+ previously_applied_offset,
+ });
+
+ self.push_item(&item);
+ id
+ }
+
+ pub fn push_iframe(
+ &mut self,
+ bounds: LayoutRect,
+ clip_rect: LayoutRect,
+ space_and_clip: &di::SpaceAndClipInfo,
+ pipeline_id: PipelineId,
+ ignore_missing_pipeline: bool
+ ) {
+ let item = di::DisplayItem::Iframe(di::IframeDisplayItem {
+ bounds,
+ clip_rect,
+ space_and_clip: *space_and_clip,
+ pipeline_id,
+ ignore_missing_pipeline,
+ });
+ self.push_item(&item);
+ }
+
+ pub fn push_shadow(
+ &mut self,
+ space_and_clip: &di::SpaceAndClipInfo,
+ shadow: di::Shadow,
+ should_inflate: bool,
+ ) {
+ let item = di::DisplayItem::PushShadow(di::PushShadowDisplayItem {
+ space_and_clip: *space_and_clip,
+ shadow,
+ should_inflate,
+ });
+ self.push_item(&item);
+ }
+
+ pub fn pop_all_shadows(&mut self) {
+ self.push_item(&di::DisplayItem::PopAllShadows);
+ }
+
+ pub fn start_item_group(&mut self) {
+ debug_assert!(!self.writing_to_chunk);
+ debug_assert!(self.pending_chunk.is_empty());
+
+ self.writing_to_chunk = true;
+ }
+
+ fn flush_pending_item_group(&mut self, key: di::ItemKey) {
+ // Push RetainedItems-marker to extra_data section.
+ self.push_retained_items(key);
+
+ // Push pending chunk to extra_data section.
+ self.extra_data.append(&mut self.pending_chunk);
+
+ // Push ReuseItems-marker to data section.
+ self.push_reuse_items(key);
+ }
+
+ pub fn finish_item_group(&mut self, key: di::ItemKey) -> bool {
+ debug_assert!(self.writing_to_chunk);
+ self.writing_to_chunk = false;
+
+ if self.pending_chunk.is_empty() {
+ return false;
+ }
+
+ self.flush_pending_item_group(key);
+ true
+ }
+
+ pub fn cancel_item_group(&mut self, discard: bool) {
+ debug_assert!(self.writing_to_chunk);
+ self.writing_to_chunk = false;
+
+ if discard {
+ self.pending_chunk.clear();
+ } else {
+ // Push pending chunk to data section.
+ self.data.append(&mut self.pending_chunk);
+ }
+ }
+
+ pub fn push_reuse_items(&mut self, key: di::ItemKey) {
+ self.push_item_to_section(
+ &di::DisplayItem::ReuseItems(key),
+ DisplayListSection::Data
+ );
+ }
+
+ fn push_retained_items(&mut self, key: di::ItemKey) {
+ self.push_item_to_section(
+ &di::DisplayItem::RetainedItems(key),
+ DisplayListSection::ExtraData
+ );
+ }
+
+ pub fn set_cache_size(&mut self, cache_size: usize) {
+ self.cache_size = cache_size;
+ }
+
+ 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() {
+ println!("-- WebRender display list for {:?} --\n{}",
+ self.pipeline_id, content);
+ }
+
+ // Add `DisplayItem::max_size` zone of zeroes to the end of display list
+ // so there is at least this amount available in the display list during
+ // serialization.
+ ensure_red_zone::<di::DisplayItem>(&mut self.data);
+
+ let extra_data_offset = self.data.len();
+
+ if self.extra_data.len() > 0 {
+ ensure_red_zone::<di::DisplayItem>(&mut self.extra_data);
+ self.data.extend(self.extra_data);
+ }
+
+ let end_time = precise_time_ns();
+ (
+ self.pipeline_id,
+ self.content_size,
+ BuiltDisplayList {
+ descriptor: BuiltDisplayListDescriptor {
+ builder_start_time: self.builder_start_time,
+ builder_finish_time: end_time,
+ send_start_time: end_time,
+ total_clip_nodes: self.next_clip_index,
+ total_spatial_nodes: self.next_spatial_index,
+ cache_size: self.cache_size,
+ extra_data_offset,
+ },
+ data: self.data,
+ },
+ )
+ }
+}
diff --git a/third_party/webrender/webrender_api/src/font.rs b/third_party/webrender/webrender_api/src/font.rs
new file mode 100644
index 00000000000..7f24736f09e
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/font.rs
@@ -0,0 +1,604 @@
+/* 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/. */
+
+#[cfg(target_os = "macos")]
+use core_foundation::string::CFString;
+#[cfg(target_os = "macos")]
+use core_graphics::font::CGFont;
+use peek_poke::PeekPoke;
+#[cfg(target_os = "macos")]
+use serde::de::{self, Deserialize, Deserializer};
+#[cfg(target_os = "macos")]
+use serde::ser::{Serialize, Serializer};
+use std::cmp::Ordering;
+use std::hash::{Hash, Hasher};
+#[cfg(not(target_os = "macos"))]
+use std::path::PathBuf;
+use std::sync::{Arc, RwLock, RwLockReadGuard, mpsc::Sender};
+use std::collections::HashMap;
+// local imports
+use crate::api::IdNamespace;
+use crate::color::ColorU;
+use crate::units::LayoutPoint;
+
+/// Hashable floating-point storage for font size.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, Deserialize, Serialize)]
+pub struct FontSize(pub f32);
+
+impl Ord for FontSize {
+ fn cmp(&self, other: &FontSize) -> Ordering {
+ self.partial_cmp(other).unwrap_or(Ordering::Equal)
+ }
+}
+
+impl Eq for FontSize {}
+
+impl Hash for FontSize {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.0.to_bits().hash(state);
+ }
+}
+
+impl From<f32> for FontSize {
+ fn from(size: f32) -> Self { FontSize(size) }
+}
+
+impl From<FontSize> for f32 {
+ fn from(size: FontSize) -> Self { size.0 }
+}
+
+impl FontSize {
+ pub fn zero() -> Self { FontSize(0.0) }
+
+ pub fn from_f32_px(size: f32) -> Self { FontSize(size) }
+
+ pub fn to_f32_px(&self) -> f32 { self.0 }
+
+ pub fn from_f64_px(size: f64) -> Self { FontSize(size as f32) }
+
+ pub fn to_f64_px(&self) -> f64 { self.0 as f64 }
+}
+
+/// Immutable description of a font instance requested by the user of the API.
+///
+/// `BaseFontInstance` can be identified by a `FontInstanceKey` so we should
+/// never need to hash it.
+#[derive(Clone, PartialEq, Eq, Debug, Ord, PartialOrd, MallocSizeOf)]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "deserialize", derive(Deserialize))]
+pub struct BaseFontInstance {
+ ///
+ pub instance_key: FontInstanceKey,
+ ///
+ pub font_key: FontKey,
+ ///
+ pub size: FontSize,
+ ///
+ pub bg_color: ColorU,
+ ///
+ pub render_mode: FontRenderMode,
+ ///
+ pub flags: FontInstanceFlags,
+ ///
+ pub synthetic_italics: SyntheticItalics,
+ ///
+ #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(skip))]
+ pub platform_options: Option<FontInstancePlatformOptions>,
+ ///
+ pub variations: Vec<FontVariation>,
+}
+
+pub type FontInstanceMap = HashMap<FontInstanceKey, Arc<BaseFontInstance>>;
+/// A map of font instance data accessed concurrently from multiple threads.
+#[derive(Clone)]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "deserialize", derive(Deserialize))]
+pub struct SharedFontInstanceMap {
+ map: Arc<RwLock<FontInstanceMap>>,
+}
+
+impl SharedFontInstanceMap {
+ /// Creates an empty shared map.
+ pub fn new() -> Self {
+ SharedFontInstanceMap {
+ map: Arc::new(RwLock::new(HashMap::default()))
+ }
+ }
+
+ /// Acquires a write lock on the shared map.
+ pub fn lock(&mut self) -> Option<RwLockReadGuard<FontInstanceMap>> {
+ self.map.read().ok()
+ }
+
+ ///
+ pub fn get_font_instance_data(&self, key: FontInstanceKey) -> Option<FontInstanceData> {
+ match self.map.read().unwrap().get(&key) {
+ Some(instance) => Some(FontInstanceData {
+ font_key: instance.font_key,
+ size: instance.size.into(),
+ options: Some(FontInstanceOptions {
+ render_mode: instance.render_mode,
+ flags: instance.flags,
+ bg_color: instance.bg_color,
+ synthetic_italics: instance.synthetic_italics,
+ }),
+ platform_options: instance.platform_options,
+ variations: instance.variations.clone(),
+ }),
+ None => None,
+ }
+ }
+
+ /// Replace the shared map with the provided map.
+ pub fn set(&mut self, map: FontInstanceMap) {
+ *self.map.write().unwrap() = map;
+ }
+
+ ///
+ pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option<Arc<BaseFontInstance>> {
+ let instance_map = self.map.read().unwrap();
+ instance_map.get(&instance_key).map(|instance| { Arc::clone(instance) })
+ }
+
+ ///
+ pub fn add_font_instance(
+ &mut self,
+ instance_key: FontInstanceKey,
+ font_key: FontKey,
+ size: f32,
+ options: Option<FontInstanceOptions>,
+ platform_options: Option<FontInstancePlatformOptions>,
+ variations: Vec<FontVariation>,
+ ) {
+ let FontInstanceOptions {
+ render_mode,
+ flags,
+ bg_color,
+ synthetic_italics,
+ ..
+ } = options.unwrap_or_default();
+
+ let instance = Arc::new(BaseFontInstance {
+ instance_key,
+ font_key,
+ size: size.into(),
+ bg_color,
+ render_mode,
+ flags,
+ synthetic_italics,
+ platform_options,
+ variations,
+ });
+
+ self.map
+ .write()
+ .unwrap()
+ .insert(instance_key, instance);
+ }
+
+ ///
+ pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) {
+ self.map.write().unwrap().remove(&instance_key);
+ }
+
+ ///
+ pub fn clear_namespace(&mut self, namespace: IdNamespace) {
+ self.map
+ .write()
+ .unwrap()
+ .retain(|key, _| key.0 != namespace);
+ }
+
+ ///
+ pub fn clone_map(&self) -> FontInstanceMap {
+ self.map.read().unwrap().clone()
+ }
+}
+
+#[cfg(not(target_os = "macos"))]
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct NativeFontHandle {
+ pub path: PathBuf,
+ pub index: u32,
+}
+
+#[cfg(target_os = "macos")]
+#[derive(Clone)]
+pub struct NativeFontHandle(pub CGFont);
+
+#[cfg(target_os = "macos")]
+impl Serialize for NativeFontHandle {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ self.0
+ .postscript_name()
+ .to_string()
+ .serialize(serializer)
+ }
+}
+
+#[cfg(target_os = "macos")]
+impl<'de> Deserialize<'de> for NativeFontHandle {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let postscript_name: String = Deserialize::deserialize(deserializer)?;
+
+ match CGFont::from_name(&CFString::new(&*postscript_name)) {
+ Ok(font) => Ok(NativeFontHandle(font)),
+ Err(_) => Err(de::Error::custom(
+ "Couldn't find a font with that PostScript name!",
+ )),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Deserialize, Serialize, Debug)]
+pub struct GlyphDimensions {
+ pub left: i32,
+ pub top: i32,
+ pub width: i32,
+ pub height: i32,
+ pub advance: f32,
+}
+
+pub struct GlyphDimensionRequest {
+ pub key: FontInstanceKey,
+ pub glyph_indices: Vec<GlyphIndex>,
+ pub sender: Sender<Vec<Option<GlyphDimensions>>>,
+}
+
+pub struct GlyphIndexRequest {
+ pub key: FontKey,
+ pub text: String,
+ pub sender: Sender<Vec<Option<u32>>>,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, Ord, PartialOrd)]
+pub struct FontKey(pub IdNamespace, pub u32);
+
+impl FontKey {
+ pub fn new(namespace: IdNamespace, key: u32) -> FontKey {
+ FontKey(namespace, key)
+ }
+}
+
+/// Container for the raw data describing a font. This might be a stream of
+/// bytes corresponding to a downloaded font, or a handle to a native font from
+/// the operating system.
+///
+/// Note that fonts need to be instantiated before being used, which involves
+/// assigning size and various other options. The word 'template' here is
+/// intended to distinguish this data from instance-specific data.
+#[derive(Clone)]
+pub enum FontTemplate {
+ Raw(Arc<Vec<u8>>, u32),
+ Native(NativeFontHandle),
+}
+
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, Hash, Eq, MallocSizeOf, PartialEq, Serialize, Deserialize, Ord, PartialOrd, PeekPoke)]
+pub enum FontRenderMode {
+ Mono = 0,
+ Alpha,
+ Subpixel,
+}
+
+impl Default for FontRenderMode {
+ fn default() -> Self {
+ FontRenderMode::Mono
+ }
+}
+
+impl FontRenderMode {
+ // Combine two font render modes such that the lesser amount of AA limits the AA of the result.
+ pub fn limit_by(self, other: FontRenderMode) -> FontRenderMode {
+ match (self, other) {
+ (FontRenderMode::Subpixel, _) | (_, FontRenderMode::Mono) => other,
+ _ => self,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialOrd, Deserialize, Serialize)]
+pub struct FontVariation {
+ pub tag: u32,
+ pub value: f32,
+}
+
+impl Ord for FontVariation {
+ fn cmp(&self, other: &FontVariation) -> Ordering {
+ self.tag.cmp(&other.tag)
+ .then(self.value.to_bits().cmp(&other.value.to_bits()))
+ }
+}
+
+impl PartialEq for FontVariation {
+ fn eq(&self, other: &FontVariation) -> bool {
+ self.tag == other.tag &&
+ self.value.to_bits() == other.value.to_bits()
+ }
+}
+
+impl Eq for FontVariation {}
+
+impl Hash for FontVariation {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.tag.hash(state);
+ self.value.to_bits().hash(state);
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize, PeekPoke)]
+pub struct GlyphOptions {
+ pub render_mode: FontRenderMode,
+ pub flags: FontInstanceFlags,
+}
+
+impl Default for GlyphOptions {
+ fn default() -> Self {
+ GlyphOptions {
+ render_mode: FontRenderMode::Subpixel,
+ flags: FontInstanceFlags::empty(),
+ }
+ }
+}
+
+bitflags! {
+ #[repr(C)]
+ #[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)]
+ pub struct FontInstanceFlags: u32 {
+ // Common flags
+ const SYNTHETIC_BOLD = 1 << 1;
+ const EMBEDDED_BITMAPS = 1 << 2;
+ const SUBPIXEL_BGR = 1 << 3;
+ const TRANSPOSE = 1 << 4;
+ const FLIP_X = 1 << 5;
+ const FLIP_Y = 1 << 6;
+ const SUBPIXEL_POSITION = 1 << 7;
+ const VERTICAL = 1 << 8;
+
+ // Internal flags
+ const TRANSFORM_GLYPHS = 1 << 12;
+ const TEXTURE_PADDING = 1 << 13;
+
+ // Windows flags
+ const FORCE_GDI = 1 << 16;
+ const FORCE_SYMMETRIC = 1 << 17;
+ const NO_SYMMETRIC = 1 << 18;
+
+ // Mac flags
+ const FONT_SMOOTHING = 1 << 16;
+
+ // FreeType flags
+ const FORCE_AUTOHINT = 1 << 16;
+ const NO_AUTOHINT = 1 << 17;
+ const VERTICAL_LAYOUT = 1 << 18;
+ const LCD_VERTICAL = 1 << 19;
+ }
+}
+
+impl Default for FontInstanceFlags {
+ #[cfg(target_os = "windows")]
+ fn default() -> FontInstanceFlags {
+ FontInstanceFlags::SUBPIXEL_POSITION
+ }
+
+ #[cfg(target_os = "macos")]
+ fn default() -> FontInstanceFlags {
+ FontInstanceFlags::SUBPIXEL_POSITION |
+ FontInstanceFlags::FONT_SMOOTHING
+ }
+
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
+ fn default() -> FontInstanceFlags {
+ FontInstanceFlags::SUBPIXEL_POSITION
+ }
+}
+
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)]
+pub struct SyntheticItalics {
+ // Angle in degrees (-90..90) for synthetic italics in 8.8 fixed-point.
+ pub angle: i16,
+}
+
+impl SyntheticItalics {
+ pub const ANGLE_SCALE: f32 = 256.0;
+
+ pub fn from_degrees(degrees: f32) -> Self {
+ SyntheticItalics { angle: (degrees.max(-89.0).min(89.0) * Self::ANGLE_SCALE) as i16 }
+ }
+
+ pub fn to_degrees(self) -> f32 {
+ self.angle as f32 / Self::ANGLE_SCALE
+ }
+
+ pub fn to_radians(self) -> f32 {
+ self.to_degrees().to_radians()
+ }
+
+ pub fn to_skew(self) -> f32 {
+ self.to_radians().tan()
+ }
+
+ pub fn enabled() -> Self {
+ Self::from_degrees(14.0)
+ }
+
+ pub fn disabled() -> Self {
+ SyntheticItalics { angle: 0 }
+ }
+
+ pub fn is_enabled(self) -> bool {
+ self.angle != 0
+ }
+}
+
+impl Default for SyntheticItalics {
+ fn default() -> Self {
+ SyntheticItalics::disabled()
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
+pub struct FontInstanceOptions {
+ pub render_mode: FontRenderMode,
+ pub flags: FontInstanceFlags,
+ /// When bg_color.a is != 0 and render_mode is FontRenderMode::Subpixel,
+ /// the text will be rendered with bg_color.r/g/b as an opaque estimated
+ /// background color.
+ pub bg_color: ColorU,
+ pub synthetic_italics: SyntheticItalics,
+}
+
+impl Default for FontInstanceOptions {
+ fn default() -> FontInstanceOptions {
+ FontInstanceOptions {
+ render_mode: FontRenderMode::Subpixel,
+ flags: Default::default(),
+ bg_color: ColorU::new(0, 0, 0, 0),
+ synthetic_italics: SyntheticItalics::disabled(),
+ }
+ }
+}
+
+#[cfg(target_os = "windows")]
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)]
+pub struct FontInstancePlatformOptions {
+ pub gamma: u16, // percent
+ pub contrast: u8, // percent
+ pub cleartype_level: u8, // percent
+}
+
+#[cfg(target_os = "windows")]
+impl Default for FontInstancePlatformOptions {
+ fn default() -> FontInstancePlatformOptions {
+ FontInstancePlatformOptions {
+ gamma: 180, // Default DWrite gamma
+ contrast: 100,
+ cleartype_level: 100,
+ }
+ }
+}
+
+#[cfg(target_os = "macos")]
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)]
+pub struct FontInstancePlatformOptions {
+ pub unused: u32,
+}
+
+#[cfg(target_os = "macos")]
+impl Default for FontInstancePlatformOptions {
+ fn default() -> FontInstancePlatformOptions {
+ FontInstancePlatformOptions {
+ unused: 0,
+ }
+ }
+}
+
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)]
+pub enum FontLCDFilter {
+ None,
+ Default,
+ Light,
+ Legacy,
+}
+
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)]
+pub enum FontHinting {
+ None,
+ Mono,
+ Light,
+ Normal,
+ LCD,
+}
+
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)]
+pub struct FontInstancePlatformOptions {
+ pub lcd_filter: FontLCDFilter,
+ pub hinting: FontHinting,
+}
+
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+impl Default for FontInstancePlatformOptions {
+ fn default() -> FontInstancePlatformOptions {
+ FontInstancePlatformOptions {
+ lcd_filter: FontLCDFilter::Default,
+ hinting: FontHinting::LCD,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd, MallocSizeOf, PeekPoke)]
+#[derive(Deserialize, Serialize)]
+pub struct FontInstanceKey(pub IdNamespace, pub u32);
+
+impl FontInstanceKey {
+ pub fn new(namespace: IdNamespace, key: u32) -> FontInstanceKey {
+ FontInstanceKey(namespace, key)
+ }
+}
+
+/// Data corresponding to an instantiation of a font, with size and
+/// other options specified.
+///
+/// Note that the actual font is stored out-of-band in `FontTemplate`.
+#[derive(Clone)]
+pub struct FontInstanceData {
+ pub font_key: FontKey,
+ pub size: f32,
+ pub options: Option<FontInstanceOptions>,
+ pub platform_options: Option<FontInstancePlatformOptions>,
+ pub variations: Vec<FontVariation>,
+}
+
+pub type GlyphIndex = u32;
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub struct GlyphInstance {
+ pub index: GlyphIndex,
+ pub point: LayoutPoint,
+}
+
+impl Default for GlyphInstance {
+ fn default() -> Self {
+ GlyphInstance {
+ index: 0,
+ point: LayoutPoint::zero(),
+ }
+ }
+}
+
+impl Eq for GlyphInstance {}
+
+#[cfg_attr(feature = "cargo-clippy", allow(clippy::derive_hash_xor_eq))]
+impl Hash for GlyphInstance {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // Note: this is inconsistent with the Eq impl for -0.0 (don't care).
+ self.index.hash(state);
+ self.point.x.to_bits().hash(state);
+ self.point.y.to_bits().hash(state);
+ }
+}
diff --git a/third_party/webrender/webrender_api/src/gradient_builder.rs b/third_party/webrender/webrender_api/src/gradient_builder.rs
new file mode 100644
index 00000000000..883acbafa38
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/gradient_builder.rs
@@ -0,0 +1,178 @@
+/* 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::display_item as di;
+use crate::units::*;
+
+
+/// Construct a gradient to be used in display lists.
+///
+/// Each gradient needs at least two stops.
+pub struct GradientBuilder {
+ stops: Vec<di::GradientStop>,
+}
+
+impl GradientBuilder {
+ /// Create a new gradient builder.
+ pub fn new() -> Self {
+ GradientBuilder {
+ stops: Vec::new(),
+ }
+ }
+
+ /// Create a gradient builder with a list of stops.
+ pub fn with_stops(stops: Vec<di::GradientStop>) -> GradientBuilder {
+ GradientBuilder { stops }
+ }
+
+ /// Push an additional stop for the gradient.
+ pub fn push(&mut self, stop: di::GradientStop) {
+ self.stops.push(stop);
+ }
+
+ /// Get a reference to the list of stops.
+ pub fn stops(&self) -> &[di::GradientStop] {
+ self.stops.as_ref()
+ }
+
+ /// Return the gradient stops vector.
+ pub fn into_stops(self) -> Vec<di::GradientStop> {
+ self.stops
+ }
+
+ /// Produce a linear gradient, normalize the stops.
+ pub fn gradient(
+ &mut self,
+ start_point: LayoutPoint,
+ end_point: LayoutPoint,
+ extend_mode: di::ExtendMode,
+ ) -> di::Gradient {
+ let (start_offset, end_offset) = self.normalize(extend_mode);
+ let start_to_end = end_point - start_point;
+
+ di::Gradient {
+ start_point: start_point + start_to_end * start_offset,
+ end_point: start_point + start_to_end * end_offset,
+ extend_mode,
+ }
+ }
+
+ /// Produce a radial gradient, normalize the stops.
+ ///
+ /// Will replace the gradient with a single color
+ /// if the radius negative.
+ pub fn radial_gradient(
+ &mut self,
+ center: LayoutPoint,
+ radius: LayoutSize,
+ extend_mode: di::ExtendMode,
+ ) -> di::RadialGradient {
+ if radius.width <= 0.0 || radius.height <= 0.0 {
+ // The shader cannot handle a non positive radius. So
+ // reuse the stops vector and construct an equivalent
+ // gradient.
+ let last_color = self.stops.last().unwrap().color;
+
+ self.stops.clear();
+ self.stops.push(di::GradientStop { offset: 0.0, color: last_color, });
+ self.stops.push(di::GradientStop { offset: 1.0, color: last_color, });
+
+ return di::RadialGradient {
+ center,
+ radius: LayoutSize::new(1.0, 1.0),
+ start_offset: 0.0,
+ end_offset: 1.0,
+ extend_mode,
+ };
+ }
+
+ let (start_offset, end_offset) =
+ self.normalize(extend_mode);
+
+ di::RadialGradient {
+ center,
+ radius,
+ start_offset,
+ end_offset,
+ extend_mode,
+ }
+ }
+
+ /// Produce a conic gradient, normalize the stops.
+ pub fn conic_gradient(
+ &mut self,
+ center: LayoutPoint,
+ angle: f32,
+ extend_mode: di::ExtendMode,
+ ) -> di::ConicGradient {
+ let (start_offset, end_offset) =
+ self.normalize(extend_mode);
+
+ di::ConicGradient {
+ center,
+ angle,
+ start_offset,
+ end_offset,
+ extend_mode,
+ }
+ }
+
+ /// Gradients can be defined with stops outside the range of [0, 1]
+ /// when this happens the gradient needs to be normalized by adjusting
+ /// the gradient stops and gradient line into an equivalent gradient
+ /// with stops in the range [0, 1]. this is done by moving the beginning
+ /// of the gradient line to where stop[0] and the end of the gradient line
+ /// to stop[n-1]. this function adjusts the stops in place, and returns
+ /// the amount to adjust the gradient line start and stop.
+ fn normalize(&mut self, extend_mode: di::ExtendMode) -> (f32, f32) {
+ let stops = &mut self.stops;
+ assert!(stops.len() >= 2);
+
+ let first = *stops.first().unwrap();
+ let last = *stops.last().unwrap();
+
+ assert!(first.offset <= last.offset);
+
+ let stops_delta = last.offset - first.offset;
+
+ if stops_delta > 0.000001 {
+ for stop in stops {
+ stop.offset = (stop.offset - first.offset) / stops_delta;
+ }
+
+ (first.offset, last.offset)
+ } else {
+ // We have a degenerate gradient and can't accurately transform the stops
+ // what happens here depends on the repeat behavior, but in any case
+ // we reconstruct the gradient stops to something simpler and equivalent
+ stops.clear();
+
+ match extend_mode {
+ di::ExtendMode::Clamp => {
+ // This gradient is two colors split at the offset of the stops,
+ // so create a gradient with two colors split at 0.5 and adjust
+ // the gradient line so 0.5 is at the offset of the stops
+ stops.push(di::GradientStop { color: first.color, offset: 0.0, });
+ stops.push(di::GradientStop { color: first.color, offset: 0.5, });
+ stops.push(di::GradientStop { color: last.color, offset: 0.5, });
+ stops.push(di::GradientStop { color: last.color, offset: 1.0, });
+
+ let offset = last.offset;
+
+ (offset - 0.5, offset + 0.5)
+ }
+ di::ExtendMode::Repeat => {
+ // A repeating gradient with stops that are all in the same
+ // position should just display the last color. I believe the
+ // spec says that it should be the average color of the gradient,
+ // but this matches what Gecko and Blink does
+ stops.push(di::GradientStop { color: last.color, offset: 0.0, });
+ stops.push(di::GradientStop { color: last.color, offset: 1.0, });
+
+ (0.0, 1.0)
+ }
+ }
+ }
+ }
+}
diff --git a/third_party/webrender/webrender_api/src/image.rs b/third_party/webrender/webrender_api/src/image.rs
new file mode 100644
index 00000000000..deaeb92aeb1
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/image.rs
@@ -0,0 +1,595 @@
+/* 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 euclid::{size2, Rect, num::Zero};
+use peek_poke::PeekPoke;
+use std::ops::{Add, Sub};
+use std::sync::Arc;
+// local imports
+use crate::api::{IdNamespace, PipelineId, TileSize};
+use crate::display_item::ImageRendering;
+use crate::font::{FontInstanceKey, FontInstanceData, FontKey, FontTemplate};
+use crate::units::*;
+
+/// The default tile size for blob images and regular images larger than
+/// the maximum texture size.
+pub const DEFAULT_TILE_SIZE: TileSize = 512;
+
+/// An opaque identifier describing an image registered with WebRender.
+/// This is used as a handle to reference images, and is used as the
+/// hash map key for the actual image storage in the `ResourceCache`.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub struct ImageKey(pub IdNamespace, pub u32);
+
+impl Default for ImageKey {
+ fn default() -> Self {
+ ImageKey::DUMMY
+ }
+}
+
+impl ImageKey {
+ /// Placeholder Image key, used to represent None.
+ pub const DUMMY: Self = ImageKey(IdNamespace(0), 0);
+
+ /// Mints a new ImageKey. The given ID must be unique.
+ pub fn new(namespace: IdNamespace, key: u32) -> Self {
+ ImageKey(namespace, key)
+ }
+}
+
+/// An opaque identifier describing a blob image registered with WebRender.
+/// This is used as a handle to reference blob images, and can be used as an
+/// image in display items.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct BlobImageKey(pub ImageKey);
+
+impl BlobImageKey {
+ /// Interpret this blob image as an image for a display item.
+ pub fn as_image(self) -> ImageKey {
+ self.0
+ }
+}
+
+/// An arbitrary identifier for an external image provided by the
+/// application. It must be a unique identifier for each external
+/// image.
+#[repr(C)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
+pub struct ExternalImageId(pub u64);
+
+/// The source for an external image.
+pub enum ExternalImageSource<'a> {
+ /// A raw pixel buffer.
+ RawData(&'a [u8]),
+ /// A gl::GLuint texture handle.
+ NativeTexture(u32),
+ /// An invalid source.
+ Invalid,
+}
+
+/// The data that an external client should provide about
+/// an external image. For instance, if providing video frames,
+/// the application could call wr.render() whenever a new
+/// video frame is ready. Note that the UV coords are supplied
+/// in texel-space!
+pub struct ExternalImage<'a> {
+ /// UV coordinates for the image.
+ pub uv: TexelRect,
+ /// The source for this image's contents.
+ pub source: ExternalImageSource<'a>,
+}
+
+/// The interfaces that an application can implement to support providing
+/// external image buffers.
+/// When the application passes an external image to WR, it should keep that
+/// external image life time. People could check the epoch id in RenderNotifier
+/// at the client side to make sure that the external image is not used by WR.
+/// Then, do the clean up for that external image.
+pub trait ExternalImageHandler {
+ /// Lock the external image. Then, WR could start to read the image content.
+ /// The WR client should not change the image content until the unlock()
+ /// call. Provide ImageRendering for NativeTexture external images.
+ fn lock(&mut self, key: ExternalImageId, channel_index: u8, rendering: ImageRendering) -> ExternalImage;
+ /// Unlock the external image. WR should not read the image content
+ /// after this call.
+ 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, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum TextureTarget {
+ /// Standard texture. This maps to GL_TEXTURE_2D in OpenGL.
+ 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.
+ 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
+ External = 3,
+}
+
+/// Storage format identifier for externally-managed images.
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum ExternalImageType {
+ /// The image is texture-backed.
+ TextureHandle(TextureTarget),
+ /// The image is heap-allocated by the embedding.
+ Buffer,
+}
+
+/// Descriptor for external image resources. See `ImageData`.
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub struct ExternalImageData {
+ /// The identifier of this external image, provided by the embedding.
+ pub id: ExternalImageId,
+ /// For multi-plane images (i.e. YUV), indicates the plane of the
+ /// original image that this struct represents. 0 for single-plane images.
+ pub channel_index: u8,
+ /// Storage format identifier.
+ pub image_type: ExternalImageType,
+}
+
+/// Specifies the format of a series of pixels, in driver terms.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub enum ImageFormat {
+ /// One-channel, byte storage. The "red" doesn't map to the color
+ /// red per se, and is just the way that OpenGL has historically referred
+ /// to single-channel buffers.
+ R8 = 1,
+ /// One-channel, short storage
+ R16 = 2,
+ /// Four channels, byte storage.
+ BGRA8 = 3,
+ /// Four channels, float storage.
+ RGBAF32 = 4,
+ /// Two-channels, byte storage. Similar to `R8`, this just means
+ /// "two channels" rather than "red and green".
+ RG8 = 5,
+ /// Two-channels, byte storage. Similar to `R16`, this just means
+ /// "two channels" rather than "red and green".
+ RG16 = 6,
+
+ /// Four channels, signed integer storage.
+ RGBAI32 = 7,
+ /// Four channels, byte storage.
+ RGBA8 = 8,
+}
+
+impl ImageFormat {
+ /// Returns the number of bytes per pixel for the given format.
+ pub fn bytes_per_pixel(self) -> i32 {
+ match self {
+ ImageFormat::R8 => 1,
+ ImageFormat::R16 => 2,
+ ImageFormat::BGRA8 => 4,
+ ImageFormat::RGBAF32 => 16,
+ ImageFormat::RG8 => 2,
+ ImageFormat::RG16 => 4,
+ ImageFormat::RGBAI32 => 16,
+ ImageFormat::RGBA8 => 4,
+ }
+ }
+}
+
+/// Specifies the color depth of an image. Currently only used for YUV images.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
+pub enum ColorDepth {
+ /// 8 bits image (most common)
+ Color8,
+ /// 10 bits image
+ Color10,
+ /// 12 bits image
+ Color12,
+ /// 16 bits image
+ Color16,
+}
+
+impl Default for ColorDepth {
+ fn default() -> Self {
+ ColorDepth::Color8
+ }
+}
+
+impl ColorDepth {
+ /// Return the numerical bit depth value for the type.
+ pub fn bit_depth(self) -> u32 {
+ match self {
+ ColorDepth::Color8 => 8,
+ ColorDepth::Color10 => 10,
+ ColorDepth::Color12 => 12,
+ ColorDepth::Color16 => 16,
+ }
+ }
+ /// 10 and 12 bits images are encoded using 16 bits integer, we need to
+ /// rescale the 10 or 12 bits value to extend to 16 bits.
+ pub fn rescaling_factor(self) -> f32 {
+ match self {
+ ColorDepth::Color8 => 1.0,
+ ColorDepth::Color10 => 64.0,
+ ColorDepth::Color12 => 16.0,
+ ColorDepth::Color16 => 1.0,
+ }
+ }
+}
+
+bitflags! {
+ /// Various flags that are part of an image descriptor.
+ #[derive(Deserialize, Serialize)]
+ pub struct ImageDescriptorFlags: u32 {
+ /// Whether this image is opaque, or has an alpha channel. Avoiding blending
+ /// for opaque surfaces is an important optimization.
+ const IS_OPAQUE = 1;
+ /// Whether to allow the driver to automatically generate mipmaps. If images
+ /// are already downscaled appropriately, mipmap generation can be wasted
+ /// work, and cause performance problems on some cards/drivers.
+ ///
+ /// See https://github.com/servo/webrender/pull/2555/
+ const ALLOW_MIPMAPS = 2;
+ }
+}
+
+/// Metadata (but not storage) describing an image In WebRender.
+#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ImageDescriptor {
+ /// Format of the image data.
+ pub format: ImageFormat,
+ /// Width and length of the image data, in pixels.
+ pub size: DeviceIntSize,
+ /// The number of bytes from the start of one row to the next. If non-None,
+ /// `compute_stride` will return this value, otherwise it returns
+ /// `width * bpp`. Different source of images have different alignment
+ /// constraints for rows, so the stride isn't always equal to width * bpp.
+ pub stride: Option<i32>,
+ /// Offset in bytes of the first pixel of this image in its backing buffer.
+ /// This is used for tiling, wherein WebRender extracts chunks of input images
+ /// in order to cache, manipulate, and render them individually. This offset
+ /// tells the texture upload machinery where to find the bytes to upload for
+ /// this tile. Non-tiled images generally set this to zero.
+ pub offset: i32,
+ /// Various bool flags related to this descriptor.
+ pub flags: ImageDescriptorFlags,
+}
+
+impl ImageDescriptor {
+ /// Mints a new ImageDescriptor.
+ pub fn new(
+ width: i32,
+ height: i32,
+ format: ImageFormat,
+ flags: ImageDescriptorFlags,
+ ) -> Self {
+ ImageDescriptor {
+ size: size2(width, height),
+ format,
+ stride: None,
+ offset: 0,
+ flags,
+ }
+ }
+
+ /// Returns the stride, either via an explicit stride stashed on the object
+ /// or by the default computation.
+ pub fn compute_stride(&self) -> i32 {
+ self.stride.unwrap_or(self.size.width * self.format.bytes_per_pixel())
+ }
+
+ /// Computes the total size of the image, in bytes.
+ pub fn compute_total_size(&self) -> i32 {
+ self.compute_stride() * self.size.height
+ }
+
+ /// Computes the bounding rectangle for the image, rooted at (0, 0).
+ pub fn full_rect(&self) -> DeviceIntRect {
+ DeviceIntRect::new(
+ DeviceIntPoint::zero(),
+ self.size,
+ )
+ }
+
+ /// Returns true if this descriptor is opaque
+ pub fn is_opaque(&self) -> bool {
+ self.flags.contains(ImageDescriptorFlags::IS_OPAQUE)
+ }
+
+ /// Returns true if this descriptor allows mipmaps
+ pub fn allow_mipmaps(&self) -> bool {
+ self.flags.contains(ImageDescriptorFlags::ALLOW_MIPMAPS)
+ }
+}
+
+/// Represents the backing store of an arbitrary series of pixels for display by
+/// WebRender. This storage can take several forms.
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum ImageData {
+ /// A simple series of bytes, provided by the embedding and owned by WebRender.
+ /// The format is stored out-of-band, currently in ImageDescriptor.
+ Raw(#[serde(with = "serde_image_data_raw")] Arc<Vec<u8>>),
+ /// An image owned by the embedding, and referenced by WebRender. This may
+ /// take the form of a texture or a heap-allocated buffer.
+ External(ExternalImageData),
+}
+
+mod serde_image_data_raw {
+ extern crate serde_bytes;
+
+ use std::sync::Arc;
+ use serde::{Deserializer, Serializer};
+
+ pub fn serialize<S: Serializer>(bytes: &Arc<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error> {
+ serde_bytes::serialize(bytes.as_slice(), serializer)
+ }
+
+ pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Arc<Vec<u8>>, D::Error> {
+ serde_bytes::deserialize(deserializer).map(Arc::new)
+ }
+}
+
+impl ImageData {
+ /// Mints a new raw ImageData, taking ownership of the bytes.
+ pub fn new(bytes: Vec<u8>) -> Self {
+ ImageData::Raw(Arc::new(bytes))
+ }
+
+ /// Mints a new raw ImageData from Arc-ed bytes.
+ pub fn new_shared(bytes: Arc<Vec<u8>>) -> Self {
+ ImageData::Raw(bytes)
+ }
+}
+
+/// The resources exposed by the resource cache available for use by the blob rasterizer.
+pub trait BlobImageResources {
+ /// Returns the `FontTemplate` for the given key.
+ fn get_font_data(&self, key: FontKey) -> &FontTemplate;
+ /// Returns the `FontInstanceData` for the given key, if found.
+ fn get_font_instance_data(&self, key: FontInstanceKey) -> Option<FontInstanceData>;
+}
+
+/// A handler on the render backend that can create rasterizer objects which will
+/// be sent to the scene builder thread to execute the rasterization.
+///
+/// The handler is responsible for collecting resources, managing/updating blob commands
+/// and creating the rasterizer objects, but isn't expected to do any rasterization itself.
+pub trait BlobImageHandler: Send {
+ /// Creates a snapshot of the current state of blob images in the handler.
+ fn create_blob_rasterizer(&mut self) -> Box<dyn AsyncBlobImageRasterizer>;
+
+ /// Creates an empty blob handler of the same type.
+ ///
+ /// This is used to allow creating new API endpoints with blob handlers installed on them.
+ fn create_similar(&self) -> Box<dyn BlobImageHandler>;
+
+ /// A hook to let the blob image handler update any state related to resources that
+ /// are not bundled in the blob recording itself.
+ fn prepare_resources(
+ &mut self,
+ services: &dyn BlobImageResources,
+ requests: &[BlobImageParams],
+ );
+
+ /// Register a blob image.
+ fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect,
+ tile_size: TileSize);
+
+ /// Update an already registered blob image.
+ fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect,
+ dirty_rect: &BlobDirtyRect);
+
+ /// Delete an already registered blob image.
+ fn delete(&mut self, key: BlobImageKey);
+
+ /// A hook to let the handler clean up any state related to a font which the resource
+ /// cache is about to delete.
+ fn delete_font(&mut self, key: FontKey);
+
+ /// A hook to let the handler clean up any state related to a font instance which the
+ /// resource cache is about to delete.
+ fn delete_font_instance(&mut self, key: FontInstanceKey);
+
+ /// A hook to let the handler clean up any state related a given namespace before the
+ /// resource cache deletes them.
+ fn clear_namespace(&mut self, namespace: IdNamespace);
+
+ /// Whether to allow rendering blobs on multiple threads.
+ fn enable_multithreading(&mut self, enable: bool);
+}
+
+/// A group of rasterization requests to execute synchronously on the scene builder thread.
+pub trait AsyncBlobImageRasterizer : Send {
+ /// Rasterize the requests.
+ ///
+ /// Gecko uses te priority hint to schedule work in a way that minimizes the risk
+ /// of high priority work being blocked by (or enqued behind) low priority work.
+ fn rasterize(
+ &mut self,
+ requests: &[BlobImageParams],
+ low_priority: bool
+ ) -> Vec<(BlobImageRequest, BlobImageResult)>;
+}
+
+
+/// Input parameters for the BlobImageRasterizer.
+#[derive(Copy, Clone, Debug)]
+pub struct BlobImageParams {
+ /// A key that identifies the blob image rasterization request.
+ pub request: BlobImageRequest,
+ /// Description of the format of the blob's output image.
+ pub descriptor: BlobImageDescriptor,
+ /// An optional sub-rectangle of the image to avoid re-rasterizing
+ /// the entire image when only a portion is updated.
+ ///
+ /// If set to None the entire image is rasterized.
+ pub dirty_rect: BlobDirtyRect,
+}
+
+/// The possible states of a Dirty rect.
+///
+/// This exists because people kept getting confused with `Option<Rect>`.
+#[derive(Debug, Serialize, Deserialize)]
+pub enum DirtyRect<T: Copy, U> {
+ /// Everything is Dirty, equivalent to Partial(image_bounds)
+ All,
+ /// Some specific amount is dirty
+ Partial(Rect<T, U>)
+}
+
+impl<T, U> DirtyRect<T, U>
+where
+ T: Copy + Clone
+ + PartialOrd + PartialEq
+ + Add<T, Output = T>
+ + Sub<T, Output = T>
+ + Zero
+{
+ /// Creates an empty DirtyRect (indicating nothing is invalid)
+ pub fn empty() -> Self {
+ DirtyRect::Partial(Rect::zero())
+ }
+
+ /// Returns whether the dirty rect is empty
+ pub fn is_empty(&self) -> bool {
+ match self {
+ DirtyRect::All => false,
+ DirtyRect::Partial(rect) => rect.is_empty(),
+ }
+ }
+
+ /// Replaces self with the empty rect and returns the old value.
+ pub fn replace_with_empty(&mut self) -> Self {
+ ::std::mem::replace(self, DirtyRect::empty())
+ }
+
+ /// Maps over the contents of Partial.
+ pub fn map<F>(self, func: F) -> Self
+ where F: FnOnce(Rect<T, U>) -> Rect<T, U>,
+ {
+ use crate::DirtyRect::*;
+
+ match self {
+ All => All,
+ Partial(rect) => Partial(func(rect)),
+ }
+ }
+
+ /// Unions the dirty rects.
+ pub fn union(&self, other: &Self) -> Self {
+ use crate::DirtyRect::*;
+
+ match (*self, *other) {
+ (All, _) | (_, All) => All,
+ (Partial(rect1), Partial(rect2)) => Partial(rect1.union(&rect2)),
+ }
+ }
+
+ /// Intersects the dirty rects.
+ pub fn intersection(&self, other: &Self) -> Self {
+ use crate::DirtyRect::*;
+
+ match (*self, *other) {
+ (All, rect) | (rect, All) => rect,
+ (Partial(rect1), Partial(rect2)) => {
+ Partial(rect1.intersection(&rect2).unwrap_or_else(Rect::zero))
+ }
+ }
+ }
+
+ /// Converts the dirty rect into a subrect of the given one via intersection.
+ pub fn to_subrect_of(&self, rect: &Rect<T, U>) -> Rect<T, U> {
+ use crate::DirtyRect::*;
+
+ match *self {
+ All => *rect,
+ Partial(dirty_rect) => {
+ dirty_rect.intersection(rect).unwrap_or_else(Rect::zero)
+ }
+ }
+ }
+}
+
+impl<T: Copy, U> Copy for DirtyRect<T, U> {}
+impl<T: Copy, U> Clone for DirtyRect<T, U> {
+ fn clone(&self) -> Self { *self }
+}
+
+impl<T: Copy, U> From<Rect<T, U>> for DirtyRect<T, U> {
+ fn from(rect: Rect<T, U>) -> Self {
+ DirtyRect::Partial(rect)
+ }
+}
+
+/// Backing store for blob image command streams.
+pub type BlobImageData = Vec<u8>;
+
+/// Result type for blob raserization.
+pub type BlobImageResult = Result<RasterizedBlobImage, BlobImageError>;
+
+/// Metadata (but not storage) for a blob image.
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct BlobImageDescriptor {
+ /// Surface of the image or tile to render in the same coordinate space as
+ /// the drawing commands.
+ pub rect: LayoutIntRect,
+ /// Format for the data in the backing store.
+ pub format: ImageFormat,
+}
+
+/// Representation of a rasterized blob image. This is obtained by passing
+/// `BlobImageData` to the embedding via the rasterization callback.
+pub struct RasterizedBlobImage {
+ /// The rectangle that was rasterized in device pixels, relative to the
+ /// image or tile.
+ pub rasterized_rect: DeviceIntRect,
+ /// Backing store. The format is stored out of band in `BlobImageDescriptor`.
+ pub data: Arc<Vec<u8>>,
+}
+
+/// Error code for when blob rasterization failed.
+#[derive(Clone, Debug)]
+pub enum BlobImageError {
+ /// Out of memory.
+ Oom,
+ /// Other failure, embedding-specified.
+ Other(String),
+}
+
+
+
+/// A key identifying blob image rasterization work requested from the blob
+/// image rasterizer.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub struct BlobImageRequest {
+ /// Unique handle to the image.
+ pub key: BlobImageKey,
+ /// Tiling offset in number of tiles.
+ pub tile: TileOffset,
+}
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<Self::Item> {
+ 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<i32>,
+ /// Range of tiles of the full image including tiles that are culled out.
+ image_tiles: Range<i32>,
+ /// 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<Self::Item> {
+ // 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<f32>,
+ layout_prim_start: f32,
+ device_image_range: Range<i32>,
+ 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<i32>,
+ regular_tile_size: i32,
+) -> Range<i32> {
+ // 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<i32>,
+ 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<i32>,
+ 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<i32>,
+ 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<i32>,
+ 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<TileRange> {
+ 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::<f32>()
+ .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
new file mode 100644
index 00000000000..5f274753e8f
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/lib.rs
@@ -0,0 +1,64 @@
+/* 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/. */
+
+//! The `webrender_api` crate contains an assortment types and functions used
+//! by WebRender consumers as well as, in many cases, WebRender itself.
+//!
+//! This separation allows Servo to parallelize compilation across `webrender`
+//! and other crates that depend on `webrender_api`. So in practice, we put
+//! things in this crate when Servo needs to use them. Firefox depends on the
+//! `webrender` crate directly, and so this distinction is not really relevant
+//! there.
+
+#![cfg_attr(feature = "nightly", feature(nonzero))]
+#![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))]
+
+extern crate app_units;
+#[macro_use]
+extern crate bitflags;
+extern crate byteorder;
+#[cfg(feature = "nightly")]
+extern crate core;
+#[cfg(target_os = "macos")]
+extern crate core_foundation;
+#[cfg(target_os = "macos")]
+extern crate core_graphics;
+#[macro_use]
+extern crate derive_more;
+pub extern crate euclid;
+#[macro_use]
+extern crate malloc_size_of_derive;
+extern crate serde;
+#[macro_use]
+extern crate serde_derive;
+extern crate time;
+
+extern crate malloc_size_of;
+extern crate peek_poke;
+
+mod api;
+pub mod channel;
+mod color;
+mod display_item;
+mod display_item_cache;
+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;
+pub use crate::display_list::*;
+pub use crate::font::*;
+pub use crate::gradient_builder::*;
+pub use crate::image::*;
+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<TileRange>,
+}
+
+struct FontResources {
+ templates: HashMap<FontKey, FontTemplate>,
+ instances: SharedFontInstanceMap,
+}
+
+pub struct ApiResources {
+ blob_image_templates: HashMap<BlobImageKey, BlobImageTemplate>,
+ pub blob_image_handler: Option<Box<dyn BlobImageHandler>>,
+ 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<FontInstanceData> {
+ self.instances.get_font_instance_data(key)
+ }
+}
+
+impl ApiResources {
+ pub fn new(
+ blob_image_handler: Option<Box<dyn BlobImageHandler>>,
+ 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<Arc<BlobImageData>>,
+ 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<Box<dyn AsyncBlobImageRasterizer>>, Vec<BlobImageParams>) {
+ 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<TileRange> {
+ 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::<f32>()
+ .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
new file mode 100644
index 00000000000..9913e4e6647
--- /dev/null
+++ b/third_party/webrender/webrender_api/src/units.rs
@@ -0,0 +1,324 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A collection of coordinate spaces and their corresponding Point, Size and Rect types.
+//!
+//! Physical pixels take into account the device pixel ratio and their dimensions tend
+//! to correspond to the allocated size of resources in memory, while logical pixels
+//! don't have the device pixel ratio applied which means they are agnostic to the usage
+//! of hidpi screens and the like.
+//!
+//! The terms "layer" and "stacking context" can be used interchangeably
+//! in the context of coordinate systems.
+
+pub use app_units::Au;
+use euclid::{Length, Rect, Scale, Size2D, Transform3D, Translation2D};
+use euclid::{Point2D, Point3D, Vector2D, Vector3D, SideOffsets2D, Box2D};
+use euclid::HomogeneousVector;
+use peek_poke::PeekPoke;
+// local imports
+use crate::image::DirtyRect;
+
+/// Geometry in the coordinate system of the render target (screen or intermediate
+/// surface) in physical pixels.
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
+pub struct DevicePixel;
+
+pub type DeviceIntRect = Rect<i32, DevicePixel>;
+pub type DeviceIntPoint = Point2D<i32, DevicePixel>;
+pub type DeviceIntSize = Size2D<i32, DevicePixel>;
+pub type DeviceIntLength = Length<i32, DevicePixel>;
+pub type DeviceIntSideOffsets = SideOffsets2D<i32, DevicePixel>;
+
+pub type DeviceRect = Rect<f32, DevicePixel>;
+pub type DevicePoint = Point2D<f32, DevicePixel>;
+pub type DeviceVector2D = Vector2D<f32, DevicePixel>;
+pub type DeviceSize = Size2D<f32, DevicePixel>;
+pub type DeviceHomogeneousVector = HomogeneousVector<f32, DevicePixel>;
+
+/// Geometry in the coordinate system of the framebuffer in physical pixels.
+/// It's Y-flipped comparing to DevicePixel.
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
+pub struct FramebufferPixel;
+
+pub type FramebufferIntPoint = Point2D<i32, FramebufferPixel>;
+pub type FramebufferIntSize = Size2D<i32, FramebufferPixel>;
+pub type FramebufferIntRect = Rect<i32, FramebufferPixel>;
+
+/// Geometry in the coordinate system of a Picture (intermediate
+/// surface) in physical pixels.
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct PicturePixel;
+
+pub type PictureIntRect = Rect<i32, PicturePixel>;
+pub type PictureIntPoint = Point2D<i32, PicturePixel>;
+pub type PictureIntSize = Size2D<i32, PicturePixel>;
+pub type PictureRect = Rect<f32, PicturePixel>;
+pub type PicturePoint = Point2D<f32, PicturePixel>;
+pub type PictureSize = Size2D<f32, PicturePixel>;
+pub type PicturePoint3D = Point3D<f32, PicturePixel>;
+pub type PictureVector2D = Vector2D<f32, PicturePixel>;
+pub type PictureVector3D = Vector3D<f32, PicturePixel>;
+pub type PictureBox2D = Box2D<f32, PicturePixel>;
+
+/// Geometry gets rasterized in a given root coordinate space. This
+/// is often the root spatial node (world space), but may be a local
+/// space for a variety of reasons (e.g. perspective).
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct RasterPixel;
+
+pub type RasterIntRect = Rect<i32, RasterPixel>;
+pub type RasterIntPoint = Point2D<i32, RasterPixel>;
+pub type RasterIntSize = Size2D<i32, RasterPixel>;
+pub type RasterRect = Rect<f32, RasterPixel>;
+pub type RasterPoint = Point2D<f32, RasterPixel>;
+pub type RasterSize = Size2D<f32, RasterPixel>;
+pub type RasterPoint3D = Point3D<f32, RasterPixel>;
+pub type RasterVector2D = Vector2D<f32, RasterPixel>;
+pub type RasterVector3D = Vector3D<f32, RasterPixel>;
+
+/// Geometry in a stacking context's local coordinate space (logical pixels).
+#[derive(Hash, Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Ord, PartialOrd, Deserialize, Serialize, PeekPoke)]
+pub struct LayoutPixel;
+
+pub type LayoutRect = Rect<f32, LayoutPixel>;
+pub type LayoutPoint = Point2D<f32, LayoutPixel>;
+pub type LayoutPoint3D = Point3D<f32, LayoutPixel>;
+pub type LayoutVector2D = Vector2D<f32, LayoutPixel>;
+pub type LayoutVector3D = Vector3D<f32, LayoutPixel>;
+pub type LayoutSize = Size2D<f32, LayoutPixel>;
+pub type LayoutSideOffsets = SideOffsets2D<f32, LayoutPixel>;
+
+pub type LayoutIntRect = Rect<i32, LayoutPixel>;
+pub type LayoutIntPoint = Point2D<i32, LayoutPixel>;
+pub type LayoutIntSize = Size2D<i32, LayoutPixel>;
+
+/// Geometry in the document's coordinate space (logical pixels).
+#[derive(Hash, Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Ord, PartialOrd)]
+pub struct WorldPixel;
+
+pub type WorldRect = Rect<f32, WorldPixel>;
+pub type WorldPoint = Point2D<f32, WorldPixel>;
+pub type WorldSize = Size2D<f32, WorldPixel>;
+pub type WorldPoint3D = Point3D<f32, WorldPixel>;
+pub type WorldVector2D = Vector2D<f32, WorldPixel>;
+pub type WorldVector3D = Vector3D<f32, WorldPixel>;
+
+/// Offset in number of tiles.
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct Tiles;
+pub type TileOffset = Point2D<i32, Tiles>;
+pub type TileRange = Rect<i32, Tiles>;
+
+/// Scaling ratio from world pixels to device pixels.
+pub type DevicePixelScale = Scale<f32, WorldPixel, DevicePixel>;
+/// Scaling ratio from layout to world. Used for cases where we know the layout
+/// is in world space, or specifically want to treat it this way.
+pub type LayoutToWorldScale = Scale<f32, LayoutPixel, WorldPixel>;
+/// A complete scaling ratio from layout space to device pixel space.
+pub type LayoutToDeviceScale = Scale<f32, LayoutPixel, DevicePixel>;
+
+pub type LayoutTransform = Transform3D<f32, LayoutPixel, LayoutPixel>;
+pub type LayoutToWorldTransform = Transform3D<f32, LayoutPixel, WorldPixel>;
+pub type WorldToLayoutTransform = Transform3D<f32, WorldPixel, LayoutPixel>;
+
+pub type LayoutToPictureTransform = Transform3D<f32, LayoutPixel, PicturePixel>;
+pub type PictureToLayoutTransform = Transform3D<f32, PicturePixel, LayoutPixel>;
+
+pub type LayoutToRasterTransform = Transform3D<f32, LayoutPixel, RasterPixel>;
+pub type RasterToLayoutTransform = Transform3D<f32, RasterPixel, LayoutPixel>;
+
+pub type PictureToRasterTransform = Transform3D<f32, PicturePixel, RasterPixel>;
+pub type RasterToPictureTransform = Transform3D<f32, RasterPixel, PicturePixel>;
+
+// Fixed position coordinates, to avoid float precision errors.
+pub type LayoutPointAu = Point2D<Au, LayoutPixel>;
+pub type LayoutRectAu = Rect<Au, LayoutPixel>;
+pub type LayoutSizeAu = Size2D<Au, LayoutPixel>;
+pub type LayoutVector2DAu = Vector2D<Au, LayoutPixel>;
+pub type LayoutSideOffsetsAu = SideOffsets2D<Au, LayoutPixel>;
+
+pub type ImageDirtyRect = DirtyRect<i32, DevicePixel>;
+pub type BlobDirtyRect = DirtyRect<i32, LayoutPixel>;
+
+pub type BlobToDeviceTranslation = Translation2D<i32, LayoutPixel, DevicePixel>;
+
+/// Stores two coordinates in texel space. The coordinates
+/// are stored in texel coordinates because the texture atlas
+/// 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, Serialize, Deserialize)]
+pub struct TexelRect {
+ pub uv0: DevicePoint,
+ pub uv1: DevicePoint,
+}
+
+impl TexelRect {
+ pub fn new(u0: f32, v0: f32, u1: f32, v1: f32) -> Self {
+ TexelRect {
+ uv0: DevicePoint::new(u0, v0),
+ uv1: DevicePoint::new(u1, v1),
+ }
+ }
+
+ pub fn invalid() -> Self {
+ TexelRect {
+ uv0: DevicePoint::new(-1.0, -1.0),
+ uv1: DevicePoint::new(-1.0, -1.0),
+ }
+ }
+}
+
+impl Into<TexelRect> for DeviceIntRect {
+ fn into(self) -> TexelRect {
+ TexelRect {
+ uv0: self.min().to_f32(),
+ uv1: self.max().to_f32(),
+ }
+ }
+}
+
+const MAX_AU_FLOAT: f32 = 1.0e6;
+
+pub trait AuHelpers<T> {
+ fn from_au(data: T) -> Self;
+ fn to_au(&self) -> T;
+}
+
+impl AuHelpers<LayoutSizeAu> for LayoutSize {
+ fn from_au(size: LayoutSizeAu) -> Self {
+ LayoutSize::new(
+ size.width.to_f32_px(),
+ size.height.to_f32_px(),
+ )
+ }
+
+ fn to_au(&self) -> LayoutSizeAu {
+ let width = self.width.min(2.0 * MAX_AU_FLOAT);
+ let height = self.height.min(2.0 * MAX_AU_FLOAT);
+
+ LayoutSizeAu::new(
+ Au::from_f32_px(width),
+ Au::from_f32_px(height),
+ )
+ }
+}
+
+impl AuHelpers<LayoutVector2DAu> for LayoutVector2D {
+ fn from_au(size: LayoutVector2DAu) -> Self {
+ LayoutVector2D::new(
+ size.x.to_f32_px(),
+ size.y.to_f32_px(),
+ )
+ }
+
+ fn to_au(&self) -> LayoutVector2DAu {
+ LayoutVector2DAu::new(
+ Au::from_f32_px(self.x),
+ Au::from_f32_px(self.y),
+ )
+ }
+}
+
+impl AuHelpers<LayoutPointAu> for LayoutPoint {
+ fn from_au(point: LayoutPointAu) -> Self {
+ LayoutPoint::new(
+ point.x.to_f32_px(),
+ point.y.to_f32_px(),
+ )
+ }
+
+ fn to_au(&self) -> LayoutPointAu {
+ let x = self.x.min(MAX_AU_FLOAT).max(-MAX_AU_FLOAT);
+ let y = self.y.min(MAX_AU_FLOAT).max(-MAX_AU_FLOAT);
+
+ LayoutPointAu::new(
+ Au::from_f32_px(x),
+ Au::from_f32_px(y),
+ )
+ }
+}
+
+impl AuHelpers<LayoutRectAu> for LayoutRect {
+ fn from_au(rect: LayoutRectAu) -> Self {
+ LayoutRect::new(
+ LayoutPoint::from_au(rect.origin),
+ LayoutSize::from_au(rect.size),
+ )
+ }
+
+ fn to_au(&self) -> LayoutRectAu {
+ LayoutRectAu::new(
+ self.origin.to_au(),
+ self.size.to_au(),
+ )
+ }
+}
+
+impl AuHelpers<LayoutSideOffsetsAu> for LayoutSideOffsets {
+ fn from_au(offsets: LayoutSideOffsetsAu) -> Self {
+ LayoutSideOffsets::new(
+ offsets.top.to_f32_px(),
+ offsets.right.to_f32_px(),
+ offsets.bottom.to_f32_px(),
+ offsets.left.to_f32_px(),
+ )
+ }
+
+ fn to_au(&self) -> LayoutSideOffsetsAu {
+ LayoutSideOffsetsAu::new(
+ Au::from_f32_px(self.top),
+ Au::from_f32_px(self.right),
+ Au::from_f32_px(self.bottom),
+ Au::from_f32_px(self.left),
+ )
+ }
+}
+
+pub trait RectExt {
+ type Point;
+ fn top_left(&self) -> Self::Point;
+ fn top_right(&self) -> Self::Point;
+ fn bottom_left(&self) -> Self::Point;
+ fn bottom_right(&self) -> Self::Point;
+}
+
+impl<U> RectExt for Rect<f32, U> {
+ type Point = Point2D<f32, U>;
+ fn top_left(&self) -> Self::Point {
+ self.min()
+ }
+ fn top_right(&self) -> Self::Point {
+ Point2D::new(self.max_x(), self.min_y())
+ }
+ fn bottom_left(&self) -> Self::Point {
+ Point2D::new(self.min_x(), self.max_y())
+ }
+ fn bottom_right(&self) -> Self::Point {
+ self.max()
+ }
+}
+
+// A few helpers to convert to cast between coordinate spaces that are often equivalent.
+
+#[inline]
+pub fn layout_rect_as_picture_rect(layout_rect: &LayoutRect) -> PictureRect {
+ layout_rect.cast_unit()
+}
+
+#[inline]
+pub fn layout_vector_as_picture_vector(layout_vector: LayoutVector2D) -> PictureVector2D {
+ layout_vector.cast_unit()
+}
+
+#[inline]
+pub fn device_size_as_framebuffer_size(framebuffer_size: DeviceIntSize) -> FramebufferIntSize {
+ framebuffer_size.cast_unit()
+}
+
+#[inline]
+pub fn device_rect_as_framebuffer_rect(framebuffer_rect: &DeviceIntRect) -> FramebufferIntRect {
+ framebuffer_rect.cast_unit()
+}