Skip to content

Commit

Permalink
Auto merge of #2980 - gw3583:clip-opt-3, r=kvark
Browse files Browse the repository at this point in the history
Correctness and optimizations for clips in different coord systems.

For clips that are in a different coordinate system, it's not
generally feasible to get a correct mapping into a different
coordinate system - so handle these clips in world space. For
now, the world space is actually screen space. However, it is
trivial to modify this so that the world space is the coordinate
system that we are rasterizing this picture in.

As an optimization, if we encounter a primitive that is large,
and has clips from a different coord system, but no local clips,
then segment it into a uniform grid. This allows large primitives
that would not otherwise be segmented to opt in to segmenting,
which can significantly reduce the size of the allocated clip
masks for that primitive.

As a further optimization, we are now able to generate a custom
clip chain instance for each segment, rather than using the
clip chain from the primitive. This allows us to build a more
optimized clip chain, based on the segment rectangle, which can
often remove redundant clips from the mask for each segment, or
even determine that this segment is not affected by any clips
at all.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/webrender/2980)
<!-- Reviewable:end -->
  • Loading branch information
bors-servo committed Aug 24, 2018
2 parents 9399766 + fc4b78f commit 0ad9b62
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 197 deletions.
301 changes: 187 additions & 114 deletions webrender/src/clip.rs

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions webrender/src/display_list_flattener.rs
Expand Up @@ -24,7 +24,7 @@ use hit_test::{HitTestingItem, HitTestingRun};
use image::simplify_repeated_primitive;
use internal_types::{FastHashMap, FastHashSet};
use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor};
use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
use prim_store::{EdgeAaSegmentMask, ImageSource};
use prim_store::{BorderSource, BrushSegment, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
Expand Down Expand Up @@ -1706,7 +1706,6 @@ impl<'a> DisplayListFlattener<'a> {
);
let descriptor = BrushSegmentDescriptor {
segments,
clip_mask_kind: BrushClipMaskKind::Unknown,
};

let brush_kind = match border.source {
Expand Down
149 changes: 83 additions & 66 deletions webrender/src/prim_store.rs
Expand Up @@ -28,7 +28,7 @@ use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
use scene::SceneProperties;
use segment::SegmentBuilder;
use std::{mem, usize};
use std::{cmp, mem, usize};
use util::{MatrixHelpers, calculate_screen_bounding_rect};
use util::{pack_as_float, recycle_vec, TransformedRectKind};

Expand Down Expand Up @@ -482,17 +482,9 @@ impl BrushSegment {
}
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BrushClipMaskKind {
Unknown,
Individual,
Global,
}

#[derive(Debug)]
pub struct BrushSegmentDescriptor {
pub segments: Vec<BrushSegment>,
pub clip_mask_kind: BrushClipMaskKind,
}

#[derive(Debug)]
Expand Down Expand Up @@ -1659,21 +1651,9 @@ impl PrimitiveStore {
}
};

let clipped_device_rect = match calculate_screen_bounding_rect(
&prim_context.spatial_node.world_content_transform,
&clip_chain.local_bounding_rect,
frame_context.device_pixel_scale,
None,
) {
Some(rect) => rect,
None => {
if cfg!(debug_assertions) && is_chased {
println!("\tculled for being behind the near plane of transform: {:?}",
prim_context.spatial_node.world_content_transform);
}
return None
}
};
let clipped_device_rect = (clip_chain.world_clip_rect * frame_context.device_pixel_scale)
.round_out()
.to_i32();

let clipped_device_rect = match clipped_device_rect.intersection(&frame_context.screen_rect) {
Some(clipped_device_rect) => clipped_device_rect,
Expand Down Expand Up @@ -1971,12 +1951,9 @@ fn write_brush_segment_description(
frame_state: &mut FrameBuildingState,
) {
match brush.segment_desc {
Some(ref segment_desc) => {
// If we already have a segment descriptor, only run through the
// clips list if we haven't already determined the mask kind.
if segment_desc.clip_mask_kind == clip_chain.clip_mask_kind {
return;
}
Some(..) => {
// If we already have a segment descriptor, skip segment build.
return;
}
None => {
// If no segment descriptor built yet, see if it is a brush
Expand Down Expand Up @@ -2006,6 +1983,7 @@ fn write_brush_segment_description(
);

// Segment the primitive on all the local-space clip sources that we can.
let mut local_clip_count = 0;
for i in 0 .. clip_chain.clips_range.count {
let (clip_node, flags) = frame_state.clip_store.get_node_from_range(&clip_chain.clips_range, i);

Expand All @@ -2018,6 +1996,8 @@ fn write_brush_segment_description(
continue;
}

local_clip_count += 1;

let (local_clip_rect, radius, mode) = match clip_node.item {
ClipItem::RoundedRectangle(rect, radii, clip_mode) => {
rect_clips_only = false;
Expand Down Expand Up @@ -2063,10 +2043,40 @@ fn write_brush_segment_description(
}

if is_large || rect_clips_only {
match brush.segment_desc {
Some(ref mut segment_desc) => {
segment_desc.clip_mask_kind = clip_chain.clip_mask_kind;
// If there were no local clips, then we will subdivide the primitive into
// a uniform grid (up to 8x8 segments). This will typically result in
// a significant number of those segments either being completely clipped,
// or determined to not need a clip mask for that segment.
if local_clip_count == 0 && clip_chain.clips_range.count > 0 {
let x_clip_count = cmp::min(8, (metadata.local_rect.size.width / 128.0).ceil() as i32);
let y_clip_count = cmp::min(8, (metadata.local_rect.size.height / 128.0).ceil() as i32);

for y in 0 .. y_clip_count {
let y0 = metadata.local_rect.size.height * y as f32 / y_clip_count as f32;
let y1 = metadata.local_rect.size.height * (y+1) as f32 / y_clip_count as f32;

for x in 0 .. x_clip_count {
let x0 = metadata.local_rect.size.width * x as f32 / x_clip_count as f32;
let x1 = metadata.local_rect.size.width * (x+1) as f32 / x_clip_count as f32;

let rect = LayoutRect::new(
LayoutPoint::new(
x0 + metadata.local_rect.origin.x,
y0 + metadata.local_rect.origin.y,
),
LayoutSize::new(
x1 - x0,
y1 - y0,
),
);

segment_builder.push_mask_region(rect, LayoutRect::zero(), None);
}
}
}

match brush.segment_desc {
Some(..) => panic!("bug: should not already have descriptor"),
None => {
// TODO(gw): We can probably make the allocation
// patterns of this and the segment
Expand All @@ -2088,7 +2098,6 @@ fn write_brush_segment_description(

brush.segment_desc = Some(BrushSegmentDescriptor {
segments,
clip_mask_kind: clip_chain.clip_mask_kind,
});
}
}
Expand All @@ -2099,7 +2108,7 @@ impl Primitive {
fn update_clip_task_for_brush(
&mut self,
prim_context: &PrimitiveContext,
clip_chain: &ClipChainInstance,
prim_clip_chain: &ClipChainInstance,
combined_outer_rect: &DeviceIntRect,
pic_state: &mut PictureState,
frame_context: &FrameBuildingContext,
Expand All @@ -2115,50 +2124,59 @@ impl Primitive {
write_brush_segment_description(
brush,
&self.metadata,
clip_chain,
prim_clip_chain,
frame_state,
);

let segment_desc = match brush.segment_desc {
Some(ref mut description) => description,
None => return false,
};
let clip_mask_kind = segment_desc.clip_mask_kind;

for segment in &mut segment_desc.segments {
if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global {
segment.clip_task_id = BrushSegmentTaskId::Opaque;
continue;
}
// Build a clip chain for the smaller segment rect. This will
// often manage to eliminate most/all clips, and sometimes
// clip the segment completely.
let segment_clip_chain = frame_state
.clip_store
.build_clip_chain_instance(
self.metadata.clip_chain_id,
segment.local_rect,
self.metadata.local_clip_rect,
prim_context.spatial_node_index,
&frame_context.clip_scroll_tree,
frame_state.gpu_cache,
frame_state.resource_cache,
frame_context.device_pixel_scale,
);

let intersected_rect = calculate_screen_bounding_rect(
&prim_context.spatial_node.world_content_transform,
&segment.local_rect,
frame_context.device_pixel_scale,
Some(&combined_outer_rect),
);
match segment_clip_chain {
Some(segment_clip_chain) => {
if segment_clip_chain.clips_range.count == 0 {
segment.clip_task_id = BrushSegmentTaskId::Opaque;
continue;
}

let bounds = (segment_clip_chain.world_clip_rect * frame_context.device_pixel_scale)
.round_out()
.to_i32();

let clip_task = RenderTask::new_mask(
bounds,
segment_clip_chain.clips_range,
frame_state.clip_store,
frame_state.gpu_cache,
frame_state.resource_cache,
frame_state.render_tasks,
);

let bounds = match intersected_rect {
Some(bounds) => bounds,
let clip_task_id = frame_state.render_tasks.add(clip_task);
pic_state.tasks.push(clip_task_id);
segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
}
None => {
segment.clip_task_id = BrushSegmentTaskId::Empty;
continue;
}
};

if clip_chain.clips_range.count > 0 {
let clip_task = RenderTask::new_mask(
bounds,
clip_chain.clips_range,
frame_state.clip_store,
frame_state.gpu_cache,
frame_state.resource_cache,
frame_state.render_tasks,
);

let clip_task_id = frame_state.render_tasks.add(clip_task);
pic_state.tasks.push(clip_task_id);
segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
}
}

Expand Down Expand Up @@ -2754,7 +2772,6 @@ impl Primitive {
if needs_update {
brush.segment_desc = Some(BrushSegmentDescriptor {
segments: new_segments,
clip_mask_kind: BrushClipMaskKind::Unknown,
});

// The segments have changed, so force the GPU cache to
Expand Down
19 changes: 5 additions & 14 deletions webrender/src/util.rs
Expand Up @@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use api::{BorderRadius, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
use api::{DevicePoint, DeviceRect, DeviceSize, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize};
use api::{DeviceRect, LayoutPixel, LayoutRect};
use api::{WorldPixel, WorldRect};
use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D};
use euclid::{TypedTransform2D, TypedTransform3D, TypedVector2D};
Expand Down Expand Up @@ -331,15 +331,6 @@ pub trait MaxRect {
fn max_rect() -> Self;
}

impl MaxRect for LayoutRect {
fn max_rect() -> Self {
LayoutRect::new(
LayoutPoint::new(f32::MIN / 2.0, f32::MIN / 2.0),
LayoutSize::new(f32::MAX, f32::MAX),
)
}
}

impl MaxRect for DeviceIntRect {
fn max_rect() -> Self {
DeviceIntRect::new(
Expand All @@ -349,7 +340,7 @@ impl MaxRect for DeviceIntRect {
}
}

impl MaxRect for DeviceRect {
impl<U> MaxRect for TypedRect<f32, U> {
fn max_rect() -> Self {
// Having an unlimited bounding box is fine up until we try
// to cast it to `i32`, where we get `-2147483648` for any
Expand All @@ -359,9 +350,9 @@ impl MaxRect for DeviceRect {
// with explanation left as an exercise for the reader.
const MAX_COORD: f32 = 1.0e9;

DeviceRect::new(
DevicePoint::new(-MAX_COORD, -MAX_COORD),
DeviceSize::new(2.0 * MAX_COORD, 2.0 * MAX_COORD),
TypedRect::new(
TypedPoint2D::new(-MAX_COORD, -MAX_COORD),
TypedSize2D::new(2.0 * MAX_COORD, 2.0 * MAX_COORD),
)
}
}
Expand Down
Binary file modified wrench/reftests/boxshadow/box-shadow-stretch-mode-x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified wrench/reftests/clip/border-with-rounded-clip.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wrench/reftests/transforms/coord-system.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified wrench/reftests/transforms/perspective-mask.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified wrench/reftests/transforms/perspective.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion wrench/reftests/transforms/reftest.list
Expand Up @@ -10,7 +10,7 @@ platform(linux) fuzzy(1,630) == perspective.yaml perspective.png
platform(linux,mac) fuzzy(1,156) == prim-suite.yaml prim-suite.png
== segments-bug.yaml segments-bug-ref.yaml
platform(linux,mac) == content-offset.yaml content-offset.png
platform(linux,mac) == coord-system.yaml blank.yaml
platform(linux,mac) == coord-system.yaml coord-system.png
platform(linux,mac) zoom(4) == border-zoom.yaml border-zoom.png
platform(linux) fuzzy(1,520) == perspective-origin.yaml perspective-origin.png
platform(linux,mac) color_targets(1) alpha_targets(0) fuzzy(1,180) == screen-space-blit.yaml screen-space-blit.png
Expand All @@ -19,3 +19,4 @@ platform(linux,mac) == nested-rotate-x.yaml nested-rotate-x.png
platform(linux,mac) == nested-preserve-3d.yaml nested-preserve-3d.png
platform(linux,mac) == near-plane-clip.yaml near-plane-clip.png
platform(linux,mac) == perspective-mask.yaml perspective-mask.png
rotate-clip.yaml rotate-clip-ref.yaml
7 changes: 7 additions & 0 deletions wrench/reftests/transforms/rotate-clip-ref.yaml
@@ -0,0 +1,7 @@
---
root:
items:
-
bounds: [100, 146, 150, 107]
type: rect
color: 0 128 0 1.0000
18 changes: 18 additions & 0 deletions wrench/reftests/transforms/rotate-clip.yaml
@@ -0,0 +1,18 @@
---
root:
items:
-
type: clip
bounds: [0, 0, 2000, 2000]
clip-rect: [0, 0, 2000, 2000]
items:
-
bounds: [100, 0, 150, 150]
type: "stacking-context"
transform: rotate-x(45)
transform-origin: [0, 500]
items:
-
bounds: [0, 0, 150, 150]
type: rect
color: 0 128 0 1.0000

0 comments on commit 0ad9b62

Please sign in to comment.