diff --git a/.taskcluster.yml b/.taskcluster.yml
index 3a874006e1..f08f4f2b29 100644
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -64,12 +64,7 @@ tasks:
image: 'staktrace/webrender-test:debian-v3'
env:
RUST_BACKTRACE: 'full'
- #############################################################
- ### Temporarily disable until https://bugzil.la/1564873 lands
- ### in order to unblock https://bugzil.la/1575648 (which is
- ### time-critical)
- # RUSTFLAGS: '--deny warnings'
- #############################################################
+ RUSTFLAGS: '--deny warnings'
command:
- /bin/bash
- '--login'
@@ -96,12 +91,7 @@ tasks:
image: 'staktrace/webrender-test:debian-v3'
env:
RUST_BACKTRACE: 'full'
- #############################################################
- ### Temporarily disable until https://bugzil.la/1564873 lands
- ### in order to unblock https://bugzil.la/1575648 (which is
- ### time-critical)
- # RUSTFLAGS: '--deny warnings'
- #############################################################
+ RUSTFLAGS: '--deny warnings'
command:
- /bin/bash
- '--login'
@@ -119,8 +109,8 @@ tasks:
# Mozilla releng doesn't have any spare OS X machines for us at this time.
# Talk to :kats or :jrmuizel if you need more details about this. The machines
# are hooked up to taskcluster using taskcluster-worker; they use a workerpool
- # of webrender/ci-macos. They are set up with all the dependencies needed
- # to build and test webrender, including Rust (currently 1.30), servo-tidy,
+ # of proj-webrender/ci-macos. They are set up with all the dependencies needed
+ # to build and test webrender, including Rust (currently 1.41.1), servo-tidy,
# mako, zlib, etc. Note that unlike the docker-worker used for Linux, these
# machines WILL persist state from one run to the next, so any cleanup needs
# to be handled explicitly.
@@ -145,12 +135,7 @@ tasks:
source $HOME/servotidy-venv/bin/activate
servo-tidy
export RUST_BACKTRACE=full
- #############################################################
- ### Temporarily disable until https://bugzil.la/1564873 lands
- ### in order to unblock https://bugzil.la/1575648 (which is
- ### time-critical)
- # export RUSTFLAGS='--deny warnings'
- #############################################################
+ export RUSTFLAGS='--deny warnings'
export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH"
echo 'exec make -j1 "$@"' > $HOME/make # See #2638
chmod +x $HOME/make
@@ -179,12 +164,7 @@ tasks:
source $HOME/servotidy-venv/bin/activate
servo-tidy
export RUST_BACKTRACE=full
- #############################################################
- ### Temporarily disable until https://bugzil.la/1564873 lands
- ### in order to unblock https://bugzil.la/1575648 (which is
- ### time-critical)
- # export RUSTFLAGS='--deny warnings'
- #############################################################
+ export RUSTFLAGS='--deny warnings'
export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH"
echo 'exec make -j1 "$@"' > $HOME/make # See #2638
chmod +x $HOME/make
diff --git a/appveyor.yml b/appveyor.yml
index 4b6507292f..2137e7a5a2 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -9,8 +9,8 @@ environment:
TARGET: x86_64-pc-windows-msvc
install:
- - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.36.0-${env:TARGET}.msi"
- - msiexec /passive /i "rust-1.36.0-%TARGET%.msi" ADDLOCAL=Rustc,Cargo,Std INSTALLDIR=C:\Rust
+ - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.41.1-${env:TARGET}.msi"
+ - msiexec /passive /i "rust-1.41.1-%TARGET%.msi" ADDLOCAL=Rustc,Cargo,Std INSTALLDIR=C:\Rust
- rustc -V
- cargo -V
diff --git a/examples/animation.rs b/examples/animation.rs
index 4cd7f343ce..d442ab0309 100644
--- a/examples/animation.rs
+++ b/examples/animation.rs
@@ -187,6 +187,7 @@ impl Example for App {
value: self.opacity,
}
],
+ colors: vec![],
},
);
txn.generate_frame();
diff --git a/tileview/src/main.rs b/tileview/src/main.rs
index 38ebfbfc22..20758185b4 100644
--- a/tileview/src/main.rs
+++ b/tileview/src/main.rs
@@ -15,7 +15,7 @@
use webrender::{TileNode, TileNodeKind, InvalidationReason, TileOffset};
use webrender::{TileSerializer, TileCacheInstanceSerializer, TileCacheLoggerUpdateLists};
-use webrender::{PrimitiveCompareResultDetail, CompareHelperResult, UpdateKind, ItemUid};
+use webrender::{PrimitiveCompareResultDetail, CompareHelperResult, ItemUid};
use serde::Deserialize;
use std::fs::File;
use std::io::prelude::*;
@@ -516,20 +516,15 @@ macro_rules! updatelist_to_html_macro {
html += &format!("
\n\n",
stringify!($name));
for list in &update_lists.$name.1 {
- let mut insert_count = 0;
- for update in &list.updates {
- match update.kind {
- UpdateKind::Insert => {
- html += &format!("
{} {}
\n",
- update.uid.get_uid(),
- format!("({:?})", list.data[insert_count]));
- insert_count = insert_count + 1;
- }
- _ => {
- html += &format!("
{}
\n",
- update.uid.get_uid());
- }
- };
+ for insertion in &list.insertions {
+ html += &format!("
{} {}
\n",
+ insertion.uid.get_uid(),
+ format!("({:?})", insertion.value));
+ }
+
+ for removal in &list.removals {
+ html += &format!("
{}
\n",
+ removal.uid.get_uid());
}
}
html += "
\n";
diff --git a/webrender/res/brush_conic_gradient.glsl b/webrender/res/brush_conic_gradient.glsl
index fcc6d4912a..56bd3c25a9 100644
--- a/webrender/res/brush_conic_gradient.glsl
+++ b/webrender/res/brush_conic_gradient.glsl
@@ -13,7 +13,9 @@
#define V_GRADIENT_ADDRESS flat_varying_highp_int_address_0
#define V_CENTER flat_varying_vec4_0.xy
-#define V_ANGLE flat_varying_vec4_0.z
+#define V_START_OFFSET flat_varying_vec4_0.z
+#define V_END_OFFSET flat_varying_vec4_0.w
+#define V_ANGLE flat_varying_vec4_1.w
// Size of the gradient pattern's rectangle, used to compute horizontal and vertical
// repetitions. Not to be confused with another kind of repetition of the pattern
@@ -29,12 +31,13 @@
#define V_TILE_REPEAT flat_varying_vec4_2.xy
#endif
-#define PI 3.1415926538
+#define PI 3.141592653589793
#ifdef WR_VERTEX_SHADER
struct ConicGradient {
vec2 center_point;
+ vec2 start_end_offset;
float angle;
int extend_mode;
vec2 stretch_size;
@@ -44,9 +47,10 @@ ConicGradient fetch_gradient(int address) {
vec4 data[2] = fetch_from_gpu_cache_2(address);
return ConicGradient(
data[0].xy,
- float(data[0].z),
- int(data[1].x),
- data[1].yz
+ data[0].zw,
+ float(data[1].x),
+ int(data[1].y),
+ data[1].zw
);
}
@@ -74,6 +78,8 @@ void conic_gradient_brush_vs(
V_CENTER = gradient.center_point;
V_ANGLE = gradient.angle;
+ V_START_OFFSET = gradient.start_end_offset.x;
+ V_END_OFFSET = gradient.start_end_offset.y;
vec2 tile_repeat = local_rect.size / gradient.stretch_size;
V_REPEATED_SIZE = gradient.stretch_size;
@@ -115,7 +121,8 @@ Fragment conic_gradient_brush_fs() {
vec2 current_dir = pos - V_CENTER;
float current_angle = atan(current_dir.y, current_dir.x) + (PI / 2.0 - V_ANGLE);
- float offset = mod(current_angle / (2.0 * PI), 1.0);
+ float offset = mod(current_angle / (2.0 * PI), 1.0) - V_START_OFFSET;
+ offset = offset / (V_END_OFFSET - V_START_OFFSET);
vec4 color = sample_gradient(V_GRADIENT_ADDRESS,
offset,
diff --git a/webrender/res/brush_mix_blend.glsl b/webrender/res/brush_mix_blend.glsl
index c73f2e3f0d..774f2fcb67 100644
--- a/webrender/res/brush_mix_blend.glsl
+++ b/webrender/res/brush_mix_blend.glsl
@@ -38,16 +38,18 @@ void mix_blend_brush_vs(
V_OP = prim_user_data.x;
PictureTask src_task = fetch_picture_task(prim_user_data.z);
- vec2 src_uv = device_pos +
+ vec2 src_device_pos = vi.world_pos.xy * (src_task.device_pixel_scale / max(0.0, vi.world_pos.w));
+ vec2 src_uv = src_device_pos +
src_task.common_data.task_rect.p0 -
src_task.content_origin;
V_SRC_UV = src_uv / texture_size;
V_SRC_LAYER = src_task.common_data.texture_layer_index;
RenderTaskCommonData backdrop_task = fetch_render_task_common_data(prim_user_data.y);
+ float src_to_backdrop_scale = pic_task.device_pixel_scale / src_task.device_pixel_scale;
vec2 backdrop_uv = device_pos +
backdrop_task.task_rect.p0 -
- src_task.content_origin;
+ src_task.content_origin * src_to_backdrop_scale;
V_BACKDROP_UV = backdrop_uv / texture_size;
V_BACKDROP_LAYER = backdrop_task.texture_layer_index;
}
diff --git a/webrender/res/composite.glsl b/webrender/res/composite.glsl
index da19b76e78..a63df80e02 100644
--- a/webrender/res/composite.glsl
+++ b/webrender/res/composite.glsl
@@ -56,7 +56,7 @@ void main(void) {
write_uv_rect(
aUvRect0.xy,
- aUvRect0.xy + aUvRect0.zw,
+ aUvRect0.zw,
aTextureLayers.x,
uv,
TEX_SIZE(sColor0),
@@ -65,7 +65,7 @@ void main(void) {
);
write_uv_rect(
aUvRect1.xy,
- aUvRect1.xy + aUvRect1.zw,
+ aUvRect1.zw,
aTextureLayers.y,
uv,
TEX_SIZE(sColor1),
@@ -74,7 +74,7 @@ void main(void) {
);
write_uv_rect(
aUvRect2.xy,
- aUvRect2.xy + aUvRect2.zw,
+ aUvRect2.zw,
aTextureLayers.z,
uv,
TEX_SIZE(sColor2),
diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs
index f9f5330181..db81681852 100644
--- a/webrender/src/batch.rs
+++ b/webrender/src/batch.rs
@@ -1227,12 +1227,10 @@ impl BatchBuilder {
.map(&prim_info.combined_local_clip_rect)
.expect("bug: unable to map clip rect");
let device_clip_rect = (world_clip_rect * ctx.global_device_pixel_scale).round();
- let z_id = composite_state.z_generator.next();
composite_state.push_surface(
tile_cache,
device_clip_rect,
- z_id,
ctx.global_device_pixel_scale,
ctx.resource_cache,
gpu_cache,
diff --git a/webrender/src/box_shadow.rs b/webrender/src/box_shadow.rs
index 124d44d5de..eb8d4d0d4e 100644
--- a/webrender/src/box_shadow.rs
+++ b/webrender/src/box_shadow.rs
@@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, PrimitiveKeyKind};
+use api::PropertyBinding;
use api::MAX_BLUR_RADIUS;
use api::units::*;
use crate::clip::{ClipItemKey, ClipItemKeyKind};
@@ -163,7 +164,7 @@ impl<'a> SceneBuilder<'a> {
&LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
clips,
PrimitiveKeyKind::Rectangle {
- color: color.into(),
+ color: PropertyBinding::Value(color.into()),
},
);
} else {
@@ -189,7 +190,7 @@ impl<'a> SceneBuilder<'a> {
// Draw the box-shadow as a solid rect, using a box-shadow
// clip mask item.
let prim = PrimitiveKeyKind::Rectangle {
- color: color.into(),
+ color: PropertyBinding::Value(color.into()),
};
// Create the box-shadow clip item.
diff --git a/webrender/src/composite.rs b/webrender/src/composite.rs
index 58400c042d..a65a38f457 100644
--- a/webrender/src/composite.rs
+++ b/webrender/src/composite.rs
@@ -2,14 +2,14 @@
* 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 api::{ColorF, ImageKey, YuvColorSpace, YuvFormat, ImageRendering};
+use api::{ColorF, YuvColorSpace, YuvFormat, ImageRendering};
use api::units::{DeviceRect, DeviceIntSize, DeviceIntRect, DeviceIntPoint, WorldRect};
-use api::units::{DevicePixelScale, DevicePoint, PictureRect};
+use api::units::{DevicePixelScale, DevicePoint, PictureRect, TexelRect};
use crate::batch::{resolve_image, get_buffer_kind};
use crate::gpu_cache::GpuCache;
use crate::gpu_types::{ZBufferId, ZBufferIdGenerator};
use crate::internal_types::TextureSource;
-use crate::picture::{ResolvedSurfaceTexture, TileCacheInstance, TileSurface};
+use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileSurface};
use crate::prim_store::DeferredResolve;
use crate::renderer::ImageBufferKind;
use crate::resource_cache::{ImageRequest, ResourceCache};
@@ -62,26 +62,18 @@ pub enum CompositeTileSurface {
color: ColorF,
},
Clear,
+ ExternalSurface {
+ external_surface_index: ResolvedExternalSurfaceIndex,
+ },
}
/// The surface format for a tile being composited.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CompositeSurfaceFormat {
Rgba,
Yuv,
}
-/// The ordering that this tile should be composited with.
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Debug, Copy, Clone)]
-pub enum TileCompositeMode {
- /// Normal z-ordering for this tile
- Default,
- /// This tile overlaps a compositor surface - draw it after them
- Over,
-}
-
/// Describes the geometry and surface of a tile to be composited
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -92,8 +84,6 @@ pub struct CompositeTile {
pub dirty_rect: DeviceRect,
pub valid_rect: DeviceRect,
pub z_id: ZBufferId,
- pub tile_id: Option,
- pub mode: TileCompositeMode,
}
/// Describes information about drawing a primitive as a compositor surface.
@@ -101,13 +91,21 @@ pub struct CompositeTile {
/// this will also support RGBA images.
pub struct ExternalSurfaceDescriptor {
pub local_rect: PictureRect,
+ pub world_rect: WorldRect,
pub device_rect: DeviceRect,
pub clip_rect: DeviceRect,
- pub image_keys: [ImageKey; 3],
+ pub image_dependencies: [ImageDependency; 3],
pub image_rendering: ImageRendering,
pub yuv_color_space: YuvColorSpace,
pub yuv_format: YuvFormat,
pub yuv_rescale: f32,
+ pub z_id: ZBufferId,
+ /// If native compositing is enabled, the native compositor surface handle.
+ /// Otherwise, this will be None
+ pub native_surface_id: Option,
+ /// If the native surface needs to be updated, this will contain the size
+ /// of the native surface as Some(size). If not dirty, this is None.
+ pub update_params: Option,
}
/// Information about a plane in a YUV surface.
@@ -116,7 +114,7 @@ pub struct ExternalSurfaceDescriptor {
pub struct YuvPlaneDescriptor {
pub texture: TextureSource,
pub texture_layer: i32,
- pub uv_rect: DeviceRect,
+ pub uv_rect: TexelRect,
}
impl YuvPlaneDescriptor {
@@ -124,11 +122,16 @@ impl YuvPlaneDescriptor {
YuvPlaneDescriptor {
texture: TextureSource::Invalid,
texture_layer: 0,
- uv_rect: DeviceRect::zero(),
+ uv_rect: TexelRect::invalid(),
}
}
}
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Copy, Clone)]
+pub struct ResolvedExternalSurfaceIndex(pub usize);
+
/// An ExternalSurfaceDescriptor that has had image keys
/// resolved to texture handles. This contains all the
/// information that the compositor step in renderer
@@ -136,17 +139,16 @@ impl YuvPlaneDescriptor {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ResolvedExternalSurface {
- // Common information
- pub device_rect: DeviceRect,
- pub clip_rect: DeviceRect,
- pub z_id: ZBufferId,
-
// YUV specific information
+ pub image_dependencies: [ImageDependency; 3],
pub yuv_planes: [YuvPlaneDescriptor; 3],
pub yuv_color_space: YuvColorSpace,
pub yuv_format: YuvFormat,
pub yuv_rescale: f32,
pub image_buffer_kind: ImageBufferKind,
+
+ // Update information for a native surface if it's dirty
+ pub update_params: Option<(NativeSurfaceId, DeviceIntSize)>,
}
/// Public interface specified in `RendererOptions` that configures
@@ -227,7 +229,7 @@ impl Default for CompositorKind {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
struct Occluder {
- slice: usize,
+ z_id: ZBufferId,
device_rect: DeviceIntRect,
}
@@ -236,10 +238,14 @@ struct Occluder {
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(PartialEq, Clone)]
pub struct CompositeSurfaceDescriptor {
- pub slice: usize,
pub surface_id: Option,
pub offset: DevicePoint,
pub clip_rect: DeviceRect,
+ // A list of image keys and generations that this compositor surface
+ // depends on. This avoids composites being skipped when the only
+ // thing that has changed is the generation of an compositor surface
+ // image dependency.
+ pub image_dependencies: [ImageDependency; 3],
}
/// Describes surface properties used to composite a frame. This
@@ -332,22 +338,22 @@ impl CompositeState {
/// used during frame building to occlude tiles.
pub fn register_occluder(
&mut self,
- slice: usize,
+ z_id: ZBufferId,
rect: WorldRect,
) {
let device_rect = (rect * self.global_device_pixel_scale).round().to_i32();
self.occluders.push(Occluder {
device_rect,
- slice,
+ z_id,
});
}
- /// Returns true if a tile with the specified rectangle and slice
+ /// Returns true if a tile with the specified rectangle and z_id
/// is occluded by an opaque surface in front of it.
pub fn is_tile_occluded(
&self,
- slice: usize,
+ z_id: ZBufferId,
device_rect: DeviceRect,
) -> bool {
// It's often the case that a tile is only occluded by considering multiple
@@ -367,7 +373,7 @@ impl CompositeState {
let ref_area = device_rect.size.width * device_rect.size.height;
// Calculate the non-overlapping area of the valid occluders.
- let cover_area = area_of_occluders(&self.occluders, slice, &device_rect);
+ let cover_area = area_of_occluders(&self.occluders, z_id, &device_rect);
debug_assert!(cover_area <= ref_area);
// Check if the tile area is completely covered
@@ -379,13 +385,13 @@ impl CompositeState {
&mut self,
tile_cache: &TileCacheInstance,
device_clip_rect: DeviceRect,
- z_id: ZBufferId,
global_device_pixel_scale: DevicePixelScale,
resource_cache: &ResourceCache,
gpu_cache: &mut GpuCache,
deferred_resolves: &mut Vec,
) {
- let mut visible_tile_count = 0;
+ let mut visible_opaque_tile_count = 0;
+ let mut visible_alpha_tile_count = 0;
for tile in tile_cache.tiles.values() {
if !tile.is_visible {
@@ -393,8 +399,6 @@ impl CompositeState {
continue;
}
- visible_tile_count += 1;
-
let device_rect = (tile.world_tile_rect * global_device_pixel_scale).round();
let surface = tile.surface.as_ref().expect("no tile surface set!");
@@ -414,30 +418,10 @@ impl CompositeState {
}
};
- let tile_id = tile_cache.native_surface_id.map(|surface_id| {
- NativeTileId {
- surface_id,
- x: tile.tile_offset.x,
- y: tile.tile_offset.y,
- }
- });
-
- // Determine ordering of this tile, based on presence of compositor
- // surfaces that intersect the tile.
- let mut mode = TileCompositeMode::Default;
-
- if tile.has_compositor_surface {
- // TODO(gw): This will almost always select over blend, due to the
- // background rectangle. In future, we can optimize this
- // case to only check items that come _after_ the compositor
- // surface z_id? A better option might be to tweak the z_id
- // values so that the alpha pixels get z-rejected?
- for surface in &tile_cache.external_surfaces {
- if surface.device_rect.intersects(&tile.device_valid_rect) {
- mode = TileCompositeMode::Over;
- break;
- }
- }
+ if is_opaque {
+ visible_opaque_tile_count += 1;
+ } else {
+ visible_alpha_tile_count += 1;
}
let tile = CompositeTile {
@@ -446,14 +430,24 @@ impl CompositeState {
valid_rect: tile.device_valid_rect.translate(-device_rect.origin.to_vector()),
dirty_rect: tile.device_dirty_rect.translate(-device_rect.origin.to_vector()),
clip_rect: device_clip_rect,
- z_id,
- tile_id,
- mode,
+ z_id: tile.z_id,
};
self.push_tile(tile, is_opaque);
}
+ // Add opaque surface before any compositor surfaces
+ if visible_opaque_tile_count > 0 {
+ self.descriptor.surfaces.push(
+ CompositeSurfaceDescriptor {
+ surface_id: tile_cache.native_surface.as_ref().map(|s| s.opaque),
+ offset: tile_cache.device_position,
+ clip_rect: device_clip_rect,
+ image_dependencies: [ImageDependency::INVALID; 3],
+ }
+ );
+ }
+
// For each compositor surface that was promoted, build the
// information required for the compositor to draw it
for external_surface in &tile_cache.external_surfaces {
@@ -468,7 +462,7 @@ impl CompositeState {
let mut valid_plane_count = 0;
for i in 0 .. required_plane_count {
- let key = external_surface.image_keys[i];
+ let key = external_surface.image_dependencies[i].key;
let plane = &mut yuv_planes[i];
let request = ImageRequest {
@@ -490,7 +484,7 @@ impl CompositeState {
*plane = YuvPlaneDescriptor {
texture: cache_item.texture_id,
texture_layer: cache_item.texture_layer,
- uv_rect: cache_item.uv_rect.to_f32(),
+ uv_rect: cache_item.uv_rect.into(),
};
}
}
@@ -509,29 +503,65 @@ impl CompositeState {
.intersection(&device_clip_rect)
.unwrap_or_else(DeviceRect::zero);
- // z_id for compositor surfaces can be the same as the surface it
- // exists on, because we use LessEqual depth function. We could
- // in future consider disabling z-read completely for drawing
- // surface overlay tiles, since it doesn't do anything useful.
+ // Get a new z_id for each compositor surface, to ensure correct ordering
+ // when drawing with the simple (Draw) compositor.
+
+ let surface = CompositeTileSurface::ExternalSurface {
+ external_surface_index: ResolvedExternalSurfaceIndex(self.external_surfaces.len()),
+ };
+
+ // If the external surface descriptor reports that the native surface
+ // needs to be updated, create an update params tuple for the renderer
+ // to use.
+ let update_params = external_surface.update_params.map(|surface_size| {
+ (
+ external_surface.native_surface_id.expect("bug: no native surface!"),
+ surface_size
+ )
+ });
+
self.external_surfaces.push(ResolvedExternalSurface {
- device_rect: external_surface.device_rect,
- clip_rect,
- z_id,
yuv_color_space: external_surface.yuv_color_space,
yuv_format: external_surface.yuv_format,
yuv_rescale: external_surface.yuv_rescale,
image_buffer_kind: get_buffer_kind(yuv_planes[0].texture),
+ image_dependencies: external_surface.image_dependencies,
yuv_planes,
+ update_params,
});
+
+ let tile = CompositeTile {
+ surface,
+ rect: external_surface.device_rect,
+ valid_rect: external_surface.device_rect.translate(-external_surface.device_rect.origin.to_vector()),
+ dirty_rect: external_surface.device_rect.translate(-external_surface.device_rect.origin.to_vector()),
+ clip_rect,
+ z_id: external_surface.z_id,
+ };
+
+ // Add a surface descriptor for each compositor surface. For the Draw
+ // compositor, this is used to avoid composites being skipped by adding
+ // a dependency on the compositor surface external image keys / generations.
+ self.descriptor.surfaces.push(
+ CompositeSurfaceDescriptor {
+ surface_id: external_surface.native_surface_id,
+ offset: tile.rect.origin,
+ clip_rect: tile.clip_rect,
+ image_dependencies: external_surface.image_dependencies,
+ }
+ );
+
+ self.push_tile(tile, true);
}
- if visible_tile_count > 0 {
+ // Add alpha / overlay tiles after compositor surfaces
+ if visible_alpha_tile_count > 0 {
self.descriptor.surfaces.push(
CompositeSurfaceDescriptor {
- slice: tile_cache.slice,
- surface_id: tile_cache.native_surface_id,
+ surface_id: tile_cache.native_surface.as_ref().map(|s| s.alpha),
offset: tile_cache.device_position,
clip_rect: device_clip_rect,
+ image_dependencies: [ImageDependency::INVALID; 3],
}
);
}
@@ -554,21 +584,17 @@ impl CompositeState {
self.clear_tiles.push(tile);
}
CompositeTileSurface::Texture { .. } => {
- match tile.mode {
- TileCompositeMode::Default => {
- // Texture surfaces get bucketed by opaque/alpha, for z-rejection
- // on the Draw compositor mode.
- if is_opaque {
- self.opaque_tiles.push(tile);
- } else {
- self.alpha_tiles.push(tile);
- }
- }
- TileCompositeMode::Over => {
- self.alpha_tiles.push(tile);
- }
+ // Texture surfaces get bucketed by opaque/alpha, for z-rejection
+ // on the Draw compositor mode.
+ if is_opaque {
+ self.opaque_tiles.push(tile);
+ } else {
+ self.alpha_tiles.push(tile);
}
}
+ CompositeTileSurface::ExternalSurface { .. } => {
+ self.opaque_tiles.push(tile);
+ }
}
}
}
@@ -717,7 +743,7 @@ pub trait Compositor {
/// overlapping areas between those rectangles.
fn area_of_occluders(
occluders: &[Occluder],
- slice: usize,
+ z_id: ZBufferId,
clip_rect: &DeviceIntRect,
) -> i32 {
// This implementation is based on the article https://leetcode.com/articles/rectangle-area-ii/.
@@ -758,7 +784,7 @@ fn area_of_occluders(
let mut events = Vec::with_capacity(occluders.len() * 2);
for occluder in occluders {
// Only consider occluders in front of this rect
- if occluder.slice > slice {
+ if occluder.z_id.0 > z_id.0 {
// Clip the source rect to the rectangle we care about, since we only
// want to record area for the tile we are comparing to.
if let Some(rect) = occluder.device_rect.intersection(clip_rect) {
diff --git a/webrender/src/device/gl.rs b/webrender/src/device/gl.rs
index 202b699a9c..cd08775638 100644
--- a/webrender/src/device/gl.rs
+++ b/webrender/src/device/gl.rs
@@ -486,14 +486,21 @@ pub struct ExternalTexture {
id: gl::GLuint,
target: gl::GLuint,
swizzle: Swizzle,
+ uv_rect: TexelRect,
}
impl ExternalTexture {
- pub fn new(id: u32, target: TextureTarget, swizzle: Swizzle) -> Self {
+ pub fn new(
+ id: u32,
+ target: TextureTarget,
+ swizzle: Swizzle,
+ uv_rect: TexelRect,
+ ) -> Self {
ExternalTexture {
id,
target: get_gl_target(target),
swizzle,
+ uv_rect,
}
}
@@ -501,6 +508,10 @@ impl ExternalTexture {
pub fn internal_id(&self) -> gl::GLuint {
self.id
}
+
+ pub fn get_uv_rect(&self) -> TexelRect {
+ self.uv_rect
+ }
}
bitflags! {
@@ -618,6 +629,13 @@ impl Texture {
id: self.id,
target: self.target,
swizzle: Swizzle::default(),
+ // TODO(gw): Support custom UV rect for external textures during captures
+ uv_rect: TexelRect::new(
+ 0.0,
+ 0.0,
+ self.size.width as f32,
+ self.size.height as f32,
+ ),
};
self.id = 0; // don't complain, moved out
ext
diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs
index 138aacab58..deb2c9a5c3 100644
--- a/webrender/src/frame_builder.rs
+++ b/webrender/src/frame_builder.rs
@@ -402,8 +402,13 @@ impl FrameBuilder {
// we need to manually clean up any native compositor surfaces that were
// allocated by these tiles.
for (_, mut cache_state) in visibility_state.retained_tiles.caches.drain() {
- if let Some(native_surface_id) = cache_state.native_surface_id.take() {
- visibility_state.resource_cache.destroy_compositor_surface(native_surface_id);
+ if let Some(native_surface) = cache_state.native_surface.take() {
+ visibility_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
+ visibility_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
+ }
+
+ for (_, external_surface) in cache_state.external_native_surface_cache.drain() {
+ visibility_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
}
}
}
diff --git a/webrender/src/glyph_rasterizer/mod.rs b/webrender/src/glyph_rasterizer/mod.rs
index 2c89ba191b..e180ea9290 100644
--- a/webrender/src/glyph_rasterizer/mod.rs
+++ b/webrender/src/glyph_rasterizer/mod.rs
@@ -338,14 +338,27 @@ impl FontTransform {
self.pre_scale(x_scale.recip() as f32, y_scale.recip() as f32)
}
- pub fn synthesize_italics(&self, angle: SyntheticItalics) -> Self {
+ pub fn synthesize_italics(&self, angle: SyntheticItalics, size: f64, vertical: bool) -> (Self, (f64, f64)) {
let skew_factor = angle.to_skew();
- FontTransform::new(
- self.scale_x,
- self.skew_x - self.scale_x * skew_factor,
- self.skew_y,
- self.scale_y - self.skew_y * skew_factor,
- )
+ if vertical {
+ // origin delta to be applied so that we effectively skew around
+ // the middle rather than edge of the glyph
+ let (tx, ty) = (0.0, -size * 0.5 * skew_factor as f64);
+ (FontTransform::new(
+ self.scale_x + self.skew_x * skew_factor,
+ self.skew_x,
+ self.skew_y + self.scale_y * skew_factor,
+ self.scale_y,
+ ), (self.scale_x as f64 * tx + self.skew_x as f64 * ty,
+ self.skew_y as f64 * tx + self.scale_y as f64 * ty))
+ } else {
+ (FontTransform::new(
+ self.scale_x,
+ self.skew_x - self.scale_x * skew_factor,
+ self.skew_y,
+ self.scale_y - self.skew_y * skew_factor,
+ ), (0.0, 0.0))
+ }
}
pub fn swap_xy(&self) -> Self {
@@ -552,6 +565,10 @@ impl FontInstance {
0
}
}
+
+ pub fn synthesize_italics(&self, transform: FontTransform, size: f64) -> (FontTransform, (f64, f64)) {
+ transform.synthesize_italics(self.synthetic_italics, size, self.flags.contains(FontInstanceFlags::VERTICAL))
+ }
}
#[repr(u32)]
diff --git a/webrender/src/gpu_types.rs b/webrender/src/gpu_types.rs
index fb1f9c67cb..57b9042a9a 100644
--- a/webrender/src/gpu_types.rs
+++ b/webrender/src/gpu_types.rs
@@ -23,7 +23,7 @@ pub const VECS_PER_TRANSFORM: usize = 8;
#[repr(C)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ZBufferId(i32);
+pub struct ZBufferId(pub i32);
// We get 24 bits of Z value - use up 22 bits of it to give us
// 4 bits to account for GPU issues. This seems to manifest on
@@ -255,7 +255,7 @@ pub struct CompositeInstance {
yuv_rescale: f32,
// UV rectangles (pixel space) for color / yuv texture planes
- uv_rects: [DeviceRect; 3],
+ uv_rects: [TexelRect; 3],
// Texture array layers for color / yuv texture planes
texture_layers: [f32; 3],
@@ -278,7 +278,7 @@ impl CompositeInstance {
yuv_format: 0.0,
yuv_rescale: 0.0,
texture_layers: [layer, 0.0, 0.0],
- uv_rects: [DeviceRect::zero(); 3],
+ uv_rects: [TexelRect::invalid(); 3],
}
}
@@ -290,7 +290,7 @@ impl CompositeInstance {
yuv_format: YuvFormat,
yuv_rescale: f32,
texture_layers: [f32; 3],
- uv_rects: [DeviceRect; 3],
+ uv_rects: [TexelRect; 3],
) -> Self {
CompositeInstance {
rect,
diff --git a/webrender/src/intern.rs b/webrender/src/intern.rs
index 884421c532..db7fd0c1c9 100644
--- a/webrender/src/intern.rs
+++ b/webrender/src/intern.rs
@@ -52,11 +52,46 @@ struct Epoch(u64);
/// provided by the interning structure.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(MallocSizeOf)]
pub struct UpdateList {
- /// The additions and removals to apply.
- pub updates: Vec,
- /// Actual new data to insert.
- pub data: Vec,
+ /// Items to insert.
+ pub insertions: Vec>,
+
+ /// Items to remove.
+ pub removals: Vec,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(MallocSizeOf)]
+pub struct Insertion {
+ pub index: usize,
+ pub uid: ItemUid,
+ pub value: S,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(MallocSizeOf)]
+pub struct Removal {
+ pub index: usize,
+ pub uid: ItemUid,
+}
+
+impl UpdateList {
+ fn new() -> UpdateList {
+ UpdateList {
+ insertions: Vec::new(),
+ removals: Vec::new(),
+ }
+ }
+
+ fn take_and_preallocate(&mut self) -> UpdateList {
+ UpdateList {
+ insertions: self.insertions.take_and_preallocate(),
+ removals: self.removals.take_and_preallocate(),
+ }
+ }
}
lazy_static! {
@@ -112,23 +147,6 @@ impl Handle {
}
}
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(MallocSizeOf)]
-pub enum UpdateKind {
- Insert,
- Remove,
-}
-
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(MallocSizeOf)]
-pub struct Update {
- pub index: usize,
- pub uid: ItemUid,
- pub kind: UpdateKind,
-}
-
pub trait InternDebug {
fn on_interned(&self, _uid: ItemUid) {}
}
@@ -158,25 +176,18 @@ impl DataStore {
update_list: UpdateList,
profile_counter: &mut ResourceProfileCounter,
) {
- let mut data_iter = update_list.data.into_iter();
- for update in update_list.updates {
- match update.kind {
- UpdateKind::Insert => {
- let value = data_iter.next().unwrap().into();
- self.items
- .entry(update.index)
- .set(Some(value));
- }
- UpdateKind::Remove => {
- self.items[update.index] = None;
- }
- }
+ for insertion in update_list.insertions {
+ self.items
+ .entry(insertion.index)
+ .set(Some(insertion.value.into()));
+ }
+
+ for removal in update_list.removals {
+ self.items[removal.index] = None;
}
let per_item_size = mem::size_of::() + mem::size_of::();
profile_counter.set(self.items.len(), per_item_size * self.items.len());
-
- debug_assert!(data_iter.next().is_none());
}
}
@@ -210,9 +221,7 @@ pub struct Interner {
/// List of free slots in the data store for re-use.
free_list: Vec,
/// Pending list of updates that need to be applied.
- updates: Vec,
- /// Pending new data to insert.
- update_data: Vec,
+ update_list: UpdateList,
/// The current epoch for the interner.
current_epoch: Epoch,
/// The information associated with each interned
@@ -225,8 +234,7 @@ impl Default for Interner {
Interner {
map: FastHashMap::default(),
free_list: Vec::new(),
- updates: Vec::new(),
- update_data: Vec::new(),
+ update_list: UpdateList::new(),
current_epoch: Epoch(1),
local_data: Vec::new(),
}
@@ -265,12 +273,11 @@ impl Interner {
let uid = ItemUid::next_uid();
// Add a pending update to insert the new data.
- self.updates.push(Update {
+ self.update_list.insertions.push(Insertion {
index,
uid,
- kind: UpdateKind::Insert,
+ value: data.clone(),
});
- self.update_data.alloc().init(data.clone());
// Generate a handle for access via the data store.
let handle = Handle {
@@ -298,8 +305,7 @@ impl Interner {
/// that need to be applied to the data store. Also run
/// a GC step that removes old entries.
pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList {
- let mut updates = self.updates.take_and_preallocate();
- let data = self.update_data.take_and_preallocate();
+ let mut update_list = self.update_list.take_and_preallocate();
let free_list = &mut self.free_list;
let current_epoch = self.current_epoch.0;
@@ -315,28 +321,23 @@ impl Interner {
if handle.epoch.0 + 10 < current_epoch {
// To expire an item:
// - Add index to the free-list for re-use.
- // - Add an update to the data store to invalidate this slow.
+ // - Add an update to the data store to invalidate this slot.
// - Remove from the hash map.
free_list.push(handle.index as usize);
- updates.push(Update {
+ update_list.removals.push(Removal {
index: handle.index as usize,
uid: handle.uid,
- kind: UpdateKind::Remove,
});
return false;
}
true
});
- let updates = UpdateList {
- updates,
- data,
- };
// Begin the next epoch
self.current_epoch = Epoch(self.current_epoch.0 + 1);
- updates
+ update_list
}
}
diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs
index 8a704d8c1c..98c11cb92a 100644
--- a/webrender/src/lib.rs
+++ b/webrender/src/lib.rs
@@ -224,4 +224,4 @@ pub use webrender_build::shader::ProgramSourceDigest;
pub use crate::picture::{TileDescriptor, TileId, InvalidationReason};
pub use crate::picture::{PrimitiveCompareResult, PrimitiveCompareResultDetail, CompareHelperResult};
pub use crate::picture::{TileNode, TileNodeKind, TileSerializer, TileCacheInstanceSerializer, TileOffset, TileCacheLoggerUpdateLists};
-pub use crate::intern::{Update, UpdateKind, ItemUid};
+pub use crate::intern::ItemUid;
diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs
index b601793234..6331159735 100644
--- a/webrender/src/picture.rs
+++ b/webrender/src/picture.rs
@@ -69,7 +69,7 @@
//! content will all be squashed into a single slice, in order to save GPU memory
//! and compositing performance.
//!
-//! ## Overlay Tiles
+//! ## Compositor Surfaces
//!
//! Sometimes, a primitive would prefer to exist as a native compositor surface.
//! This allows a large and/or regularly changing primitive (such as a video, or
@@ -79,12 +79,12 @@
//! Since drawing a primitive as a compositor surface alters the ordering of
//! primitives in a tile, we use 'overlay tiles' to ensure correctness. If a
//! tile has a compositor surface, _and_ that tile has primitives that overlap
-//! the compositor surface rect, the tile switches to be drawn in overlay mode.
+//! the compositor surface rect, the tile switches to be drawn in alpha mode.
//!
//! We rely on only promoting compositor surfaces that are opaque primitives.
//! With this assumption, the tile(s) that intersect the compositor surface get
//! a 'cutout' in the rectangle where the compositor surface exists (not the
-//! entire tile), allowing that tile to be drawn as an overlay after the
+//! entire tile), allowing that tile to be drawn as an alpha tile after the
//! compositor surface.
//!
//! Tiles are only drawn in overlay mode if there is content that exists on top
@@ -96,7 +96,7 @@
use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
-use api::{DebugFlags, RasterSpace, ImageKey, ColorF, PrimitiveFlags};
+use api::{DebugFlags, RasterSpace, ImageKey, ColorF, ColorU, PrimitiveFlags, MAX_BLUR_RADIUS};
use api::units::*;
use crate::box_shadow::{BLUR_SAMPLE_SCALE};
use crate::clip::{ClipStore, ClipChainInstance, ClipDataHandle, ClipChainId};
@@ -114,12 +114,13 @@ use crate::intern::ItemUid;
use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter, PlaneSplitAnchor, TextureSource};
use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
-use crate::gpu_types::UvRectKind;
+use crate::gpu_types::{UvRectKind, ZBufferId};
use plane_split::{Clipper, Polygon, Splitter};
use crate::prim_store::{SpaceMapper, PrimitiveVisibilityMask, PointKey, PrimitiveTemplateKind};
use crate::prim_store::{SpaceSnapper, PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, RectangleKey};
use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex};
+use crate::prim_store::{ColorBindingStorage, ColorBindingIndex};
use crate::print_tree::{PrintTree, PrintTreePrinter};
use crate::render_backend::DataStores;
use crate::render_task_graph::RenderTaskId;
@@ -140,8 +141,6 @@ use ron;
use crate::scene_builder_thread::InternerUpdates;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::intern::{Internable, UpdateList};
-#[cfg(any(feature = "replay"))]
-use crate::intern::{UpdateKind};
#[cfg(any(feature = "capture", feature = "replay"))]
use api::{ClipIntern, FilterDataIntern, PrimitiveKeyKind};
#[cfg(any(feature = "capture", feature = "replay"))]
@@ -264,6 +263,8 @@ pub struct PictureCacheState {
spatial_nodes: FastHashMap,
/// State of opacity bindings from previous frame
opacity_bindings: FastHashMap,
+ /// State of color bindings from previous frame
+ color_bindings: FastHashMap,
/// The current transform of the picture cache root spatial node
root_transform: TransformKey,
/// The current tile size in device pixels
@@ -271,13 +272,14 @@ pub struct PictureCacheState {
/// Various allocations we want to avoid re-doing.
allocations: PictureCacheRecycledAllocations,
/// Currently allocated native compositor surface for this picture cache.
- pub native_surface_id: Option,
- /// True if the entire picture cache is opaque.
- is_opaque: bool,
+ pub native_surface: Option,
+ /// A cache of compositor surfaces that are retained between display lists
+ pub external_native_surface_cache: FastHashMap,
}
pub struct PictureCacheRecycledAllocations {
old_opacity_bindings: FastHashMap,
+ old_color_bindings: FastHashMap,
compare_cache: FastHashMap,
}
@@ -401,33 +403,39 @@ fn clampf(value: f32, low: f32, high: f32) -> f32 {
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PrimitiveDependencyIndex(pub u32);
-/// Information about the state of an opacity binding.
+/// Information about the state of a binding.
#[derive(Debug)]
-pub struct OpacityBindingInfo {
+pub struct BindingInfo {
/// The current value retrieved from dynamic scene properties.
- value: f32,
+ value: T,
/// True if it was changed (or is new) since the last frame build.
changed: bool,
}
-/// Information stored in a tile descriptor for an opacity binding.
+/// Information stored in a tile descriptor for a binding.
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum OpacityBinding {
- Value(f32),
+pub enum Binding {
+ Value(T),
Binding(PropertyBindingId),
}
-impl From> for OpacityBinding {
- fn from(binding: PropertyBinding) -> OpacityBinding {
+impl From> for Binding {
+ fn from(binding: PropertyBinding) -> Binding {
match binding {
- PropertyBinding::Binding(key, _) => OpacityBinding::Binding(key.id),
- PropertyBinding::Value(value) => OpacityBinding::Value(value),
+ PropertyBinding::Binding(key, _) => Binding::Binding(key.id),
+ PropertyBinding::Value(value) => Binding::Value(value),
}
}
}
+pub type OpacityBinding = Binding;
+pub type OpacityBindingInfo = BindingInfo;
+
+pub type ColorBinding = Binding;
+pub type ColorBindingInfo = BindingInfo;
+
/// Information about the state of a spatial node value
#[derive(Debug)]
pub struct SpatialNodeDependency {
@@ -476,11 +484,27 @@ struct TilePostUpdateContext<'a> {
/// Information about opacity bindings from the picture cache.
opacity_bindings: &'a FastHashMap,
+ /// Information about color bindings from the picture cache.
+ color_bindings: &'a FastHashMap,
+
/// Current size in device pixels of tiles for this cache
current_tile_size: DeviceIntSize,
/// The local rect of the overall picture cache
local_rect: PictureRect,
+
+ /// A list of the external surfaces that are present on this slice
+ external_surfaces: &'a [ExternalSurfaceDescriptor],
+
+ /// Pre-allocated z-id to assign to opaque tiles during post_update. We
+ /// use a different z-id for opaque/alpha tiles, so that compositor
+ /// surfaces (such as videos) can have a z-id between these values,
+ /// which allows compositor surfaces to occlude opaque tiles, but not
+ /// alpha tiles.
+ z_id_opaque: ZBufferId,
+
+ /// Pre-allocated z-id to assign to alpha tiles during post_update
+ z_id_alpha: ZBufferId,
}
// Mutable state passed to picture cache tiles during post_update
@@ -515,6 +539,9 @@ struct PrimitiveDependencyInfo {
/// Opacity bindings this primitive depends on.
opacity_bindings: SmallVec<[OpacityBinding; 4]>,
+ /// Color binding this primitive depends on.
+ color_binding: Option,
+
/// Clips that this primitive depends on.
clips: SmallVec<[ItemUid; 8]>,
@@ -537,6 +564,7 @@ impl PrimitiveDependencyInfo {
prim_origin,
images: SmallVec::new(),
opacity_bindings: SmallVec::new(),
+ color_binding: None,
clip_by_tile: false,
prim_clip_rect,
clips: SmallVec::new(),
@@ -688,6 +716,8 @@ pub enum PrimitiveCompareResult {
Image,
/// The value of an opacity binding changed
OpacityBinding,
+ /// The value of a color binding changed
+ ColorBinding,
}
/// A more detailed version of PrimitiveCompareResult used when
@@ -718,7 +748,11 @@ pub enum PrimitiveCompareResultDetail {
/// The value of an opacity binding changed
OpacityBinding {
detail: CompareHelperResult,
- }
+ },
+ /// The value of a color binding changed
+ ColorBinding {
+ detail: CompareHelperResult,
+ },
}
/// Debugging information about why a tile was invalidated
@@ -832,6 +866,14 @@ pub struct Tile {
invalidation_reason: Option,
/// If true, this tile has one or more compositor surfaces affecting it.
pub has_compositor_surface: bool,
+ /// The local space valid rect for any primitives found prior to the first compositor
+ /// surface that affects this tile.
+ bg_local_valid_rect: PictureRect,
+ /// The local space valid rect for any primitives found after the first compositor
+ /// surface that affects this tile.
+ fg_local_valid_rect: PictureRect,
+ /// z-buffer id for this tile, which is one of z_id_opaque or z_id_alpha, depending on tile opacity
+ pub z_id: ZBufferId,
}
impl Tile {
@@ -858,6 +900,9 @@ impl Tile {
background_color: None,
invalidation_reason: None,
has_compositor_surface: false,
+ bg_local_valid_rect: PictureRect::zero(),
+ fg_local_valid_rect: PictureRect::zero(),
+ z_id: ZBufferId::invalid(),
}
}
@@ -886,6 +931,7 @@ impl Tile {
state.resource_cache,
ctx.spatial_nodes,
ctx.opacity_bindings,
+ ctx.color_bindings,
);
let mut dirty_rect = PictureRect::zero();
@@ -974,6 +1020,8 @@ impl Tile {
),
ctx.tile_size,
);
+ self.bg_local_valid_rect = PictureRect::zero();
+ self.fg_local_valid_rect = PictureRect::zero();
self.invalidation_reason = None;
self.has_compositor_surface = false;
@@ -1038,8 +1086,17 @@ impl Tile {
// Incorporate the bounding rect of the primitive in the local valid rect
// for this tile. This is used to minimize the size of the scissor rect
// during rasterization and the draw rect during composition of partial tiles.
- self.current_descriptor.local_valid_rect =
- self.current_descriptor.local_valid_rect.union(&info.prim_clip_rect);
+
+ // Once we have encountered 1+ compositor surfaces affecting this tile, include
+ // this bounding rect in the foreground. Otherwise, include in the background rect.
+ // This allows us to determine if we found any primitives that are on top of the
+ // compositor surface(s) for this tile. If so, we need to draw the tile with alpha
+ // blending as an overlay.
+ if self.has_compositor_surface {
+ self.fg_local_valid_rect = self.fg_local_valid_rect.union(&info.prim_clip_rect);
+ } else {
+ self.bg_local_valid_rect = self.bg_local_valid_rect.union(&info.prim_clip_rect);
+ }
}
// Include any image keys this tile depends on.
@@ -1054,6 +1111,12 @@ impl Tile {
// Include any transforms that this primitive depends on.
self.current_descriptor.transforms.extend_from_slice(&info.spatial_nodes);
+ // Include any color bindings this primitive depends on.
+ if info.color_binding.is_some() {
+ self.current_descriptor.color_bindings.insert(
+ self.current_descriptor.color_bindings.len(), info.color_binding.unwrap());
+ }
+
// TODO(gw): The origin of background rects produced by APZ changes
// in Gecko during scrolling. Consider investigating this so the
// hack / workaround below is not required.
@@ -1106,6 +1169,7 @@ impl Tile {
clip_dep_count: info.clips.len() as u8,
image_dep_count: info.images.len() as u8,
opacity_binding_dep_count: info.opacity_bindings.len() as u8,
+ color_binding_dep_count: if info.color_binding.is_some() { 1 } else { 0 } as u8,
});
// Add this primitive to the dirty rect quadtree.
@@ -1127,6 +1191,11 @@ impl Tile {
return false;
}
+ // Calculate the overall valid rect for this tile, including both the foreground
+ // and background local valid rects.
+ self.current_descriptor.local_valid_rect =
+ self.bg_local_valid_rect.union(&self.fg_local_valid_rect);
+
// TODO(gw): In theory, the local tile rect should always have an
// intersection with the overall picture rect. In practice,
// due to some accuracy issues with how fract_offset (and
@@ -1181,7 +1250,50 @@ impl Tile {
let clipped_rect = self.current_descriptor.local_valid_rect
.intersection(&ctx.local_clip_rect)
.unwrap_or_else(PictureRect::zero);
- self.is_opaque = ctx.backdrop.rect.contains_rect(&clipped_rect);
+ let mut is_opaque = ctx.backdrop.rect.contains_rect(&clipped_rect);
+
+ if self.has_compositor_surface {
+ // If we found primitive(s) that are ordered _after_ the first compositor
+ // surface, _and_ intersect with any compositor surface, then we will need
+ // to draw this tile with alpha blending, as an overlay to the compositor surface.
+ let fg_world_valid_rect = ctx.pic_to_world_mapper
+ .map(&self.fg_local_valid_rect)
+ .expect("bug: map fg local valid rect");
+ let fg_device_valid_rect = fg_world_valid_rect * ctx.global_device_pixel_scale;
+
+ for surface in ctx.external_surfaces {
+ if surface.device_rect.intersects(&fg_device_valid_rect) {
+ is_opaque = false;
+ break;
+ }
+ }
+ }
+
+ // Set the correct z_id for this tile based on opacity
+ if is_opaque {
+ self.z_id = ctx.z_id_opaque;
+ } else {
+ self.z_id = ctx.z_id_alpha;
+ }
+
+ if is_opaque != self.is_opaque {
+ // If opacity changed, the native compositor surface and all tiles get invalidated.
+ // (this does nothing if not using native compositor mode).
+ // TODO(gw): This property probably changes very rarely, so it is OK to invalidate
+ // everything in this case. If it turns out that this isn't true, we could
+ // consider other options, such as per-tile opacity (natively supported
+ // on CoreAnimation, and supported if backed by non-virtual surfaces in
+ // DirectComposition).
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface {
+ if let Some(id) = id.take() {
+ state.resource_cache.destroy_compositor_tile(id);
+ }
+ }
+
+ // Invalidate the entire tile to force a redraw.
+ self.invalidate(None, InvalidationReason::SurfaceOpacityChanged { became_opaque: is_opaque });
+ self.is_opaque = is_opaque;
+ }
// Check if the selected composite mode supports dirty rect updates. For Draw composite
// mode, we can always update the content with smaller dirty rects. For native composite
@@ -1317,6 +1429,7 @@ pub struct PrimitiveDescriptor {
image_dep_count: u8,
opacity_binding_dep_count: u8,
clip_dep_count: u8,
+ color_binding_dep_count: u8,
}
impl PartialEq for PrimitiveDescriptor {
@@ -1467,6 +1580,10 @@ pub struct TileDescriptor {
/// Picture space rect that contains valid pixels region of this tile.
local_valid_rect: PictureRect,
+
+ /// List of the effects of color that we care about
+ /// tracking for this tile.
+ color_bindings: Vec,
}
impl TileDescriptor {
@@ -1478,6 +1595,7 @@ impl TileDescriptor {
images: Vec::new(),
transforms: Vec::new(),
local_valid_rect: PictureRect::zero(),
+ color_bindings: Vec::new(),
}
}
@@ -1495,11 +1613,12 @@ impl TileDescriptor {
prim.prim_clip_rect.w,
prim.prim_clip_rect.h,
));
- pt.add_item(format!("deps: t={} i={} o={} c={}",
+ pt.add_item(format!("deps: t={} i={} o={} c={} color={}",
prim.transform_dep_count,
prim.image_dep_count,
prim.opacity_binding_dep_count,
prim.clip_dep_count,
+ prim.color_binding_dep_count,
));
pt.end_level();
}
@@ -1542,6 +1661,15 @@ impl TileDescriptor {
pt.end_level();
}
+ if !self.color_bindings.is_empty() {
+ pt.new_level("color_bindings".to_string());
+ for color_binding in &self.color_bindings {
+ pt.new_level(format!("binding={:?}", color_binding));
+ pt.end_level();
+ }
+ pt.end_level();
+ }
+
pt.end_level();
}
@@ -1554,6 +1682,7 @@ impl TileDescriptor {
self.images.clear();
self.transforms.clear();
self.local_valid_rect = PictureRect::zero();
+ self.color_bindings.clear();
}
}
@@ -1818,17 +1947,10 @@ macro_rules! declare_tile_cache_logger_updatelists {
$(
{
for list in &self.$name.1 {
- let mut insert_count = 0;
- for update in &list.updates {
- match update.kind {
- UpdateKind::Insert => {
- itemuid_to_string.insert(
- update.uid,
- format!("{:?}", list.data[insert_count]));
- insert_count = insert_count + 1;
- },
- _ => {}
- }
+ for insertion in &list.insertions {
+ itemuid_to_string.insert(
+ insertion.uid,
+ format!("{:?}", insertion.value));
}
}
}
@@ -1990,6 +2112,46 @@ impl TileCacheLogger {
}
}
+/// Represents the native surfaces created for a picture cache, if using
+/// a native compositor. An opaque and alpha surface is always created,
+/// but tiles are added to a surface based on current opacity. If the
+/// calculated opacity of a tile changes, the tile is invalidated and
+/// attached to a different native surface. This means that we don't
+/// need to invalidate the entire surface if only some tiles are changing
+/// opacity. It also means we can take advantage of opaque tiles on cache
+/// slices where only some of the tiles are opaque. There is an assumption
+/// that creating a native surface is cheap, and only when a tile is added
+/// to a surface is there a significant cost. This assumption holds true
+/// for the current native compositor implementations on Windows and Mac.
+pub struct NativeSurface {
+ /// Native surface for opaque tiles
+ pub opaque: NativeSurfaceId,
+ /// Native surface for alpha tiles
+ pub alpha: NativeSurfaceId,
+}
+
+/// Hash key for an external native compositor surface
+#[derive(PartialEq, Eq, Hash)]
+pub struct ExternalNativeSurfaceKey {
+ /// The YUV image keys that are used to draw this surface.
+ pub image_keys: [ImageKey; 3],
+ /// The current device size of the surface.
+ pub size: DeviceIntSize,
+}
+
+/// Information about a native compositor surface cached between frames.
+pub struct ExternalNativeSurface {
+ /// If true, the surface was used this frame. Used for a simple form
+ /// of GC to remove old surfaces.
+ pub used_this_frame: bool,
+ /// The native compositor surface handle
+ pub native_surface_id: NativeSurfaceId,
+ /// List of image keys, and current image generations, that are drawn in this surface.
+ /// The image generations are used to check if the compositor surface is dirty and
+ /// needs to be updated.
+ pub image_dependencies: [ImageDependency; 3],
+}
+
/// Represents a cache of tiles that make up a picture primitives.
pub struct TileCacheInstance {
/// Index of the tile cache / slice for this frame builder. It's determined
@@ -2026,6 +2188,11 @@ pub struct TileCacheInstance {
/// calculate invalid relative transforms when building the spatial
/// nodes hash above.
used_spatial_nodes: FastHashSet,
+ /// List of color bindings, with some extra information
+ /// about whether they changed since last frame.
+ color_bindings: FastHashMap,
+ /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
+ old_color_bindings: FastHashMap,
/// The current dirty region tracker for this picture.
pub dirty_region: DirtyRegion,
/// Current size of tiles in picture units.
@@ -2070,21 +2237,23 @@ pub struct TileCacheInstance {
/// keep around the hash map used as compare_cache to avoid reallocating it each
/// frame.
compare_cache: FastHashMap,
- /// The allocated compositor surface for this picture cache. May be None if
+ /// The allocated compositor surfaces for this picture cache. May be None if
/// not using native compositor, or if the surface was destroyed and needs
/// to be reallocated next time this surface contains valid tiles.
- pub native_surface_id: Option,
+ pub native_surface: Option,
/// The current device position of this cache. Used to set the compositor
/// offset of the surface when building the visual tree.
pub device_position: DevicePoint,
- /// True if the entire picture cache surface is opaque.
- is_opaque: bool,
/// The currently considered tile size override. Used to check if we should
/// re-evaluate tile size, even if the frame timer hasn't expired.
tile_size_override: Option,
/// List of external surfaces that have been promoted from primitives
/// in this tile cache.
pub external_surfaces: Vec,
+ /// z-buffer ID assigned to opaque tiles in this slice
+ pub z_id_opaque: ZBufferId,
+ /// A cache of compositor surfaces that are retained between frames
+ pub external_native_surface_cache: FastHashMap,
}
impl TileCacheInstance {
@@ -2114,6 +2283,8 @@ impl TileCacheInstance {
spatial_nodes: FastHashMap::default(),
old_spatial_nodes: FastHashMap::default(),
used_spatial_nodes: FastHashSet::default(),
+ color_bindings: FastHashMap::default(),
+ old_color_bindings: FastHashMap::default(),
dirty_region: DirtyRegion::new(),
tile_size: PictureSize::zero(),
tile_rect: TileRect::zero(),
@@ -2132,11 +2303,12 @@ impl TileCacheInstance {
frames_until_size_eval: 0,
fract_offset: PictureVector2D::zero(),
compare_cache: FastHashMap::default(),
- native_surface_id: None,
+ native_surface: None,
device_position: DevicePoint::zero(),
- is_opaque: true,
tile_size_override: None,
external_surfaces: Vec::new(),
+ z_id_opaque: ZBufferId::invalid(),
+ external_native_surface_cache: FastHashMap::default(),
}
}
@@ -2175,7 +2347,7 @@ impl TileCacheInstance {
(p0, p1)
}
- /// Update transforms, opacity bindings and tile rects.
+ /// Update transforms, opacity, color bindings and tile rects.
pub fn pre_update(
&mut self,
pic_rect: PictureRect,
@@ -2188,6 +2360,11 @@ impl TileCacheInstance {
self.local_rect = pic_rect;
self.local_clip_rect = PictureRect::max_rect();
+ // Opaque surfaces get the first z_id. Compositor surfaces then get
+ // allocated a z_id each. After all compositor surfaces are added,
+ // then we allocate a z_id for alpha tiles.
+ self.z_id_opaque = frame_state.composite_state.z_generator.next();
+
// Reset the opaque rect + subpixel mode, as they are calculated
// during the prim dependency checks.
self.backdrop = BackdropInfo::empty();
@@ -2257,9 +2434,10 @@ impl TileCacheInstance {
self.root_transform = prev_state.root_transform;
self.spatial_nodes = prev_state.spatial_nodes;
self.opacity_bindings = prev_state.opacity_bindings;
+ self.color_bindings = prev_state.color_bindings;
self.current_tile_size = prev_state.current_tile_size;
- self.native_surface_id = prev_state.native_surface_id;
- self.is_opaque = prev_state.is_opaque;
+ self.native_surface = prev_state.native_surface;
+ self.external_native_surface_cache = prev_state.external_native_surface_cache;
fn recycle_map(
ideal_len: usize,
@@ -2280,6 +2458,11 @@ impl TileCacheInstance {
&mut self.old_opacity_bindings,
prev_state.allocations.old_opacity_bindings,
);
+ recycle_map(
+ self.color_bindings.len(),
+ &mut self.old_color_bindings,
+ prev_state.allocations.old_color_bindings,
+ );
recycle_map(
prev_state.allocations.compare_cache.len(),
&mut self.compare_cache,
@@ -2287,6 +2470,15 @@ impl TileCacheInstance {
);
}
+ // At the start of the frame, step through each current compositor surface
+ // and mark it as unused. Later, this is used to free old compositor surfaces.
+ // TODO(gw): In future, we might make this more sophisticated - for example,
+ // retaining them for >1 frame if unused, or retaining them in some
+ // kind of pool to reduce future allocations.
+ for external_native_surface in self.external_native_surface_cache.values_mut() {
+ external_native_surface.used_this_frame = false;
+ }
+
// Only evaluate what tile size to use fairly infrequently, so that we don't end
// up constantly invalidating and reallocating tiles if the picture rect size is
// changing near a threshold value.
@@ -2316,8 +2508,9 @@ impl TileCacheInstance {
if desired_tile_size != self.current_tile_size {
// Destroy any native surfaces on the tiles that will be dropped due
// to resizing.
- if let Some(native_surface_id) = self.native_surface_id.take() {
- frame_state.resource_cache.destroy_compositor_surface(native_surface_id);
+ if let Some(native_surface) = self.native_surface.take() {
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
}
self.tiles.clear();
self.current_tile_size = desired_tile_size;
@@ -2380,6 +2573,23 @@ impl TileCacheInstance {
});
}
+ // Do a hacky diff of color binding values from the last frame. This is
+ // used later on during tile invalidation tests.
+ let current_properties = frame_context.scene_properties.color_properties();
+ mem::swap(&mut self.color_bindings, &mut self.old_color_bindings);
+
+ self.color_bindings.clear();
+ for (id, value) in current_properties {
+ let changed = match self.old_color_bindings.get(id) {
+ Some(old_property) => old_property.value != (*value).into(),
+ None => true,
+ };
+ self.color_bindings.insert(*id, ColorBindingInfo {
+ value: (*value).into(),
+ changed,
+ });
+ }
+
let world_tile_size = WorldSize::new(
self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0,
self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0,
@@ -2494,22 +2704,29 @@ impl TileCacheInstance {
}
// If compositor mode is changed, need to drop all incompatible tiles.
- match (frame_context.config.compositor_kind, self.native_surface_id) {
- (CompositorKind::Draw { .. }, Some(_)) => {
- frame_state.composite_state.destroy_native_tiles(
- self.tiles.values_mut(),
- frame_state.resource_cache,
- );
+ match frame_context.config.compositor_kind {
+ CompositorKind::Draw { .. } => {
for tile in self.tiles.values_mut() {
- tile.surface = None;
- // Invalidate the entire tile to force a redraw.
- tile.invalidate(None, InvalidationReason::CompositorKindChanged);
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
+ if let Some(id) = id.take() {
+ frame_state.resource_cache.destroy_compositor_tile(id);
+ tile.surface = None;
+ // Invalidate the entire tile to force a redraw.
+ tile.invalidate(None, InvalidationReason::CompositorKindChanged);
+ }
+ }
}
- if let Some(native_surface_id) = self.native_surface_id.take() {
- frame_state.resource_cache.destroy_compositor_surface(native_surface_id);
+
+ if let Some(native_surface) = self.native_surface.take() {
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
+ }
+
+ for (_, external_surface) in self.external_native_surface_cache.drain() {
+ frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
}
}
- (CompositorKind::Native { .. }, None) => {
+ CompositorKind::Native { .. } => {
// This could hit even when compositor mode is not changed,
// then we need to check if there are incompatible tiles.
for tile in self.tiles.values_mut() {
@@ -2520,7 +2737,6 @@ impl TileCacheInstance {
}
}
}
- (_, _) => {}
}
world_culling_rect
@@ -2537,11 +2753,12 @@ impl TileCacheInstance {
data_stores: &DataStores,
clip_store: &ClipStore,
pictures: &[PicturePrimitive],
- resource_cache: &ResourceCache,
+ resource_cache: &mut ResourceCache,
opacity_binding_store: &OpacityBindingStorage,
+ color_bindings: &ColorBindingStorage,
image_instances: &ImageInstanceStorage,
surface_stack: &[SurfaceIndex],
- composite_state: &CompositeState,
+ composite_state: &mut CompositeState,
) -> bool {
// This primitive exists on the last element on the current surface stack.
let prim_surface_index = *surface_stack.last().unwrap();
@@ -2675,13 +2892,15 @@ impl TileCacheInstance {
prim_info.opacity_bindings.push(binding.into());
}
}
- PrimitiveInstanceKind::Rectangle { data_handle, opacity_binding_index, .. } => {
+ PrimitiveInstanceKind::Rectangle { data_handle, opacity_binding_index, color_binding_index, .. } => {
if opacity_binding_index == OpacityBindingIndex::INVALID {
// Rectangles can only form a backdrop candidate if they are known opaque.
// TODO(gw): We could resolve the opacity binding here, but the common
// case for background rects is that they don't have animated opacity.
let color = match data_stores.prim[data_handle].kind {
- PrimitiveTemplateKind::Rectangle { color, .. } => color,
+ PrimitiveTemplateKind::Rectangle { color, .. } => {
+ frame_context.scene_properties.resolve_color(&color)
+ }
_ => unreachable!(),
};
if color.a >= 1.0 {
@@ -2694,6 +2913,10 @@ impl TileCacheInstance {
}
}
+ if color_binding_index != ColorBindingIndex::INVALID {
+ prim_info.color_binding = Some(color_bindings[color_binding_index].into());
+ }
+
prim_info.clip_by_tile = true;
}
PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
@@ -2727,10 +2950,9 @@ impl TileCacheInstance {
// extract the logic below and support RGBA compositor surfaces too.
let mut promote_to_surface = false;
- // For initial implementation, only the simple (draw) compositor mode
- // supports primitives as compositor surfaces. We can remove this restriction
- // as a follow up, when we support this for native compositor modes.
- if let CompositorKind::Draw { .. } = composite_state.compositor_kind {
+
+ // If picture caching is disabled, we can't support any compositor surfaces.
+ if composite_state.picture_caching_is_enabled {
// Check if this primitive _wants_ to be promoted to a compositor surface.
if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
promote_to_surface = true;
@@ -2809,15 +3031,88 @@ impl TileCacheInstance {
let device_rect = (world_rect * frame_context.global_device_pixel_scale).round();
let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
+ // Build dependency for each YUV plane, with current image generation for
+ // later detection of when the composited surface has changed.
+ let mut image_dependencies = [ImageDependency::INVALID; 3];
+ for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
+ *dep = ImageDependency {
+ key,
+ generation: resource_cache.get_image_generation(key),
+ }
+ }
+
+ // When using native compositing, we need to find an existing native surface
+ // handle to use, or allocate a new one. For existing native surfaces, we can
+ // also determine whether this needs to be updated, depending on whether the
+ // image generation(s) of the YUV planes have changed since last composite.
+ let (native_surface_id, update_params) = match composite_state.compositor_kind {
+ CompositorKind::Draw { .. } => {
+ (None, None)
+ }
+ CompositorKind::Native { .. } => {
+ let native_surface_size = device_rect.size.round().to_i32();
+
+ let key = ExternalNativeSurfaceKey {
+ image_keys: prim_data.kind.yuv_key,
+ size: native_surface_size,
+ };
+
+ let native_surface = self.external_native_surface_cache
+ .entry(key)
+ .or_insert_with(|| {
+ // No existing surface, so allocate a new compositor surface and
+ // a single compositor tile that covers the entire compositor surface.
+
+ let native_surface_id = resource_cache.create_compositor_surface(
+ native_surface_size,
+ true,
+ );
+
+ let tile_id = NativeTileId {
+ surface_id: native_surface_id,
+ x: 0,
+ y: 0,
+ };
+
+ resource_cache.create_compositor_tile(tile_id);
+
+ ExternalNativeSurface {
+ used_this_frame: true,
+ native_surface_id,
+ image_dependencies: [ImageDependency::INVALID; 3],
+ }
+ });
+
+ // Mark that the surface is referenced this frame so that the
+ // backing native surface handle isn't freed.
+ native_surface.used_this_frame = true;
+
+ // If the image dependencies match, there is no need to update
+ // the backing native surface.
+ let update_params = if image_dependencies == native_surface.image_dependencies {
+ None
+ } else {
+ Some(native_surface_size)
+ };
+
+ (Some(native_surface.native_surface_id), update_params)
+ }
+ };
+
+ // Each compositor surface allocates a unique z-id
self.external_surfaces.push(ExternalSurfaceDescriptor {
local_rect: prim_info.prim_clip_rect,
- image_keys: prim_data.kind.yuv_key,
+ world_rect,
+ image_dependencies,
image_rendering: prim_data.kind.image_rendering,
device_rect,
clip_rect,
yuv_color_space: prim_data.kind.color_space,
yuv_format: prim_data.kind.format,
yuv_rescale: prim_data.kind.color_depth.rescaling_factor(),
+ z_id: composite_state.z_generator.next(),
+ native_surface_id,
+ update_params,
});
}
} else {
@@ -3024,13 +3319,36 @@ impl TileCacheInstance {
.map(&backdrop_rect)
.expect("bug: unable to map backdrop to world space");
+ // Since we register the entire backdrop rect, use the opaque z-id for the
+ // picture cache slice.
frame_state.composite_state.register_occluder(
- self.slice,
+ self.z_id_opaque,
world_backdrop_rect,
);
}
}
+ // Register any external compositor surfaces as potential occluders. This
+ // is especially useful when viewing video in full-screen mode, as it is
+ // able to occlude every background tile (avoiding allocation, rasterizion
+ // and compositing).
+ for external_surface in &self.external_surfaces {
+ frame_state.composite_state.register_occluder(
+ external_surface.z_id,
+ external_surface.world_rect,
+ );
+ }
+
+ // A simple GC of the native external surface cache, to remove and free any
+ // surfaces that were not referenced during the update_prim_dependencies pass.
+ self.external_native_surface_cache.retain(|_, surface| {
+ if !surface.used_this_frame {
+ frame_state.resource_cache.destroy_compositor_surface(surface.native_surface_id);
+ }
+
+ surface.used_this_frame
+ });
+
// Detect if the picture cache was scrolled or scaled. In this case,
// the device space dirty rects aren't applicable (until we properly
// integrate with OS compositors that can handle scrolling slices).
@@ -3088,6 +3406,9 @@ impl TileCacheInstance {
frame_context.spatial_tree,
);
+ // All compositor surfaces have allocated a z_id, so reserve a z_id for alpha tiles.
+ let z_id_alpha = frame_state.composite_state.z_generator.next();
+
let ctx = TilePostUpdateContext {
pic_to_world_mapper,
global_device_pixel_scale: frame_context.global_device_pixel_scale,
@@ -3095,8 +3416,12 @@ impl TileCacheInstance {
backdrop: self.backdrop,
spatial_nodes: &self.spatial_nodes,
opacity_bindings: &self.opacity_bindings,
+ color_bindings: &self.color_bindings,
current_tile_size: self.current_tile_size,
local_rect: self.local_rect,
+ external_surfaces: &self.external_surfaces,
+ z_id_opaque: self.z_id_opaque,
+ z_id_alpha,
};
let mut state = TilePostUpdateState {
@@ -3107,38 +3432,8 @@ impl TileCacheInstance {
// Step through each tile and invalidate if the dependencies have changed. Determine
// the current opacity setting and whether it's changed.
- let mut tile_cache_is_opaque = true;
for tile in self.tiles.values_mut() {
- if tile.post_update(&ctx, &mut state, frame_context) {
- tile_cache_is_opaque &= tile.is_opaque;
- }
- }
-
- // If opacity changed, the native compositor surface and all tiles get invalidated.
- // (this does nothing if not using native compositor mode).
- // TODO(gw): This property probably changes very rarely, so it is OK to invalidate
- // everything in this case. If it turns out that this isn't true, we could
- // consider other options, such as per-tile opacity (natively supported
- // on CoreAnimation, and supported if backed by non-virtual surfaces in
- // DirectComposition).
- if self.is_opaque != tile_cache_is_opaque {
- if let Some(native_surface_id) = self.native_surface_id.take() {
- // Since the native surface will be destroyed, need to clear the compositor tile
- // handle for all tiles. This means the tiles will be reallocated on demand
- // when the tiles are added to render tasks.
- for tile in self.tiles.values_mut() {
- if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
- *id = None;
- }
- // Invalidate the entire tile to force a redraw.
- tile.invalidate(None, InvalidationReason::SurfaceOpacityChanged {
- became_opaque: tile_cache_is_opaque });
- }
- // Destroy the compositor surface. It will be reallocated with the correct
- // opacity flag when render tasks are generated for tiles.
- frame_state.resource_cache.destroy_compositor_surface(native_surface_id);
- }
- self.is_opaque = tile_cache_is_opaque;
+ tile.post_update(&ctx, &mut state, frame_context);
}
// When under test, record a copy of the dirty region to support
@@ -3418,6 +3713,13 @@ pub struct RasterConfig {
pub surface_index: SurfaceIndex,
/// Whether this picture establishes a rasterization root.
pub establishes_raster_root: bool,
+ /// Scaling factor applied to fit within MAX_SURFACE_SIZE when
+ /// establishing a raster root.
+ /// Most code doesn't need to know about it, since it is folded
+ /// into device_pixel_scale when the rendertask is set up.
+ /// However e.g. text rasterization uses it to ensure consistent
+ /// on-screen font size.
+ pub root_scaling_factor: f32,
}
bitflags! {
@@ -3468,7 +3770,7 @@ impl PictureCompositeMode {
Filter::DropShadows(shadows) => {
let mut max_inflation: f32 = 0.0;
for shadow in shadows {
- let inflation_factor = shadow.blur_radius.round() * BLUR_SAMPLE_SCALE;
+ let inflation_factor = shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE;
max_inflation = max_inflation.max(inflation_factor);
}
result_rect = picture_rect.inflate(max_inflation, max_inflation);
@@ -3485,7 +3787,7 @@ impl PictureCompositeMode {
input.inflate(inflation_factor, inflation_factor)
}
FilterPrimitiveKind::DropShadow(ref primitive) => {
- let inflation_factor = primitive.shadow.blur_radius.round() * BLUR_SAMPLE_SCALE;
+ let inflation_factor = primitive.shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE;
let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
let shadow_rect = input.inflate(inflation_factor, inflation_factor);
input.union(&shadow_rect.translate(primitive.shadow.offset * Scale::new(1.0)))
@@ -3970,7 +4272,7 @@ impl PicturePrimitive {
&mut self,
retained_tiles: &mut RetainedTiles,
) {
- if let Some(mut tile_cache) = self.tile_cache.take() {
+ if let Some(tile_cache) = self.tile_cache.take() {
if !tile_cache.tiles.is_empty() {
retained_tiles.caches.insert(
tile_cache.slice,
@@ -3978,12 +4280,14 @@ impl PicturePrimitive {
tiles: tile_cache.tiles,
spatial_nodes: tile_cache.spatial_nodes,
opacity_bindings: tile_cache.opacity_bindings,
+ color_bindings: tile_cache.color_bindings,
root_transform: tile_cache.root_transform,
current_tile_size: tile_cache.current_tile_size,
- native_surface_id: tile_cache.native_surface_id.take(),
- is_opaque: tile_cache.is_opaque,
+ native_surface: tile_cache.native_surface,
+ external_native_surface_cache: tile_cache.external_native_surface_cache,
allocations: PictureCacheRecycledAllocations {
old_opacity_bindings: tile_cache.old_opacity_bindings,
+ old_color_bindings: tile_cache.old_color_bindings,
compare_cache: tile_cache.compare_cache,
},
},
@@ -4131,14 +4435,14 @@ impl PicturePrimitive {
};
match self.raster_config {
- Some(ref raster_config) => {
+ Some(ref mut raster_config) => {
let pic_rect = PictureRect::from_untyped(&self.precise_local_rect.to_untyped());
- let device_pixel_scale = frame_state
+ let mut device_pixel_scale = frame_state
.surfaces[raster_config.surface_index.0]
.device_pixel_scale;
- let (clipped, unclipped) = match get_raster_rects(
+ let (mut clipped, mut unclipped) = match get_raster_rects(
pic_rect,
&map_pic_to_raster,
&map_raster_to_world,
@@ -4152,11 +4456,68 @@ impl PicturePrimitive {
};
let transform = map_pic_to_raster.get_transform();
+ /// If the picture (raster_config) establishes a raster root,
+ /// its requested resolution won't be clipped by the parent or
+ /// viewport; so we need to make sure the requested resolution is
+ /// "reasonable", ie. <= MAX_SURFACE_SIZE. If not, scale the
+ /// picture down until it fits that limit. This results in a new
+ /// device_rect, a new unclipped rect, and a new device_pixel_scale.
+ ///
+ /// Since the adjusted device_pixel_scale is passed into the
+ /// RenderTask (and then the shader via RenderTaskData) this mostly
+ /// works transparently, reusing existing support for variable DPI
+ /// support. The on-the-fly scaling can be seen as on-the-fly,
+ /// per-task DPI adjustment. Logical pixels are unaffected.
+ ///
+ /// The scaling factor is returned to the caller; blur radius,
+ /// font size, etc. need to be scaled accordingly.
+ fn adjust_scale_for_max_surface_size(
+ raster_config: &RasterConfig,
+ pic_rect: PictureRect,
+ map_pic_to_raster: &SpaceMapper,
+ map_raster_to_world: &SpaceMapper,
+ clipped_prim_bounding_rect: WorldRect,
+ device_pixel_scale : &mut DevicePixelScale,
+ device_rect: &mut DeviceIntRect,
+ unclipped: &mut DeviceRect) -> Option
+ {
+ if raster_config.establishes_raster_root &&
+ (device_rect.size.width > (MAX_SURFACE_SIZE as i32) ||
+ device_rect.size.height > (MAX_SURFACE_SIZE as i32))
+ {
+ // round_out will grow by 1 integer pixel if origin is on a
+ // fractional position, so keep that margin for error with -1:
+ let scale = (MAX_SURFACE_SIZE as f32 - 1.0) /
+ (i32::max(device_rect.size.width, device_rect.size.height) as f32);
+ *device_pixel_scale = *device_pixel_scale * Scale::new(scale);
+ let new_device_rect = device_rect.to_f32() * Scale::new(scale);
+ *device_rect = new_device_rect.round_out().try_cast::().unwrap();
+
+ *unclipped = match get_raster_rects(
+ pic_rect,
+ &map_pic_to_raster,
+ &map_raster_to_world,
+ clipped_prim_bounding_rect,
+ *device_pixel_scale
+ ) {
+ Some(info) => info.1,
+ None => {
+ return None
+ }
+ };
+ Some(scale)
+ }
+ else
+ {
+ None
+ }
+ }
+
let dep_info = match raster_config.composite_mode {
PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => {
let blur_std_deviation = blur_radius * device_pixel_scale.0;
let scale_factors = scale_factors(&transform);
- let blur_std_deviation = DeviceSize::new(
+ let mut blur_std_deviation = DeviceSize::new(
blur_std_deviation * scale_factors.0,
blur_std_deviation * scale_factors.1
);
@@ -4189,7 +4550,7 @@ impl PicturePrimitive {
clipped
};
- let original_size = device_rect.size;
+ let mut original_size = device_rect.size;
// Adjust the size to avoid introducing sampling errors during the down-scaling passes.
// what would be even better is to rasterize the picture at the down-scaled size
@@ -4199,6 +4560,16 @@ impl PicturePrimitive {
blur_std_deviation,
);
+ if let Some(scale) = adjust_scale_for_max_surface_size(
+ raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world,
+ clipped_prim_bounding_rect,
+ &mut device_pixel_scale, &mut device_rect, &mut unclipped)
+ {
+ blur_std_deviation = blur_std_deviation * scale;
+ original_size = (original_size.to_f32() * scale).try_cast::().unwrap();
+ raster_config.root_scaling_factor = scale;
+ }
+
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
@@ -4241,7 +4612,6 @@ impl PicturePrimitive {
max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius * device_pixel_scale.0);
}
- max_std_deviation = max_std_deviation.round();
let max_blur_range = (max_std_deviation * BLUR_SAMPLE_SCALE).ceil();
// We cast clipped to f32 instead of casting unclipped to i32
// because unclipped can overflow an i32.
@@ -4262,6 +4632,15 @@ impl PicturePrimitive {
DeviceSize::new(max_std_deviation, max_std_deviation),
);
+ if let Some(scale) = adjust_scale_for_max_surface_size(
+ raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world,
+ clipped_prim_bounding_rect,
+ &mut device_pixel_scale, &mut device_rect, &mut unclipped)
+ {
+ // std_dev adjusts automatically from using device_pixel_scale
+ raster_config.root_scaling_factor = scale;
+ }
+
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
@@ -4294,10 +4673,15 @@ impl PicturePrimitive {
self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
let mut blur_render_task_id = picture_task_id;
+ let scale_factors = scale_factors(&transform);
for shadow in shadows {
- let std_dev = f32::round(shadow.blur_radius * device_pixel_scale.0);
+ // TODO(cbrewster): We should take the scale factors into account when clamping the max
+ // std deviation for the blur earlier so that we don't overinflate.
blur_render_task_id = RenderTask::new_blur(
- DeviceSize::new(std_dev, std_dev),
+ DeviceSize::new(
+ f32::min(shadow.blur_radius * scale_factors.0, MAX_BLUR_RADIUS) * device_pixel_scale.0,
+ f32::min(shadow.blur_radius * scale_factors.1, MAX_BLUR_RADIUS) * device_pixel_scale.0,
+ ),
picture_task_id,
frame_state.render_tasks,
RenderTargetKind::Color,
@@ -4347,6 +4731,15 @@ impl PicturePrimitive {
Some((render_task_id, render_task_id))
}
PictureCompositeMode::Filter(..) => {
+
+ if let Some(scale) = adjust_scale_for_max_surface_size(
+ raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world,
+ clipped_prim_bounding_rect,
+ &mut device_pixel_scale, &mut clipped, &mut unclipped)
+ {
+ raster_config.root_scaling_factor = scale;
+ }
+
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
@@ -4372,6 +4765,14 @@ impl PicturePrimitive {
Some((render_task_id, render_task_id))
}
PictureCompositeMode::ComponentTransferFilter(..) => {
+ if let Some(scale) = adjust_scale_for_max_surface_size(
+ raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world,
+ clipped_prim_bounding_rect,
+ &mut device_pixel_scale, &mut clipped, &mut unclipped)
+ {
+ raster_config.root_scaling_factor = scale;
+ }
+
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
@@ -4425,7 +4826,7 @@ impl PicturePrimitive {
// then mark it as not visible and skip drawing. When it's not occluded
// it will fail this test, and get rasterized by the render task setup
// code below.
- if frame_state.composite_state.is_tile_occluded(tile_cache.slice, device_draw_rect) {
+ if frame_state.composite_state.is_tile_occluded(tile.z_id, device_draw_rect) {
// If this tile has an allocated native surface, free it, since it's completely
// occluded. We will need to re-allocate this surface if it becomes visible,
// but that's likely to be rare (e.g. when there is no content display list
@@ -4532,20 +4933,36 @@ impl PicturePrimitive {
// Allocate a native surface id if we're in native compositing mode,
// and we don't have a surface yet (due to first frame, or destruction
// due to tile size changing etc).
- if tile_cache.native_surface_id.is_none() {
- let surface_id = frame_state
+ if tile_cache.native_surface.is_none() {
+ let opaque = frame_state
.resource_cache
.create_compositor_surface(
tile_cache.current_tile_size,
- tile_cache.is_opaque,
+ true,
);
- tile_cache.native_surface_id = Some(surface_id);
+ let alpha = frame_state
+ .resource_cache
+ .create_compositor_surface(
+ tile_cache.current_tile_size,
+ false,
+ );
+
+ tile_cache.native_surface = Some(NativeSurface {
+ opaque,
+ alpha,
+ });
}
// Create the tile identifier and allocate it.
+ let surface_id = if tile.is_opaque {
+ tile_cache.native_surface.as_ref().unwrap().opaque
+ } else {
+ tile_cache.native_surface.as_ref().unwrap().alpha
+ };
+
let tile_id = NativeTileId {
- surface_id: tile_cache.native_surface_id.unwrap(),
+ surface_id,
x: tile.tile_offset.x,
y: tile.tile_offset.y,
};
@@ -4651,6 +5068,14 @@ impl PicturePrimitive {
}
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::Blit(_) => {
+ if let Some(scale) = adjust_scale_for_max_surface_size(
+ raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world,
+ clipped_prim_bounding_rect,
+ &mut device_pixel_scale, &mut clipped, &mut unclipped)
+ {
+ raster_config.root_scaling_factor = scale;
+ }
+
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
@@ -4676,6 +5101,15 @@ impl PicturePrimitive {
Some((render_task_id, render_task_id))
}
PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
+
+ if let Some(scale) = adjust_scale_for_max_surface_size(
+ raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world,
+ clipped_prim_bounding_rect,
+ &mut device_pixel_scale, &mut clipped, &mut unclipped)
+ {
+ raster_config.root_scaling_factor = scale;
+ }
+
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
@@ -5120,6 +5554,7 @@ impl PicturePrimitive {
composite_mode,
establishes_raster_root,
surface_index: state.push_surface(surface),
+ root_scaling_factor: 1.0,
});
}
@@ -5272,7 +5707,9 @@ impl PicturePrimitive {
// Check if any of the surfaces can't be rasterized in local space but want to.
if raster_config.establishes_raster_root
&& (surface_rect.size.width > MAX_SURFACE_SIZE
- || surface_rect.size.height > MAX_SURFACE_SIZE) {
+ || surface_rect.size.height > MAX_SURFACE_SIZE)
+ && frame_context.debug_flags.contains(DebugFlags::DISABLE_RASTER_ROOT_SCALING)
+ {
raster_config.establishes_raster_root = false;
state.are_raster_roots_assigned = false;
}
@@ -5528,8 +5965,15 @@ struct PrimitiveComparisonKey {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ImageDependency {
- key: ImageKey,
- generation: ImageGeneration,
+ pub key: ImageKey,
+ pub generation: ImageGeneration,
+}
+
+impl ImageDependency {
+ pub const INVALID: ImageDependency = ImageDependency {
+ key: ImageKey::DUMMY,
+ generation: ImageGeneration::INVALID,
+ };
}
/// A helper struct to compare a primitive and all its sub-dependencies.
@@ -5538,9 +5982,11 @@ struct PrimitiveComparer<'a> {
transform_comparer: CompareHelper<'a, SpatialNodeIndex>,
image_comparer: CompareHelper<'a, ImageDependency>,
opacity_comparer: CompareHelper<'a, OpacityBinding>,
+ color_comparer: CompareHelper<'a, ColorBinding>,
resource_cache: &'a ResourceCache,
spatial_nodes: &'a FastHashMap,
opacity_bindings: &'a FastHashMap,
+ color_bindings: &'a FastHashMap,
}
impl<'a> PrimitiveComparer<'a> {
@@ -5550,6 +5996,7 @@ impl<'a> PrimitiveComparer<'a> {
resource_cache: &'a ResourceCache,
spatial_nodes: &'a FastHashMap,
opacity_bindings: &'a FastHashMap,
+ color_bindings: &'a FastHashMap,
) -> Self {
let clip_comparer = CompareHelper::new(
&prev.clips,
@@ -5571,14 +6018,21 @@ impl<'a> PrimitiveComparer<'a> {
&curr.opacity_bindings,
);
+ let color_comparer = CompareHelper::new(
+ &prev.color_bindings,
+ &curr.color_bindings,
+ );
+
PrimitiveComparer {
clip_comparer,
transform_comparer,
image_comparer,
opacity_comparer,
+ color_comparer,
resource_cache,
spatial_nodes,
opacity_bindings,
+ color_bindings,
}
}
@@ -5587,6 +6041,7 @@ impl<'a> PrimitiveComparer<'a> {
self.transform_comparer.reset();
self.image_comparer.reset();
self.opacity_comparer.reset();
+ self.color_comparer.reset();
}
fn advance_prev(&mut self, prim: &PrimitiveDescriptor) {
@@ -5594,6 +6049,7 @@ impl<'a> PrimitiveComparer<'a> {
self.transform_comparer.advance_prev(prim.transform_dep_count);
self.image_comparer.advance_prev(prim.image_dep_count);
self.opacity_comparer.advance_prev(prim.opacity_binding_dep_count);
+ self.color_comparer.advance_prev(prim.color_binding_dep_count);
}
fn advance_curr(&mut self, prim: &PrimitiveDescriptor) {
@@ -5601,6 +6057,7 @@ impl<'a> PrimitiveComparer<'a> {
self.transform_comparer.advance_curr(prim.transform_dep_count);
self.image_comparer.advance_curr(prim.image_dep_count);
self.opacity_comparer.advance_curr(prim.opacity_binding_dep_count);
+ self.color_comparer.advance_curr(prim.color_binding_dep_count);
}
/// Check if two primitive descriptors are the same.
@@ -5613,6 +6070,7 @@ impl<'a> PrimitiveComparer<'a> {
let resource_cache = self.resource_cache;
let spatial_nodes = self.spatial_nodes;
let opacity_bindings = self.opacity_bindings;
+ let color_bindings = self.color_bindings;
// Check equality of the PrimitiveDescriptor
if prev != curr {
@@ -5692,6 +6150,30 @@ impl<'a> PrimitiveComparer<'a> {
return PrimitiveCompareResult::OpacityBinding;
}
+ // Check if any of the color bindings this prim has are different.
+ let mut bind_result = CompareHelperResult::Equal;
+ if !self.color_comparer.is_same(
+ prev.color_binding_dep_count,
+ curr.color_binding_dep_count,
+ |curr| {
+ if let ColorBinding::Binding(id) = curr {
+ if color_bindings
+ .get(id)
+ .map_or(true, |info| info.changed) {
+ return true;
+ }
+ }
+
+ true
+ },
+ if opt_detail.is_some() { Some(&mut bind_result) } else { None },
+ ) {
+ if let Some(detail) = opt_detail {
+ *detail = PrimitiveCompareResultDetail::ColorBinding{ detail: bind_result };
+ }
+ return PrimitiveCompareResult::ColorBinding;
+ }
+
PrimitiveCompareResult::Equal
}
}
diff --git a/webrender/src/platform/macos/font.rs b/webrender/src/platform/macos/font.rs
index 73c051db39..e3f0c342bf 100644
--- a/webrender/src/platform/macos/font.rs
+++ b/webrender/src/platform/macos/font.rs
@@ -385,17 +385,21 @@ impl FontContext {
if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
shape = shape.swap_xy();
}
+ let (mut tx, mut ty) = (0.0, 0.0);
if font.synthetic_italics.is_enabled() {
- shape = shape.synthesize_italics(font.synthetic_italics);
+ let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size.to_f64_px());
+ shape = shape_;
+ tx = tx_;
+ ty = ty_;
}
- let transform = if !shape.is_identity() {
+ let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) {
Some(CGAffineTransform {
a: shape.scale_x as f64,
b: -shape.skew_y as f64,
c: -shape.skew_x as f64,
d: shape.scale_y as f64,
- tx: 0.0,
- ty: 0.0,
+ tx: tx,
+ ty: -ty,
})
} else {
None
@@ -521,17 +525,21 @@ impl FontContext {
if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
shape = shape.swap_xy();
}
+ let (mut tx, mut ty) = (0.0, 0.0);
if font.synthetic_italics.is_enabled() {
- shape = shape.synthesize_italics(font.synthetic_italics);
+ let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size.to_f64_px());
+ shape = shape_;
+ tx = tx_;
+ ty = ty_;
}
- let transform = if !shape.is_identity() {
+ let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) {
Some(CGAffineTransform {
a: shape.scale_x as f64,
b: -shape.skew_y as f64,
c: -shape.skew_x as f64,
d: shape.scale_y as f64,
- tx: 0.0,
- ty: 0.0,
+ tx: tx,
+ ty: -ty,
})
} else {
None
@@ -640,8 +648,8 @@ impl FontContext {
// CG Origin is bottom left, WR is top left. Need -y offset
let mut draw_origin = CGPoint {
- x: -metrics.rasterized_left as f64 + x_offset,
- y: metrics.rasterized_descent as f64 - y_offset,
+ x: -metrics.rasterized_left as f64 + x_offset + tx,
+ y: metrics.rasterized_descent as f64 - y_offset - ty,
};
if let Some(transform) = transform {
diff --git a/webrender/src/platform/unix/font.rs b/webrender/src/platform/unix/font.rs
index a98068bcf1..e34aad4ea3 100644
--- a/webrender/src/platform/unix/font.rs
+++ b/webrender/src/platform/unix/font.rs
@@ -12,7 +12,7 @@ use freetype::freetype::{FT_F26Dot6, FT_Face, FT_Glyph_Format, FT_Long, FT_UInt}
use freetype::freetype::{FT_GlyphSlot, FT_LcdFilter, FT_New_Face, FT_New_Memory_Face};
use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
use freetype::freetype::{FT_Library, FT_Outline_Get_CBox, FT_Set_Char_Size, FT_Select_Size};
-use freetype::freetype::{FT_Fixed, FT_Matrix, FT_Set_Transform, FT_String, FT_ULong};
+use freetype::freetype::{FT_Fixed, FT_Matrix, FT_Set_Transform, FT_String, FT_ULong, FT_Vector};
use freetype::freetype::{FT_Err_Unimplemented_Feature, FT_MulFix, FT_Outline_Embolden};
use freetype::freetype::{FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_FORCE_AUTOHINT};
use freetype::freetype::{FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, FT_LOAD_NO_AUTOHINT};
@@ -224,7 +224,7 @@ pub struct FontContext {
// a given FontContext so it is safe to move the latter between threads.
unsafe impl Send for FontContext {}
-fn get_skew_bounds(bottom: i32, top: i32, skew_factor: f32) -> (f32, f32) {
+fn get_skew_bounds(bottom: i32, top: i32, skew_factor: f32, _vertical: bool) -> (f32, f32) {
let skew_min = ((bottom as f32 + 0.5) * skew_factor).floor();
let skew_max = ((top as f32 - 0.5) * skew_factor).ceil();
(skew_min, skew_max)
@@ -237,10 +237,11 @@ fn skew_bitmap(
left: i32,
top: i32,
skew_factor: f32,
+ vertical: bool, // TODO: vertical skew not yet implemented!
) -> (Vec, usize, i32) {
let stride = width * 4;
// Calculate the skewed horizontal offsets of the bottom and top of the glyph.
- let (skew_min, skew_max) = get_skew_bounds(top - height as i32, top, skew_factor);
+ let (skew_min, skew_max) = get_skew_bounds(top - height as i32, top, skew_factor, vertical);
// Allocate enough extra width for the min/max skew offsets.
let skew_width = width + (skew_max - skew_min) as usize;
let mut skew_buffer = vec![0u8; skew_width * height * 4];
@@ -478,8 +479,12 @@ impl FontContext {
if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
shape = shape.swap_xy();
}
+ let (mut tx, mut ty) = (0.0, 0.0);
if font.synthetic_italics.is_enabled() {
- shape = shape.synthesize_italics(font.synthetic_italics);
+ let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, y_scale * req_size);
+ shape = shape_;
+ tx = tx_;
+ ty = ty_;
};
let mut ft_shape = FT_Matrix {
xx: (shape.scale_x * 65536.0) as FT_Fixed,
@@ -487,8 +492,13 @@ impl FontContext {
yx: (shape.skew_y * -65536.0) as FT_Fixed,
yy: (shape.scale_y * 65536.0) as FT_Fixed,
};
+ // The delta vector for FT_Set_Transform is in units of 1/64 pixel.
+ let mut ft_delta = FT_Vector {
+ x: (tx * 64.0) as FT_F26Dot6,
+ y: (ty * -64.0) as FT_F26Dot6,
+ };
unsafe {
- FT_Set_Transform(face, &mut ft_shape, ptr::null_mut());
+ FT_Set_Transform(face, &mut ft_shape, &mut ft_delta);
FT_Set_Char_Size(
face,
(req_size * x_scale * 64.0 + 0.5) as FT_F26Dot6,
@@ -660,6 +670,7 @@ impl FontContext {
top - height as i32,
top,
font.synthetic_italics.to_skew(),
+ font.flags.contains(FontInstanceFlags::VERTICAL),
);
left += skew_min as i32;
width += (skew_max - skew_min) as i32;
@@ -949,6 +960,7 @@ impl FontContext {
left,
top,
font.synthetic_italics.to_skew(),
+ font.flags.contains(FontInstanceFlags::VERTICAL),
);
final_buffer = skew_buffer;
actual_width = skew_width;
diff --git a/webrender/src/platform/windows/font.rs b/webrender/src/platform/windows/font.rs
index d07e4fdcee..2ab2d7e678 100644
--- a/webrender/src/platform/windows/font.rs
+++ b/webrender/src/platform/windows/font.rs
@@ -477,9 +477,9 @@ impl FontContext {
fn get_glyph_parameters(font: &FontInstance, key: &GlyphKey) -> (f32, f64, bool, Option) {
let (_, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
- let size = (font.size.to_f64_px() * y_scale) as f32;
+ let scaled_size = font.size.to_f64_px() * y_scale;
let bitmaps = is_bitmap_font(font);
- let (mut shape, (x_offset, y_offset)) = if bitmaps {
+ let (mut shape, (mut x_offset, mut y_offset)) = if bitmaps {
(FontTransform::identity(), (0.0, 0.0))
} else {
(font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
@@ -493,9 +493,15 @@ impl FontContext {
if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
shape = shape.swap_xy();
}
+ let (mut tx, mut ty) = (0.0, 0.0);
if font.synthetic_italics.is_enabled() {
- shape = shape.synthesize_italics(font.synthetic_italics);
- }
+ let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, scaled_size);
+ shape = shape_;
+ tx = tx_;
+ ty = ty_;
+ };
+ x_offset += tx;
+ y_offset += ty;
let transform = if !shape.is_identity() || (x_offset, y_offset) != (0.0, 0.0) {
Some(dwrote::DWRITE_MATRIX {
m11: shape.scale_x,
@@ -508,7 +514,7 @@ impl FontContext {
} else {
None
};
- (size, y_scale, bitmaps, transform)
+ (scaled_size as f32, y_scale, bitmaps, transform)
}
pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult {
diff --git a/webrender/src/prim_store/gradient.rs b/webrender/src/prim_store/gradient.rs
index 58021ace07..b8d59c8030 100644
--- a/webrender/src/prim_store/gradient.rs
+++ b/webrender/src/prim_store/gradient.rs
@@ -568,15 +568,19 @@ impl IsVisible for RadialGradient {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, Clone, MallocSizeOf, PartialEq)]
-pub struct ConicGradientAngle {
+pub struct ConicGradientParams {
pub angle: f32, // in radians
+ pub start_offset: f32,
+ pub end_offset: f32,
}
-impl Eq for ConicGradientAngle {}
+impl Eq for ConicGradientParams {}
-impl hash::Hash for ConicGradientAngle {
+impl hash::Hash for ConicGradientParams {
fn hash(&self, state: &mut H) {
self.angle.to_bits().hash(state);
+ self.start_offset.to_bits().hash(state);
+ self.end_offset.to_bits().hash(state);
}
}
@@ -588,7 +592,7 @@ pub struct ConicGradientKey {
pub common: PrimKeyCommonData,
pub extend_mode: ExtendMode,
pub center: PointKey,
- pub angle: ConicGradientAngle,
+ pub params: ConicGradientParams,
pub stretch_size: SizeKey,
pub stops: Vec,
pub tile_spacing: SizeKey,
@@ -608,7 +612,7 @@ impl ConicGradientKey {
},
extend_mode: conic_grad.extend_mode,
center: conic_grad.center,
- angle: conic_grad.angle,
+ params: conic_grad.params,
stretch_size: conic_grad.stretch_size,
stops: conic_grad.stops,
tile_spacing: conic_grad.tile_spacing,
@@ -626,7 +630,7 @@ pub struct ConicGradientTemplate {
pub common: PrimTemplateCommonData,
pub extend_mode: ExtendMode,
pub center: LayoutPoint,
- pub angle: ConicGradientAngle,
+ pub params: ConicGradientParams,
pub stretch_size: LayoutSize,
pub tile_spacing: LayoutSize,
pub brush_segments: Vec,
@@ -667,7 +671,7 @@ impl From for ConicGradientTemplate {
common,
center: item.center.into(),
extend_mode: item.extend_mode,
- angle: item.angle,
+ params: item.params,
stretch_size: item.stretch_size.into(),
tile_spacing: item.tile_spacing.into(),
brush_segments,
@@ -692,14 +696,14 @@ impl ConicGradientTemplate {
request.push([
self.center.x,
self.center.y,
- self.angle.angle,
- 0.0,
+ self.params.start_offset,
+ self.params.end_offset,
]);
request.push([
+ self.params.angle,
pack_as_float(self.extend_mode as u32),
self.stretch_size.width,
self.stretch_size.height,
- 0.0,
]);
// write_segment_gpu_blocks
@@ -732,7 +736,7 @@ pub type ConicGradientDataHandle = InternHandle;
pub struct ConicGradient {
pub extend_mode: ExtendMode,
pub center: PointKey,
- pub angle: ConicGradientAngle,
+ pub params: ConicGradientParams,
pub stretch_size: SizeKey,
pub stops: Vec,
pub tile_spacing: SizeKey,
@@ -1000,7 +1004,7 @@ fn test_struct_sizes() {
assert_eq!(mem::size_of::(), 120, "RadialGradientTemplate size changed");
assert_eq!(mem::size_of::(), 88, "RadialGradientKey size changed");
- assert_eq!(mem::size_of::(), 64, "ConicGradient size changed");
- assert_eq!(mem::size_of::(), 112, "ConicGradientTemplate size changed");
- assert_eq!(mem::size_of::(), 80, "ConicGradientKey size changed");
+ assert_eq!(mem::size_of::(), 72, "ConicGradient size changed");
+ assert_eq!(mem::size_of::(), 120, "ConicGradientTemplate size changed");
+ assert_eq!(mem::size_of::(), 88, "ConicGradientKey size changed");
}
diff --git a/webrender/src/prim_store/line_dec.rs b/webrender/src/prim_store/line_dec.rs
index 1dbda9a5a5..4102409aef 100644
--- a/webrender/src/prim_store/line_dec.rs
+++ b/webrender/src/prim_store/line_dec.rs
@@ -18,6 +18,8 @@ use crate::prim_store::{
};
use crate::prim_store::PrimitiveInstanceKind;
+/// Maximum resolution in device pixels at which line decorations are rasterized.
+pub const MAX_LINE_DECORATION_RESOLUTION: u32 = 4096;
#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
diff --git a/webrender/src/prim_store/mod.rs b/webrender/src/prim_store/mod.rs
index 9626d5413f..ef50dfbf71 100644
--- a/webrender/src/prim_store/mod.rs
+++ b/webrender/src/prim_store/mod.rs
@@ -2,7 +2,7 @@
* 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 api::{BorderRadius, ClipMode, ColorF};
+use api::{BorderRadius, ClipMode, ColorF, ColorU};
use api::{ImageRendering, RepeatMode, PrimitiveFlags};
use api::{PremultipliedColorF, PropertyBinding, Shadow, GradientStop};
use api::{BoxShadowClipMode, LineStyle, LineOrientation, BorderStyle};
@@ -34,7 +34,7 @@ use crate::prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle};
use crate::prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientStopKey};
use crate::prim_store::gradient::{LinearGradientPrimitive, LinearGradientDataHandle, RadialGradientDataHandle, ConicGradientDataHandle};
use crate::prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle};
-use crate::prim_store::line_dec::LineDecorationDataHandle;
+use crate::prim_store::line_dec::{LineDecorationDataHandle,MAX_LINE_DECORATION_RESOLUTION};
use crate::prim_store::picture::PictureDataHandle;
use crate::prim_store::text_run::{TextRunDataHandle, TextRunPrimitive};
#[cfg(debug_assertions)]
@@ -720,7 +720,7 @@ impl intern::InternDebug for PrimitiveKey {}
#[derive(MallocSizeOf)]
pub enum PrimitiveTemplateKind {
Rectangle {
- color: ColorF,
+ color: PropertyBinding,
},
Clear,
}
@@ -812,7 +812,8 @@ impl PrimitiveTemplateKind {
/// Write any GPU blocks for the primitive template to the given request object.
fn write_prim_gpu_blocks(
&self,
- request: &mut GpuDataRequest
+ request: &mut GpuDataRequest,
+ scene_properties: &SceneProperties,
) {
match *self {
PrimitiveTemplateKind::Clear => {
@@ -820,7 +821,7 @@ impl PrimitiveTemplateKind {
request.push(PremultipliedColorF::BLACK);
}
PrimitiveTemplateKind::Rectangle { ref color, .. } => {
- request.push(color.premultiplied());
+ request.push(scene_properties.resolve_color(color).premultiplied())
}
}
}
@@ -834,9 +835,10 @@ impl PrimitiveTemplate {
pub fn update(
&mut self,
frame_state: &mut FrameBuildingState,
+ scene_properties: &SceneProperties,
) {
if let Some(mut request) = frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
- self.kind.write_prim_gpu_blocks(&mut request);
+ self.kind.write_prim_gpu_blocks(&mut request, scene_properties);
}
self.opacity = match self.kind {
@@ -844,7 +846,7 @@ impl PrimitiveTemplate {
PrimitiveOpacity::translucent()
}
PrimitiveTemplateKind::Rectangle { ref color, .. } => {
- PrimitiveOpacity::from_alpha(color.a)
+ PrimitiveOpacity::from_alpha(scene_properties.resolve_color(color).a)
}
};
}
@@ -873,7 +875,7 @@ impl InternablePrimitive for PrimitiveKeyKind {
fn make_instance_kind(
key: PrimitiveKey,
data_handle: PrimitiveDataHandle,
- _: &mut PrimitiveStore,
+ prim_store: &mut PrimitiveStore,
_reference_frame_relative_offset: LayoutVector2D,
) -> PrimitiveInstanceKind {
match key.kind {
@@ -882,11 +884,18 @@ impl InternablePrimitive for PrimitiveKeyKind {
data_handle
}
}
- PrimitiveKeyKind::Rectangle { .. } => {
+ PrimitiveKeyKind::Rectangle { color, .. } => {
+ let color_binding_index = match color {
+ PropertyBinding::Binding(..) => {
+ prim_store.color_bindings.push(color)
+ }
+ PropertyBinding::Value(..) => ColorBindingIndex::INVALID,
+ };
PrimitiveInstanceKind::Rectangle {
data_handle,
opacity_binding_index: OpacityBindingIndex::INVALID,
segment_instance_index: SegmentInstanceIndex::INVALID,
+ color_binding_index,
}
}
}
@@ -918,8 +927,7 @@ impl OpacityBinding {
}
// Resolve the current value of each opacity binding, and
- // store that as a single combined opacity. Returns true
- // if the opacity value changed from last time.
+ // store that as a single combined opacity.
pub fn update(&mut self, scene_properties: &SceneProperties) {
let mut new_opacity = 1.0;
@@ -1327,7 +1335,10 @@ impl IsVisible for PrimitiveKeyKind {
true
}
PrimitiveKeyKind::Rectangle { ref color, .. } => {
- color.a > 0
+ match *color {
+ PropertyBinding::Value(value) => value.a > 0,
+ PropertyBinding::Binding(..) => true,
+ }
}
}
}
@@ -1344,7 +1355,7 @@ impl CreateShadow for PrimitiveKeyKind {
match *self {
PrimitiveKeyKind::Rectangle { .. } => {
PrimitiveKeyKind::Rectangle {
- color: shadow.color.into(),
+ color: PropertyBinding::Value(shadow.color.into()),
}
}
PrimitiveKeyKind::Clear => {
@@ -1405,6 +1416,7 @@ pub enum PrimitiveInstanceKind {
data_handle: PrimitiveDataHandle,
opacity_binding_index: OpacityBindingIndex,
segment_instance_index: SegmentInstanceIndex,
+ color_binding_index: ColorBindingIndex,
},
YuvImage {
/// Handle to the common interned data for this primitive.
@@ -1661,6 +1673,8 @@ pub type TextRunIndex = storage::Index;
pub type TextRunStorage = storage::Storage;
pub type OpacityBindingIndex = storage::Index;
pub type OpacityBindingStorage = storage::Storage;
+pub type ColorBindingIndex = storage::Index>;
+pub type ColorBindingStorage = storage::Storage>;
pub type BorderHandleStorage = storage::Storage;
pub type SegmentStorage = storage::Storage;
pub type SegmentsRange = storage::Range;
@@ -1800,6 +1814,7 @@ pub struct PrimitiveStoreStats {
opacity_binding_count: usize,
image_count: usize,
linear_gradient_count: usize,
+ color_binding_count: usize,
}
impl PrimitiveStoreStats {
@@ -1810,6 +1825,7 @@ impl PrimitiveStoreStats {
opacity_binding_count: 0,
image_count: 0,
linear_gradient_count: 0,
+ color_binding_count: 0,
}
}
}
@@ -1827,6 +1843,8 @@ pub struct PrimitiveStore {
/// List of animated opacity bindings for a primitive.
pub opacity_bindings: OpacityBindingStorage,
+ /// animated color bindings for this primitive.
+ pub color_bindings: ColorBindingStorage,
}
impl PrimitiveStore {
@@ -1836,6 +1854,7 @@ impl PrimitiveStore {
text_runs: TextRunStorage::new(stats.text_run_count),
images: ImageInstanceStorage::new(stats.image_count),
opacity_bindings: OpacityBindingStorage::new(stats.opacity_binding_count),
+ color_bindings: ColorBindingStorage::new(stats.color_binding_count),
linear_gradients: LinearGradientStorage::new(stats.linear_gradient_count),
}
}
@@ -1847,6 +1866,7 @@ impl PrimitiveStore {
image_count: self.images.len(),
opacity_binding_count: self.opacity_bindings.len(),
linear_gradient_count: self.linear_gradients.len(),
+ color_binding_count: self.color_bindings.len(),
}
}
@@ -2151,9 +2171,10 @@ impl PrimitiveStore {
&self.pictures,
frame_state.resource_cache,
&self.opacity_bindings,
+ &self.color_bindings,
&self.images,
&frame_state.surface_stack,
- &frame_state.composite_state,
+ &mut frame_state.composite_state,
) {
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
// Ensure the primitive clip is popped - perhaps we can use
@@ -2657,7 +2678,7 @@ impl PrimitiveStore {
self.pictures[pic_index.0].requested_composite_mode = None;
}
- pub fn prepare_prim_for_render(
+ fn prepare_prim_for_render(
&mut self,
prim_instance: &mut PrimitiveInstance,
prim_spatial_node_index: SpatialNodeIndex,
@@ -2899,7 +2920,14 @@ impl PrimitiveStore {
// TODO(gw): Do we ever need / want to support scales for text decorations
// based on the current transform?
let scale_factor = Scale::new(1.0) * device_pixel_scale;
- let task_size = (LayoutSize::from_au(cache_key.size) * scale_factor).ceil().to_i32();
+ let mut task_size = (LayoutSize::from_au(cache_key.size) * scale_factor).ceil().to_i32();
+ if task_size.width > MAX_LINE_DECORATION_RESOLUTION as i32 ||
+ task_size.height > MAX_LINE_DECORATION_RESOLUTION as i32 {
+ let max_extent = cmp::max(task_size.width, task_size.height);
+ let task_scale_factor = Scale::new(MAX_LINE_DECORATION_RESOLUTION as f32 / max_extent as f32);
+ task_size = (LayoutSize::from_au(cache_key.size) * scale_factor * task_scale_factor)
+ .ceil().to_i32();
+ }
// Request a pre-rendered image task.
// TODO(gw): This match is a bit untidy, but it should disappear completely
@@ -2947,6 +2975,10 @@ impl PrimitiveStore {
let raster_space = pic.get_raster_space(frame_context.spatial_tree);
let surface = &frame_state.surfaces[pic_context.surface_index.0];
let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
+ let root_scaling_factor = match pic.raster_config {
+ Some(ref raster_config) => raster_config.root_scaling_factor,
+ None => 1.0
+ };
run.request_resources(
prim_offset,
@@ -2957,6 +2989,7 @@ impl PrimitiveStore {
surface,
prim_spatial_node_index,
raster_space,
+ root_scaling_factor,
&pic_context.subpixel_mode,
frame_state.resource_cache,
frame_state.gpu_cache,
@@ -2976,7 +3009,7 @@ impl PrimitiveStore {
// Update the template this instane references, which may refresh the GPU
// cache with any shared template data.
- prim_data.update(frame_state);
+ prim_data.update(frame_state, frame_context.scene_properties);
}
PrimitiveInstanceKind::NormalBorder { data_handle, ref mut cache_handles, .. } => {
let prim_data = &mut data_stores.normal_border[*data_handle];
@@ -3066,13 +3099,37 @@ impl PrimitiveStore {
// cache with any shared template data.
prim_data.kind.update(&mut prim_data.common, frame_state);
}
- PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, .. } => {
+ PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, color_binding_index, .. } => {
let prim_data = &mut data_stores.prim[*data_handle];
prim_data.common.may_need_repetition = false;
+ if *color_binding_index != ColorBindingIndex::INVALID {
+ match self.color_bindings[*color_binding_index] {
+ PropertyBinding::Binding(..) => {
+ // We explicitly invalidate the gpu cache
+ // if the color is animating.
+ let gpu_cache_handle =
+ if *segment_instance_index == SegmentInstanceIndex::INVALID {
+ None
+ } else if *segment_instance_index == SegmentInstanceIndex::UNUSED {
+ Some(&prim_data.common.gpu_cache_handle)
+ } else {
+ Some(&scratch.segment_instances[*segment_instance_index].gpu_cache_handle)
+ };
+ if let Some(gpu_cache_handle) = gpu_cache_handle {
+ frame_state.gpu_cache.invalidate(gpu_cache_handle);
+ }
+ }
+ PropertyBinding::Value(..) => {},
+ }
+ }
+
// Update the template this instane references, which may refresh the GPU
// cache with any shared template data.
- prim_data.update(frame_state);
+ prim_data.update(
+ frame_state,
+ frame_context.scene_properties,
+ );
update_opacity_binding(
&mut self.opacity_bindings,
@@ -3088,6 +3145,7 @@ impl PrimitiveStore {
|request| {
prim_data.kind.write_prim_gpu_blocks(
request,
+ frame_context.scene_properties,
);
}
);
@@ -3389,14 +3447,14 @@ impl PrimitiveStore {
request.push([
prim_data.center.x,
prim_data.center.y,
- prim_data.angle.angle,
- 0.0,
+ prim_data.params.start_offset,
+ prim_data.params.end_offset,
]);
request.push([
+ prim_data.params.angle,
pack_as_float(prim_data.extend_mode as u32),
prim_data.stretch_size.width,
prim_data.stretch_size.height,
- 0.0,
]);
},
);
@@ -4306,13 +4364,10 @@ fn update_opacity_binding(
opacity_bindings: &mut OpacityBindingStorage,
opacity_binding_index: OpacityBindingIndex,
scene_properties: &SceneProperties,
-) -> f32 {
- if opacity_binding_index == OpacityBindingIndex::INVALID {
- 1.0
- } else {
+) {
+ if opacity_binding_index != OpacityBindingIndex::INVALID {
let binding = &mut opacity_bindings[opacity_binding_index];
binding.update(scene_properties);
- binding.current
}
}
@@ -4346,8 +4401,8 @@ fn test_struct_sizes() {
// be done with care, and after checking if talos performance regresses badly.
assert_eq!(mem::size_of::(), 88, "PrimitiveInstance size changed");
assert_eq!(mem::size_of::(), 40, "PrimitiveInstanceKind size changed");
- assert_eq!(mem::size_of::(), 40, "PrimitiveTemplate size changed");
- assert_eq!(mem::size_of::(), 20, "PrimitiveTemplateKind size changed");
- assert_eq!(mem::size_of::(), 20, "PrimitiveKey size changed");
- assert_eq!(mem::size_of::(), 5, "PrimitiveKeyKind size changed");
+ assert_eq!(mem::size_of::(), 48, "PrimitiveTemplate size changed");
+ assert_eq!(mem::size_of::(), 28, "PrimitiveTemplateKind size changed");
+ assert_eq!(mem::size_of::(), 28, "PrimitiveKey size changed");
+ assert_eq!(mem::size_of::(), 16, "PrimitiveKeyKind size changed");
}
diff --git a/webrender/src/prim_store/text_run.rs b/webrender/src/prim_store/text_run.rs
index d541c016d3..be0817291e 100644
--- a/webrender/src/prim_store/text_run.rs
+++ b/webrender/src/prim_store/text_run.rs
@@ -229,6 +229,7 @@ impl TextRunPrimitive {
subpixel_mode: &SubpixelMode,
raster_space: RasterSpace,
prim_rect: PictureRect,
+ root_scaling_factor: f32,
spatial_tree: &SpatialTree,
) -> bool {
// If local raster space is specified, include that in the scale
@@ -239,7 +240,10 @@ impl TextRunPrimitive {
// will no longer be required.
let raster_scale = raster_space.local_scale().unwrap_or(1.0).max(0.001);
- let dps = surface.device_pixel_scale.0;
+ // root_scaling_factor is used to scale very large pictures that establish
+ // a raster root back to something sane, thus scale the device size accordingly.
+ // to the shader it looks like a change in DPI which it already supports.
+ let dps = surface.device_pixel_scale.0 * root_scaling_factor;
let glyph_raster_scale = dps * raster_scale;
let font_size = specified_font.size.to_f32_px();
let device_font_size = font_size * glyph_raster_scale;
@@ -365,6 +369,7 @@ impl TextRunPrimitive {
surface: &SurfaceInfo,
spatial_node_index: SpatialNodeIndex,
raster_space: RasterSpace,
+ root_scaling_factor: f32,
subpixel_mode: &SubpixelMode,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
@@ -380,6 +385,7 @@ impl TextRunPrimitive {
subpixel_mode,
raster_space,
prim_rect,
+ root_scaling_factor,
spatial_tree,
);
diff --git a/webrender/src/render_task.rs b/webrender/src/render_task.rs
index 018e44acca..ac40b32fb0 100644
--- a/webrender/src/render_task.rs
+++ b/webrender/src/render_task.rs
@@ -142,7 +142,7 @@ pub struct PictureTask {
pub uv_rect_handle: GpuCacheHandle,
pub surface_spatial_node_index: SpatialNodeIndex,
uv_rect_kind: UvRectKind,
- device_pixel_scale: DevicePixelScale,
+ pub device_pixel_scale: DevicePixelScale,
/// A bitfield that describes which dirty regions should be included
/// in batches built for this picture task.
pub vis_mask: PrimitiveVisibilityMask,
diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs
index e49a1f6db4..a7e1da350c 100644
--- a/webrender/src/renderer.rs
+++ b/webrender/src/renderer.rs
@@ -38,7 +38,7 @@ use api::{ApiMsg, BlobImageHandler, ColorF, ColorU, MixBlendMode};
use api::{DocumentId, Epoch, ExternalImageHandler, ExternalImageId};
use api::{ExternalImageSource, ExternalImageType, FontRenderMode, FrameMsg, ImageFormat};
use api::{PipelineId, ImageRendering, Checkpoint, NotificationRequest, OutputImageHandler};
-use api::{DebugCommand, MemoryReport, VoidPtrToSizeFn};
+use api::{DebugCommand, MemoryReport, VoidPtrToSizeFn, PremultipliedColorF};
use api::{RenderApiSender, RenderNotifier, TextureTarget};
#[cfg(feature = "replay")]
use api::ExternalImage;
@@ -49,7 +49,7 @@ use api::channel::MsgSender;
use crate::batch::{AlphaBatchContainer, BatchKind, BatchFeatures, BatchTextures, BrushBatchKind, ClipBatchList};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
-use crate::composite::{CompositeState, CompositeTileSurface, CompositeTile};
+use crate::composite::{CompositeState, CompositeTileSurface, CompositeTile, ResolvedExternalSurface};
use crate::composite::{CompositorKind, Compositor, NativeTileId, CompositeSurfaceFormat};
use crate::composite::{CompositorConfig, NativeSurfaceOperationDetails, NativeSurfaceId, NativeSurfaceOperation};
use crate::debug_colors;
@@ -68,7 +68,7 @@ use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd};
use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData};
-use crate::gpu_types::{CompositeInstance, ResolveInstanceData};
+use crate::gpu_types::{CompositeInstance, ResolveInstanceData, ZBufferId};
use crate::internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg};
use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource};
@@ -1253,6 +1253,26 @@ impl TextureResolver {
}
}
+ // Retrieve the deferred / resolved UV rect if an external texture, otherwise
+ // return the default supplied UV rect.
+ fn get_uv_rect(
+ &self,
+ source: &TextureSource,
+ default_value: TexelRect,
+ ) -> TexelRect {
+ match source {
+ TextureSource::External(ref external_image) => {
+ let texture = self.external_images
+ .get(&(external_image.id, external_image.channel_index))
+ .expect("BUG: External image should be resolved by now");
+ texture.get_uv_rect()
+ }
+ _ => {
+ default_value
+ }
+ }
+ }
+
fn report_memory(&self) -> MemoryReport {
let mut report = MemoryReport::default();
@@ -3779,8 +3799,8 @@ impl Renderer {
// composite operation in this batch.
let (readback_rect, readback_layer) = readback.get_target_rect();
let (backdrop_rect, _) = backdrop.get_target_rect();
- let backdrop_screen_origin = match backdrop.kind {
- RenderTaskKind::Picture(ref task_info) => task_info.content_origin,
+ let (backdrop_screen_origin, backdrop_scale) = match backdrop.kind {
+ RenderTaskKind::Picture(ref task_info) => (task_info.content_origin, task_info.device_pixel_scale),
_ => panic!("bug: composite on non-picture?"),
};
let source_screen_origin = match source.kind {
@@ -3798,8 +3818,10 @@ impl Renderer {
false,
);
+ let source_in_backdrop_space = source_screen_origin.to_f32() * backdrop_scale.0;
+
let mut src = DeviceIntRect::new(
- source_screen_origin + (backdrop_rect.origin - backdrop_screen_origin),
+ (source_in_backdrop_space + (backdrop_rect.origin - backdrop_screen_origin).to_f32()).to_i32(),
readback_rect.size,
);
let mut dest = readback_rect.to_i32();
@@ -4229,10 +4251,132 @@ impl Renderer {
}
}
+ /// Rasterize any external compositor surfaces that require updating
+ fn update_external_native_surfaces(
+ &mut self,
+ external_surfaces: &[ResolvedExternalSurface],
+ results: &mut RenderResults,
+ ) {
+ if external_surfaces.is_empty() {
+ return;
+ }
+
+ let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
+
+ self.device.disable_depth();
+ self.set_blend(false, FramebufferKind::Main);
+
+ for surface in external_surfaces {
+ // See if this surface needs to be updated
+ let (native_surface_id, surface_size) = match surface.update_params {
+ Some(params) => params,
+ None => continue,
+ };
+
+ // When updating an external surface, the entire surface rect is used
+ // for all of the draw, dirty, valid and clip rect parameters.
+ let surface_rect = surface_size.into();
+
+ // Bind the native compositor surface to update
+ let surface_info = self.compositor_config
+ .compositor()
+ .unwrap()
+ .bind(
+ NativeTileId {
+ surface_id: native_surface_id,
+ x: 0,
+ y: 0,
+ },
+ surface_rect,
+ surface_rect,
+ );
+
+ // Bind the native surface to current FBO target
+ let draw_target = DrawTarget::NativeSurface {
+ offset: surface_info.origin,
+ external_fbo_id: surface_info.fbo_id,
+ dimensions: surface_size,
+ };
+ self.device.bind_draw_target(draw_target);
+
+ let projection = Transform3D::ortho(
+ 0.0,
+ surface_size.width as f32,
+ 0.0,
+ surface_size.height as f32,
+ ORTHO_NEAR_PLANE,
+ ORTHO_FAR_PLANE,
+ );
+
+ // Bind an appropriate YUV shader for the texture format kind
+ self.shaders
+ .borrow_mut()
+ .get_composite_shader(
+ CompositeSurfaceFormat::Yuv,
+ surface.image_buffer_kind,
+ ).bind(
+ &mut self.device,
+ &projection,
+ &mut self.renderer_errors
+ );
+
+ let textures = BatchTextures {
+ colors: [
+ surface.yuv_planes[0].texture,
+ surface.yuv_planes[1].texture,
+ surface.yuv_planes[2].texture,
+ ],
+ };
+
+ // When the texture is an external texture, the UV rect is not known when
+ // the external surface descriptor is created, because external textures
+ // are not resolved until the lock() callback is invoked at the start of
+ // the frame render. To handle this, query the texture resolver for the
+ // UV rect if it's an external texture, otherwise use the default UV rect.
+ let uv_rects = [
+ self.texture_resolver.get_uv_rect(&textures.colors[0], surface.yuv_planes[0].uv_rect),
+ self.texture_resolver.get_uv_rect(&textures.colors[1], surface.yuv_planes[1].uv_rect),
+ self.texture_resolver.get_uv_rect(&textures.colors[2], surface.yuv_planes[2].uv_rect),
+ ];
+
+ let instance = CompositeInstance::new_yuv(
+ surface_rect.to_f32(),
+ surface_rect.to_f32(),
+ // z-id is not relevant when updating a native compositor surface.
+ // TODO(gw): Support compositor surfaces without z-buffer, for memory / perf win here.
+ ZBufferId(0),
+ surface.yuv_color_space,
+ surface.yuv_format,
+ surface.yuv_rescale,
+ [
+ surface.yuv_planes[0].texture_layer as f32,
+ surface.yuv_planes[1].texture_layer as f32,
+ surface.yuv_planes[2].texture_layer as f32,
+ ],
+ uv_rects,
+ );
+
+ self.draw_instanced_batch(
+ &[instance],
+ VertexArrayKind::Composite,
+ &textures,
+ &mut results.stats,
+ );
+
+ self.compositor_config
+ .compositor()
+ .unwrap()
+ .unbind();
+ }
+
+ self.gpu_profile.finish_sampler(opaque_sampler);
+ }
+
/// Draw a list of tiles to the framebuffer
fn draw_tile_list<'a, I: Iterator- >(
&mut self,
tiles_iter: I,
+ external_surfaces: &[ResolvedExternalSurface],
projection: &default::Transform3D,
partial_present_mode: Option,
stats: &mut RendererStats,
@@ -4248,27 +4392,11 @@ impl Renderer {
&mut self.renderer_errors
);
+ let mut current_shader_params = (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray);
let mut current_textures = BatchTextures::no_texture();
let mut instances = Vec::new();
for tile in tiles_iter {
- // Work out the draw params based on the tile surface
- let (texture, layer, color) = match tile.surface {
- CompositeTileSurface::Color { color } => {
- (TextureSource::Dummy, 0.0, color)
- }
- CompositeTileSurface::Clear => {
- (TextureSource::Dummy, 0.0, ColorF::BLACK)
- }
- CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::TextureCache { texture, layer } } => {
- (texture, layer as f32, ColorF::WHITE)
- }
- CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { .. } } => {
- unreachable!("bug: found native surface in simple composite path");
- }
- };
- let textures = BatchTextures::color(texture);
-
// Determine a clip rect to apply to this tile, depending on what
// the partial present mode is.
let partial_clip_rect = match partial_present_mode {
@@ -4292,106 +4420,125 @@ impl Renderer {
None => continue,
};
- // Flush this batch if the textures aren't compatible
- if !current_textures.is_compatible_with(&textures) {
- self.draw_instanced_batch(
- &instances,
- VertexArrayKind::Composite,
- ¤t_textures,
- stats,
- );
- instances.clear();
- }
- current_textures = textures;
-
- // Create the instance and add to current batch
- let instance = CompositeInstance::new(
- tile.rect,
- clip_rect,
- color.premultiplied(),
- layer,
- tile.z_id,
- );
- instances.push(instance);
- }
-
- // Flush the last batch
- if !instances.is_empty() {
- self.draw_instanced_batch(
- &instances,
- VertexArrayKind::Composite,
- ¤t_textures,
- stats,
- );
- }
- }
+ // Work out the draw params based on the tile surface
+ let (instance, textures, shader_params) = match tile.surface {
+ CompositeTileSurface::Color { color } => {
+ (
+ CompositeInstance::new(
+ tile.rect,
+ clip_rect,
+ color.premultiplied(),
+ 0.0,
+ tile.z_id,
+ ),
+ BatchTextures::color(TextureSource::Dummy),
+ (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray),
+ )
+ }
+ CompositeTileSurface::Clear => {
+ (
+ CompositeInstance::new(
+ tile.rect,
+ clip_rect,
+ PremultipliedColorF::BLACK,
+ 0.0,
+ tile.z_id,
+ ),
+ BatchTextures::color(TextureSource::Dummy),
+ (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray),
+ )
+ }
+ CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::TextureCache { texture, layer } } => {
+ (
+ CompositeInstance::new(
+ tile.rect,
+ clip_rect,
+ PremultipliedColorF::WHITE,
+ layer as f32,
+ tile.z_id,
+ ),
+ BatchTextures::color(texture),
+ (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray),
+ )
+ }
+ CompositeTileSurface::ExternalSurface { external_surface_index } => {
+ let surface = &external_surfaces[external_surface_index.0];
+
+ let textures = BatchTextures {
+ colors: [
+ surface.yuv_planes[0].texture,
+ surface.yuv_planes[1].texture,
+ surface.yuv_planes[2].texture,
+ ],
+ };
- /// Draw a list of external compositor surfaces
- fn draw_external_surface_list(
- &mut self,
- composite_state: &CompositeState,
- projection: &default::Transform3D,
- stats: &mut RendererStats,
- ) {
- // Only opaque compositor surfaces are supported
- let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
- self.set_blend(false, FramebufferKind::Main);
+ // When the texture is an external texture, the UV rect is not known when
+ // the external surface descriptor is created, because external textures
+ // are not resolved until the lock() callback is invoked at the start of
+ // the frame render. To handle this, query the texture resolver for the
+ // UV rect if it's an external texture, otherwise use the default UV rect.
+ let uv_rects = [
+ self.texture_resolver.get_uv_rect(&textures.colors[0], surface.yuv_planes[0].uv_rect),
+ self.texture_resolver.get_uv_rect(&textures.colors[1], surface.yuv_planes[1].uv_rect),
+ self.texture_resolver.get_uv_rect(&textures.colors[2], surface.yuv_planes[2].uv_rect),
+ ];
+
+ (
+ CompositeInstance::new_yuv(
+ tile.rect,
+ clip_rect,
+ tile.z_id,
+ surface.yuv_color_space,
+ surface.yuv_format,
+ surface.yuv_rescale,
+ [
+ surface.yuv_planes[0].texture_layer as f32,
+ surface.yuv_planes[1].texture_layer as f32,
+ surface.yuv_planes[2].texture_layer as f32,
+ ],
+ uv_rects,
+ ),
+ textures,
+ (CompositeSurfaceFormat::Yuv, surface.image_buffer_kind),
+ )
+ }
+ CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { .. } } => {
+ unreachable!("bug: found native surface in simple composite path");
+ }
+ };
- let mut current_textures = BatchTextures::no_texture();
- let mut instances = Vec::new();
+ // Flush batch if shader params or textures changed
+ let flush_batch = !current_textures.is_compatible_with(&textures) ||
+ shader_params != current_shader_params;
- for surface in &composite_state.external_surfaces {
- // Bind an appropriate YUV shader for the texture format kind
- self.shaders
- .borrow_mut()
- .get_composite_shader(
- CompositeSurfaceFormat::Yuv,
- surface.image_buffer_kind,
- ).bind(
- &mut self.device,
- projection,
- &mut self.renderer_errors
- );
+ if flush_batch {
+ if !instances.is_empty() {
+ self.draw_instanced_batch(
+ &instances,
+ VertexArrayKind::Composite,
+ ¤t_textures,
+ stats,
+ );
+ instances.clear();
+ }
+ }
- let textures = BatchTextures {
- colors: [
- surface.yuv_planes[0].texture,
- surface.yuv_planes[1].texture,
- surface.yuv_planes[2].texture,
- ],
- };
+ if shader_params != current_shader_params {
+ self.shaders
+ .borrow_mut()
+ .get_composite_shader(shader_params.0, shader_params.1)
+ .bind(
+ &mut self.device,
+ projection,
+ &mut self.renderer_errors
+ );
- // Flush this batch if the textures aren't compatible
- if !current_textures.is_compatible_with(&textures) {
- self.draw_instanced_batch(
- &instances,
- VertexArrayKind::Composite,
- ¤t_textures,
- stats,
- );
- instances.clear();
+ current_shader_params = shader_params;
}
+
current_textures = textures;
- // Create the instance and add to current batch
- let instance = CompositeInstance::new_yuv(
- surface.device_rect,
- surface.clip_rect,
- surface.z_id,
- surface.yuv_color_space,
- surface.yuv_format,
- surface.yuv_rescale,
- [
- surface.yuv_planes[0].texture_layer as f32,
- surface.yuv_planes[1].texture_layer as f32,
- surface.yuv_planes[2].texture_layer as f32,
- ],
- [
- surface.yuv_planes[0].uv_rect,
- surface.yuv_planes[1].uv_rect,
- surface.yuv_planes[2].uv_rect,
- ],
- );
+ // Add instance to current batch
instances.push(instance);
}
@@ -4404,8 +4551,6 @@ impl Renderer {
stats,
);
}
-
- self.gpu_profile.finish_sampler(opaque_sampler);
}
/// Composite picture cache tiles into the framebuffer. This is currently
@@ -4445,19 +4590,6 @@ impl Renderer {
combined_dirty_rect = combined_dirty_rect.union(&dirty_rect);
}
- // Include any external surfaces in the partial present dirty rect. For now,
- // we assume that the external surfaces are always dirty / updating and always
- // need to be presented.
- // TODO(gw): We could check if the epoch of the external image has changed, and
- // skip including the external surface in the partial present dirty
- // rect if it hasn't changed.
- for surface in &composite_state.external_surfaces {
- let dirty_rect = surface.device_rect
- .intersection(&surface.clip_rect)
- .unwrap_or_else(DeviceRect::zero);
- combined_dirty_rect = combined_dirty_rect.union(&dirty_rect);
- }
-
let combined_dirty_rect = combined_dirty_rect.round();
let combined_dirty_rect_i32 = combined_dirty_rect.to_i32();
// If nothing has changed, don't return any dirty rects at all (the client
@@ -4508,15 +4640,6 @@ impl Renderer {
+ composite_state.alpha_tiles.len();
self.profile_counters.total_picture_cache_tiles.set(num_tiles);
- // Draw external compositor surfaces
- if !composite_state.external_surfaces.is_empty() {
- self.draw_external_surface_list(
- composite_state,
- projection,
- &mut results.stats,
- );
- }
-
// Draw opaque tiles first, front-to-back to get maxmum
// z-reject efficiency.
if !composite_state.opaque_tiles.is_empty() {
@@ -4525,6 +4648,7 @@ impl Renderer {
self.set_blend(false, FramebufferKind::Main);
self.draw_tile_list(
composite_state.opaque_tiles.iter().rev(),
+ &composite_state.external_surfaces,
projection,
partial_present_mode,
&mut results.stats,
@@ -4539,6 +4663,7 @@ impl Renderer {
self.device.set_blend_mode_premultiplied_dest_out();
self.draw_tile_list(
composite_state.clear_tiles.iter(),
+ &composite_state.external_surfaces,
projection,
partial_present_mode,
&mut results.stats,
@@ -4554,6 +4679,7 @@ impl Renderer {
self.set_blend_mode_premultiplied_alpha(FramebufferKind::Main);
self.draw_tile_list(
composite_state.alpha_tiles.iter(),
+ &composite_state.external_surfaces,
projection,
partial_present_mode,
&mut results.stats,
@@ -5149,7 +5275,12 @@ impl Renderer {
let texture = match image.source {
ExternalImageSource::NativeTexture(texture_id) => {
- ExternalTexture::new(texture_id, texture_target, Swizzle::default())
+ ExternalTexture::new(
+ texture_id,
+ texture_target,
+ Swizzle::default(),
+ image.uv,
+ )
}
ExternalImageSource::Invalid => {
warn!("Invalid ext-image");
@@ -5159,7 +5290,12 @@ impl Renderer {
ext_image.channel_index
);
// Just use 0 as the gl handle for this failed case.
- ExternalTexture::new(0, texture_target, Swizzle::default())
+ ExternalTexture::new(
+ 0,
+ texture_target,
+ Swizzle::default(),
+ image.uv,
+ )
}
ExternalImageSource::RawData(_) => {
panic!("Raw external data is not expected for deferred resolves!");
@@ -5433,6 +5569,10 @@ impl Renderer {
// to specify how to composite each of the picture cache surfaces.
match self.current_compositor_kind {
CompositorKind::Native { .. } => {
+ self.update_external_native_surfaces(
+ &frame.composite_state.external_surfaces,
+ results,
+ );
let compositor = self.compositor_config.compositor().unwrap();
frame.composite_state.composite_native(&mut **compositor);
}
diff --git a/webrender/src/scene.rs b/webrender/src/scene.rs
index f00cf5b7ca..0416ec8f68 100644
--- a/webrender/src/scene.rs
+++ b/webrender/src/scene.rs
@@ -22,6 +22,7 @@ use std::sync::Arc;
pub struct SceneProperties {
transform_properties: FastHashMap,
float_properties: FastHashMap,
+ color_properties: FastHashMap,
current_properties: DynamicProperties,
pending_properties: Option,
}
@@ -31,6 +32,7 @@ impl SceneProperties {
SceneProperties {
transform_properties: FastHashMap::default(),
float_properties: FastHashMap::default(),
+ color_properties: FastHashMap::default(),
current_properties: DynamicProperties::default(),
pending_properties: None,
}
@@ -78,6 +80,11 @@ impl SceneProperties {
.insert(property.key.id, property.value);
}
+ for property in &pending_properties.colors {
+ self.color_properties
+ .insert(property.key.id, property.value);
+ }
+
self.current_properties = pending_properties.clone();
properties_changed = true;
}
@@ -121,6 +128,27 @@ impl SceneProperties {
pub fn float_properties(&self) -> &FastHashMap {
&self.float_properties
}
+
+ /// Get the current value for a color property.
+ pub fn resolve_color(
+ &self,
+ property: &PropertyBinding
+ ) -> ColorF {
+ match *property {
+ PropertyBinding::Value(value) => value,
+ PropertyBinding::Binding(ref key, v) => {
+ self.color_properties
+ .get(&key.id)
+ .cloned()
+ .unwrap_or(v)
+ }
+ }
+ }
+
+ pub fn color_properties(&self) -> &FastHashMap {
+ &self.color_properties
+ }
+
}
/// A representation of the layout within the display port for a given document or iframe.
diff --git a/webrender/src/scene_building.rs b/webrender/src/scene_building.rs
index 2991c757ea..04c3ad498b 100644
--- a/webrender/src/scene_building.rs
+++ b/webrender/src/scene_building.rs
@@ -31,7 +31,7 @@ use crate::prim_store::{register_prim_chase_id, get_line_decoration_size};
use crate::prim_store::{SpaceSnapper};
use crate::prim_store::backdrop::Backdrop;
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
-use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, ConicGradientAngle};
+use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, ConicGradientParams};
use crate::prim_store::image::{Image, YuvImage};
use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey};
use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey};
@@ -1214,7 +1214,7 @@ impl<'a> SceneBuilder<'a> {
self.add_solid_rectangle(
clip_and_scroll,
&layout,
- ColorF::TRANSPARENT,
+ PropertyBinding::Value(ColorF::TRANSPARENT),
);
}
DisplayItem::ClearRectangle(ref info) => {
@@ -1326,6 +1326,8 @@ impl<'a> SceneBuilder<'a> {
&layout,
info.gradient.center,
info.gradient.angle,
+ info.gradient.start_offset,
+ info.gradient.end_offset,
item.gradient_stops(),
info.gradient.extend_mode,
tile_size,
@@ -2749,13 +2751,19 @@ impl<'a> SceneBuilder<'a> {
&mut self,
clip_and_scroll: ScrollNodeAndClipChain,
info: &LayoutPrimitiveInfo,
- color: ColorF,
+ color: PropertyBinding,
) {
- if color.a == 0.0 {
- // Don't add transparent rectangles to the draw list, but do consider them for hit
- // testing. This allows specifying invisible hit testing areas.
- self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
- return;
+ match color {
+ PropertyBinding::Value(value) => {
+ if value.a == 0.0 {
+ // Don't add transparent rectangles to the draw list,
+ // but do consider them for hit testing. This allows
+ // specifying invisible hit testing areas.
+ self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+ return;
+ }
+ },
+ PropertyBinding::Binding(..) => {},
}
self.add_primitive(
@@ -2934,6 +2942,8 @@ impl<'a> SceneBuilder<'a> {
&info,
gradient.center,
gradient.angle,
+ gradient.start_offset,
+ gradient.end_offset,
gradient_stops,
gradient.extend_mode,
LayoutSize::new(border.height as f32, border.width as f32),
@@ -3066,6 +3076,8 @@ impl<'a> SceneBuilder<'a> {
info: &LayoutPrimitiveInfo,
center: LayoutPoint,
angle: f32,
+ start_offset: f32,
+ end_offset: f32,
stops: ItemRange,
extend_mode: ExtendMode,
stretch_size: LayoutSize,
@@ -3085,7 +3097,7 @@ impl<'a> SceneBuilder<'a> {
ConicGradient {
extend_mode,
center: center.into(),
- angle: ConicGradientAngle { angle },
+ params: ConicGradientParams { angle, start_offset, end_offset },
stretch_size: stretch_size.into(),
tile_spacing: tile_spacing.into(),
nine_patch,
diff --git a/webrender_api/src/api.rs b/webrender_api/src/api.rs
index 302ab3d2e9..3a2d5b6d61 100644
--- a/webrender_api/src/api.rs
+++ b/webrender_api/src/api.rs
@@ -1154,7 +1154,7 @@ pub enum PrimitiveKeyKind {
///
Rectangle {
///
- color: ColorU,
+ color: PropertyBinding,
},
}
@@ -1427,6 +1427,10 @@ bitflags! {
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;
}
}
@@ -1821,7 +1825,7 @@ impl PropertyBindingId {
/// 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, PartialEq, Serialize, PeekPoke)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
pub struct PropertyBindingKey {
///
pub id: PropertyBindingId,
@@ -1853,7 +1857,7 @@ impl PropertyBindingKey {
/// 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, PartialEq, Serialize, PeekPoke)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
pub enum PropertyBinding {
/// Non-animated value.
Value(T),
@@ -1873,6 +1877,46 @@ impl From for PropertyBinding {
}
}
+impl From> for PropertyBindingKey {
+ fn from(key: PropertyBindingKey) -> PropertyBindingKey {
+ PropertyBindingKey {
+ id: key.id.clone(),
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl From> for PropertyBindingKey {
+ fn from(key: PropertyBindingKey) -> PropertyBindingKey {
+ PropertyBindingKey {
+ id: key.id.clone(),
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl From> for PropertyBinding {
+ fn from(value: PropertyBinding) -> PropertyBinding {
+ match value {
+ PropertyBinding::Value(value) => PropertyBinding::Value(value.into()),
+ PropertyBinding::Binding(k, v) => {
+ PropertyBinding::Binding(k.into(), v.into())
+ }
+ }
+ }
+}
+
+impl From> for PropertyBinding {
+ fn from(value: PropertyBinding) -> PropertyBinding {
+ match value {
+ PropertyBinding::Value(value) => PropertyBinding::Value(value.into()),
+ PropertyBinding::Binding(k, v) => {
+ PropertyBinding::Binding(k.into(), v.into())
+ }
+ }
+ }
+}
+
/// The current value of an animated property. This is
/// supplied by the calling code.
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
@@ -1890,8 +1934,10 @@ pub struct PropertyValue {
pub struct DynamicProperties {
///
pub transforms: Vec>,
- ///
+ /// opacity
pub floats: Vec>,
+ /// background color
+ pub colors: Vec>,
}
/// A handler to integrate WebRender with the thread that contains the `Renderer`.
diff --git a/webrender_api/src/display_item.rs b/webrender_api/src/display_item.rs
index 3a3e1d9ad2..c87249df6d 100644
--- a/webrender_api/src/display_item.rs
+++ b/webrender_api/src/display_item.rs
@@ -299,11 +299,11 @@ pub struct ScrollFrameDisplayItem {
pub external_scroll_offset: LayoutVector2D,
}
-/// A solid color to draw (may not actually be a rectangle due to complex clips)
+/// 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 color: ColorF,
+ pub color: PropertyBinding,
}
/// Clears all colors from the area, making it possible to cut holes in the window.
diff --git a/webrender_api/src/display_list.rs b/webrender_api/src/display_list.rs
index 8d0a27d0e5..2f7d3f1aff 100644
--- a/webrender_api/src/display_list.rs
+++ b/webrender_api/src/display_list.rs
@@ -1116,7 +1116,19 @@ impl DisplayListBuilder {
) {
let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem {
common: *common,
- color
+ color: PropertyBinding::Value(color),
+ });
+ self.push_item(&item);
+ }
+
+ pub fn push_rect_with_animation(
+ &mut self,
+ common: &di::CommonItemProperties,
+ color: PropertyBinding,
+ ) {
+ let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem {
+ common: *common,
+ color,
});
self.push_item(&item);
}
diff --git a/webrender_api/src/font.rs b/webrender_api/src/font.rs
index 7363d81624..442f64cd60 100644
--- a/webrender_api/src/font.rs
+++ b/webrender_api/src/font.rs
@@ -179,6 +179,7 @@ bitflags! {
const FLIP_X = 1 << 5;
const FLIP_Y = 1 << 6;
const SUBPIXEL_POSITION = 1 << 7;
+ const VERTICAL = 1 << 8;
// Windows flags
const FORCE_GDI = 1 << 16;
diff --git a/webrender_api/src/gradient_builder.rs b/webrender_api/src/gradient_builder.rs
index ef3dec8d16..883acbafa3 100644
--- a/webrender_api/src/gradient_builder.rs
+++ b/webrender_api/src/gradient_builder.rs
@@ -106,8 +106,6 @@ impl GradientBuilder {
angle: f32,
extend_mode: di::ExtendMode,
) -> di::ConicGradient {
- // XXX(ntim): Possibly handle negative angles ?
-
let (start_offset, end_offset) =
self.normalize(extend_mode);
diff --git a/webrender_api/src/units.rs b/webrender_api/src/units.rs
index 9ae21f13b4..00546acd70 100644
--- a/webrender_api/src/units.rs
+++ b/webrender_api/src/units.rs
@@ -170,6 +170,15 @@ impl TexelRect {
}
}
+impl Into 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 {
diff --git a/wrench/reftests/filters/filter-drop-shadow-scaled-ref.yaml b/wrench/reftests/filters/filter-drop-shadow-scaled-ref.yaml
new file mode 100644
index 0000000000..9173528be4
--- /dev/null
+++ b/wrench/reftests/filters/filter-drop-shadow-scaled-ref.yaml
@@ -0,0 +1,11 @@
+# Ensure scales from enclosing SCs get applied to drop-shadows
+---
+root:
+ items:
+ - type: stacking-context
+ bounds: [0, 0, 500, 500]
+ filters: drop-shadow([0, 0], 10, [255, 0, 0, 1])
+ items:
+ - type: rect
+ bounds: [50, 50, 250, 250]
+ color: 0 255 0 1.0
diff --git a/wrench/reftests/filters/filter-drop-shadow-scaled.yaml b/wrench/reftests/filters/filter-drop-shadow-scaled.yaml
new file mode 100644
index 0000000000..87fac1ee6d
--- /dev/null
+++ b/wrench/reftests/filters/filter-drop-shadow-scaled.yaml
@@ -0,0 +1,15 @@
+# Ensure scales from enclosing SCs get applied to drop-shadows
+---
+root:
+ items:
+ - type: reference-frame
+ bounds: [0, 0, 100, 100]
+ transform: [5, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
+ items:
+ - type: stacking-context
+ bounds: [0, 0, 400, 400]
+ filters: drop-shadow([0, 0], 2, [255, 0, 0, 1])
+ items:
+ - type: rect
+ bounds: [10, 10, 50, 50]
+ color: 0 255 0 1.0
diff --git a/wrench/reftests/filters/reftest.list b/wrench/reftests/filters/reftest.list
index ba15f50453..7222c7b5c4 100644
--- a/wrench/reftests/filters/reftest.list
+++ b/wrench/reftests/filters/reftest.list
@@ -36,6 +36,7 @@ platform(linux,mac) == filter-drop-shadow-on-viewport-edge.yaml filter-drop-shad
platform(linux,mac) == blend-clipped.yaml blend-clipped.png
platform(linux,mac) == filter-drop-shadow-clip.yaml filter-drop-shadow-clip.png
platform(linux,mac) == filter-drop-shadow-clip-2.yaml filter-drop-shadow-clip-2.png
+fuzzy(5,100000) == filter-drop-shadow-scaled.yaml filter-drop-shadow-scaled-ref.yaml
== filter-segments.yaml filter-segments-ref.yaml
== iframe-dropshadow.yaml iframe-dropshadow-ref.yaml
skip_on(android,device) == filter-mix-blend-mode.yaml filter-mix-blend-mode-ref.yaml # fails on Pixel2
diff --git a/wrench/reftests/gradient/reftest.list b/wrench/reftests/gradient/reftest.list
index 8b5789964f..79fef2120c 100644
--- a/wrench/reftests/gradient/reftest.list
+++ b/wrench/reftests/gradient/reftest.list
@@ -42,9 +42,8 @@ platform(linux,mac) fuzzy(1,80000) == radial-ellipse.yaml radial-ellipse-ref.png
== norm-conic-1.yaml norm-conic-1-ref.yaml
== norm-conic-2.yaml norm-conic-2-ref.yaml
-# Bug 1616255 - These should pass
-# == norm-conic-3.yaml norm-conic-3-ref.yaml
-# == norm-conic-4.yaml norm-conic-4-ref.yaml
+== norm-conic-3.yaml norm-conic-3-ref.yaml
+== norm-conic-4.yaml norm-conic-4-ref.yaml
== norm-conic-degenerate.yaml norm-conic-degenerate-ref.yaml
# fuzzy because of differences from normalization
@@ -53,9 +52,8 @@ fuzzy(255,1200) == repeat-linear.yaml repeat-linear-ref.yaml
fuzzy(255,1200) == repeat-linear-reverse.yaml repeat-linear-ref.yaml
fuzzy(255,2664) == repeat-radial.yaml repeat-radial-ref.yaml
fuzzy(255,2664) == repeat-radial-negative.yaml repeat-radial-ref.yaml
-# Bug 1616255 - These should pass
-# == repeat-conic.yaml repeat-conic-ref.yaml
-# == repeat-conic-negative.yaml repeat-conic-ref.yaml
+fuzzy(255,1652) == repeat-conic.yaml repeat-conic-ref.yaml
+fuzzy(255,1652) == repeat-conic-negative.yaml repeat-conic-ref.yaml
# fuzzy because of thin spaced out column of pixels that are 1 off
fuzzy(1,83164) == tiling-linear-1.yaml tiling-linear-1-ref.yaml
diff --git a/wrench/reftests/text/large-line-decoration.yaml b/wrench/reftests/text/large-line-decoration.yaml
new file mode 100644
index 0000000000..ec12a5dc31
--- /dev/null
+++ b/wrench/reftests/text/large-line-decoration.yaml
@@ -0,0 +1,43 @@
+---
+root:
+ items:
+ - type: line
+ baseline: 0
+ start: 0
+ end: 50
+ width: 5000
+ thickness: 5000
+ orientation: horizontal
+ color: red
+ style: solid
+
+ - type: line
+ baseline: 0
+ start: 100
+ end: 150
+ width: 5000
+ thickness: 5000
+ orientation: horizontal
+ color: green
+ style: dashed
+
+ - type: line
+ baseline: 0
+ start: 200
+ end: 250
+ width: 5000
+ thickness: 5000
+ orientation: horizontal
+ color: blue
+ style: dotted
+
+ - type: line
+ baseline: 0
+ start: 300
+ end: 350
+ width: 5000
+ thickness: 5000
+ orientation: horizontal
+ color: yellow
+ style: wavy
+
diff --git a/wrench/reftests/text/raster_root_C_8192.yaml b/wrench/reftests/text/raster_root_C_8192.yaml
new file mode 100644
index 0000000000..016d481504
--- /dev/null
+++ b/wrench/reftests/text/raster_root_C_8192.yaml
@@ -0,0 +1,385 @@
+root:
+ items:
+ - type: "stacking-context"
+ transform: scale(0.125)
+ items:
+ - type: "stacking-context"
+ perspective: 100
+ perspective-origin: 100 50
+ items:
+ - image: checkerboard(0, 512, 16);
+ bounds: [1600, 1600, 8192, 8192]
+ - type: rect
+ color: [180, 140, 120, 0.4]
+ bounds: 2400 2400 8192 8192
+ - type: "stacking-context"
+ bounds: [0, 0, 8192, 8192]
+ filters: [invert(1)]
+ mix-blend-mode: exclusion
+ complex-clip:
+ rect: [1920, 1920, 4096, 4096]
+ radius: [2048, 2048]
+ items:
+ - type: "stacking-context"
+ transform: scale(24)
+ items:
+ - type: line
+ baseline: 16
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 24
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 32
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 40
+ start: 16
+ end: 208
+ width: 4
+ thickness: 1
+ orientation: horizontal
+ color: red
+ style: wavy
+
+ - type: line
+ baseline: 48
+ start: 16
+ end: 208
+ width: 2
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 64
+ start: 16
+ end: 208
+ width: 2
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 80
+ start: 16
+ end: 207 # pruposefully cut off
+ width: 2
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 96
+ start: 16
+ end: 208
+ width: 6
+ thickness: 2
+ orientation: horizontal
+ color: red
+ style: wavy
+
+ -
+ type: "shadow"
+ bounds: [8, 100, 225, 50]
+ blur-radius: 0
+ offset: [2, 2]
+ color: red
+ - type: line
+ baseline: 112
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: [0,0,0,0]
+ style: solid
+ - type: line
+ baseline: 120
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: [0,0,0,0]
+ style: dashed
+ - type: line
+ baseline: 128
+ start: 16
+ end: 209
+ width: 1
+ orientation: horizontal
+ color: [0,0,0,0]
+ style: dotted
+ - type: line
+ baseline: 136
+ start: 16
+ end: 208
+ width: 4
+ thickness: 1
+ orientation: horizontal
+ color: [0,0,0,0]
+ style: wavy
+ -
+ type: pop-all-shadows
+
+ -
+ type: "shadow"
+ bounds: [8, 145, 225, 65]
+ blur-radius: 1
+ offset: [2, 3]
+ color: red
+ - type: line
+ baseline: 160
+ start: 16
+ end: 208
+ width: 2
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 168
+ start: 16
+ end: 208
+ width: 2
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 184
+ start: 16
+ end: 207 # purposefully cut off
+ width: 2
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 192
+ start: 16
+ end: 208
+ width: 6
+ thickness: 2
+ orientation: horizontal
+ color: red
+ style: wavy
+ -
+ type: pop-all-shadows
+
+ -
+ type: "shadow"
+ bounds: [8, 220, 225, 40]
+ blur-radius: 0
+ offset: [5, 7]
+ color: red
+ - type: line
+ baseline: 232
+ start: 16
+ end: 208
+ width: 8
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 248
+ start: 16
+ end: 208
+ width: 8
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 272
+ start: 16
+ end: 205 # purposefully cut off
+ width: 8
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 296
+ start: 16
+ end: 208
+ width: 12
+ thickness: 3
+ orientation: horizontal
+ color: black
+ style: wavy
+ -
+ type: "pop-all-shadows"
+
+ -
+ type: "shadow"
+ bounds: [0, 320, 240, 140]
+ blur-radius: 3
+ offset: [5, 7]
+ color: red
+ - type: line
+ baseline: 320
+ start: 16
+ end: 208
+ width: 8
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 352
+ start: 16
+ end: 208
+ width: 8
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 368
+ start: 16
+ end: 205 # purposefully cut off
+ width: 8
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 392
+ start: 16
+ end: 208
+ width: 16
+ thickness: 4
+ orientation: horizontal
+ color: black
+ style: wavy
+ -
+ type: "pop-all-shadows"
+
+ - type: line
+ baseline: 224
+ start: 16
+ end: 208
+ width: 1
+ orientation: vertical
+ color: black
+ style: solid
+ - type: line
+ baseline: 232
+ start: 16
+ end: 208
+ width: 1
+ orientation: vertical
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 240
+ start: 16
+ end: 208
+ width: 1
+ orientation: vertical
+ color: green
+ style: dotted
+ - type: line
+ baseline: 256
+ start: 16
+ end: 208
+ thickness: 1
+ width: 4
+ orientation: vertical
+ color: red
+ style: wavy
+
+ - type: line
+ baseline: 272
+ start: 16
+ end: 208
+ width: 2
+ orientation: vertical
+ color: black
+ style: solid
+ - type: line
+ baseline: 296
+ start: 16
+ end: 208
+ width: 2
+ orientation: vertical
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 320
+ start: 16
+ end: 207 # purposefully cut off
+ width: 2
+ orientation: vertical
+ color: green
+ style: dotted
+ - type: line
+ baseline: 336
+ start: 16
+ end: 208
+ thickness: 2
+ width: 6
+ orientation: vertical
+ color: red
+ style: wavy
+
+ -
+ type: "shadow"
+ bounds: [350, 0, 120, 240]
+ blur-radius: 3
+ offset: [5, 2]
+ color: black
+ - type: line
+ baseline: 384
+ start: 16
+ end: 208
+ width: 8
+ orientation: vertical
+ color: yellow
+ style: solid
+ - type: line
+ baseline: 400
+ start: 16
+ end: 208
+ width: 8
+ orientation: vertical
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 416
+ start: 16
+ end: 205 # purposefully cut off
+ width: 8
+ orientation: vertical
+ color: green
+ style: dotted
+ - type: line
+ baseline: 440
+ start: 16
+ end: 208
+ thickness: 4
+ width: 16
+ orientation: vertical
+ color: red
+ style: wavy
+ -
+ type: "pop-all-shadows"
+ - text: "side-left"
+ origin: 80 120
+ size: 32
+ transpose: true
+ flip-x: true
+ font: "VeraBd.ttf"
+ color: [40,40,40,1.0]
+ - text: "side-right"
+ origin: 240 240
+ size: 32
+ color: [190,180,200,1.0]
+ transpose: true
+ flip-y: true
+ font: "FreeSans.ttf"
+
diff --git a/wrench/reftests/text/raster_root_C_ref.yaml b/wrench/reftests/text/raster_root_C_ref.yaml
new file mode 100644
index 0000000000..2712186a77
--- /dev/null
+++ b/wrench/reftests/text/raster_root_C_ref.yaml
@@ -0,0 +1,385 @@
+root:
+ items:
+ - type: "stacking-context"
+ transform: scale(0.5)
+ items:
+ - type: "stacking-context"
+ perspective: 100
+ perspective-origin: 100 50
+ items:
+ - image: checkerboard(0, 128, 16);
+ bounds: [400, 400, 2048, 2048]
+ - type: rect
+ color: [180, 140, 120, 0.4]
+ bounds: 600 600 2048 2048
+ - type: "stacking-context"
+ bounds: [0, 0, 2048, 2048]
+ filters: [invert(1)]
+ mix-blend-mode: exclusion
+ complex-clip:
+ rect: [480, 480, 1024, 1024]
+ radius: [512, 512]
+ items:
+ - type: "stacking-context"
+ transform: scale(6)
+ items:
+ - type: line
+ baseline: 16
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 24
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 32
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 40
+ start: 16
+ end: 208
+ width: 4
+ thickness: 1
+ orientation: horizontal
+ color: red
+ style: wavy
+
+ - type: line
+ baseline: 48
+ start: 16
+ end: 208
+ width: 2
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 64
+ start: 16
+ end: 208
+ width: 2
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 80
+ start: 16
+ end: 207 # pruposefully cut off
+ width: 2
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 96
+ start: 16
+ end: 208
+ width: 6
+ thickness: 2
+ orientation: horizontal
+ color: red
+ style: wavy
+
+ -
+ type: "shadow"
+ bounds: [8, 100, 225, 50]
+ blur-radius: 0
+ offset: [2, 2]
+ color: red
+ - type: line
+ baseline: 112
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: [0,0,0,0]
+ style: solid
+ - type: line
+ baseline: 120
+ start: 16
+ end: 208
+ width: 1
+ orientation: horizontal
+ color: [0,0,0,0]
+ style: dashed
+ - type: line
+ baseline: 128
+ start: 16
+ end: 209
+ width: 1
+ orientation: horizontal
+ color: [0,0,0,0]
+ style: dotted
+ - type: line
+ baseline: 136
+ start: 16
+ end: 208
+ width: 4
+ thickness: 1
+ orientation: horizontal
+ color: [0,0,0,0]
+ style: wavy
+ -
+ type: pop-all-shadows
+
+ -
+ type: "shadow"
+ bounds: [8, 145, 225, 65]
+ blur-radius: 1
+ offset: [2, 3]
+ color: red
+ - type: line
+ baseline: 160
+ start: 16
+ end: 208
+ width: 2
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 168
+ start: 16
+ end: 208
+ width: 2
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 184
+ start: 16
+ end: 207 # purposefully cut off
+ width: 2
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 192
+ start: 16
+ end: 208
+ width: 6
+ thickness: 2
+ orientation: horizontal
+ color: red
+ style: wavy
+ -
+ type: pop-all-shadows
+
+ -
+ type: "shadow"
+ bounds: [8, 220, 225, 40]
+ blur-radius: 0
+ offset: [5, 7]
+ color: red
+ - type: line
+ baseline: 232
+ start: 16
+ end: 208
+ width: 8
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 248
+ start: 16
+ end: 208
+ width: 8
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 272
+ start: 16
+ end: 205 # purposefully cut off
+ width: 8
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 296
+ start: 16
+ end: 208
+ width: 12
+ thickness: 3
+ orientation: horizontal
+ color: black
+ style: wavy
+ -
+ type: "pop-all-shadows"
+
+ -
+ type: "shadow"
+ bounds: [0, 320, 240, 140]
+ blur-radius: 3
+ offset: [5, 7]
+ color: red
+ - type: line
+ baseline: 320
+ start: 16
+ end: 208
+ width: 8
+ orientation: horizontal
+ color: black
+ style: solid
+ - type: line
+ baseline: 352
+ start: 16
+ end: 208
+ width: 8
+ orientation: horizontal
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 368
+ start: 16
+ end: 205 # purposefully cut off
+ width: 8
+ orientation: horizontal
+ color: green
+ style: dotted
+ - type: line
+ baseline: 392
+ start: 16
+ end: 208
+ width: 16
+ thickness: 4
+ orientation: horizontal
+ color: black
+ style: wavy
+ -
+ type: "pop-all-shadows"
+
+ - type: line
+ baseline: 224
+ start: 16
+ end: 208
+ width: 1
+ orientation: vertical
+ color: black
+ style: solid
+ - type: line
+ baseline: 232
+ start: 16
+ end: 208
+ width: 1
+ orientation: vertical
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 240
+ start: 16
+ end: 208
+ width: 1
+ orientation: vertical
+ color: green
+ style: dotted
+ - type: line
+ baseline: 256
+ start: 16
+ end: 208
+ thickness: 1
+ width: 4
+ orientation: vertical
+ color: red
+ style: wavy
+
+ - type: line
+ baseline: 272
+ start: 16
+ end: 208
+ width: 2
+ orientation: vertical
+ color: black
+ style: solid
+ - type: line
+ baseline: 296
+ start: 16
+ end: 208
+ width: 2
+ orientation: vertical
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 320
+ start: 16
+ end: 207 # purposefully cut off
+ width: 2
+ orientation: vertical
+ color: green
+ style: dotted
+ - type: line
+ baseline: 336
+ start: 16
+ end: 208
+ thickness: 2
+ width: 6
+ orientation: vertical
+ color: red
+ style: wavy
+
+ -
+ type: "shadow"
+ bounds: [350, 0, 120, 240]
+ blur-radius: 3
+ offset: [5, 2]
+ color: black
+ - type: line
+ baseline: 384
+ start: 16
+ end: 208
+ width: 8
+ orientation: vertical
+ color: yellow
+ style: solid
+ - type: line
+ baseline: 400
+ start: 16
+ end: 208
+ width: 8
+ orientation: vertical
+ color: blue
+ style: dashed
+ - type: line
+ baseline: 416
+ start: 16
+ end: 205 # purposefully cut off
+ width: 8
+ orientation: vertical
+ color: green
+ style: dotted
+ - type: line
+ baseline: 440
+ start: 16
+ end: 208
+ thickness: 4
+ width: 16
+ orientation: vertical
+ color: red
+ style: wavy
+ -
+ type: "pop-all-shadows"
+ - text: "side-left"
+ origin: 80 120
+ size: 32
+ transpose: true
+ flip-x: true
+ font: "VeraBd.ttf"
+ color: [40,40,40,1.0]
+ - text: "side-right"
+ origin: 240 240
+ size: 32
+ color: [190,180,200,1.0]
+ transpose: true
+ flip-y: true
+ font: "FreeSans.ttf"
+
diff --git a/wrench/reftests/text/reftest.list b/wrench/reftests/text/reftest.list
index 3f045bc4a6..42f1180c79 100644
--- a/wrench/reftests/text/reftest.list
+++ b/wrench/reftests/text/reftest.list
@@ -66,6 +66,7 @@ fuzzy(1,113) platform(linux) == raster-space.yaml raster-space.png
skip_on(android) skip_on(mac,>=10.14) != allow-subpixel.yaml allow-subpixel-ref.yaml # Android: we don't enable sub-px aa on this platform.
skip_on(android,device) == bg-color.yaml bg-color-ref.yaml # Fails on Pixel2
!= large-glyphs.yaml blank.yaml
+!= large-line-decoration.yaml blank.yaml
skip_on(android,device) == snap-text-offset.yaml snap-text-offset-ref.yaml
fuzzy(5,4435) == shadow-border.yaml shadow-solid-ref.yaml
fuzzy(5,4435) == shadow-image.yaml shadow-solid-ref.yaml
@@ -75,3 +76,9 @@ fuzzy(1,39) options(disable-subpixel) == raster-space-snap.yaml raster-space-sna
# == intermediate-transform.yaml intermediate-transform-ref.yaml # fails because of AA inavailable with an intermediate surface
platform(linux) allow_sacrificing_subpixel_aa(false) == text-fixed-slice.yaml text-fixed-slice-slow.png
platform(linux) allow_sacrificing_subpixel_aa(true) == text-fixed-slice.yaml text-fixed-slice-fast.png
+
+# a 8544x8544 raster root vs. 2136x2136
+# most pixels are off by a small amount, but a few pixels on the edge vary by a lot, pushing up the fuzzy max-diff;
+# the main goal of the test is that everything is in the same place, at the same scale, clipped the same way,
+# despite 4x on-the-fly scale change.
+skip_on(android) fuzzy-range(<=3,*20569,<=20,*2525,<=115,*543) == raster_root_C_8192.yaml raster_root_C_ref.yaml
diff --git a/wrench/reftests/transforms/raster_root_A_8192.yaml b/wrench/reftests/transforms/raster_root_A_8192.yaml
new file mode 100644
index 0000000000..034631d031
--- /dev/null
+++ b/wrench/reftests/transforms/raster_root_A_8192.yaml
@@ -0,0 +1,20 @@
+root:
+ items:
+ - type: "stacking-context"
+ transform: scale(0.125)
+ items:
+ - type: "stacking-context"
+ perspective: 100
+ perspective-origin: 100 50
+ items:
+ - image: checkerboard(0, 512, 16);
+ bounds: [1600, 1600, 8192, 8192]
+ - type: "stacking-context"
+ bounds: [0, 0, 8192, 8192]
+ mix-blend-mode: difference
+ complex-clip:
+ rect: [2048, 2048, 4096, 4096]
+ radius: [1024, 1024]
+ items:
+ - image: checkerboard(0, 4096, 2);
+ bounds: [0, 0, 8192, 8192]
diff --git a/wrench/reftests/transforms/raster_root_A_ref.yaml b/wrench/reftests/transforms/raster_root_A_ref.yaml
new file mode 100644
index 0000000000..b5e28256bf
--- /dev/null
+++ b/wrench/reftests/transforms/raster_root_A_ref.yaml
@@ -0,0 +1,20 @@
+root:
+ items:
+ - type: "stacking-context"
+ transform: scale(0.5)
+ items:
+ - type: "stacking-context"
+ perspective: 100
+ perspective-origin: 100 50
+ items:
+ - image: checkerboard(0, 128, 16);
+ bounds: 400 400 2048 2048
+ - type: "stacking-context"
+ bounds: [0, 0, 2048, 2048]
+ mix-blend-mode: difference
+ complex-clip:
+ rect: [512, 512, 1024, 1024]
+ radius: [256, 256]
+ items:
+ - image: checkerboard(0, 1024, 2);
+ bounds: [0, 0, 2048, 2048]
diff --git a/wrench/reftests/transforms/raster_root_B_8192.yaml b/wrench/reftests/transforms/raster_root_B_8192.yaml
new file mode 100644
index 0000000000..9f8a58f5cc
--- /dev/null
+++ b/wrench/reftests/transforms/raster_root_B_8192.yaml
@@ -0,0 +1,14 @@
+root:
+ items:
+ - type: stacking-context
+ bounds: [0, 0, 600, 600]
+ perspective: 100
+ items:
+ - type: stacking-context
+ transform: rotate-z(20) rotate-x(60)
+ filters: [invert(1)]
+ mix-blend-mode: difference
+ items:
+ - type: rect
+ bounds: [0, 0, 20000, 100]
+ color: [20, 120, 18, 1.0]
diff --git a/wrench/reftests/transforms/raster_root_B_ref.yaml b/wrench/reftests/transforms/raster_root_B_ref.yaml
new file mode 100644
index 0000000000..3fea3a19db
--- /dev/null
+++ b/wrench/reftests/transforms/raster_root_B_ref.yaml
@@ -0,0 +1,14 @@
+root:
+ items:
+ - type: stacking-context
+ bounds: [0, 0, 600, 600]
+ perspective: 100
+ items:
+ - type: stacking-context
+ transform: rotate-z(20) rotate-x(60)
+ filters: [invert(1)]
+ mix-blend-mode: difference
+ items:
+ - type: rect
+ bounds: [0, 0, 4000, 100]
+ color: [20, 120, 18, 1.0]
diff --git a/wrench/reftests/transforms/reftest.list b/wrench/reftests/transforms/reftest.list
index 8d2f0c3be2..3630e6bb31 100644
--- a/wrench/reftests/transforms/reftest.list
+++ b/wrench/reftests/transforms/reftest.list
@@ -42,3 +42,8 @@ platform(linux,mac) fuzzy(1,74) == border-scale-4.yaml border-scale-4.png
== flatten-twice.yaml flatten-twice-ref.yaml
== strange-w.yaml strange-w-ref.yaml
== big-axis-aligned-scale.yaml big-axis-aligned-scale-ref.yaml
+# Compare ~8K raster root (>MAX_SURFACE_SIZE) with ~2K raster root. fuzzy due to lerping on edges.
+skip_on(android) fuzzy-range(<=3,*3077,<=10,*133,<=93,*490) == raster_root_A_8192.yaml raster_root_A_ref.yaml
+# Same as large-raster-root.yaml but resulting in a 10302×100 raster root (= >4096) vs 4000x100 in ref:
+skip_on(android) fuzzy(60,917) == raster_root_B_8192.yaml raster_root_B_ref.yaml
+
diff --git a/wrench/src/parse_function.rs b/wrench/src/parse_function.rs
index 6e695547e1..6308232b09 100644
--- a/wrench/src/parse_function.rs
+++ b/wrench/src/parse_function.rs
@@ -4,12 +4,12 @@
use std::str::CharIndices;
-// support arguments like '4', 'ab', '4.0', '>=10.14'
+// support arguments like '4', 'ab', '4.0', '>=10.14', '*123'
fn acceptable_arg_character(c: char) -> bool {
- c.is_alphanumeric() || c == '.' || c == '-' || c == '<' || c == '>' || c == '='
+ c.is_alphanumeric() || c == '.' || c == '-' || c == '<' || c == '>' || c == '=' || c == '*'
}
-// A crapy parser for parsing strings like "translate(1, 3) blahblah"
+// A crappy parser for parsing strings like "translate(1, 3) blahblah"
// Returns a tuple with three components:
// - First component is the function name (e.g. "translate")
// - Second component is the list of arguments (e.g. vec!["1", "3"])
diff --git a/wrench/src/rawtest.rs b/wrench/src/rawtest.rs
index eeb3af87af..030ccac79a 100644
--- a/wrench/src/rawtest.rs
+++ b/wrench/src/rawtest.rs
@@ -68,7 +68,7 @@ impl<'a> RawtestHarness<'a> {
match image1.compare(&image2) {
ReftestImageComparison::Equal => {}
- ReftestImageComparison::NotEqual { max_difference, count_different } => {
+ ReftestImageComparison::NotEqual { max_difference, count_different, .. } => {
let t = "rawtest";
println!(
"{} | {} | {}: {}, {}: {}",
diff --git a/wrench/src/reftest.rs b/wrench/src/reftest.rs
index 98bc22451e..3fceda1eca 100644
--- a/wrench/src/reftest.rs
+++ b/wrench/src/reftest.rs
@@ -97,13 +97,17 @@ impl ExtraCheck {
}
}
+pub struct RefTestFuzzy {
+ max_difference: usize,
+ num_differences: usize,
+}
+
pub struct Reftest {
op: ReftestOp,
test: Vec,
reference: PathBuf,
font_render_mode: Option,
- max_difference: usize,
- num_differences: usize,
+ fuzziness: Vec,
extra_checks: Vec,
disable_dual_source_blending: bool,
allow_mipmaps: bool,
@@ -123,16 +127,100 @@ impl Reftest {
ReftestImageComparison::Equal => {
true
}
- ReftestImageComparison::NotEqual { max_difference, count_different } => {
- if max_difference > self.max_difference || count_different > self.num_differences {
+ ReftestImageComparison::NotEqual { difference_histogram, max_difference, count_different } => {
+ // Each entry in the sorted self.fuzziness list represents a bucket which
+ // allows at most num_differences pixels with a difference of at most
+ // max_difference -- but with the caveat that a difference which is small
+ // enough to be less than a max_difference of an earlier bucket, must be
+ // counted against that bucket.
+ //
+ // Thus the test will fail if the number of pixels with a difference
+ // > fuzzy[j-1].max_difference and <= fuzzy[j].max_difference
+ // exceeds fuzzy[j].num_differences.
+ //
+ // (For the first entry, consider fuzzy[j-1] to allow zero pixels of zero
+ // difference).
+ //
+ // For example, say we have this histogram of differences:
+ //
+ // | [0] [1] [2] [3] [4] [5] [6] ... [255]
+ // ------+------------------------------------------
+ // Hist. | 0 3 2 1 6 2 0 ... 0
+ //
+ // Ie. image comparison found 3 pixels that differ by 1, 2 that differ by 2, etc.
+ // (Note that entry 0 is always zero, we don't count matching pixels.)
+ //
+ // First we calculate an inclusive prefix sum:
+ //
+ // | [0] [1] [2] [3] [4] [5] [6] ... [255]
+ // ------+------------------------------------------
+ // Hist. | 0 3 2 1 6 2 0 ... 0
+ // Sum | 0 3 5 6 12 14 14 ... 14
+ //
+ // Let's say the fuzzy statements are:
+ // Fuzzy( 2, 6 ) -- allow up to 6 pixels that differ by 2 or less
+ // Fuzzy( 4, 8 ) -- allow up to 8 pixels that differ by 4 or less _but_
+ // also by more than 2 (= by 3 or 4).
+ //
+ // The first check is Sum[2] <= max 6 which passes: 5 <= 6.
+ // The second check is Sum[4] - Sum[2] <= max 8 which passes: 12-5 <= 8.
+ // Finally we check if there are any pixels that exceed the max difference (4)
+ // by checking Sum[255] - Sum[4] which shows there are 14-12 == 2 so we fail.
+
+ let prefix_sum = difference_histogram.iter()
+ .scan(0, |sum, i| { *sum += i; Some(*sum) })
+ .collect::>();
+
+ // check each fuzzy statement for violations.
+ assert_eq!(0, difference_histogram[0]);
+ assert_eq!(0, prefix_sum[0]);
+
+ // loop invariant: this is the max_difference of the previous iteration's 'fuzzy'
+ let mut previous_max_diff = 0;
+
+ // loop invariant: this is the number of pixels to ignore as they have been counted
+ // against previous iterations' fuzzy statements.
+ let mut previous_sum_fail = 0; // == prefix_sum[previous_max_diff]
+
+ let mut is_failing = false;
+ let mut fail_text = String::new();
+
+ for fuzzy in &self.fuzziness {
+ let fuzzy_max_difference = cmp::min(255, fuzzy.max_difference);
+ let num_differences = prefix_sum[fuzzy_max_difference] - previous_sum_fail;
+ if num_differences > fuzzy.num_differences {
+ fail_text.push_str(
+ &format!("{} differences > {} and <= {} (allowed {}); ",
+ num_differences,
+ previous_max_diff, fuzzy_max_difference,
+ fuzzy.num_differences));
+ is_failing = true;
+ }
+ previous_max_diff = fuzzy_max_difference;
+ previous_sum_fail = prefix_sum[previous_max_diff];
+ }
+ // do we have any pixels with a difference above the highest allowed
+ // max difference? if so, we fail the test:
+ let num_differences = prefix_sum[255] - previous_sum_fail;
+ if num_differences > 0 {
+ fail_text.push_str(
+ &format!("{} num_differences > {} and <= {} (allowed {}); ",
+ num_differences,
+ previous_max_diff, 255,
+ 0));
+ is_failing = true;
+ }
+
+ if is_failing {
println!(
- "{} | {} | {}: {}, {}: {}",
+ "{} | {} | {}: {}, {}: {} | {}",
"REFTEST TEST-UNEXPECTED-FAIL",
self,
"image comparison, max difference",
max_difference,
"number of differing pixels",
- count_different
+ count_different,
+ fail_text,
);
println!("REFTEST IMAGE 1 (TEST): {}", test.clone().create_data_uri());
println!(
@@ -175,10 +263,12 @@ pub struct ReftestImage {
pub size: DeviceIntSize,
}
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Clone)]
pub enum ReftestImageComparison {
Equal,
NotEqual {
+ /// entry[j] = number of pixels with a difference of exactly j
+ difference_histogram: Vec,
max_difference: usize,
count_different: usize,
},
@@ -190,6 +280,7 @@ impl ReftestImage {
assert_eq!(self.data.len(), other.data.len());
assert_eq!(self.data.len() % 4, 0);
+ let mut histogram = [0usize; 256];
let mut count = 0;
let mut max = 0;
@@ -202,12 +293,19 @@ impl ReftestImage {
.unwrap();
count += 1;
+ assert!(pixel_max < 256, "pixel values are not 8 bit, update the histogram binning code");
+ // deliberately avoid counting pixels that match --
+ // histogram[0] stays at zero.
+ // this helps our prefix sum later during analysis to
+ // only count actual differences.
+ histogram[pixel_max as usize] += 1;
max = cmp::max(max, pixel_max);
}
}
if count != 0 {
ReftestImageComparison::NotEqual {
+ difference_histogram: histogram.to_vec(),
max_difference: max,
count_different: count,
}
@@ -267,8 +365,7 @@ impl ReftestManifest {
let tokens: Vec<&str> = s.split_whitespace().collect();
- let mut max_difference = 0;
- let mut max_count = 0;
+ let mut fuzziness = Vec::new();
let mut op = None;
let mut font_render_mode = None;
let mut extra_checks = vec![];
@@ -314,10 +411,29 @@ impl ReftestManifest {
let (_, args, _) = parse_function(function);
allow_sacrificing_subpixel_aa = Some(args[0].parse().unwrap());
}
+ function if function.starts_with("fuzzy-range") => { // make sure this comes before 'fuzzy'
+ let (_, args, _) = parse_function(function);
+ let num_range = args.len() / 2;
+ for range in 0..num_range {
+ let mut max = args[range * 2 + 0];
+ let mut num = args[range * 2 + 1];
+ if max.starts_with("<=") { // trim_start_matches would allow <=<=123
+ max = &max[2..];
+ }
+ if num.starts_with("*") {
+ num = &num[1..];
+ }
+ let max_difference = max.parse().unwrap();
+ let num_differences = num.parse().unwrap();
+ fuzziness.push(RefTestFuzzy { max_difference, num_differences });
+ }
+ }
function if function.starts_with("fuzzy") => {
let (_, args, _) = parse_function(function);
- max_difference = args[0].parse().unwrap();
- max_count = args[1].parse().unwrap();
+ let max_difference = args[0].parse().unwrap();
+ let num_differences = args[1].parse().unwrap();
+ assert!(fuzziness.is_empty()); // if this fires, consider fuzzy-range instead
+ fuzziness.push(RefTestFuzzy { max_difference, num_differences });
}
function if function.starts_with("draw_calls") => {
let (_, args, _) = parse_function(function);
@@ -389,13 +505,38 @@ impl ReftestManifest {
let reference = paths.pop().unwrap();
let test = paths;
+ // to avoid changing the meaning of existing tests, the case of
+ // only a single (or no) 'fuzzy' keyword means we use the max
+ // of that fuzzy and options.allow_.. (we don't want that to
+ // turn into a test that allows fuzzy.allow_ *plus* options.allow_):
+ match fuzziness.len() {
+ 0 => fuzziness.push(RefTestFuzzy {
+ max_difference: options.allow_max_difference,
+ num_differences: options.allow_num_differences }),
+ 1 => {
+ let mut fuzzy = &mut fuzziness[0];
+ fuzzy.max_difference = cmp::max(fuzzy.max_difference, options.allow_max_difference);
+ fuzzy.num_differences = cmp::max(fuzzy.num_differences, options.allow_num_differences);
+ },
+ _ => {
+ // ignore options, use multiple fuzzy keywords instead. make sure
+ // the list is sorted to speed up counting violations.
+ fuzziness.sort_by(|a, b| a.max_difference.cmp(&b.max_difference));
+ for pair in fuzziness.windows(2) {
+ if pair[0].max_difference == pair[1].max_difference {
+ println!("Warning: repeated fuzzy of max_difference {} ignored.",
+ pair[1].max_difference);
+ }
+ }
+ }
+ }
+
reftests.push(Reftest {
op,
test,
reference,
font_render_mode,
- max_difference: cmp::max(max_difference, options.allow_max_difference),
- num_differences: cmp::max(max_count, options.allow_num_differences),
+ fuzziness,
extra_checks,
disable_dual_source_blending,
allow_mipmaps,
diff --git a/wrench/src/scene.rs b/wrench/src/scene.rs
index d2e708a9b3..818d000edc 100644
--- a/wrench/src/scene.rs
+++ b/wrench/src/scene.rs
@@ -14,6 +14,7 @@ use webrender::api::units::{LayoutSize, LayoutTransform};
pub struct SceneProperties {
transform_properties: HashMap,
float_properties: HashMap,
+ color_properties: HashMap,
}
impl SceneProperties {
@@ -21,6 +22,7 @@ impl SceneProperties {
pub fn set_properties(&mut self, properties: &DynamicProperties) {
self.transform_properties.clear();
self.float_properties.clear();
+ self.color_properties.clear();
for property in &properties.transforms {
self.transform_properties
@@ -31,6 +33,11 @@ impl SceneProperties {
self.float_properties
.insert(property.key.id, property.value);
}
+
+ for property in &properties.colors {
+ self.color_properties
+ .insert(property.key.id, property.value);
+ }
}
/// Get the current value for a transform property.
@@ -57,6 +64,17 @@ impl SceneProperties {
.unwrap_or(v),
}
}
+
+ /// Get the current value for a color property.
+ pub fn resolve_color(&self, property: &PropertyBinding) -> ColorF {
+ match *property {
+ PropertyBinding::Value(value) => value,
+ PropertyBinding::Binding(ref key, v) => self.color_properties
+ .get(&key.id)
+ .cloned()
+ .unwrap_or(v),
+ }
+ }
}
/// A representation of the layout within the display port for a given document or iframe.
diff --git a/wrench/src/yaml_frame_writer.rs b/wrench/src/yaml_frame_writer.rs
index c7b64055f5..6a1da76809 100644
--- a/wrench/src/yaml_frame_writer.rs
+++ b/wrench/src/yaml_frame_writer.rs
@@ -1002,7 +1002,13 @@ impl YamlFrameWriter {
DisplayItem::Rectangle(item) => {
str_node(&mut v, "type", "rect");
common_node(&mut v, clip_id_mapper, &item.common);
- color_node(&mut v, "color", item.color);
+
+ let key_label = match item.color {
+ PropertyBinding::Value(..) => "color",
+ PropertyBinding::Binding(..) => "animating-color",
+ };
+ color_node(&mut v, key_label,
+ scene.properties.resolve_color(&item.color));
}
DisplayItem::HitTest(item) => {
str_node(&mut v, "type", "hit-test");