Bug 1618000: Part 2: Clamp blur radius based on scale factors r=gfx-reviewers,nical

Differential Revision: https://phabricator.services.mozilla.com/D65805

--HG--
extra : moz-landing-system : lando
This commit is contained in:
cbrewster 2020-03-23 18:22:32 +00:00
Родитель 0c74db8b9e
Коммит 34e8ddc337
14 изменённых файлов: 198 добавлений и 97 удалений

Просмотреть файл

@ -4,7 +4,6 @@
use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, PrimitiveKeyKind};
use api::PropertyBinding;
use api::MAX_BLUR_RADIUS;
use api::units::*;
use crate::clip::{ClipItemKey, ClipItemKeyKind};
use crate::scene_building::SceneBuilder;
@ -51,6 +50,10 @@ pub struct BoxShadowClipSource {
// The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
// Maximum blur radius for box-shadows (different than blur filters).
// Taken from nsCSSRendering.cpp in Gecko.
pub const MAX_BLUR_RADIUS: f32 = 300.;
// A cache key that uniquely identifies a minimally sized
// and blurred box-shadow rect that can be stored in the
// texture cache and applied to clip-masks.

Просмотреть файл

@ -323,6 +323,7 @@ impl FrameBuilder {
global_screen_world_rect,
&scene.spatial_tree,
global_device_pixel_scale,
(1.0, 1.0),
);
surfaces.push(root_surface);

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{ColorF, DebugCommand, DocumentId, ExternalImageData, ExternalImageId, PrimitiveFlags};
use api::{ImageFormat, ItemTag, NotificationRequest, Shadow, FilterOp, MAX_BLUR_RADIUS};
use api::{ImageFormat, ItemTag, NotificationRequest, Shadow, FilterOp};
use api::units::*;
use api;
use crate::composite::NativeSurfaceOperation;
@ -86,22 +86,6 @@ pub enum Filter {
}
impl Filter {
/// Ensure that the parameters for a filter operation
/// are sensible.
pub fn sanitize(&mut self) {
match self {
Filter::Blur(ref mut radius) => {
*radius = radius.min(MAX_BLUR_RADIUS);
}
Filter::DropShadows(ref mut stack) => {
for shadow in stack {
shadow.blur_radius = shadow.blur_radius.min(MAX_BLUR_RADIUS);
}
}
_ => {},
}
}
pub fn is_visible(&self) -> bool {
match *self {
Filter::Identity |

Просмотреть файл

@ -96,9 +96,9 @@
use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
use api::{DebugFlags, RasterSpace, ImageKey, ColorF, ColorU, PrimitiveFlags, MAX_BLUR_RADIUS};
use api::{DebugFlags, RasterSpace, ImageKey, ColorF, ColorU, PrimitiveFlags};
use api::units::*;
use crate::box_shadow::{BLUR_SAMPLE_SCALE};
use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::clip::{ClipStore, ClipChainInstance, ClipDataHandle, ClipChainId};
use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX,
SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace
@ -133,7 +133,7 @@ use smallvec::SmallVec;
use std::{mem, u8, marker, u32};
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::texture_cache::TextureCacheHandle;
use crate::util::{MaxRect, scale_factors, VecHelper, RectHelpers, MatrixHelpers};
use crate::util::{MaxRect, VecHelper, RectHelpers, MatrixHelpers};
use crate::filterdata::{FilterDataHandle};
#[cfg(any(feature = "capture", feature = "replay"))]
use ron;
@ -170,6 +170,10 @@ use crate::scene_building::{SliceFlags};
// used by tileview so don't use an internal_types FastHashMap
use std::collections::HashMap;
// Maximum blur radius for blur filter (different than box-shadow blur).
// Taken from FilterNodeSoftware.cpp in Gecko.
pub const MAX_BLUR_RADIUS: f32 = 100.;
/// Specify whether a surface allows subpixel AA text rendering.
#[derive(Debug, Clone, PartialEq)]
pub enum SubpixelMode {
@ -399,6 +403,20 @@ fn clampf(value: f32, low: f32, high: f32) -> f32 {
value.max(low).min(high)
}
/// Clamps the blur radius depending on scale factors.
fn clamp_blur_radius(blur_radius: f32, scale_factors: (f32, f32)) -> f32 {
// Clamping must occur after scale factors are applied, but scale factors are not applied
// until later on. To clamp the blur radius, we first apply the scale factors and then clamp
// and finally revert the scale factors.
// TODO: the clamping should be done on a per-axis basis, but WR currently only supports
// having a single value for both x and y blur.
let largest_scale_factor = f32::max(scale_factors.0, scale_factors.1);
let adjusted_blur_radius = blur_radius * largest_scale_factor;
let clamped_blur_radius = f32::min(adjusted_blur_radius, MAX_BLUR_RADIUS);
clamped_blur_radius / largest_scale_factor
}
/// An index into the prims array in a TileDescriptor.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
@ -3764,6 +3782,8 @@ pub struct SurfaceInfo {
pub inflation_factor: f32,
/// The device pixel ratio specific to this surface.
pub device_pixel_scale: DevicePixelScale,
/// The scale factors of the surface to raster transform.
pub scale_factors: (f32, f32),
}
impl SurfaceInfo {
@ -3774,6 +3794,7 @@ impl SurfaceInfo {
world_rect: WorldRect,
spatial_tree: &SpatialTree,
device_pixel_scale: DevicePixelScale,
scale_factors: (f32, f32),
) -> Self {
let map_surface_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
@ -3799,6 +3820,7 @@ impl SurfaceInfo {
surface_spatial_node_index,
inflation_factor,
device_pixel_scale,
scale_factors,
}
}
}
@ -3861,19 +3883,20 @@ pub enum PictureCompositeMode {
}
impl PictureCompositeMode {
pub fn inflate_picture_rect(&self, picture_rect: PictureRect, inflation_factor: f32) -> PictureRect {
pub fn inflate_picture_rect(&self, picture_rect: PictureRect, scale_factors: (f32, f32)) -> PictureRect {
let mut result_rect = picture_rect;
match self {
PictureCompositeMode::Filter(filter) => match filter {
Filter::Blur(_) => {
Filter::Blur(blur_radius) => {
let inflation_factor = clamp_blur_radius(*blur_radius, scale_factors).ceil() * BLUR_SAMPLE_SCALE;
result_rect = picture_rect.inflate(inflation_factor, inflation_factor);
},
Filter::DropShadows(shadows) => {
let mut max_inflation: f32 = 0.0;
for shadow in shadows {
let inflation_factor = shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE;
max_inflation = max_inflation.max(inflation_factor);
max_inflation = max_inflation.max(shadow.blur_radius);
}
max_inflation = clamp_blur_radius(max_inflation, scale_factors).ceil() * BLUR_SAMPLE_SCALE;
result_rect = picture_rect.inflate(max_inflation, max_inflation);
},
_ => {}
@ -4544,6 +4567,10 @@ impl PicturePrimitive {
.surfaces[raster_config.surface_index.0]
.device_pixel_scale;
let scale_factors = frame_state
.surfaces[raster_config.surface_index.0]
.scale_factors;
let (mut clipped, mut unclipped) = match get_raster_rects(
pic_rect,
&map_pic_to_raster,
@ -4617,15 +4644,14 @@ impl PicturePrimitive {
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 = clamp_blur_radius(blur_radius, scale_factors) * device_pixel_scale.0;
let mut blur_std_deviation = DeviceSize::new(
blur_std_deviation * scale_factors.0,
blur_std_deviation * scale_factors.1
);
let mut device_rect = if self.options.inflate_if_required {
let inflation_factor = frame_state.surfaces[raster_config.surface_index.0].inflation_factor;
let inflation_factor = (inflation_factor * device_pixel_scale.0).ceil();
let inflation_factor = inflation_factor * device_pixel_scale.0;
// The clipped field is the part of the picture that is visible
// on screen. The unclipped field is the screen-space rect of
@ -4638,7 +4664,7 @@ impl PicturePrimitive {
// We cast clipped to f32 instead of casting unclipped to i32
// because unclipped can overflow an i32.
let device_rect = clipped.to_f32()
.inflate(inflation_factor, inflation_factor)
.inflate(inflation_factor * scale_factors.0, inflation_factor * scale_factors.1)
.intersection(&unclipped)
.unwrap();
@ -4711,14 +4737,15 @@ impl PicturePrimitive {
for shadow in shadows {
// TODO(nical) presumably we should compute the clipped rect for each shadow
// and compute the union of them to determine what we need to rasterize and blur?
max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius * device_pixel_scale.0);
max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius);
}
max_std_deviation = clamp_blur_radius(max_std_deviation, scale_factors) * device_pixel_scale.0;
let max_blur_range = max_std_deviation * BLUR_SAMPLE_SCALE;
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.
let device_rect = clipped.to_f32()
.inflate(max_blur_range, max_blur_range)
.inflate(max_blur_range * scale_factors.0, max_blur_range * scale_factors.1)
.intersection(&unclipped)
.unwrap();
@ -4731,7 +4758,10 @@ impl PicturePrimitive {
device_rect.size = RenderTask::adjusted_blur_source_size(
device_rect.size,
DeviceSize::new(max_std_deviation, max_std_deviation),
DeviceSize::new(
max_std_deviation * scale_factors.0,
max_std_deviation * scale_factors.1
),
);
if let Some(scale) = adjust_scale_for_max_surface_size(
@ -4775,14 +4805,12 @@ 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 {
// 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.
let blur_radius = clamp_blur_radius(shadow.blur_radius, scale_factors) * device_pixel_scale.0;
blur_render_task_id = RenderTask::new_blur(
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,
blur_radius * scale_factors.0,
blur_radius * scale_factors.1,
),
picture_task_id,
frame_state.render_tasks,
@ -5602,33 +5630,6 @@ impl PicturePrimitive {
let parent_raster_node_index = state.current_surface().raster_spatial_node_index;
let surface_spatial_node_index = self.spatial_node_index;
// This inflation factor is to be applied to all primitives within the surface.
let inflation_factor = match composite_mode {
PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => {
// Only inflate if the caller hasn't already inflated
// the bounding rects for this filter.
if self.options.inflate_if_required {
// The amount of extra space needed for primitives inside
// this picture to ensure the visibility check is correct.
BLUR_SAMPLE_SCALE * blur_radius
} else {
0.0
}
}
PictureCompositeMode::SvgFilter(ref primitives, _) if self.options.inflate_if_required => {
let mut max = 0.0;
for primitive in primitives {
if let FilterPrimitiveKind::Blur(ref blur) = primitive.kind {
max = f32::max(max, blur.radius * BLUR_SAMPLE_SCALE);
}
}
max
}
_ => {
0.0
}
};
// Filters must be applied before transforms, to do this, we can mark this picture as establishing a raster root.
let has_svg_filter = if let PictureCompositeMode::SvgFilter(..) = composite_mode {
true
@ -5642,17 +5643,49 @@ impl PicturePrimitive {
.get_relative_transform(surface_spatial_node_index, parent_raster_node_index)
.is_perspective();
let raster_spatial_node_index = if establishes_raster_root {
surface_spatial_node_index
} else {
parent_raster_node_index
};
let scale_factors = frame_context
.spatial_tree
.get_relative_transform(surface_spatial_node_index, raster_spatial_node_index)
.scale_factors();
// This inflation factor is to be applied to all primitives within the surface.
// Only inflate if the caller hasn't already inflated the bounding rects for this filter.
let mut inflation_factor = 0.0;
if self.options.inflate_if_required {
match composite_mode {
PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => {
let blur_radius = clamp_blur_radius(blur_radius, scale_factors);
// The amount of extra space needed for primitives inside
// this picture to ensure the visibility check is correct.
inflation_factor = blur_radius * BLUR_SAMPLE_SCALE;
}
PictureCompositeMode::SvgFilter(ref primitives, _) => {
let mut max = 0.0;
for primitive in primitives {
if let FilterPrimitiveKind::Blur(ref blur) = primitive.kind {
max = f32::max(max, blur.radius);
}
}
inflation_factor = clamp_blur_radius(max, scale_factors) * BLUR_SAMPLE_SCALE;
}
_ => {}
}
}
let surface = SurfaceInfo::new(
surface_spatial_node_index,
if establishes_raster_root {
surface_spatial_node_index
} else {
parent_raster_node_index
},
raster_spatial_node_index,
inflation_factor,
frame_context.global_screen_world_rect,
&frame_context.spatial_tree,
frame_context.global_device_pixel_scale,
scale_factors,
);
self.raster_config = Some(RasterConfig {
@ -5786,7 +5819,7 @@ impl PicturePrimitive {
// Inflate the local bounding rect if required by the filter effect.
// This inflaction factor is to be applied to the surface itself.
if self.options.inflate_if_required {
surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.inflation_factor);
surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.scale_factors);
// The picture's local rect is calculated as the union of the
// snapped primitive rects, which should result in a snapped

Просмотреть файл

@ -2384,7 +2384,8 @@ impl PrimitiveStore {
surface.device_pixel_scale,
frame_context.spatial_tree,
);
surface_rect = rc.composite_mode.inflate_picture_rect(surface_rect, surface.inflation_factor);
surface_rect = rc.composite_mode.inflate_picture_rect(surface_rect, surface.scale_factors);
surface_rect = snap_pic_to_raster.snap_rect(&surface_rect);
}

Просмотреть файл

@ -2567,8 +2567,7 @@ impl<'a> SceneBuilder<'a> {
// blur radius is 0, the code in Picture::prepare_for_render will
// detect this and mark the picture to be drawn directly into the
// parent picture, which avoids an intermediate surface and blur.
let mut blur_filter = Filter::Blur(std_deviation);
blur_filter.sanitize();
let blur_filter = Filter::Blur(std_deviation);
let composite_mode = PictureCompositeMode::Filter(blur_filter);
let composite_mode_key = Some(composite_mode.clone()).into();
@ -3475,8 +3474,6 @@ impl<'a> SceneBuilder<'a> {
// For each filter, create a new image with that composite mode.
let mut current_filter_data_index = 0;
for filter in &mut filter_ops {
filter.sanitize();
let composite_mode = Some(match *filter {
Filter::ComponentTransfer => {
let filter_data =

Просмотреть файл

@ -12,10 +12,6 @@ use crate::color::ColorF;
use crate::image::{ColorDepth, ImageKey};
use crate::units::*;
// Maximum blur radius.
// Taken from nsCSSRendering.cpp in Gecko.
pub const MAX_BLUR_RADIUS: f32 = 300.;
// ******************************************************************
// * NOTE: some of these structs have an "IMPLICIT" comment. *
// * This indicates that the BuiltDisplayList will have serialized *
@ -871,12 +867,6 @@ pub struct BlurPrimitive {
pub radius: f32,
}
impl BlurPrimitive {
pub fn sanitize(&mut self) {
self.radius = self.radius.min(MAX_BLUR_RADIUS);
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct OpacityPrimitive {
@ -905,12 +895,6 @@ pub struct DropShadowPrimitive {
pub shadow: Shadow,
}
impl DropShadowPrimitive {
pub fn sanitize(&mut self) {
self.shadow.blur_radius = self.shadow.blur_radius.min(MAX_BLUR_RADIUS);
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
pub struct ComponentTransferPrimitive {
@ -968,9 +952,7 @@ impl FilterPrimitiveKind {
pub fn sanitize(&mut self) {
match self {
FilterPrimitiveKind::Flood(flood) => flood.sanitize(),
FilterPrimitiveKind::Blur(blur) => blur.sanitize(),
FilterPrimitiveKind::Opacity(opacity) => opacity.sanitize(),
FilterPrimitiveKind::DropShadow(drop_shadow) => drop_shadow.sanitize(),
// No sanitization needed.
FilterPrimitiveKind::Identity(..) |
@ -978,6 +960,8 @@ impl FilterPrimitiveKind {
FilterPrimitiveKind::ColorMatrix(..) |
FilterPrimitiveKind::Offset(..) |
FilterPrimitiveKind::Composite(..) |
FilterPrimitiveKind::Blur(..) |
FilterPrimitiveKind::DropShadow(..) |
// Component transfer's filter data is sanitized separately.
FilterPrimitiveKind::ComponentTransfer(..) => {}
}

Просмотреть файл

@ -0,0 +1,18 @@
# Ensures that blur clamping happens after scale factors are applied
---
root:
items:
- type: stacking-context
bounds: [100, 100, 300, 300]
filters: blur(100)
items:
- type: rect
bounds: [0, 0, 100, 100]
color: 0 255 0 1.0
- type: stacking-context
bounds: [400, 100, 300, 300]
filters: blur(50)
items:
- type: rect
bounds: [0, 0, 100, 100]
color: 255 0 0 1.0

Просмотреть файл

@ -0,0 +1,30 @@
# Ensures that blur clamping happens after scale factors are applied
---
root:
items:
- type: stacking-context
bounds: [100, 100, 300, 300]
transform: scale(10)
items:
- type: stacking-context
bounds: [0, 0, 300, 300]
# Blur will be 20 * 10(scale) = 200 and it should then be clamped to 100
filters: blur(20)
items:
- type: rect
bounds: [0, 0, 10, 10]
color: 0 255 0 1.0
- type: stacking-context
bounds: [400, 100, 300, 300]
transform: scale(0.1)
items:
- type: stacking-context
bounds: [0, 0, 300, 300]
# Blur should be 500 * 0.1(scale) = 50. This tests to make sure clamping
# does not occur before applying scale factors, otherwise 500 would be
# clamped to 100.
filters: blur(500)
items:
- type: rect
bounds: [0, 0, 1000, 1000]
color: 255 0 0 1.0

Просмотреть файл

@ -0,0 +1,18 @@
# Ensures that blur clamping happens after scale factors are applied
---
root:
items:
- type: stacking-context
bounds: [100, 100, 300, 300]
filters: drop-shadow([0, 0], 100, blue)
items:
- type: rect
bounds: [0, 0, 100, 100]
color: 0 255 0 1.0
- type: stacking-context
bounds: [400, 100, 300, 300]
filters: drop-shadow([0, 0], 50, green)
items:
- type: rect
bounds: [0, 0, 100, 100]
color: 255 0 0 1.0

Просмотреть файл

@ -0,0 +1,30 @@
# Ensures that blur clamping happens after scale factors are applied
---
root:
items:
- type: stacking-context
bounds: [100, 100, 300, 300]
transform: scale(10)
items:
- type: stacking-context
bounds: [0, 0, 300, 300]
# Blur will be 20 * 10(scale) = 200 and it should then be clamped to 100
filters: drop-shadow([0, 0], 20, blue)
items:
- type: rect
bounds: [0, 0, 10, 10]
color: 0 255 0 1.0
- type: stacking-context
bounds: [400, 100, 300, 300]
transform: scale(0.1)
items:
- type: stacking-context
bounds: [0, 0, 300, 300]
# Blur should be 500 * 0.1(scale) = 50. This tests to make sure clamping
# does not occur before applying scale factors, otherwise 500 would be
# clamped to 100.
filters: drop-shadow([0, 0], 500, green)
items:
- type: rect
bounds: [0, 0, 1000, 1000]
color: 255 0 0 1.0

Просмотреть файл

@ -7,11 +7,11 @@ root:
items:
- type: stacking-context
bounds: [0, 0, 1000, 1000]
transform: scale-y(999999.25)
transform: scale-y(100)
items:
- type: stacking-context
bounds: [0, 0, 1000, 1000]
filters: drop-shadow([999999, 999999], 999999, [255, 0, 0, 1])
filters: drop-shadow([0, 0], 999999, [255, 0, 0, 1])
items:
- image: checkerboard(2, 16, 16)
bounds: [0, 0, 1000, 1000]

Просмотреть файл

@ -45,7 +45,9 @@ skip_on(android,device) == filter-mix-blend-mode.yaml filter-mix-blend-mode-ref.
!= filter-blur-huge.yaml blank.yaml
!= filter-drop-shadow-huge.yaml blank.yaml
!= filter-drop-shadow-transform-huge.yaml blank.yaml
== filter-drop-shadow-blur-clamping.yaml filter-drop-shadow-blur-clamping-ref.yaml
== filter-blur-scaled.yaml filter-blur-scaled-ref.yaml
== filter-blur-clamping.yaml filter-blur-clamping-ref.yaml
skip_on(android,device) fuzzy(1,104) == filter-blur-scaled-xonly.yaml filter-blur-scaled-xonly.png # fails on Pixel2
== svg-filter-component-transfer.yaml filter-component-transfer-ref.yaml
== svg-filter-flood.yaml svg-filter-flood-ref.yaml

Просмотреть файл

@ -43,7 +43,7 @@ fuzzy(0-1,0-6400) fuzzy-if(skiaContent,0-1,0-6404) fuzzy-if(webrender,178-178,30
fuzzy-if(webrender,0-2,0-304) == feGaussianBlur-2.svg feGaussianBlur-2-ref.svg
# != feGaussianBlur-3.svg feGaussianBlur-3-ref.svg
fuzzy-if(webrender,3-5,5500-8168) == feGaussianBlur-4.svg feGaussianBlur-4-ref.svg
== feGaussianBlur-5.svg feGaussianBlur-5-ref.svg
fuzzy-if(geckoview&&webrender,0-4,0-7) == feGaussianBlur-5.svg feGaussianBlur-5-ref.svg
== feGaussianBlur-6.svg feGaussianBlur-6-ref.svg
skip-if(d2d) == feGaussianBlur-cap-large-directional-radius-on-software.html feGaussianBlur-cap-large-directional-radius-on-software-ref.html