diff options
Diffstat (limited to 'third_party/webrender/webrender_api/src')
-rw-r--r-- | third_party/webrender/webrender_api/src/api.rs | 2123 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/channel.rs | 131 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/color.rs | 160 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/display_item.rs | 1589 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/display_item_cache.rs | 115 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/display_list.rs | 1969 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/font.rs | 604 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/gradient_builder.rs | 178 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/image.rs | 595 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/image_tiling.rs | 815 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/lib.rs | 64 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/resources.rs | 327 | ||||
-rw-r--r-- | third_party/webrender/webrender_api/src/units.rs | 324 |
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() +} |