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");