Upgrade WebRender to e491e1ae637b2eed1e7195855d88357e5eb3ddf9 (#30323)

* Upgrade vendored version of WebRender

* Patch WebRender: upgrade version of gleam

* Restore hit testing implementation

* Fix WebRender warnings

* Adapt Servo to new WebRender

* Update results

* Add a workaround for #30313

This slightly expands text boundaries in order to take into account the
fact that layout isn't measuring glyph boundaries.
This commit is contained in:
Martin Robinson 2023-09-10 14:38:56 +02:00 committed by GitHub
parent c079acb3c3
commit a9d37cb85a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
563 changed files with 48524 additions and 51657 deletions

View file

@ -26,6 +26,7 @@ serde_bytes = "0.11"
time = "0.1"
malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of", package = "wr_malloc_size_of" }
peek-poke = { version = "0.2", path = "../peek-poke", features = ["extras"] }
crossbeam-channel = "0.5"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9"

File diff suppressed because it is too large Load diff

View file

@ -2,12 +2,19 @@
* 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 crate::{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;
pub use crossbeam_channel as crossbeam;
#[cfg(not(target_os = "windows"))]
pub use crossbeam_channel::{Sender, Receiver};
#[cfg(target_os = "windows")]
pub use std::sync::mpsc::{Sender, Receiver};
#[derive(Clone)]
pub struct Payload {
@ -74,7 +81,7 @@ pub type PayloadSender = MsgSender<Payload>;
pub type PayloadReceiver = MsgReceiver<Payload>;
pub struct MsgReceiver<T> {
rx: mpsc::Receiver<T>,
rx: Receiver<T>,
}
impl<T> MsgReceiver<T> {
@ -82,14 +89,14 @@ impl<T> MsgReceiver<T> {
self.rx.recv().map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))
}
pub fn to_mpsc_receiver(self) -> mpsc::Receiver<T> {
pub fn to_crossbeam_receiver(self) -> Receiver<T> {
self.rx
}
}
#[derive(Clone)]
pub struct MsgSender<T> {
tx: mpsc::Sender<T>,
tx: Sender<T>,
}
impl<T> MsgSender<T> {
@ -99,12 +106,12 @@ impl<T> MsgSender<T> {
}
pub fn payload_channel() -> Result<(PayloadSender, PayloadReceiver), Error> {
let (tx, rx) = mpsc::channel();
let (tx, rx) = unbounded_channel();
Ok((PayloadSender { tx }, PayloadReceiver { rx }))
}
pub fn msg_channel<T>() -> Result<(MsgSender<T>, MsgReceiver<T>), Error> {
let (tx, rx) = mpsc::channel();
let (tx, rx) = unbounded_channel();
Ok((MsgSender { tx }, MsgReceiver { rx }))
}
@ -129,3 +136,45 @@ impl<'de, T> Deserialize<'de> for MsgSender<T> {
unreachable!();
}
}
/// A create a channel intended for one-shot uses, for example the channels
/// created to block on a synchronous query and then discarded,
#[cfg(not(target_os = "windows"))]
pub fn single_msg_channel<T>() -> (Sender<T>, Receiver<T>) {
crossbeam_channel::bounded(1)
}
/// A fast MPMC message channel that can hold a fixed number of messages.
///
/// If the channel is full, the sender will block upon sending extra messages
/// until the receiver has consumed some messages.
/// The capacity parameter should be chosen either:
/// - high enough to avoid blocking on the common cases,
/// - or, on the contrary, using the blocking behavior as a means to prevent
/// fast producers from building up work faster than it is consumed.
#[cfg(not(target_os = "windows"))]
pub fn fast_channel<T>(capacity: usize) -> (Sender<T>, Receiver<T>) {
crossbeam_channel::bounded(capacity)
}
/// Creates an MPMC channel that is a bit slower than the fast_channel but doesn't
/// have a limit on the number of messages held at a given time and therefore
/// doesn't block when sending.
#[cfg(not(target_os = "windows"))]
pub use crossbeam_channel::unbounded as unbounded_channel;
#[cfg(target_os = "windows")]
pub fn fast_channel<T>(_cap: usize) -> (Sender<T>, Receiver<T>) {
std::sync::mpsc::channel()
}
#[cfg(target_os = "windows")]
pub fn unbounded_channel<T>() -> (Sender<T>, Receiver<T>) {
std::sync::mpsc::channel()
}
#[cfg(target_os = "windows")]
pub fn single_msg_channel<T>() -> (Sender<T>, Receiver<T>) {
std::sync::mpsc::channel()
}

View file

@ -2,15 +2,16 @@
* 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 euclid::{SideOffsets2D, Angle};
use peek_poke::PeekPoke;
use std::ops::Not;
// local imports
use crate::font;
use crate::api::{PipelineId, PropertyBinding};
use crate::{PipelineId, PropertyBinding};
use crate::color::ColorF;
use crate::image::{ColorDepth, ImageKey};
use crate::units::*;
use std::hash::{Hash, Hasher};
// ******************************************************************
// * NOTE: some of these structs have an "IMPLICIT" comment. *
@ -47,6 +48,10 @@ bitflags! {
/// compositor surface under certain (implementation specific) conditions. This
/// is typically used for large videos, and canvas elements.
const PREFER_COMPOSITOR_SURFACE = 1 << 3;
/// If set, this primitive can be passed directly to the compositor via its
/// ExternalImageId, and the compositor will use the native image directly.
/// Used as a further extension on top of PREFER_COMPOSITOR_SURFACE.
const SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE = 1 << 4;
}
}
@ -68,10 +73,6 @@ pub struct CommonItemProperties {
pub clip_id: ClipId,
/// The coordinate-space the item is in (yes, it can be really granular)
pub spatial_id: SpatialId,
/// Opaque bits for our clients to use for hit-testing. This is the most
/// dubious "common" field, but because it's an Option, it usually only
/// wastes a single byte (for None).
pub hit_info: Option<ItemTag>,
/// Various flags describing properties of this primitive.
pub flags: PrimitiveFlags,
}
@ -86,7 +87,6 @@ impl CommonItemProperties {
clip_rect,
spatial_id: space_and_clip.spatial_id,
clip_id: space_and_clip.clip_id,
hit_info: None,
flags: PrimitiveFlags::default(),
}
}
@ -155,6 +155,7 @@ pub enum DisplayItem {
SetFilterOps,
SetFilterData,
SetFilterPrimitives,
SetPoints,
// These marker items terminate a scope introduced by a previous item.
PopReferenceFrame,
@ -203,6 +204,7 @@ pub enum DebugDisplayItem {
SetFilterOps(Vec<FilterOp>),
SetFilterData(FilterData),
SetFilterPrimitives(Vec<FilterPrimitive>),
SetPoints(Vec<LayoutPoint>),
PopReferenceFrame,
PopStackingContext,
@ -214,7 +216,8 @@ pub struct ImageMaskClipDisplayItem {
pub id: ClipId,
pub parent_space_and_clip: SpaceAndClipInfo,
pub image_mask: ImageMask,
}
pub fill_rule: FillRule,
} // IMPLICIT points: Vec<LayoutPoint>
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct RectClipDisplayItem {
@ -306,7 +309,7 @@ pub struct ScrollFrameDisplayItem {
pub content_rect: LayoutRect,
pub clip_rect: LayoutRect,
pub parent_space_and_clip: SpaceAndClipInfo,
pub external_id: Option<ExternalScrollId>,
pub external_id: 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
@ -338,6 +341,7 @@ pub struct ClearRectangleDisplayItem {
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct HitTestDisplayItem {
pub common: CommonItemProperties,
pub tag: ItemTag,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
@ -623,6 +627,15 @@ pub struct Gradient {
pub extend_mode: ExtendMode,
} // IMPLICIT: stops: Vec<GradientStop>
impl Gradient {
pub fn is_valid(&self) -> bool {
self.start_point.x.is_finite() &&
self.start_point.y.is_finite() &&
self.end_point.x.is_finite() &&
self.end_point.y.is_finite()
}
}
/// The area
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct GradientDisplayItem {
@ -655,6 +668,15 @@ pub struct RadialGradient {
pub extend_mode: ExtendMode,
} // IMPLICIT stops: Vec<GradientStop>
impl RadialGradient {
pub fn is_valid(&self) -> bool {
self.center.x.is_finite() &&
self.center.y.is_finite() &&
self.start_offset.is_finite() &&
self.end_offset.is_finite()
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct ConicGradient {
pub center: LayoutPoint,
@ -664,6 +686,16 @@ pub struct ConicGradient {
pub extend_mode: ExtendMode,
} // IMPLICIT stops: Vec<GradientStop>
impl ConicGradient {
pub fn is_valid(&self) -> bool {
self.center.x.is_finite() &&
self.center.y.is_finite() &&
self.angle.is_finite() &&
self.start_offset.is_finite() &&
self.end_offset.is_finite()
}
}
/// Just an abstraction for bundling up a bunch of clips into a "super clip".
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct ClipChainItem {
@ -711,23 +743,87 @@ pub struct ReferenceFrameDisplayListItem {
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
pub enum ReferenceFrameKind {
/// Zoom reference frames must be a scale + translation only
Zoom,
/// A normal transform matrix, may contain perspective (the CSS transform property)
Transform,
Transform {
/// Optionally marks the transform as only ever having a simple 2D scale or translation,
/// allowing for optimizations.
is_2d_scale_translation: bool,
/// Marks that the transform should be snapped. Used for transforms which animate in
/// response to scrolling, eg for zooming or dynamic toolbar fixed-positioning.
should_snap: bool,
},
/// A perspective transform, that optionally scrolls relative to a specific scroll node
Perspective {
scrolling_relative_to: Option<ExternalScrollId>,
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
pub enum Rotation {
Degree0,
Degree90,
Degree180,
Degree270,
}
impl Rotation {
pub fn to_matrix(
&self,
size: LayoutSize,
) -> LayoutTransform {
let (shift_center_to_origin, angle) = match self {
Rotation::Degree0 => {
(LayoutTransform::translation(-size.width / 2., -size.height / 2., 0.), Angle::degrees(0.))
},
Rotation::Degree90 => {
(LayoutTransform::translation(-size.height / 2., -size.width / 2., 0.), Angle::degrees(90.))
},
Rotation::Degree180 => {
(LayoutTransform::translation(-size.width / 2., -size.height / 2., 0.), Angle::degrees(180.))
},
Rotation::Degree270 => {
(LayoutTransform::translation(-size.height / 2., -size.width / 2., 0.), Angle::degrees(270.))
},
};
let shift_origin_to_center = LayoutTransform::translation(size.width / 2., size.height / 2., 0.);
shift_center_to_origin
.then(&LayoutTransform::rotation(0., 0., 1.0, angle))
.then(&shift_origin_to_center)
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
pub enum ReferenceTransformBinding {
/// Standard reference frame which contains a precomputed transform.
Static {
binding: PropertyBinding<LayoutTransform>,
},
/// Computed reference frame which dynamically calculates the transform
/// based on the given parameters. The reference is the content size of
/// the parent iframe, which is affected by snapping.
Computed {
scale_from: Option<LayoutSize>,
vertical_flip: bool,
rotation: Rotation,
},
}
impl Default for ReferenceTransformBinding {
fn default() -> Self {
ReferenceTransformBinding::Static {
binding: Default::default(),
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct ReferenceFrame {
pub kind: ReferenceFrameKind,
pub transform_style: TransformStyle,
/// The transform matrix, either the perspective matrix or the transform
/// matrix.
pub transform: PropertyBinding<LayoutTransform>,
pub transform: ReferenceTransformBinding,
pub id: SpatialId,
}
@ -762,7 +858,7 @@ pub enum TransformStyle {
/// when we want to cache the output, and performance is
/// important. Note that this is a performance hint only,
/// which WR may choose to ignore.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, MallocSizeOf, Serialize, PeekPoke)]
#[repr(u8)]
pub enum RasterSpace {
// Rasterize in local-space, applying supplied scale to primitives.
@ -785,6 +881,23 @@ impl RasterSpace {
}
}
impl Eq for RasterSpace {}
impl Hash for RasterSpace {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
RasterSpace::Screen => {
0.hash(state);
}
RasterSpace::Local(scale) => {
// Note: this is inconsistent with the Eq impl for -0.0 (don't care).
1.hash(state);
scale.to_bits().hash(state);
}
}
}
}
bitflags! {
#[repr(C)]
#[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)]
@ -911,7 +1024,8 @@ impl FloodPrimitive {
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct BlurPrimitive {
pub input: FilterPrimitiveInput,
pub radius: f32,
pub width: f32,
pub height: f32,
}
#[repr(C)]
@ -1038,7 +1152,7 @@ pub enum FilterOp {
/// Filter that does no transformation of the colors, needed for
/// debug purposes only.
Identity,
Blur(f32),
Blur(f32, f32),
Brightness(f32),
Contrast(f32),
Grayscale(f32),
@ -1230,6 +1344,7 @@ pub enum YuvColorSpace {
Rec601 = 0,
Rec709 = 1,
Rec2020 = 2,
Identity = 3, // aka RGB as per ISO/IEC 23091-2:2019
}
#[repr(u8)]
@ -1404,6 +1519,34 @@ impl ComplexClipRegion {
}
}
pub const POLYGON_CLIP_VERTEX_MAX: usize = 16;
#[repr(u8)]
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)]
pub enum FillRule {
Nonzero = 0x1, // Behaves as the SVG fill-rule definition for nonzero.
Evenodd = 0x2, // Behaves as the SVG fill-rule definition for evenodd.
}
impl From<u8> for FillRule {
fn from(fill_rule: u8) -> Self {
match fill_rule {
0x1 => FillRule::Nonzero,
0x2 => FillRule::Evenodd,
_ => panic!("Unexpected FillRule value."),
}
}
}
impl From<FillRule> for u8 {
fn from(fill_rule: FillRule) -> Self {
match fill_rule {
FillRule::Nonzero => 0x1,
FillRule::Evenodd => 0x2,
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)]
pub struct ClipChainId(pub u64, pub PipelineId);
@ -1531,6 +1674,7 @@ impl DisplayItem {
DisplayItem::SetFilterOps => "set_filter_ops",
DisplayItem::SetFilterData => "set_filter_data",
DisplayItem::SetFilterPrimitives => "set_filter_primitives",
DisplayItem::SetPoints => "set_points",
DisplayItem::RadialGradient(..) => "radial_gradient",
DisplayItem::Rectangle(..) => "rectangle",
DisplayItem::ScrollFrame(..) => "scroll_frame",
@ -1572,8 +1716,13 @@ impl_default_for_enums! {
FilterOp => Identity,
ComponentTransferFuncType => Identity,
ClipMode => Clip,
FillRule => Nonzero,
ClipId => ClipId::invalid(),
ReferenceFrameKind => Transform,
ReferenceFrameKind => Transform {
is_2d_scale_translation: false,
should_snap: false,
},
Rotation => Degree0,
TransformStyle => Flat,
RasterSpace => Local(f32::default()),
MixBlendMode => Normal,

View file

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use euclid::SideOffsets2D;
use peek_poke::{ensure_red_zone, peek_from_slice, poke_extend_vec};
use peek_poke::{ensure_red_zone, peek_from_slice, poke_extend_vec, strip_red_zone};
use peek_poke::{poke_inplace_slice, poke_into_vec, Poke};
#[cfg(feature = "deserialize")]
use serde::de::Deserializer;
@ -20,7 +20,7 @@ use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
// local imports
use crate::display_item as di;
use crate::display_item_cache::*;
use crate::api::{PipelineId, PropertyBinding};
use crate::{PipelineId, PropertyBinding};
use crate::gradient_builder::GradientBuilder;
use crate::color::ColorF;
use crate::font::{FontInstanceKey, GlyphInstance, GlyphOptions};
@ -115,6 +115,18 @@ pub struct BuiltDisplayList {
descriptor: BuiltDisplayListDescriptor,
}
#[repr(C)]
#[derive(Copy, Clone, Deserialize, Serialize)]
pub enum GeckoDisplayListType {
None,
Partial(f64),
Full(f64),
}
impl Default for GeckoDisplayListType {
fn default() -> Self { GeckoDisplayListType::None }
}
/// Describes the memory layout of a display list.
///
/// A display list consists of some number of display list items, followed by a number of display
@ -122,6 +134,8 @@ pub struct BuiltDisplayList {
#[repr(C)]
#[derive(Copy, Clone, Default, Deserialize, Serialize)]
pub struct BuiltDisplayListDescriptor {
/// Gecko specific information about the display list.
gecko_display_list_type: GeckoDisplayListType,
/// The first IPC time stamp: before any work has been done
builder_start_time: u64,
/// The second IPC time stamp: after serialization
@ -168,6 +182,10 @@ impl DisplayListWithCache {
self.display_list.descriptor()
}
pub fn times(&self) -> (u64, u64, u64) {
self.display_list.times()
}
pub fn data(&self) -> &[u8] {
self.display_list.data()
}
@ -221,6 +239,7 @@ pub struct BuiltDisplayListIter<'a> {
cur_filter_primitives: ItemRange<'a, di::FilterPrimitive>,
cur_clip_chain_items: ItemRange<'a, di::ClipId>,
cur_complex_clip: ItemRange<'a, di::ComplexClipRegion>,
cur_points: ItemRange<'a, LayoutPoint>,
peeking: Peek,
/// Should just be initialized but never populated in release builds
debug_stats: DebugStats,
@ -263,7 +282,7 @@ impl DebugStats {
/// Logs the stats for the given serialized slice
#[cfg(feature = "display_list_stats")]
fn log_slice<T: Peek>(
fn log_slice<T: Copy + Default + peek_poke::Peek>(
&mut self,
slice_name: &'static str,
range: &ItemRange<T>,
@ -318,6 +337,10 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
self.iter.cur_complex_clip
}
pub fn points(&self) -> ItemRange<LayoutPoint> {
self.iter.cur_points
}
pub fn glyphs(&self) -> ItemRange<GlyphInstance> {
self.iter.glyphs()
}
@ -391,6 +414,14 @@ impl BuiltDisplayList {
)
}
pub fn gecko_display_list_stats(&self) -> (f64, bool) {
match self.descriptor.gecko_display_list_type {
GeckoDisplayListType::Full(duration) => (duration, true),
GeckoDisplayListType::Partial(duration) => (duration, false),
_ => (0.0, false)
}
}
pub fn total_clip_nodes(&self) -> usize {
self.descriptor.total_clip_nodes
}
@ -472,6 +503,9 @@ impl BuiltDisplayList {
Real::SetGradientStops => Debug::SetGradientStops(
item.iter.cur_stops.iter().collect()
),
Real::SetPoints => Debug::SetPoints(
item.iter.cur_points.iter().collect()
),
Real::RectClip(v) => Debug::RectClip(v),
Real::RoundedRectClip(v) => Debug::RoundedRectClip(v),
Real::ImageMaskClip(v) => Debug::ImageMaskClip(v),
@ -541,6 +575,7 @@ impl<'a> BuiltDisplayListIter<'a> {
cur_filter_primitives: ItemRange::default(),
cur_clip_chain_items: ItemRange::default(),
cur_complex_clip: ItemRange::default(),
cur_points: ItemRange::default(),
peeking: Peek::NotPeeking,
debug_stats: DebugStats {
last_addr: data.as_ptr() as usize,
@ -609,6 +644,7 @@ impl<'a> BuiltDisplayListIter<'a> {
self.cur_stops = ItemRange::default();
self.cur_complex_clip = ItemRange::default();
self.cur_clip_chain_items = ItemRange::default();
self.cur_points = ItemRange::default();
self.cur_filters = ItemRange::default();
self.cur_filter_primitives = ItemRange::default();
self.cur_filter_data.clear();
@ -619,7 +655,8 @@ impl<'a> BuiltDisplayListIter<'a> {
SetGradientStops |
SetFilterOps |
SetFilterData |
SetFilterPrimitives => {
SetFilterPrimitives |
SetPoints => {
// These are marker items for populating other display items, don't yield them.
continue;
}
@ -681,6 +718,10 @@ impl<'a> BuiltDisplayListIter<'a> {
self.cur_filter_primitives = skip_slice::<di::FilterPrimitive>(&mut self.data);
self.debug_stats.log_slice("set_filter_primitives.primitives", &self.cur_filter_primitives);
}
SetPoints => {
self.cur_points = skip_slice::<LayoutPoint>(&mut self.data);
self.debug_stats.log_slice("set_points.points", &self.cur_points);
}
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);
@ -894,6 +935,10 @@ impl<'de> Deserialize<'de> for BuiltDisplayList {
DisplayListBuilder::push_iter_impl(&mut temp, stops);
Real::SetGradientStops
},
Debug::SetPoints(points) => {
DisplayListBuilder::push_iter_impl(&mut temp, points);
Real::SetPoints
},
Debug::RectClip(v) => Real::RectClip(v),
Debug::RoundedRectClip(v) => Real::RoundedRectClip(v),
Debug::ImageMaskClip(v) => Real::ImageMaskClip(v),
@ -931,6 +976,7 @@ impl<'de> Deserialize<'de> for BuiltDisplayList {
Ok(BuiltDisplayList {
data,
descriptor: BuiltDisplayListDescriptor {
gecko_display_list_type: GeckoDisplayListType::None,
builder_start_time: 0,
builder_finish_time: 1,
send_start_time: 1,
@ -976,9 +1022,6 @@ pub struct DisplayListBuilder {
next_clip_chain_id: u64,
builder_start_time: u64,
/// The size of the content of this display list. This is used to allow scrolling
/// outside the bounds of the display list items themselves.
content_size: LayoutSize,
save_state: Option<SaveState>,
cache_size: usize,
@ -986,13 +1029,12 @@ pub struct DisplayListBuilder {
}
impl DisplayListBuilder {
pub fn new(pipeline_id: PipelineId, content_size: LayoutSize) -> Self {
Self::with_capacity(pipeline_id, content_size, 0)
pub fn new(pipeline_id: PipelineId) -> Self {
Self::with_capacity(pipeline_id, 0)
}
pub fn with_capacity(
pipeline_id: PipelineId,
content_size: LayoutSize,
capacity: usize,
) -> Self {
let start_time = precise_time_ns();
@ -1009,18 +1051,12 @@ impl DisplayListBuilder {
next_spatial_index: FIRST_SPATIAL_NODE_INDEX,
next_clip_chain_id: 0,
builder_start_time: start_time,
content_size,
save_state: None,
cache_size: 0,
serialized_content_buffer: None,
}
}
/// Return the content size for this display list
pub fn content_size(&self) -> LayoutSize {
self.content_size
}
/// Saves the current display list state, so it may be `restore()`'d.
///
/// # Conditions:
@ -1075,11 +1111,15 @@ impl DisplayListBuilder {
W: Write
{
let mut temp = BuiltDisplayList::default();
ensure_red_zone::<di::DisplayItem>(&mut self.data);
temp.descriptor.extra_data_offset = self.data.len();
mem::swap(&mut temp.data, &mut self.data);
let mut index: usize = 0;
{
let mut iter = temp.iter();
let mut cache = DisplayItemCache::new();
cache.update(&temp);
let mut iter = temp.iter_with_cache(&cache);
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();
@ -1089,6 +1129,7 @@ impl DisplayListBuilder {
}
self.data = temp.data;
strip_red_zone::<di::DisplayItem>(&mut self.data);
index
}
@ -1235,9 +1276,11 @@ impl DisplayListBuilder {
pub fn push_hit_test(
&mut self,
common: &di::CommonItemProperties,
tag: di::ItemTag,
) {
let item = di::DisplayItem::HitTest(di::HitTestDisplayItem {
common: *common,
tag,
});
self.push_item(&item);
}
@ -1534,7 +1577,9 @@ impl DisplayListBuilder {
origin,
reference_frame: di::ReferenceFrame {
transform_style,
transform,
transform: di::ReferenceTransformBinding::Static {
binding: transform,
},
kind,
id,
},
@ -1544,6 +1589,38 @@ impl DisplayListBuilder {
id
}
pub fn push_computed_frame(
&mut self,
origin: LayoutPoint,
parent_spatial_id: di::SpatialId,
scale_from: Option<LayoutSize>,
vertical_flip: bool,
rotation: di::Rotation,
) -> di::SpatialId {
let id = self.generate_spatial_index();
let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem {
parent_spatial_id,
origin,
reference_frame: di::ReferenceFrame {
transform_style: di::TransformStyle::Flat,
transform: di::ReferenceTransformBinding::Computed {
scale_from,
vertical_flip,
rotation,
},
kind: di::ReferenceFrameKind::Transform {
is_2d_scale_translation: false,
should_snap: false,
},
id,
},
});
self.push_item(&item);
id
}
pub fn pop_reference_frame(&mut self) {
self.push_item(&di::DisplayItem::PopReferenceFrame);
}
@ -1696,7 +1773,7 @@ impl DisplayListBuilder {
pub fn define_scroll_frame(
&mut self,
parent_space_and_clip: &di::SpaceAndClipInfo,
external_id: Option<di::ExternalScrollId>,
external_id: di::ExternalScrollId,
content_rect: LayoutRect,
clip_rect: LayoutRect,
scroll_sensitivity: di::ScrollSensitivity,
@ -1742,14 +1819,25 @@ impl DisplayListBuilder {
&mut self,
parent_space_and_clip: &di::SpaceAndClipInfo,
image_mask: di::ImageMask,
points: &[LayoutPoint],
fill_rule: di::FillRule,
) -> di::ClipId {
let id = self.generate_clip_index();
let item = di::DisplayItem::ImageMaskClip(di::ImageMaskClipDisplayItem {
id,
parent_space_and_clip: *parent_space_and_clip,
image_mask,
fill_rule,
});
// We only need to supply points if there are at least 3, which is the
// minimum to specify a polygon. BuiltDisplayListIter.next ensures that points
// are cleared between processing other display items, so we'll correctly get
// zero points when no SetPoints item has been pushed.
if points.len() >= 3 {
self.push_item(&di::DisplayItem::SetPoints);
self.push_iter(points);
}
self.push_item(&item);
id
}
@ -1928,7 +2016,7 @@ impl DisplayListBuilder {
self.cache_size = cache_size;
}
pub fn finalize(mut self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
pub fn finalize(mut self) -> (PipelineId, BuiltDisplayList) {
assert!(self.save_state.is_none(), "Finalized DisplayListBuilder with a pending save");
if let Some(content) = self.serialized_content_buffer.take() {
@ -1951,9 +2039,9 @@ impl DisplayListBuilder {
let end_time = precise_time_ns();
(
self.pipeline_id,
self.content_size,
BuiltDisplayList {
descriptor: BuiltDisplayListDescriptor {
gecko_display_list_type: GeckoDisplayListType::None,
builder_start_time: self.builder_start_time,
builder_finish_time: end_time,
send_start_time: end_time,

View file

@ -15,10 +15,11 @@ 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::sync::{Arc, RwLock, RwLockReadGuard};
use std::collections::HashMap;
// local imports
use crate::api::IdNamespace;
use crate::IdNamespace;
use crate::channel::Sender;
use crate::color::ColorU;
use crate::units::LayoutPoint;

View file

@ -132,7 +132,9 @@ impl GradientBuilder {
let first = *stops.first().unwrap();
let last = *stops.last().unwrap();
assert!(first.offset <= last.offset);
// Express the assertion so that if one of the offsets is NaN, we don't panic
// and instead take the branch that handles degenerate gradients.
assert!(!(first.offset > last.offset));
let stops_delta = last.offset - first.offset;

View file

@ -9,7 +9,7 @@ use peek_poke::PeekPoke;
use std::ops::{Add, Sub};
use std::sync::Arc;
// local imports
use crate::api::{IdNamespace, PipelineId, TileSize};
use crate::{IdNamespace, TileSize};
use crate::display_item::ImageRendering;
use crate::font::{FontInstanceKey, FontInstanceData, FontKey, FontTemplate};
use crate::units::*;
@ -100,38 +100,24 @@ pub trait ExternalImageHandler {
fn unlock(&mut self, key: ExternalImageId, channel_index: u8);
}
/// Allows callers to receive a texture with the contents of a specific
/// pipeline copied to it.
pub trait OutputImageHandler {
/// Return the native texture handle and the size of the texture.
fn lock(&mut self, pipeline_id: PipelineId) -> Option<(u32, FramebufferIntSize)>;
/// Unlock will only be called if the lock() call succeeds, when WR has issued
/// the GL commands to copy the output to the texture handle.
fn unlock(&mut self, pipeline_id: PipelineId);
}
/// Specifies the type of texture target in driver terms.
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum TextureTarget {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub enum ImageBufferKind {
/// 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,
Texture2D = 0,
/// 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,
TextureRect = 1,
/// 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,
TextureExternal = 2,
}
/// Storage format identifier for externally-managed images.
@ -139,7 +125,7 @@ pub enum TextureTarget {
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum ExternalImageType {
/// The image is texture-backed.
TextureHandle(TextureTarget),
TextureHandle(ImageBufferKind),
/// The image is heap-allocated by the embedding.
Buffer,
}
@ -340,10 +326,9 @@ pub enum ImageData {
}
mod serde_image_data_raw {
use serde_bytes;
use std::sync::Arc;
use serde::{Deserializer, Serializer};
use serde_bytes;
use std::sync::Arc;
pub fn serialize<S: Serializer>(bytes: &Arc<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error> {
serde_bytes::serialize(bytes.as_slice(), serializer)

View file

@ -1,815 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
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));
}
}

View file

@ -15,25 +15,21 @@
#![cfg_attr(feature = "cargo-clippy", allow(clippy::float_cmp, clippy::too_many_arguments))]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::unreadable_literal, clippy::new_without_default))]
pub use crossbeam_channel;
pub use euclid;
#[macro_use]
extern crate bitflags;
#[cfg(feature = "nightly")]
use core;
#[cfg(target_os = "macos")]
use core_foundation;
#[cfg(target_os = "macos")]
use core_graphics;
#[macro_use]
extern crate derive_more;
pub use euclid;
extern crate core;
#[macro_use]
extern crate malloc_size_of_derive;
#[macro_use]
extern crate serde_derive;
use malloc_size_of;
use peek_poke;
mod api;
pub mod channel;
mod color;
mod display_item;
@ -42,13 +38,8 @@ 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;
@ -56,4 +47,619 @@ 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;
use crate::units::*;
use crate::channel::Receiver;
use std::marker::PhantomData;
use std::sync::Arc;
use std::os::raw::c_void;
use peek_poke::PeekPoke;
/// Width and height in device pixels of image tiles.
pub type TileSize = u16;
/// Various settings that the caller can select based on desired tradeoffs
/// between rendering quality and performance / power usage.
#[derive(Copy, Clone, Deserialize, Serialize)]
pub struct QualitySettings {
/// If true, disable creating separate picture cache slices when the
/// scroll root changes. This gives maximum opportunity to find an
/// opaque background, which enables subpixel AA. However, it is
/// usually significantly more expensive to render when scrolling.
pub force_subpixel_aa_where_possible: bool,
}
impl Default for QualitySettings {
fn default() -> Self {
QualitySettings {
// Prefer performance over maximum subpixel AA quality, since WR
// already enables subpixel AA in more situations than other browsers.
force_subpixel_aa_where_possible: false,
}
}
}
/// An epoch identifies the state of a pipeline in time.
///
/// This is mostly used as a synchronization mechanism to observe how/when particular pipeline
/// updates propagate through WebRender and are applied at various stages.
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Epoch(pub u32);
impl Epoch {
/// Magic invalid epoch value.
pub fn invalid() -> Epoch {
Epoch(u32::MAX)
}
}
/// ID namespaces uniquely identify different users of WebRender's API.
///
/// For example in Gecko each content process uses a separate id namespace.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, MallocSizeOf, PartialEq, Hash, Ord, PartialOrd, PeekPoke)]
#[derive(Deserialize, Serialize)]
pub struct IdNamespace(pub u32);
/// A key uniquely identifying a WebRender document.
///
/// Instances can manage one or several documents (using the same render backend thread).
/// Each document will internally correspond to a single scene, and scenes are made of
/// one or several pipelines.
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
pub struct DocumentId {
///
pub namespace_id: IdNamespace,
///
pub id: u32,
}
impl DocumentId {
///
pub fn new(namespace_id: IdNamespace, id: u32) -> Self {
DocumentId {
namespace_id,
id,
}
}
///
pub const INVALID: DocumentId = DocumentId { namespace_id: IdNamespace(0), id: 0 };
}
/// This type carries no valuable semantics for WR. However, it reflects the fact that
/// clients (Servo) may generate pipelines by different semi-independent sources.
/// These pipelines still belong to the same `IdNamespace` and the same `DocumentId`.
/// Having this extra Id field enables them to generate `PipelineId` without collision.
pub type PipelineSourceId = u32;
/// From the point of view of WR, `PipelineId` is completely opaque and generic as long as
/// it's clonable, serializable, comparable, and hashable.
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
pub struct PipelineId(pub PipelineSourceId, pub u32);
impl Default for PipelineId {
fn default() -> Self {
PipelineId::dummy()
}
}
impl PipelineId {
///
pub fn dummy() -> Self {
PipelineId(!0, !0)
}
}
/// An opaque pointer-sized value.
#[repr(C)]
#[derive(Clone)]
pub struct ExternalEvent {
raw: usize,
}
unsafe impl Send for ExternalEvent {}
impl ExternalEvent {
/// Creates the event from an opaque pointer-sized value.
pub fn from_raw(raw: usize) -> Self {
ExternalEvent { raw }
}
/// Consumes self to make it obvious that the event should be forwarded only once.
pub fn unwrap(self) -> usize {
self.raw
}
}
/// Describe whether or not scrolling should be clamped by the content bounds.
#[derive(Clone, Deserialize, Serialize)]
pub enum ScrollClamping {
///
ToContentBounds,
///
NoClamping,
}
/// A handler to integrate WebRender with the thread that contains the `Renderer`.
pub trait RenderNotifier: Send {
///
fn clone(&self) -> Box<dyn RenderNotifier>;
/// Wake the thread containing the `Renderer` up (after updates have been put
/// in the renderer's queue).
fn wake_up(
&self,
composite_needed: bool,
);
/// Notify the thread containing the `Renderer` that a new frame is ready.
fn new_frame_ready(&self, _: DocumentId, scrolled: bool, composite_needed: bool, render_time_ns: Option<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. 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;
}
/// A hit tester requested to the render backend thread but not necessarily ready yet.
///
/// The request should be resolved as late as possible to reduce the likelihood of blocking.
pub struct HitTesterRequest {
#[doc(hidden)]
pub rx: Receiver<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()
}
}
/// Describe an item that matched a hit-test query.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct HitTestItem {
/// The pipeline that the display item that was hit belongs to.
pub pipeline: PipelineId,
/// The tag of the hit display item.
pub tag: ItemTag,
/// The hit point in the coordinate space of the "viewport" of the display item. The
/// viewport is the scroll node formed by the root reference frame of the display item's
/// pipeline.
pub point_in_viewport: LayoutPoint,
/// The coordinates of the original hit test point relative to the origin of this item.
/// This is useful for calculating things like text offsets in the client.
pub point_relative_to_item: LayoutPoint,
}
/// Returned by `RenderApi::hit_test`.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct HitTestResult {
/// List of items that are match the hit-test query.
pub items: Vec<HitTestItem>,
}
bitflags! {
#[derive(Deserialize, MallocSizeOf, Serialize)]
///
pub struct HitTestFlags: u8 {
///
const FIND_ALL = 0b00000001;
///
const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010;
}
}
impl Drop for NotificationRequest {
fn drop(&mut self) {
if let Some(ref mut handler) = self.handler {
handler.notify(Checkpoint::TransactionDropped);
}
}
}
// This Clone impl yields an "empty" request because we don't want the requests
// to be notified twice so the request is owned by only one of the API messages
// (the original one) after the clone.
// This works in practice because the notifications requests are used for
// synchronization so we don't need to include them in the recording mechanism
// in wrench that clones the messages.
impl Clone for NotificationRequest {
fn clone(&self) -> Self {
NotificationRequest {
when: self.when,
handler: None,
}
}
}
/// A key to identify an animated property binding.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)]
pub struct PropertyBindingId {
pub namespace: IdNamespace,
pub uid: u32,
}
impl PropertyBindingId {
/// Constructor.
pub fn new(value: u64) -> Self {
PropertyBindingId {
namespace: IdNamespace((value >> 32) as u32),
uid: value as u32,
}
}
}
/// A unique key that is used for connecting animated property
/// values to bindings in the display list.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
pub struct PropertyBindingKey<T> {
///
pub id: PropertyBindingId,
#[doc(hidden)]
pub _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 C function that takes a pointer to a heap allocation and returns its size.
///
/// This is borrowed from the malloc_size_of crate, upon which we want to avoid
/// a dependency from WebRender.
pub type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize;
bitflags! {
/// Flags to enable/disable various builtin debugging tools.
#[repr(C)]
#[derive(Default, Deserialize, MallocSizeOf, Serialize)]
pub struct DebugFlags: u32 {
/// Display the frame profiler on screen.
const PROFILER_DBG = 1 << 0;
/// Display intermediate render targets on screen.
const RENDER_TARGET_DBG = 1 << 1;
/// Display all texture cache pages on screen.
const TEXTURE_CACHE_DBG = 1 << 2;
/// Display GPU timing results.
const GPU_TIME_QUERIES = 1 << 3;
/// Query the number of pixels that pass the depth test divided and show it
/// in the profiler as a percentage of the number of pixels in the screen
/// (window width times height).
const GPU_SAMPLE_QUERIES = 1 << 4;
/// Render each quad with their own draw call.
///
/// Terrible for performance but can help with understanding the drawing
/// order when inspecting renderdoc or apitrace recordings.
const DISABLE_BATCHING = 1 << 5;
/// Display the pipeline epochs.
const EPOCHS = 1 << 6;
/// Print driver messages to stdout.
const ECHO_DRIVER_MESSAGES = 1 << 7;
/// Show an overlay displaying overdraw amount.
const SHOW_OVERDRAW = 1 << 8;
/// Display the contents of GPU cache.
const GPU_CACHE_DBG = 1 << 9;
/// Clear evicted parts of the texture cache for debugging purposes.
const TEXTURE_CACHE_DBG_CLEAR_EVICTED = 1 << 10;
/// Show picture caching debug overlay
const PICTURE_CACHING_DBG = 1 << 11;
/// Highlight all primitives with colors based on kind.
const PRIMITIVE_DBG = 1 << 12;
/// Draw a zoom widget showing part of the framebuffer zoomed in.
const ZOOM_DBG = 1 << 13;
/// Scale the debug renderer down for a smaller screen. This will disrupt
/// any mapping between debug display items and page content, so shouldn't
/// be used with overlays like the picture caching or primitive display.
const SMALL_SCREEN = 1 << 14;
/// Disable various bits of the WebRender pipeline, to help narrow
/// down where slowness might be coming from.
const DISABLE_OPAQUE_PASS = 1 << 15;
///
const DISABLE_ALPHA_PASS = 1 << 16;
///
const DISABLE_CLIP_MASKS = 1 << 17;
///
const DISABLE_TEXT_PRIMS = 1 << 18;
///
const DISABLE_GRADIENT_PRIMS = 1 << 19;
///
const OBSCURE_IMAGES = 1 << 20;
/// Taint the transparent area of the glyphs with a random opacity to easily
/// see when glyphs are re-rasterized.
const GLYPH_FLASHING = 1 << 21;
/// The profiler only displays information that is out of the ordinary.
const SMART_PROFILER = 1 << 22;
/// If set, dump picture cache invalidation debug to console.
const INVALIDATION_DBG = 1 << 23;
/// Log tile cache to memory for later saving as part of wr-capture
const TILE_CACHE_LOGGING_DBG = 1 << 24;
/// Collect and dump profiler statistics to captures.
const PROFILER_CAPTURE = (1 as u32) << 25; // need "as u32" until we have cbindgen#556
/// Invalidate picture tiles every frames (useful when inspecting GPU work in external tools).
const FORCE_PICTURE_INVALIDATION = (1 as u32) << 26;
const USE_BATCHED_TEXTURE_UPLOADS = (1 as u32) << 27;
const USE_DRAW_CALLS_FOR_TEXTURE_COPY = (1 as u32) << 28;
}
}
/// Information specific to a primitive type that
/// uniquely identifies a primitive template by key.
#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash, Serialize, Deserialize)]
pub enum PrimitiveKeyKind {
/// Clear an existing rect, used for special effects on some platforms.
Clear,
///
Rectangle {
///
color: PropertyBinding<ColorU>,
},
}
///
#[derive(Clone)]
pub struct ScrollNodeState {
///
pub id: ExternalScrollId,
///
pub scroll_offset: LayoutVector2D,
}
///
#[derive(Clone, Copy, Debug)]
pub enum ScrollLocation {
/// Scroll by a certain amount.
Delta(LayoutVector2D),
/// Scroll to very top of element.
Start,
/// Scroll to very bottom of element.
End,
}
/// Represents a zoom factor.
#[derive(Clone, Copy, Debug)]
pub struct ZoomFactor(f32);
impl ZoomFactor {
/// Construct a new zoom factor.
pub fn new(scale: f32) -> Self {
ZoomFactor(scale)
}
/// Get the zoom factor as an untyped float.
pub fn get(self) -> f32 {
self.0
}
}
/// Crash annotations included in crash reports.
#[repr(C)]
#[derive(Clone, Copy)]
pub enum CrashAnnotation {
CompileShader = 0,
DrawShader = 1,
}
/// Handler to expose support for annotating crash reports.
pub trait CrashAnnotator : Send {
fn set(&self, annotation: CrashAnnotation, value: &std::ffi::CStr);
fn clear(&self, annotation: CrashAnnotation);
fn box_clone(&self) -> Box<dyn CrashAnnotator>;
}
impl Clone for Box<dyn CrashAnnotator> {
fn clone(&self) -> Box<dyn CrashAnnotator> {
self.box_clone()
}
}
/// Guard to add a crash annotation at creation, and clear it at destruction.
pub struct CrashAnnotatorGuard<'a> {
annotator: &'a Option<Box<dyn CrashAnnotator>>,
annotation: CrashAnnotation,
}
impl<'a> CrashAnnotatorGuard<'a> {
pub fn new(
annotator: &'a Option<Box<dyn CrashAnnotator>>,
annotation: CrashAnnotation,
value: &std::ffi::CStr,
) -> Self {
if let Some(ref annotator) = annotator {
annotator.set(annotation, value);
}
Self {
annotator,
annotation,
}
}
}
impl<'a> Drop for CrashAnnotatorGuard<'a> {
fn drop(&mut self) {
if let Some(ref annotator) = self.annotator {
annotator.clear(self.annotation);
}
}
}

View file

@ -1,327 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
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),
})
}

View file

@ -32,6 +32,7 @@ pub type DeviceIntLength = Length<i32, DevicePixel>;
pub type DeviceIntSideOffsets = SideOffsets2D<i32, DevicePixel>;
pub type DeviceRect = Rect<f32, DevicePixel>;
pub type DeviceBox2D = Box2D<f32, DevicePixel>;
pub type DevicePoint = Point2D<f32, DevicePixel>;
pub type DeviceVector2D = Vector2D<f32, DevicePixel>;
pub type DeviceSize = Size2D<f32, DevicePixel>;
@ -149,7 +150,7 @@ pub type BlobToDeviceTranslation = Translation2D<i32, LayoutPixel, DevicePixel>;
/// 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)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TexelRect {
pub uv0: DevicePoint,
pub uv1: DevicePoint,