From b562ddf6dcdb2b583829e82f16fa8e38bbf38584 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 4 Dec 2017 13:07:11 +1000 Subject: [PATCH] Convert rectangle from prim to brush shader and support segments. Add support for brushes to be drawn as a list of segments that are part of a larger primitive. Initially, these segments are nine-patches only, although they may be expanded in the future to work with generic segment rects. This allows large brush primitives to be split into a number of segments that can be split among the opaque and alpha passes, which means that large rectangles are often rendered with the inner rectangle as part of the opaque pass. Additionally, edge segments are rendered in the opaque pass when the primitive does not have a rotation or perspective transform applied. When a rectangle is split, the clip tasks are generated only where required for individual segments. This can significantly reduce the amount of allocated area for clip masks, and also the amount of GPU time spent drawing clip masks. Previously, rectangle splitting could only work on LocalClip items specified as part of the item, which meant that rectangles in Gecko would never be split. Now, we calculate splitting during prepare_prim_for_render, consulting the current clip chain for the primitive. This means that we get the benefits of rectangle splitting in Gecko, which doesn't use LocalClip API. Having rectangle splitting handled as segments also means that we don't add extra top-level primitives that take extra time to do visibility culling on, saving some CPU time. Future work: - Support more cases of primitive splitting. - Port images and gradients to be brushes so they automatically get splitting. --- webrender/res/brush.glsl | 135 +++++- webrender/res/brush_mask_rounded_rect.glsl | 8 +- webrender/res/brush_solid.glsl | 47 ++ webrender/res/clip_shared.glsl | 34 +- webrender/res/cs_clip_image.glsl | 3 +- webrender/res/cs_clip_rectangle.glsl | 3 +- webrender/res/prim_shared.glsl | 14 - webrender/res/ps_rectangle.glsl | 54 --- webrender/src/border.rs | 68 ++- webrender/src/box_shadow.rs | 45 +- webrender/src/clip_scroll_node.rs | 3 +- webrender/src/frame.rs | 114 +---- webrender/src/frame_builder.rs | 50 ++- webrender/src/gpu_types.rs | 6 +- webrender/src/internal_types.rs | 15 - webrender/src/picture.rs | 2 +- webrender/src/prim_store.rs | 400 ++++++++++++++---- webrender/src/render_task.rs | 95 +---- webrender/src/renderer.rs | 94 ++-- webrender/src/tiling.rs | 172 ++++---- webrender/src/util.rs | 67 +-- webrender/tests/angle_shader_validation.rs | 8 +- wrench/reftests/transforms/reftest.list | 2 +- wrench/reftests/transforms/rotated-image.png | Bin 7469 -> 7481 bytes .../reftests/transforms/segments-bug-ref.yaml | 24 ++ wrench/reftests/transforms/segments-bug.yaml | 28 ++ 26 files changed, 821 insertions(+), 670 deletions(-) create mode 100644 webrender/res/brush_solid.glsl delete mode 100644 webrender/res/ps_rectangle.glsl create mode 100644 wrench/reftests/transforms/segments-bug-ref.yaml create mode 100644 wrench/reftests/transforms/segments-bug.yaml diff --git a/webrender/res/brush.glsl b/webrender/res/brush.glsl index 1d3bc3f9ec..81e90736e9 100644 --- a/webrender/res/brush.glsl +++ b/webrender/res/brush.glsl @@ -14,6 +14,22 @@ void brush_vs( #define RASTERIZATION_MODE_LOCAL_SPACE 0.0 #define RASTERIZATION_MODE_SCREEN_SPACE 1.0 +#define SEGMENT_ALL 0 +#define SEGMENT_TOP_LEFT 1 +#define SEGMENT_TOP_RIGHT 2 +#define SEGMENT_BOTTOM_RIGHT 3 +#define SEGMENT_BOTTOM_LEFT 4 +#define SEGMENT_TOP_MID 5 +#define SEGMENT_MID_RIGHT 6 +#define SEGMENT_BOTTOM_MID 7 +#define SEGMENT_MID_LEFT 8 +#define SEGMENT_CENTER 9 + +#define AA_KIND_DEFAULT 0 +#define AA_KIND_SEGMENT 1 + +#define VECS_PER_BRUSH_PRIM 4 + struct BrushInstance { int picture_address; int prim_address; @@ -21,7 +37,7 @@ struct BrushInstance { int scroll_node_id; int clip_address; int z; - int flags; + int segment_kind; ivec2 user_data; }; @@ -34,12 +50,32 @@ BrushInstance load_brush() { bi.scroll_node_id = aData0.z % 65536; bi.clip_address = aData0.w; bi.z = aData1.x; - bi.flags = aData1.y; + bi.segment_kind = aData1.y; bi.user_data = aData1.zw; return bi; } +struct BrushPrimitive { + RectWithSize local_rect; + RectWithSize local_clip_rect; + vec4 offsets; + int aa_kind; +}; + +BrushPrimitive fetch_brush_primitive(int address) { + vec4 data[4] = fetch_from_resource_cache_4(address); + + BrushPrimitive prim = BrushPrimitive( + RectWithSize(data[0].xy, data[0].zw), + RectWithSize(data[1].xy, data[1].zw), + data[2], + int(data[3].x) + ); + + return prim; +} + void main(void) { // Load the brush instance from vertex attributes. BrushInstance brush = load_brush(); @@ -47,17 +83,78 @@ void main(void) { // Load the geometry for this brush. For now, this is simply the // local rect of the primitive. In the future, this will support // loading segment rects, and other rect formats (glyphs). - PrimitiveGeometry geom = fetch_primitive_geometry(brush.prim_address); + BrushPrimitive brush_prim = fetch_brush_primitive(brush.prim_address); + + // Fetch the segment of this brush primitive we are drawing. + RectWithSize local_segment_rect; + vec4 edge_aa_segment_mask; + + // p0 = origin of outer rect + // p1 = origin of inner rect + // p2 = bottom right corner of inner rect + // p3 = bottom right corner of outer rect + vec2 p0 = brush_prim.local_rect.p0; + vec2 p1 = brush_prim.local_rect.p0 + brush_prim.offsets.xy; + vec2 p2 = brush_prim.local_rect.p0 + brush_prim.local_rect.size - brush_prim.offsets.zw; + vec2 p3 = brush_prim.local_rect.p0 + brush_prim.local_rect.size; + + switch (brush.segment_kind) { + case SEGMENT_ALL: + local_segment_rect = brush_prim.local_rect; + break; + + case SEGMENT_TOP_LEFT: + local_segment_rect = RectWithSize(p0, p1 - p0); + break; + case SEGMENT_TOP_RIGHT: + local_segment_rect = RectWithSize(vec2(p2.x, p0.y), vec2(p3.x - p2.x, p1.y - p0.y)); + break; + case SEGMENT_BOTTOM_RIGHT: + local_segment_rect = RectWithSize(vec2(p2.x, p2.y), vec2(p3.x - p2.x, p3.y - p2.y)); + break; + case SEGMENT_BOTTOM_LEFT: + local_segment_rect = RectWithSize(vec2(p0.x, p2.y), vec2(p1.x - p0.x, p3.y - p2.y)); + break; + + case SEGMENT_TOP_MID: + local_segment_rect = RectWithSize(vec2(p1.x, p0.y), vec2(p2.x - p1.x, p1.y - p0.y)); + break; + case SEGMENT_MID_RIGHT: + local_segment_rect = RectWithSize(vec2(p2.x, p1.y), vec2(p3.x - p2.x, p2.y - p1.y)); + break; + case SEGMENT_BOTTOM_MID: + local_segment_rect = RectWithSize(vec2(p1.x, p2.y), vec2(p2.x - p1.x, p3.y - p2.y)); + break; + case SEGMENT_MID_LEFT: + local_segment_rect = RectWithSize(vec2(p0.x, p1.y), vec2(p1.x - p0.x, p2.y - p1.y)); + break; + + case SEGMENT_CENTER: + local_segment_rect = RectWithSize(p1, p2 - p1); + break; + + default: + local_segment_rect = RectWithSize(vec2(0.0), vec2(0.0)); + break; + } + + switch (brush_prim.aa_kind) { + case AA_KIND_SEGMENT: + // TODO: select these correctly based on the segment kind. + edge_aa_segment_mask = vec4(1.0); + break; + case AA_KIND_DEFAULT: + edge_aa_segment_mask = vec4(1.0); + break; + } vec2 device_pos, local_pos; - RectWithSize local_rect = geom.local_rect; // Fetch the dynamic picture that we are drawing on. PictureTask pic_task = fetch_picture_task(brush.picture_address); if (pic_task.rasterization_mode == RASTERIZATION_MODE_LOCAL_SPACE) { - - local_pos = local_rect.p0 + aPosition.xy * local_rect.size; + local_pos = local_segment_rect.p0 + aPosition.xy * local_segment_rect.size; // Right now - pictures only support local positions. In the future, this // will be expanded to support transform picture types (the common kind). @@ -74,12 +171,12 @@ void main(void) { // Write the normal vertex information out. if (layer.is_axis_aligned) { vi = write_vertex( - geom.local_rect, - geom.local_clip_rect, + local_segment_rect, + brush_prim.local_clip_rect, float(brush.z), layer, pic_task, - geom.local_rect + brush_prim.local_rect ); // TODO(gw): vLocalBounds may be referenced by @@ -88,15 +185,15 @@ void main(void) { // items. For now, just ensure it has no // effect. We can tidy this up as we move // more items to be brush shaders. - vLocalBounds = vec4( - geom.local_clip_rect.p0, - geom.local_clip_rect.p0 + geom.local_clip_rect.size - ); +#ifdef WR_FEATURE_ALPHA_PASS + vLocalBounds = vec4(vec2(-1000000.0), vec2(1000000.0)); +#endif } else { - vi = write_transform_vertex(geom.local_rect, - geom.local_rect, - geom.local_clip_rect, - vec4(1.0), + vi = write_transform_vertex( + local_segment_rect, + brush_prim.local_rect, + brush_prim.local_clip_rect, + edge_aa_segment_mask, float(brush.z), layer, pic_task @@ -121,9 +218,9 @@ void main(void) { // Run the specific brush VS code to write interpolators. brush_vs( - brush.prim_address + VECS_PER_PRIM_HEADER, + brush.prim_address + VECS_PER_BRUSH_PRIM, local_pos, - local_rect, + brush_prim.local_rect, brush.user_data ); } diff --git a/webrender/res/brush_mask_rounded_rect.glsl b/webrender/res/brush_mask_rounded_rect.glsl index fd490d240c..327e7d8f23 100644 --- a/webrender/res/brush_mask_rounded_rect.glsl +++ b/webrender/res/brush_mask_rounded_rect.glsl @@ -14,7 +14,7 @@ varying vec2 vLocalPos; #ifdef WR_VERTEX_SHADER -struct BrushPrimitive { +struct RoundedRectPrimitive { float clip_mode; vec4 rect; vec2 radius_tl; @@ -23,9 +23,9 @@ struct BrushPrimitive { vec2 radius_bl; }; -BrushPrimitive fetch_brush_primitive(int address) { +RoundedRectPrimitive fetch_rounded_rect_primitive(int address) { vec4 data[4] = fetch_from_resource_cache_4(address); - return BrushPrimitive( + return RoundedRectPrimitive( data[0].x, data[1], data[2].xy, @@ -42,7 +42,7 @@ void brush_vs( ivec2 user_data ) { // Load the specific primitive. - BrushPrimitive prim = fetch_brush_primitive(prim_address); + RoundedRectPrimitive prim = fetch_rounded_rect_primitive(prim_address); // Write clip parameters vClipMode = prim.clip_mode; diff --git a/webrender/res/brush_solid.glsl b/webrender/res/brush_solid.glsl new file mode 100644 index 0000000000..7976707223 --- /dev/null +++ b/webrender/res/brush_solid.glsl @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include shared,prim_shared,brush + +flat varying vec4 vColor; + +#ifdef WR_FEATURE_ALPHA_PASS +varying vec2 vLocalPos; +#endif + +#ifdef WR_VERTEX_SHADER + +struct SolidBrush { + vec4 color; +}; + +SolidBrush fetch_solid_primitive(int address) { + vec4 data = fetch_from_resource_cache_1(address); + return SolidBrush(data); +} + +void brush_vs( + int prim_address, + vec2 local_pos, + RectWithSize local_rect, + ivec2 user_data +) { + SolidBrush prim = fetch_solid_primitive(prim_address); + vColor = prim.color; + +#ifdef WR_FEATURE_ALPHA_PASS + vLocalPos = local_pos; +#endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER +vec4 brush_fs() { + vec4 color = vColor; +#ifdef WR_FEATURE_ALPHA_PASS + color *= init_transform_fs(vLocalPos); +#endif + return color; +} +#endif diff --git a/webrender/res/clip_shared.glsl b/webrender/res/clip_shared.glsl index 2e437bc270..90da9e7799 100644 --- a/webrender/res/clip_shared.glsl +++ b/webrender/res/clip_shared.glsl @@ -50,38 +50,8 @@ RectWithSize intersect_rect(RectWithSize a, RectWithSize b) { // which is the intersection of all clip instances of a given primitive ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect, Layer layer, - ClipArea area, - int segment) { - vec2 outer_p0 = area.screen_origin; - vec2 outer_p1 = outer_p0 + area.common_data.task_rect.size; - vec2 inner_p0 = area.inner_rect.xy; - vec2 inner_p1 = area.inner_rect.zw; - - vec2 p0, p1; - switch (segment) { - case SEGMENT_ALL: - p0 = outer_p0; - p1 = outer_p1; - break; - case SEGMENT_CORNER_TL: - p0 = outer_p0; - p1 = inner_p0; - break; - case SEGMENT_CORNER_BL: - p0 = vec2(outer_p0.x, outer_p1.y); - p1 = vec2(inner_p0.x, inner_p1.y); - break; - case SEGMENT_CORNER_TR: - p0 = vec2(outer_p1.x, outer_p1.y); - p1 = vec2(inner_p1.x, inner_p1.y); - break; - case SEGMENT_CORNER_BR: - p0 = vec2(outer_p1.x, outer_p0.y); - p1 = vec2(inner_p1.x, inner_p0.y); - break; - } - - vec2 actual_pos = mix(p0, p1, aPosition.xy); + ClipArea area) { + vec2 actual_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size; vec4 layer_pos = get_layer_pos(actual_pos / uDevicePixelRatio, layer); diff --git a/webrender/res/cs_clip_image.glsl b/webrender/res/cs_clip_image.glsl index 188c5e2e03..009896e17c 100644 --- a/webrender/res/cs_clip_image.glsl +++ b/webrender/res/cs_clip_image.glsl @@ -31,8 +31,7 @@ void main(void) { ClipVertexInfo vi = write_clip_tile_vertex(local_rect, layer, - area, - cmi.segment); + area); vPos = vi.local_pos; vLayer = res.layer; diff --git a/webrender/res/cs_clip_rectangle.glsl b/webrender/res/cs_clip_rectangle.glsl index d6518df517..c1a985ce1f 100644 --- a/webrender/res/cs_clip_rectangle.glsl +++ b/webrender/res/cs_clip_rectangle.glsl @@ -64,8 +64,7 @@ void main(void) { ClipVertexInfo vi = write_clip_tile_vertex(local_rect, layer, - area, - cmi.segment); + area); vPos = vi.local_pos; vClipMode = clip.rect.mode.x; diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index ef8bb0c803..6f68882033 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -305,7 +305,6 @@ BlurTask fetch_blur_task(int address) { struct ClipArea { RenderTaskCommonData common_data; vec2 screen_origin; - vec4 inner_rect; }; ClipArea fetch_clip_area(int index) { @@ -317,13 +316,11 @@ ClipArea fetch_clip_area(int index) { 0.0 ); area.screen_origin = vec2(0.0); - area.inner_rect = vec4(0.0); } else { RenderTaskData task_data = fetch_render_task_data(index); area.common_data = task_data.common_data; area.screen_origin = task_data.data1.xy; - area.inner_rect = task_data.data2; } return area; @@ -732,17 +729,6 @@ ImageResource fetch_image_resource_direct(ivec2 address) { return ImageResource(data[0], data[1].x); } -struct Rectangle { - vec4 color; - vec4 edge_aa_segment_mask; -}; - -Rectangle fetch_rectangle(int address) { - vec4 data[2] = fetch_from_resource_cache_2(address); - vec4 mask = vec4((int(data[1].x) & ivec4(1,2,4,8)) != ivec4(0)); - return Rectangle(data[0], mask); -} - struct TextRun { vec4 color; vec4 bg_color; diff --git a/webrender/res/ps_rectangle.glsl b/webrender/res/ps_rectangle.glsl deleted file mode 100644 index bf17f91b43..0000000000 --- a/webrender/res/ps_rectangle.glsl +++ /dev/null @@ -1,54 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include shared,prim_shared - -varying vec4 vColor; - -#ifdef WR_FEATURE_TRANSFORM -varying vec2 vLocalPos; -#endif - -#ifdef WR_VERTEX_SHADER -void main(void) { - Primitive prim = load_primitive(); - Rectangle rect = fetch_rectangle(prim.specific_prim_address); - vColor = rect.color; -#ifdef WR_FEATURE_TRANSFORM - VertexInfo vi = write_transform_vertex(prim.local_rect, - prim.local_rect, - prim.local_clip_rect, - rect.edge_aa_segment_mask, - prim.z, - prim.layer, - prim.task); - vLocalPos = vi.local_pos; -#else - VertexInfo vi = write_vertex(prim.local_rect, - prim.local_clip_rect, - prim.z, - prim.layer, - prim.task, - prim.local_rect); -#endif - -#ifdef WR_FEATURE_CLIP - write_clip(vi.screen_pos, prim.clip_area); -#endif -} -#endif - -#ifdef WR_FRAGMENT_SHADER -void main(void) { - float alpha = 1.0; -#ifdef WR_FEATURE_TRANSFORM - alpha = init_transform_fs(vLocalPos); -#endif - -#ifdef WR_FEATURE_CLIP - alpha *= do_clip(); -#endif - oFragColor = vColor * alpha; -} -#endif diff --git a/webrender/src/border.rs b/webrender/src/border.rs index ed1ecd50a1..a0e627a060 100644 --- a/webrender/src/border.rs +++ b/webrender/src/border.rs @@ -9,7 +9,7 @@ use clip::ClipSource; use ellipse::Ellipse; use frame_builder::FrameBuilder; use gpu_cache::GpuDataRequest; -use internal_types::EdgeAaSegmentMask; +use prim_store::{BrushAntiAliasMode, BrushSegmentDescriptor, BrushSegmentKind}; use prim_store::{BorderPrimitiveCpu, PrimitiveContainer, TexelRect}; use util::{lerp, pack_as_float}; @@ -374,56 +374,84 @@ impl FrameBuilder { let has_no_curve = radius.is_zero(); if has_no_curve && all_corners_simple && all_edges_simple { - let p0 = info.rect.origin; - let p1 = info.rect.bottom_right(); - let rect_width = info.rect.size.width; - let rect_height = info.rect.size.height; - let mut info = info.clone(); + let inner_rect = LayerRect::new( + LayerPoint::new( + info.rect.origin.x + left_len, + info.rect.origin.y + top_len, + ), + LayerSize::new( + info.rect.size.width - left_len - right_len, + info.rect.size.height - top_len - bottom_len, + ), + ); // Add a solid rectangle for each visible edge/corner combination. if top_edge == BorderEdgeKind::Solid { - info.rect = LayerRect::new(p0, LayerSize::new(rect_width, top_len)); + let descriptor = BrushSegmentDescriptor::new( + &info.rect, + &inner_rect, + Some(&[ + BrushSegmentKind::TopLeft, + BrushSegmentKind::TopMid, + BrushSegmentKind::TopRight + ]), + ); self.add_solid_rectangle( clip_and_scroll, &info, border.top.color, - EdgeAaSegmentMask::BOTTOM, + Some(Box::new(descriptor)), + BrushAntiAliasMode::Segment, ); } if left_edge == BorderEdgeKind::Solid { - info.rect = LayerRect::new( - LayerPoint::new(p0.x, p0.y + top_len), - LayerSize::new(left_len, rect_height - top_len - bottom_len), + let descriptor = BrushSegmentDescriptor::new( + &info.rect, + &inner_rect, + Some(&[ + BrushSegmentKind::MidLeft, + ]), ); self.add_solid_rectangle( clip_and_scroll, &info, border.left.color, - EdgeAaSegmentMask::RIGHT, + Some(Box::new(descriptor)), + BrushAntiAliasMode::Segment, ); } if right_edge == BorderEdgeKind::Solid { - info.rect = LayerRect::new( - LayerPoint::new(p1.x - right_len, p0.y + top_len), - LayerSize::new(right_len, rect_height - top_len - bottom_len), + let descriptor = BrushSegmentDescriptor::new( + &info.rect, + &inner_rect, + Some(&[ + BrushSegmentKind::MidRight, + ]), ); self.add_solid_rectangle( clip_and_scroll, &info, border.right.color, - EdgeAaSegmentMask::LEFT, + Some(Box::new(descriptor)), + BrushAntiAliasMode::Segment, ); } if bottom_edge == BorderEdgeKind::Solid { - info.rect = LayerRect::new( - LayerPoint::new(p0.x, p1.y - bottom_len), - LayerSize::new(rect_width, bottom_len), + let descriptor = BrushSegmentDescriptor::new( + &info.rect, + &inner_rect, + Some(&[ + BrushSegmentKind::BottomLeft, + BrushSegmentKind::BottomMid, + BrushSegmentKind::BottomRight + ]), ); self.add_solid_rectangle( clip_and_scroll, &info, border.bottom.color, - EdgeAaSegmentMask::TOP, + Some(Box::new(descriptor)), + BrushAntiAliasMode::Segment, ); } } else { diff --git a/webrender/src/box_shadow.rs b/webrender/src/box_shadow.rs index d0a253b9dc..836130c2a8 100644 --- a/webrender/src/box_shadow.rs +++ b/webrender/src/box_shadow.rs @@ -9,8 +9,7 @@ use api::{PipelineId}; use app_units::Au; use clip::ClipSource; use frame_builder::FrameBuilder; -use internal_types::EdgeAaSegmentMask; -use prim_store::{PrimitiveContainer, RectangleContent, RectanglePrimitive}; +use prim_store::{BrushAntiAliasMode, PrimitiveContainer}; use prim_store::{BrushMaskKind, BrushKind, BrushPrimitive}; use picture::PicturePrimitive; use util::RectHelpers; @@ -131,10 +130,14 @@ impl FrameBuilder { clip_and_scroll, &fast_info, clips, - PrimitiveContainer::Rectangle(RectanglePrimitive { - content: RectangleContent::Fill(*color), - edge_aa_segment_mask: EdgeAaSegmentMask::empty(), - }), + PrimitiveContainer::Brush( + BrushPrimitive::new(BrushKind::Solid { + color: *color, + }, + None, + BrushAntiAliasMode::Primitive, + ) + ), ); } else { let blur_offset = BLUR_SAMPLE_SCALE * blur_radius; @@ -178,12 +181,14 @@ impl FrameBuilder { width = MASK_CORNER_PADDING + corner_size.width.max(BLUR_SAMPLE_SCALE * blur_radius); height = MASK_CORNER_PADDING + corner_size.height.max(BLUR_SAMPLE_SCALE * blur_radius); - brush_prim = BrushPrimitive { - kind: BrushKind::Mask { + brush_prim = BrushPrimitive::new( + BrushKind::Mask { clip_mode: brush_clip_mode, kind: BrushMaskKind::Corner(corner_size), - } - }; + }, + None, + BrushAntiAliasMode::Primitive, + ); } else { // Create a minimal size primitive mask to blur. In this // case, we ensure the size of each corner is the same, @@ -205,12 +210,14 @@ impl FrameBuilder { let clip_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(width, height)); - brush_prim = BrushPrimitive { - kind: BrushKind::Mask { + brush_prim = BrushPrimitive::new( + BrushKind::Mask { clip_mode: brush_clip_mode, kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius), - } - }; + }, + None, + BrushAntiAliasMode::Primitive, + ); }; // Construct a mask primitive to add to the picture. @@ -288,12 +295,14 @@ impl FrameBuilder { } let brush_rect = brush_rect.inflate(inflate_size, inflate_size); - let brush_prim = BrushPrimitive { - kind: BrushKind::Mask { + let brush_prim = BrushPrimitive::new( + BrushKind::Mask { clip_mode: brush_clip_mode, kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius), - } - }; + }, + None, + BrushAntiAliasMode::Primitive, + ); let brush_info = LayerPrimitiveInfo::new(brush_rect); let brush_prim_index = self.create_primitive( &brush_info, diff --git a/webrender/src/clip_scroll_node.rs b/webrender/src/clip_scroll_node.rs index 593d891cef..dd90675b9a 100644 --- a/webrender/src/clip_scroll_node.rs +++ b/webrender/src/clip_scroll_node.rs @@ -520,7 +520,8 @@ impl ClipScrollNode { state.nearest_scrolling_ancestor_viewport .translate(&info.origin_in_parent_reference_frame); - if !info.resolved_transform.preserves_2d_axis_alignment() { + if !info.resolved_transform.preserves_2d_axis_alignment() || + info.resolved_transform.has_perspective_component() { state.current_coordinate_system_id = state.next_coordinate_system_id; state.next_coordinate_system_id = state.next_coordinate_system_id.next(); } diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index 4b476a62dc..71e158fbc7 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -6,23 +6,22 @@ use api::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion}; use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, DocumentLayer, Epoch, FilterOp}; use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect}; -use api::{LayerSize, LayerVector2D}; -use api::{LayoutRect, LayoutSize}; +use api::{LayerSize, LayerVector2D, LayoutSize}; use api::{LocalClip, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState}; use api::{ScrollLocation, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext}; -use api::{ClipMode, TileOffset, TransformStyle, WorldPoint}; +use api::{TileOffset, TransformStyle, WorldPoint}; use clip::ClipRegion; use clip_scroll_node::StickyFrameInfo; use clip_scroll_tree::{ClipScrollTree, ScrollStates}; use euclid::rect; use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo}; use gpu_cache::GpuCache; -use internal_types::{EdgeAaSegmentMask, FastHashMap, FastHashSet, RenderedDocument}; +use internal_types::{FastHashMap, FastHashSet, RenderedDocument}; +use prim_store::{BrushAntiAliasMode}; use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters}; use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap}; use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties}; use tiling::CompositeOps; -use util::ComplexClipRegionHelpers; #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)] pub struct FrameId(pub u32); @@ -42,11 +41,6 @@ struct FlattenContext<'a> { tiled_image_map: TiledImageMap, pipeline_epochs: Vec<(PipelineId, Epoch)>, replacements: Vec<(ClipId, ClipId)>, - /// Opaque rectangle vector, stored here in order to - /// avoid re-allocation on each use. - opaque_parts: Vec, - /// Same for the transparent rectangles. - transparent_parts: Vec, output_pipelines: &'a FastHashSet, } @@ -111,7 +105,8 @@ impl<'a> FlattenContext<'a> { ClipAndScrollInfo::simple(root_reference_frame_id), &info, bg_color, - EdgeAaSegmentMask::empty(), + None, + BrushAntiAliasMode::Primitive, ); } } @@ -448,18 +443,13 @@ impl<'a> FlattenContext<'a> { } } SpecificDisplayItem::Rectangle(ref info) => { - if !self.try_to_add_rectangle_splitting_on_clip( + self.builder.add_solid_rectangle( + clip_and_scroll, &prim_info, info.color, - &clip_and_scroll, - ) { - self.builder.add_solid_rectangle( - clip_and_scroll, - &prim_info, - info.color, - EdgeAaSegmentMask::empty(), - ); - } + None, + BrushAntiAliasMode::Primitive, + ); } SpecificDisplayItem::ClearRectangle => { self.builder.add_clear_rectangle( @@ -631,86 +621,6 @@ impl<'a> FlattenContext<'a> { None } - /// Try to optimize the rendering of a solid rectangle that is clipped by a single - /// rounded rectangle, by only masking the parts of the rectangle that intersect - /// the rounded parts of the clip. This is pretty simple now, so has a lot of - /// potential for further optimizations. - fn try_to_add_rectangle_splitting_on_clip( - &mut self, - info: &LayerPrimitiveInfo, - color: ColorF, - clip_and_scroll: &ClipAndScrollInfo, - ) -> bool { - if info.rect.size.area() < 200.0 { // arbitrary threshold - // too few pixels, don't bother adding instances - return false; - } - // If this rectangle is not opaque, splitting the rectangle up - // into an inner opaque region just ends up hurting batching and - // doing more work than necessary. - if color.a != 1.0 { - return false; - } - - self.opaque_parts.clear(); - self.transparent_parts.clear(); - - match info.local_clip { - LocalClip::Rect(_) => return false, - LocalClip::RoundedRect(_, ref region) => { - if region.mode == ClipMode::ClipOut { - return false; - } - region.split_rectangles( - &mut self.opaque_parts, - &mut self.transparent_parts, - ); - } - }; - - let local_clip = LocalClip::from(*info.local_clip.clip_rect()); - let mut has_opaque = false; - - for opaque in &self.opaque_parts { - let prim_info = LayerPrimitiveInfo { - rect: match opaque.intersection(&info.rect) { - Some(rect) => rect, - None => continue, - }, - local_clip, - .. info.clone() - }; - self.builder.add_solid_rectangle( - *clip_and_scroll, - &prim_info, - color, - EdgeAaSegmentMask::empty(), - ); - has_opaque = true; - } - - if !has_opaque { - return false - } - - for transparent in &self.transparent_parts { - let prim_info = LayerPrimitiveInfo { - rect: match transparent.intersection(&info.rect) { - Some(rect) => rect, - None => continue, - }, - .. info.clone() - }; - self.builder.add_solid_rectangle( - *clip_and_scroll, - &prim_info, - color, - EdgeAaSegmentMask::empty(), - ); - } - true - } - /// Decomposes an image display item that is repeated into an image per individual repetition. /// We need to do this when we are unable to perform the repetition in the shader, /// for example if the image is tiled. @@ -1122,8 +1032,6 @@ impl FrameContext { tiled_image_map: resource_cache.get_tiled_image_map(), pipeline_epochs: Vec::new(), replacements: Vec::new(), - opaque_parts: Vec::new(), - transparent_parts: Vec::new(), output_pipelines, }; diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index 85abdc0421..f0fd8fdf50 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -21,13 +21,14 @@ use euclid::{SideOffsets2D, vec2}; use frame::FrameId; use glyph_rasterizer::FontInstance; use gpu_cache::GpuCache; -use internal_types::{EdgeAaSegmentMask, FastHashMap, FastHashSet}; +use gpu_types::ClipScrollNodeData; +use internal_types::{FastHashMap, FastHashSet}; use picture::{PictureCompositeMode, PictureKind, PicturePrimitive, RasterizationSpace}; -use prim_store::{TexelRect, YuvImagePrimitiveCpu}; +use prim_store::{BrushAntiAliasMode, BrushKind, BrushPrimitive, TexelRect, YuvImagePrimitiveCpu}; use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind}; use prim_store::{PrimitiveContainer, PrimitiveIndex, SpecificPrimitiveIndex}; use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu}; -use prim_store::{RectangleContent, RectanglePrimitive, TextRunPrimitiveCpu}; +use prim_store::{BrushSegmentDescriptor, TextRunPrimitiveCpu}; use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters}; use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskTree}; use resource_cache::ResourceCache; @@ -756,7 +757,8 @@ impl FrameBuilder { clip_and_scroll: ClipAndScrollInfo, info: &LayerPrimitiveInfo, color: ColorF, - edge_aa_segment_mask: EdgeAaSegmentMask, + segments: Option>, + aa_mode: BrushAntiAliasMode, ) { if color.a == 0.0 { // Don't add transparent rectangles to the draw list, but do consider them for hit @@ -765,16 +767,19 @@ impl FrameBuilder { return; } - let prim = RectanglePrimitive { - content: RectangleContent::Fill(color), - edge_aa_segment_mask, - }; + let prim = BrushPrimitive::new( + BrushKind::Solid { + color, + }, + segments, + aa_mode, + ); self.add_primitive( clip_and_scroll, info, Vec::new(), - PrimitiveContainer::Rectangle(prim), + PrimitiveContainer::Brush(prim), ); } @@ -783,16 +788,17 @@ impl FrameBuilder { clip_and_scroll: ClipAndScrollInfo, info: &LayerPrimitiveInfo, ) { - let prim = RectanglePrimitive { - content: RectangleContent::Clear, - edge_aa_segment_mask: EdgeAaSegmentMask::empty(), - }; + let prim = BrushPrimitive::new( + BrushKind::Clear, + None, + BrushAntiAliasMode::Primitive, + ); self.add_primitive( clip_and_scroll, info, Vec::new(), - PrimitiveContainer::Rectangle(prim), + PrimitiveContainer::Brush(prim), ); } @@ -807,16 +813,19 @@ impl FrameBuilder { return; } - let prim = RectanglePrimitive { - content: RectangleContent::Fill(color), - edge_aa_segment_mask: EdgeAaSegmentMask::empty(), - }; + let prim = BrushPrimitive::new( + BrushKind::Solid { + color, + }, + None, + BrushAntiAliasMode::Primitive, + ); let prim_index = self.add_primitive( clip_and_scroll, info, Vec::new(), - PrimitiveContainer::Rectangle(prim), + PrimitiveContainer::Brush(prim), ); self.scrollbar_prims.push(ScrollbarPrimitive { @@ -1576,6 +1585,7 @@ impl FrameBuilder { profile_counters: &mut FrameProfileCounters, device_pixel_ratio: f32, scene_properties: &SceneProperties, + node_data: &[ClipScrollNodeData], ) -> Option { profile_scope!("cull"); @@ -1618,6 +1628,7 @@ impl FrameBuilder { scene_properties, SpecificPrimitiveIndex(0), &self.screen_rect.to_i32(), + node_data, ); let pic = &mut self.prim_store.cpu_pictures[0]; @@ -1723,6 +1734,7 @@ impl FrameBuilder { &mut profile_counters, device_pixel_ratio, scene_properties, + &node_data, ); let mut passes = Vec::new(); diff --git a/webrender/src/gpu_types.rs b/webrender/src/gpu_types.rs index 52b70c0f47..61e36ecc56 100644 --- a/webrender/src/gpu_types.rs +++ b/webrender/src/gpu_types.rs @@ -154,7 +154,7 @@ pub struct BrushInstance { pub scroll_id: ClipScrollNodeIndex, pub clip_task_address: RenderTaskAddress, pub z: i32, - pub flags: i32, + pub segment_kind: i32, pub user_data0: i32, pub user_data1: i32, } @@ -168,7 +168,7 @@ impl From for PrimitiveInstance { ((instance.clip_id.0 as i32) << 16) | instance.scroll_id.0 as i32, instance.clip_task_address.0 as i32, instance.z, - instance.flags, + instance.segment_kind, instance.user_data0, instance.user_data1, ] @@ -186,7 +186,7 @@ pub enum BrushImageKind { Mirror = 2, // A top left corner only (mirror across x/y axes) } -#[derive(Copy, Debug, Clone)] +#[derive(Copy, Debug, Clone, PartialEq)] #[repr(C)] pub struct ClipScrollNodeIndex(pub u32); diff --git a/webrender/src/internal_types.rs b/webrender/src/internal_types.rs index a5c3a92410..f80bde7765 100644 --- a/webrender/src/internal_types.rs +++ b/webrender/src/internal_types.rs @@ -190,18 +190,3 @@ pub struct UvRect { pub uv0: DevicePoint, pub uv1: DevicePoint, } - -bitflags! { - /// Each bit of the edge AA mask is: - /// 0, when the edge of the primitive needs to be considered for AA - /// 1, when the edge of the segment needs to be considered for AA - /// - /// *Note*: the bit values have to match the shader logic in - /// `write_transform_vertex()` function. - pub struct EdgeAaSegmentMask: u8 { - const LEFT = 0x1; - const TOP = 0x2; - const RIGHT = 0x4; - const BOTTOM = 0x8; - } -} diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 0bd529e4be..b707f377a8 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -525,7 +525,7 @@ impl PicturePrimitive { } } - pub fn write_gpu_blocks(&self, mut _request: GpuDataRequest) { + pub fn write_gpu_blocks(&self, _request: &mut GpuDataRequest) { // TODO(gw): We'll need to write the GPU blocks // here specific to a brush primitive // once we start drawing pictures as brushes! diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 79e0afcfd0..1e9dedd501 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -6,16 +6,17 @@ use api::{BorderRadius, BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRe use api::{DevicePoint, ExtendMode, FontRenderMode, GlyphInstance, GlyphKey}; use api::{GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerRect}; use api::{ClipMode, LayerSize, LayerVector2D, LayerToWorldTransform, LineOrientation, LineStyle}; -use api::{ClipAndScrollInfo, PremultipliedColorF, TileOffset}; +use api::{ClipAndScrollInfo, PremultipliedColorF, TileOffset, WorldToLayerTransform}; use api::{ClipId, LayerTransform, PipelineId, YuvColorSpace, YuvFormat}; use border::BorderCornerInstance; use clip_scroll_tree::{CoordinateSystemId, ClipScrollTree}; -use clip::{ClipSourcesHandle, ClipStore}; +use clip::{ClipSource, ClipSourcesHandle, ClipStore}; use frame_builder::PrimitiveContext; use glyph_rasterizer::{FontInstance, FontTransform}; -use internal_types::{EdgeAaSegmentMask, FastHashMap}; +use internal_types::{FastHashMap}; use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks}; +use gpu_types::ClipScrollNodeData; use picture::{PictureKind, PicturePrimitive, RasterizationSpace}; use profiler::FrameProfileCounters; use render_task::{ClipChainNode, ClipChainNodeIter, ClipWorkItem, RenderTask, RenderTaskId}; @@ -23,9 +24,12 @@ use render_task::RenderTaskTree; use renderer::MAX_VERTEX_TEXTURE_WIDTH; use resource_cache::{ImageProperties, ResourceCache}; use scene::{ScenePipeline, SceneProperties}; -use std::{mem, usize}; +use std::{mem, u16, usize}; use std::rc::Rc; -use util::{pack_as_float, recycle_vec, MatrixHelpers, TransformedRect, TransformedRectKind}; +use util::{extract_inner_rect_safe, pack_as_float, recycle_vec}; +use util::{MatrixHelpers, TransformedRect}; + +const MIN_BRUSH_SPLIT_AREA: f32 = 128.0 * 128.0; #[derive(Debug)] pub struct PrimitiveRun { @@ -136,7 +140,6 @@ pub struct PrimitiveIndex(pub usize); #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum PrimitiveKind { - Rectangle, TextRun, Image, YuvImage, @@ -187,31 +190,6 @@ pub struct PrimitiveMetadata { pub tag: Option, } -#[derive(Debug,Clone,Copy)] -pub enum RectangleContent { - Fill(ColorF), - Clear, -} - -#[derive(Debug)] -pub struct RectanglePrimitive { - pub content: RectangleContent, - pub edge_aa_segment_mask: EdgeAaSegmentMask, -} - -impl ToGpuBlocks for RectanglePrimitive { - fn write_gpu_blocks(&self, mut request: GpuDataRequest) { - request.push(match self.content { - RectangleContent::Fill(color) => color.premultiplied(), - // Opaque black with operator dest out - RectangleContent::Clear => PremultipliedColorF::BLACK, - }); - request.extend_from_slice(&[GpuBlockData { - data: [self.edge_aa_segment_mask.bits() as f32, 0.0, 0.0, 0.0], - }]); - } -} - #[derive(Debug)] pub enum BrushMaskKind { //Rect, // TODO(gw): Optimization opportunity for masks with 0 border radii. @@ -224,17 +202,182 @@ pub enum BrushKind { Mask { clip_mode: ClipMode, kind: BrushMaskKind, + }, + Solid { + color: ColorF, + }, + Clear, +} + +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub enum BrushAntiAliasMode { + Primitive = 0, + Segment = 1, +} + +#[allow(dead_code)] +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub enum BrushSegmentKind { + TopLeft = 0, + TopRight, + BottomRight, + BottomLeft, + + TopMid, + MidRight, + BottomMid, + MidLeft, + + Center, +} + +#[derive(Debug)] +pub struct BrushSegment { + pub local_rect: LayerRect, + pub clip_task_id: Option, +} + +impl BrushSegment { + fn new( + origin: LayerPoint, + size: LayerSize, + ) -> BrushSegment { + BrushSegment { + local_rect: LayerRect::new(origin, size), + clip_task_id: None, + } + } +} + +#[derive(Debug)] +pub struct BrushSegmentDescriptor { + pub top_left_offset: LayerVector2D, + pub bottom_right_offset: LayerVector2D, + pub segments: [BrushSegment; 9], + pub enabled_segments: u16, + pub can_optimize_clip_mask: bool, +} + +impl BrushSegmentDescriptor { + pub fn new( + outer_rect: &LayerRect, + inner_rect: &LayerRect, + valid_segments: Option<&[BrushSegmentKind]>, + ) -> BrushSegmentDescriptor { + let p0 = outer_rect.origin; + let p1 = inner_rect.origin; + let p2 = inner_rect.bottom_right(); + let p3 = outer_rect.bottom_right(); + + let enabled_segments = match valid_segments { + Some(valid_segments) => { + valid_segments.iter().fold( + 0, + |acc, segment| acc | 1 << *segment as u32 + ) + } + None => u16::MAX, + }; + + BrushSegmentDescriptor { + enabled_segments, + can_optimize_clip_mask: false, + top_left_offset: p1 - p0, + bottom_right_offset: p3 - p2, + segments: [ + BrushSegment::new( + LayerPoint::new(p0.x, p0.y), + LayerSize::new(p1.x - p0.x, p1.y - p0.y), + ), + BrushSegment::new( + LayerPoint::new(p2.x, p0.y), + LayerSize::new(p3.x - p2.x, p1.y - p0.y), + ), + BrushSegment::new( + LayerPoint::new(p2.x, p2.y), + LayerSize::new(p3.x - p2.x, p3.y - p2.y), + ), + BrushSegment::new( + LayerPoint::new(p0.x, p2.y), + LayerSize::new(p1.x - p0.x, p3.y - p2.y), + ), + BrushSegment::new( + LayerPoint::new(p1.x, p0.y), + LayerSize::new(p2.x - p1.x, p1.y - p0.y), + ), + BrushSegment::new( + LayerPoint::new(p2.x, p1.y), + LayerSize::new(p3.x - p2.x, p2.y - p1.y), + ), + BrushSegment::new( + LayerPoint::new(p1.x, p2.y), + LayerSize::new(p2.x - p1.x, p3.y - p2.y), + ), + BrushSegment::new( + LayerPoint::new(p0.x, p1.y), + LayerSize::new(p1.x - p0.x, p2.y - p1.y), + ), + BrushSegment::new( + LayerPoint::new(p1.x, p1.y), + LayerSize::new(p2.x - p1.x, p2.y - p1.y), + ), + ], + } } } #[derive(Debug)] pub struct BrushPrimitive { pub kind: BrushKind, + pub segment_desc: Option>, + pub aa_mode: BrushAntiAliasMode, +} + +impl BrushPrimitive { + pub fn new( + kind: BrushKind, + segment_desc: Option>, + aa_mode: BrushAntiAliasMode, + ) -> BrushPrimitive { + BrushPrimitive { + kind, + segment_desc, + aa_mode, + } + } } impl ToGpuBlocks for BrushPrimitive { fn write_gpu_blocks(&self, mut request: GpuDataRequest) { + match self.segment_desc { + Some(ref segment_desc) => { + request.push([ + segment_desc.top_left_offset.x, + segment_desc.top_left_offset.y, + segment_desc.bottom_right_offset.x, + segment_desc.bottom_right_offset.y, + ]); + } + None => { + request.push([0.0; 4]); + } + } + request.push([ + self.aa_mode as u32 as f32, + 0.0, + 0.0, + 0.0, + ]); match self.kind { + BrushKind::Solid { color } => { + request.push(color.premultiplied()); + } + BrushKind::Clear => { + // Opaque black with operator dest out + request.push(PremultipliedColorF::BLACK); + } BrushKind::Mask { clip_mode, kind: BrushMaskKind::Corner(radius) } => { request.push([ radius.width, @@ -863,7 +1006,6 @@ impl ClipData { #[derive(Debug)] pub enum PrimitiveContainer { - Rectangle(RectanglePrimitive), TextRun(TextRunPrimitiveCpu), Image(ImagePrimitiveCpu), YuvImage(YuvImagePrimitiveCpu), @@ -878,7 +1020,6 @@ pub enum PrimitiveContainer { pub struct PrimitiveStore { /// CPU side information only. - pub cpu_rectangles: Vec, pub cpu_brushes: Vec, pub cpu_text_runs: Vec, pub cpu_pictures: Vec, @@ -895,7 +1036,6 @@ impl PrimitiveStore { pub fn new() -> PrimitiveStore { PrimitiveStore { cpu_metadata: Vec::new(), - cpu_rectangles: Vec::new(), cpu_brushes: Vec::new(), cpu_text_runs: Vec::new(), cpu_pictures: Vec::new(), @@ -911,7 +1051,6 @@ impl PrimitiveStore { pub fn recycle(self) -> Self { PrimitiveStore { cpu_metadata: recycle_vec(self.cpu_metadata), - cpu_rectangles: recycle_vec(self.cpu_rectangles), cpu_brushes: recycle_vec(self.cpu_brushes), cpu_text_runs: recycle_vec(self.cpu_text_runs), cpu_pictures: recycle_vec(self.cpu_pictures), @@ -945,32 +1084,20 @@ impl PrimitiveStore { screen_rect: None, tag, opacity: PrimitiveOpacity::translucent(), - prim_kind: PrimitiveKind::Rectangle, + prim_kind: PrimitiveKind::Brush, cpu_prim_index: SpecificPrimitiveIndex(0), }; let metadata = match container { - PrimitiveContainer::Rectangle(rect) => { - let opacity = match &rect.content { - &RectangleContent::Fill(ref color) => { - PrimitiveOpacity::from_alpha(color.a) - }, - &RectangleContent::Clear => PrimitiveOpacity::opaque() - }; - let metadata = PrimitiveMetadata { - opacity, - prim_kind: PrimitiveKind::Rectangle, - cpu_prim_index: SpecificPrimitiveIndex(self.cpu_rectangles.len()), - ..base_metadata + PrimitiveContainer::Brush(brush) => { + let opacity = match brush.kind { + BrushKind::Clear => PrimitiveOpacity::translucent(), + BrushKind::Solid { ref color } => PrimitiveOpacity::from_alpha(color.a), + BrushKind::Mask { .. } => PrimitiveOpacity::translucent(), }; - self.cpu_rectangles.push(rect); - - metadata - } - PrimitiveContainer::Brush(brush) => { let metadata = PrimitiveMetadata { - opacity: PrimitiveOpacity::translucent(), + opacity, prim_kind: PrimitiveKind::Brush, cpu_prim_index: SpecificPrimitiveIndex(self.cpu_brushes.len()), ..base_metadata @@ -1109,7 +1236,7 @@ impl PrimitiveStore { ) { let metadata = &mut self.cpu_metadata[prim_index.0]; match metadata.prim_kind { - PrimitiveKind::Rectangle | PrimitiveKind::Border | PrimitiveKind::Line => {} + PrimitiveKind::Border | PrimitiveKind::Line => {} PrimitiveKind::Picture => { self.cpu_pictures[metadata.cpu_prim_index.0] .prepare_for_render( @@ -1169,10 +1296,10 @@ impl PrimitiveStore { ); } } + PrimitiveKind::Brush | PrimitiveKind::AlignedGradient | PrimitiveKind::AngleGradient | - PrimitiveKind::RadialGradient | - PrimitiveKind::Brush => {} + PrimitiveKind::RadialGradient => {} } // Mark this GPU resource as required for this frame. @@ -1181,10 +1308,6 @@ impl PrimitiveStore { request.push(metadata.local_clip_rect); match metadata.prim_kind { - PrimitiveKind::Rectangle => { - let rect = &self.cpu_rectangles[metadata.cpu_prim_index.0]; - rect.write_gpu_blocks(request); - } PrimitiveKind::Line => { let line = &self.cpu_lines[metadata.cpu_prim_index.0]; line.write_gpu_blocks(request); @@ -1218,8 +1341,23 @@ impl PrimitiveStore { text.write_gpu_blocks(&mut request); } PrimitiveKind::Picture => { + // TODO(gw): This is a bit of a hack. The Picture type + // is drawn by the brush_image shader, so the + // layout here needs to conform to the same + // BrushPrimitive layout. We should tidy this + // up in the future so it's enforced that these + // types use a shared function to write out the + // GPU blocks... + request.push([0.0; 4]); + request.push([ + BrushAntiAliasMode::Primitive as u32 as f32, + 0.0, + 0.0, + 0.0, + ]); + self.cpu_pictures[metadata.cpu_prim_index.0] - .write_gpu_blocks(request); + .write_gpu_blocks(&mut request); } PrimitiveKind::Brush => { let brush = &self.cpu_brushes[metadata.cpu_prim_index.0]; @@ -1240,6 +1378,7 @@ impl PrimitiveStore { render_tasks: &mut RenderTaskTree, clip_store: &mut ClipStore, tasks: &mut Vec, + node_data: &[ClipScrollNodeData], ) -> bool { let metadata = &mut self.cpu_metadata[prim_index.0]; metadata.clip_task_id = None; @@ -1344,17 +1483,112 @@ impl PrimitiveStore { return true; } - let clip_task = RenderTask::new_mask( - None, - combined_outer_rect, - combined_inner_rect, - clips, - clip_store, - transform.transform_kind() == TransformedRectKind::AxisAligned, - prim_coordinate_system_id, - ); + let mut needs_prim_clip_task = true; + + if metadata.prim_kind == PrimitiveKind::Brush { + let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0]; + if brush.segment_desc.is_none() && metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA { + if let BrushKind::Solid { .. } = brush.kind { + if clips.len() == 1 { + let clip_item = clips.first().unwrap(); + if clip_item.coordinate_system_id == prim_coordinate_system_id { + let local_clips = clip_store.get_opt(&clip_item.clip_sources).expect("bug"); + let mut selected_clip = None; + for &(ref clip, _) in &local_clips.clips { + match *clip { + ClipSource::RoundedRectangle(rect, radii, ClipMode::Clip) => { + if selected_clip.is_some() { + selected_clip = None; + break; + } + selected_clip = Some((rect, radii, clip_item.scroll_node_data_index)); + } + ClipSource::Rectangle(..) => {} + ClipSource::RoundedRectangle(_, _, ClipMode::ClipOut) | + ClipSource::BorderCorner(..) | + ClipSource::Image(..) => { + selected_clip = None; + break; + } + } + } + if let Some((rect, radii, clip_scroll_node_data_index)) = selected_clip { + // If the scroll node transforms are different between the clip + // node and the primitive, we need to get the clip rect in the + // local space of the primitive, in order to generate correct + // local segments. + let local_clip_rect = if clip_scroll_node_data_index == prim_context.scroll_node.node_data_index { + rect + } else { + let clip_transform_data = &node_data[clip_scroll_node_data_index.0 as usize]; + let prim_transform = &prim_context.scroll_node.world_content_transform; + + let relative_transform = prim_transform + .inverse() + .unwrap_or(WorldToLayerTransform::identity()) + .pre_mul(&clip_transform_data.transform); + + relative_transform.transform_rect(&rect) + }; + brush.segment_desc = create_nine_patch( + &metadata.local_rect, + &local_clip_rect, + &radii + ); + } + } + } + } + } + + if let Some(ref mut segment_desc) = brush.segment_desc { + let enabled_segments = segment_desc.enabled_segments; + let can_optimize_clip_mask = segment_desc.can_optimize_clip_mask; + + for (i, segment) in segment_desc.segments.iter_mut().enumerate() { + // We only build clips for the corners. The ordering of the + // BrushSegmentKind enum is such that corners come first, then + // edges, then inner. + let segment_enabled = ((1 << i) & enabled_segments) != 0; + let create_clip_task = segment_enabled && + (!can_optimize_clip_mask || i <= BrushSegmentKind::BottomLeft as usize); + segment.clip_task_id = if create_clip_task { + let segment_rect = TransformedRect::new( + &segment.local_rect, + &prim_context.scroll_node.world_content_transform, + prim_context.device_pixel_ratio + ); + + combined_outer_rect.intersection(&segment_rect.bounding_rect).map(|bounds| { + let clip_task = RenderTask::new_mask( + None, + bounds, + clips.clone(), + prim_coordinate_system_id, + ); + + let clip_task_id = render_tasks.add(clip_task); + tasks.push(clip_task_id); + + clip_task_id + }) + } else { + None + }; + } + + needs_prim_clip_task = false; + } + } + + if needs_prim_clip_task { + let clip_task = RenderTask::new_mask( + None, + combined_outer_rect, + clips, + prim_coordinate_system_id, + ); - if let Some(clip_task) = clip_task { let clip_task_id = render_tasks.add(clip_task); metadata.clip_task_id = Some(clip_task_id); tasks.push(clip_task_id); @@ -1379,6 +1613,7 @@ impl PrimitiveStore { profile_counters: &mut FrameProfileCounters, pic_index: SpecificPrimitiveIndex, screen_rect: &DeviceIntRect, + node_data: &[ClipScrollNodeData], ) -> Option { // Reset the visibility of this primitive. // Do some basic checks first, that can early out @@ -1445,6 +1680,7 @@ impl PrimitiveStore { scene_properties, cpu_prim_index, screen_rect, + node_data, ); let metadata = &mut self.cpu_metadata[prim_index.0]; @@ -1500,6 +1736,7 @@ impl PrimitiveStore { render_tasks, clip_store, parent_tasks, + node_data, ) { return None; } @@ -1544,6 +1781,7 @@ impl PrimitiveStore { scene_properties: &SceneProperties, pic_index: SpecificPrimitiveIndex, screen_rect: &DeviceIntRect, + node_data: &[ClipScrollNodeData], ) -> PrimitiveRunLocalRect { let mut result = PrimitiveRunLocalRect { local_rect_in_actual_parent_space: LayerRect::zero(), @@ -1611,6 +1849,7 @@ impl PrimitiveStore { profile_counters, pic_index, screen_rect, + node_data, ) { profile_counters.visible_primitives.inc(); @@ -1683,3 +1922,20 @@ fn get_local_bounding_rect( LayerRect::new(LayerPoint::new(x0, y0), LayerSize::new(x1 - x0, y1 - y0)) } + +fn create_nine_patch( + local_rect: &LayerRect, + local_clip_rect: &LayerRect, + radii: &BorderRadius +) -> Option> { + extract_inner_rect_safe(local_clip_rect, radii).map(|inner| { + let mut desc = BrushSegmentDescriptor::new( + local_rect, + &inner, + None, + ); + desc.can_optimize_clip_mask = true; + + Box::new(desc) + }) +} diff --git a/webrender/src/render_task.rs b/webrender/src/render_task.rs index 760c2df3ae..df3d624373 100644 --- a/webrender/src/render_task.rs +++ b/webrender/src/render_task.rs @@ -5,7 +5,7 @@ use api::{ClipId, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel}; use api::{LayerPoint, LayerRect, PremultipliedColorF}; use box_shadow::BoxShadowCacheKey; -use clip::{ClipSource, ClipSourcesWeakHandle, ClipStore}; +use clip::{ClipSourcesWeakHandle}; use clip_scroll_tree::CoordinateSystemId; use euclid::TypedSize2D; use gpu_types::{ClipScrollNodeIndex}; @@ -164,25 +164,6 @@ pub enum RenderTaskLocation { Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize), } -#[derive(Debug, Copy, Clone)] -#[repr(C)] -pub enum MaskSegment { - // This must match the SEGMENT_ values in clip_shared.glsl! - All = 0, - TopLeftCorner, - TopRightCorner, - BottomLeftCorner, - BottomRightCorner, -} - -#[derive(Debug, Copy, Clone)] -#[repr(C)] -pub enum MaskGeometryKind { - Default, // Draw the entire rect - CornersOnly, // Draw the corners (simple axis aligned mask) - // TODO(gw): Add more types here (e.g. 4 rectangles outside the inner rect) -} - #[derive(Debug, Clone)] pub struct ClipWorkItem { pub scroll_node_data_index: ClipScrollNodeIndex, @@ -190,52 +171,10 @@ pub struct ClipWorkItem { pub coordinate_system_id: CoordinateSystemId, } -impl ClipWorkItem { - fn get_geometry_kind( - &self, - clip_store: &ClipStore, - prim_coordinate_system_id: CoordinateSystemId - ) -> MaskGeometryKind { - let clips = clip_store - .get_opt(&self.clip_sources) - .expect("bug: clip handle should be valid") - .clips(); - let mut rounded_rect_count = 0; - - for &(ref clip, _) in clips { - match *clip { - ClipSource::Rectangle(..) => { - if !self.has_compatible_coordinate_system(prim_coordinate_system_id) { - return MaskGeometryKind::Default; - } - }, - ClipSource::RoundedRectangle(..) => { - rounded_rect_count += 1; - } - ClipSource::Image(..) | ClipSource::BorderCorner(..) => { - return MaskGeometryKind::Default; - } - } - } - - if rounded_rect_count == 1 { - MaskGeometryKind::CornersOnly - } else { - MaskGeometryKind::Default - } - } - - fn has_compatible_coordinate_system(&self, other_id: CoordinateSystemId) -> bool { - self.coordinate_system_id == other_id - } -} - #[derive(Debug)] pub struct CacheMaskTask { actual_rect: DeviceIntRect, - inner_rect: DeviceIntRect, pub clips: Vec, - pub geometry_kind: MaskGeometryKind, pub coordinate_system_id: CoordinateSystemId, } @@ -353,38 +292,20 @@ impl RenderTask { pub fn new_mask( key: Option, outer_rect: DeviceIntRect, - inner_rect: DeviceIntRect, clips: Vec, - clip_store: &ClipStore, - is_axis_aligned: bool, prim_coordinate_system_id: CoordinateSystemId, - ) -> Option { - // TODO(gw): This optimization is very conservative for now. - // For now, only draw optimized geometry if it is - // a single aligned rect mask with rounded corners. - // In the future, we'll expand this to handle the - // more complex types of clip mask geometry. - let geometry_kind = if is_axis_aligned && - clips.len() == 1 && - inner_rect.size != DeviceIntSize::zero() { - clips[0].get_geometry_kind(clip_store, prim_coordinate_system_id) - } else { - MaskGeometryKind::Default - }; - - Some(RenderTask { + ) -> RenderTask { + RenderTask { cache_key: key.map(RenderTaskKey::CacheMask), children: Vec::new(), location: RenderTaskLocation::Dynamic(None, outer_rect.size), kind: RenderTaskKind::CacheMask(CacheMaskTask { actual_rect: outer_rect, - inner_rect: inner_rect, clips, - geometry_kind, coordinate_system_id: prim_coordinate_system_id, }), clear_mode: ClearMode::One, - }) + } } // Construct a render task to apply a blur to a primitive. @@ -529,12 +450,7 @@ impl RenderTask { task.actual_rect.origin.y as f32, 0.0, ], - [ - task.inner_rect.origin.x as f32, - task.inner_rect.origin.y as f32, - (task.inner_rect.origin.x + task.inner_rect.size.width) as f32, - (task.inner_rect.origin.y + task.inner_rect.size.height) as f32, - ], + [0.0; 4], ) } RenderTaskKind::VerticalBlur(ref task) | @@ -673,7 +589,6 @@ impl RenderTask { RenderTaskKind::CacheMask(ref task) => { pt.new_level(format!("CacheMask with {} clips", task.clips.len())); pt.add_item(format!("rect: {:?}", task.actual_rect)); - pt.add_item(format!("geometry: {:?}", task.geometry_kind)); } RenderTaskKind::VerticalBlur(ref task) => { pt.new_level("VerticalBlur".to_owned()); diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index 27f24d5fc0..a64e739a5a 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -72,6 +72,10 @@ use util::TransformedRectKind; pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024; +const GPU_TAG_BRUSH_SOLID: GpuProfileTag = GpuProfileTag { + label: "B_Solid", + color: debug_colors::RED, +}; const GPU_TAG_BRUSH_MASK: GpuProfileTag = GpuProfileTag { label: "B_Mask", color: debug_colors::BLACK, @@ -100,10 +104,6 @@ const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag { label: "data init", color: debug_colors::LIGHTGREY, }; -const GPU_TAG_PRIM_RECT: GpuProfileTag = GpuProfileTag { - label: "Rect", - color: debug_colors::RED, -}; const GPU_TAG_PRIM_LINE: GpuProfileTag = GpuProfileTag { label: "Line", color: debug_colors::DARKRED, @@ -178,7 +178,6 @@ impl TransformBatchKind { #[cfg(feature = "debugger")] fn debug_name(&self) -> &'static str { match *self { - TransformBatchKind::Rectangle(..) => "Rectangle", TransformBatchKind::TextRun(..) => "TextRun", TransformBatchKind::Image(image_buffer_kind, ..) => match image_buffer_kind { ImageBufferKind::Texture2D => "Image (2D)", @@ -198,7 +197,6 @@ impl TransformBatchKind { fn gpu_sampler_tag(&self) -> GpuProfileTag { match *self { - TransformBatchKind::Rectangle(_) => GPU_TAG_PRIM_RECT, TransformBatchKind::Line => GPU_TAG_PRIM_LINE, TransformBatchKind::TextRun(..) => GPU_TAG_PRIM_TEXT_RUN, TransformBatchKind::Image(..) => GPU_TAG_PRIM_IMAGE, @@ -220,7 +218,12 @@ impl BatchKind { BatchKind::HardwareComposite => "HardwareComposite", BatchKind::SplitComposite => "SplitComposite", BatchKind::Blend => "Blend", - BatchKind::Brush(BrushBatchKind::Image(..)) => "Brush (Image)", + BatchKind::Brush(kind) => { + match kind { + BrushBatchKind::Image(..) => "Brush (Image)", + BrushBatchKind::Solid => "Brush (Solid)", + } + } BatchKind::Transformable(_, batch_kind) => batch_kind.debug_name(), } } @@ -231,7 +234,12 @@ impl BatchKind { BatchKind::HardwareComposite => GPU_TAG_PRIM_HW_COMPOSITE, BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE, BatchKind::Blend => GPU_TAG_PRIM_BLEND, - BatchKind::Brush(BrushBatchKind::Image(_)) => GPU_TAG_BRUSH_IMAGE, + BatchKind::Brush(kind) => { + match kind { + BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE, + BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID, + } + } BatchKind::Transformable(_, batch_kind) => batch_kind.gpu_sampler_tag(), } } @@ -912,7 +920,6 @@ impl VertexDataTexture { } const TRANSFORM_FEATURE: &str = "TRANSFORM"; -const CLIP_FEATURE: &str = "CLIP"; const ALPHA_FEATURE: &str = "ALPHA_PASS"; enum ShaderKind { @@ -1355,6 +1362,7 @@ pub struct Renderer { brush_mask_rounded_rect: LazilyCompiledShader, brush_image_rgba8: BrushShader, brush_image_a8: BrushShader, + brush_solid: BrushShader, /// These are "cache clip shaders". These shaders are used to /// draw clip instances into the cached clip mask. The results @@ -1370,8 +1378,6 @@ pub struct Renderer { // shadow primitive shader stretches the box shadow cache // output, and the cache_image shader blits the results of // a cache shader (e.g. blur) to the screen. - ps_rectangle: PrimitiveShader, - ps_rectangle_clip: PrimitiveShader, ps_text_run: TextShader, ps_text_run_subpx_bg_pass1: TextShader, ps_image: Vec>, @@ -1558,6 +1564,13 @@ impl Renderer { options.precache_shaders) }; + let brush_solid = try!{ + BrushShader::new("brush_solid", + &mut device, + &[], + options.precache_shaders) + }; + let brush_image_a8 = try!{ BrushShader::new("brush_image", &mut device, @@ -1612,20 +1625,6 @@ impl Renderer { options.precache_shaders) }; - let ps_rectangle = try!{ - PrimitiveShader::new("ps_rectangle", - &mut device, - &[], - options.precache_shaders) - }; - - let ps_rectangle_clip = try!{ - PrimitiveShader::new("ps_rectangle", - &mut device, - &[ CLIP_FEATURE ], - options.precache_shaders) - }; - let ps_line = try!{ PrimitiveShader::new("ps_line", &mut device, @@ -2011,11 +2010,10 @@ impl Renderer { brush_mask_rounded_rect, brush_image_rgba8, brush_image_a8, + brush_solid, cs_clip_rectangle, cs_clip_border, cs_clip_image, - ps_rectangle, - ps_rectangle_clip, ps_text_run, ps_text_run_subpx_bg_pass1, ps_image, @@ -2787,6 +2785,15 @@ impl Renderer { } BatchKind::Brush(brush_kind) => { match brush_kind { + BrushBatchKind::Solid => { + self.brush_solid.bind( + &mut self.device, + key.blend_mode, + projection, + 0, + &mut self.renderer_errors, + ); + } BrushBatchKind::Image(target_kind) => { let shader = match target_kind { RenderTargetKind::Alpha => &mut self.brush_image_a8, @@ -2803,36 +2810,6 @@ impl Renderer { } } BatchKind::Transformable(transform_kind, batch_kind) => match batch_kind { - TransformBatchKind::Rectangle(needs_clipping) => { - debug_assert!( - !needs_clipping || match key.blend_mode { - BlendMode::PremultipliedAlpha | - BlendMode::PremultipliedDestOut | - BlendMode::SubpixelConstantTextColor(..) | - BlendMode::SubpixelVariableTextColor | - BlendMode::SubpixelWithBgColor => true, - BlendMode::None => false, - } - ); - - if needs_clipping { - self.ps_rectangle_clip.bind( - &mut self.device, - transform_kind, - projection, - 0, - &mut self.renderer_errors, - ); - } else { - self.ps_rectangle.bind( - &mut self.device, - transform_kind, - projection, - 0, - &mut self.renderer_errors, - ); - } - } TransformBatchKind::Line => { self.ps_line.bind( &mut self.device, @@ -4179,11 +4156,10 @@ impl Renderer { self.brush_mask_corner.deinit(&mut self.device); self.brush_image_rgba8.deinit(&mut self.device); self.brush_image_a8.deinit(&mut self.device); + self.brush_solid.deinit(&mut self.device); self.cs_clip_rectangle.deinit(&mut self.device); self.cs_clip_image.deinit(&mut self.device); self.cs_clip_border.deinit(&mut self.device); - self.ps_rectangle.deinit(&mut self.device); - self.ps_rectangle_clip.deinit(&mut self.device); self.ps_text_run.deinit(&mut self.device); self.ps_text_run_subpx_bg_pass1.deinit(&mut self.device); for shader in self.ps_image { diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index 9670af6618..9120164dd3 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -20,13 +20,13 @@ use gpu_types::{BlurDirection, BlurInstance, BrushInstance, BrushImageKind, Clip use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance}; use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData}; use internal_types::{FastHashMap, SourceTexture}; -use internal_types::BatchTextures; +use internal_types::{BatchTextures}; use picture::{PictureCompositeMode, PictureKind, PicturePrimitive, RasterizationSpace}; use plane_split::{BspSplitter, Polygon, Splitter}; use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore}; -use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, PrimitiveRun, RectangleContent}; +use prim_store::{BrushPrimitive, BrushMaskKind, BrushKind, BrushSegmentKind, DeferredResolve, PrimitiveRun}; use profiler::FrameProfileCounters; -use render_task::{ClipWorkItem, MaskGeometryKind, MaskSegment}; +use render_task::{ClipWorkItem}; use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKey, RenderTaskKind}; use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree}; use renderer::BlendMode; @@ -121,23 +121,6 @@ impl AlphaBatchHelpers for PrimitiveStore { FontRenderMode::Bitmap => BlendMode::PremultipliedAlpha, } }, - PrimitiveKind::Rectangle => { - let rectangle_cpu = &self.cpu_rectangles[metadata.cpu_prim_index.0]; - match rectangle_cpu.content { - RectangleContent::Fill(..) => if needs_blending { - BlendMode::PremultipliedAlpha - } else { - BlendMode::None - }, - RectangleContent::Clear => { - // TODO: If needs_blending == false, we could use BlendMode::None - // to clear the rectangle, but then we'd need to draw the rectangle - // with alpha == 0.0 instead of alpha == 1.0, and the RectanglePrimitive - // would need to know about that. - BlendMode::PremultipliedDestOut - }, - } - }, PrimitiveKind::Border | PrimitiveKind::Image | PrimitiveKind::YuvImage | @@ -410,7 +393,64 @@ fn add_to_batch( match prim_metadata.prim_kind { PrimitiveKind::Brush => { - panic!("BUG: brush type not expected in an alpha task (yet)"); + let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0]; + let base_instance = BrushInstance { + picture_address: task_address, + prim_address: prim_cache_address, + clip_id, + scroll_id, + clip_task_address, + z, + segment_kind: 0, + user_data0: 0, + user_data1: 0, + }; + + match brush.segment_desc { + Some(ref segment_desc) => { + let opaque_batch = batch_list.opaque_batch_list.get_suitable_batch( + brush.get_batch_key( + BlendMode::None + ), + item_bounding_rect + ); + let alpha_batch = batch_list.alpha_batch_list.get_suitable_batch( + brush.get_batch_key( + BlendMode::PremultipliedAlpha + ), + item_bounding_rect + ); + + for (i, segment) in segment_desc.segments.iter().enumerate() { + if ((1 << i) & segment_desc.enabled_segments) != 0 { + let is_inner = i == BrushSegmentKind::Center as usize; + let needs_blending = !prim_metadata.opacity.is_opaque || + segment.clip_task_id.is_some() || + (!is_inner && transform_kind == TransformedRectKind::Complex); + + let clip_task_address = segment + .clip_task_id + .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id)); + + let instance = PrimitiveInstance::from(BrushInstance { + segment_kind: 1 + i as i32, + clip_task_address, + ..base_instance + }); + + if needs_blending { + alpha_batch.push(instance); + } else { + opaque_batch.push(instance); + } + } + } + } + None => { + let batch = batch_list.get_suitable_batch(brush.get_batch_key(blend_mode), item_bounding_rect); + batch.push(PrimitiveInstance::from(base_instance)); + } + } } PrimitiveKind::Border => { let border_cpu = @@ -464,16 +504,6 @@ fn add_to_batch( batch.push(base_instance.build(border_segment, 0, 0)); } } - PrimitiveKind::Rectangle => { - let needs_clipping = prim_metadata.clip_task_id.is_some(); - let kind = BatchKind::Transformable( - transform_kind, - TransformBatchKind::Rectangle(needs_clipping), - ); - let key = BatchKey::new(kind, blend_mode, no_textures); - let batch = batch_list.get_suitable_batch(key, item_bounding_rect); - batch.push(base_instance.build(0, 0, 0)); - } PrimitiveKind::Line => { let kind = BatchKind::Transformable(transform_kind, TransformBatchKind::Line); @@ -609,7 +639,7 @@ fn add_to_batch( scroll_id, clip_task_address, z, - flags: 0, + segment_kind: 0, user_data0: cache_task_address.0 as i32, user_data1: BrushImageKind::Simple as i32, }; @@ -638,7 +668,7 @@ fn add_to_batch( scroll_id, clip_task_address, z, - flags: 0, + segment_kind: 0, user_data0: cache_task_address.0 as i32, user_data1: image_kind as i32, }; @@ -1083,7 +1113,6 @@ impl ClipBatcher { coordinate_system_id: CoordinateSystemId, resource_cache: &ResourceCache, gpu_cache: &GpuCache, - geometry_kind: MaskGeometryKind, clip_store: &ClipStore, ) { let mut coordinate_system_id = coordinate_system_id; @@ -1122,43 +1151,17 @@ impl ClipBatcher { if work_item.coordinate_system_id != coordinate_system_id { self.rectangles.push(ClipMaskInstance { clip_data_address: gpu_address, - segment: MaskSegment::All as i32, ..instance }); coordinate_system_id = work_item.coordinate_system_id; } - }, - ClipSource::RoundedRectangle(..) => match geometry_kind { - MaskGeometryKind::Default => { - self.rectangles.push(ClipMaskInstance { - clip_data_address: gpu_address, - segment: MaskSegment::All as i32, - ..instance - }); - } - MaskGeometryKind::CornersOnly => { - self.rectangles.push(ClipMaskInstance { - clip_data_address: gpu_address, - segment: MaskSegment::TopLeftCorner as i32, - ..instance - }); - self.rectangles.push(ClipMaskInstance { - clip_data_address: gpu_address, - segment: MaskSegment::TopRightCorner as i32, - ..instance - }); - self.rectangles.push(ClipMaskInstance { - clip_data_address: gpu_address, - segment: MaskSegment::BottomLeftCorner as i32, - ..instance - }); - self.rectangles.push(ClipMaskInstance { - clip_data_address: gpu_address, - segment: MaskSegment::BottomRightCorner as i32, - ..instance - }); - } - }, + } + ClipSource::RoundedRectangle(..) => { + self.rectangles.push(ClipMaskInstance { + clip_data_address: gpu_address, + ..instance + }); + } ClipSource::BorderCorner(ref source) => { self.border_clears.push(ClipMaskInstance { clip_data_address: gpu_address, @@ -1666,12 +1669,16 @@ impl RenderTarget for AlphaRenderTarget { scroll_id: ClipScrollNodeIndex(0), clip_task_address: RenderTaskAddress(0), z: 0, - flags: 0, + segment_kind: 0, user_data0: 0, user_data1: 0, }; let brush = &ctx.prim_store.cpu_brushes[sub_metadata.cpu_prim_index.0]; let batch = match brush.kind { + BrushKind::Solid { .. } | + BrushKind::Clear => { + unreachable!("bug: unexpected brush here"); + } BrushKind::Mask { ref kind, .. } => { match *kind { BrushMaskKind::Corner(..) => &mut self.brush_mask_corners, @@ -1702,7 +1709,6 @@ impl RenderTarget for AlphaRenderTarget { task_info.coordinate_system_id, &ctx.resource_cache, gpu_cache, - task_info.geometry_kind, clip_store, ); } @@ -1863,7 +1869,6 @@ impl RenderPass { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum TransformBatchKind { - Rectangle(bool), TextRun(GlyphFormat), Image(ImageBufferKind), YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace), @@ -1877,7 +1882,8 @@ pub enum TransformBatchKind { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum BrushBatchKind { - Image(RenderTargetKind) + Image(RenderTargetKind), + Solid, } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -2097,3 +2103,27 @@ fn make_polygon( transform.m44 as f64); Polygon::from_transformed_rect(rect.cast().unwrap(), mat, anchor) } + +impl BrushPrimitive { + fn get_batch_key(&self, blend_mode: BlendMode) -> BatchKey { + match self.kind { + BrushKind::Solid { .. } => { + BatchKey::new( + BatchKind::Brush(BrushBatchKind::Solid), + blend_mode, + BatchTextures::no_texture(), + ) + } + BrushKind::Clear => { + BatchKey::new( + BatchKind::Brush(BrushBatchKind::Solid), + BlendMode::PremultipliedDestOut, + BatchTextures::no_texture(), + ) + } + BrushKind::Mask { .. } => { + unreachable!("bug: mask brushes not expected in normal alpha pass"); + } + } + } +} diff --git a/webrender/src/util.rs b/webrender/src/util.rs index 624a0e4130..1441f2acf3 100644 --- a/webrender/src/util.rs +++ b/webrender/src/util.rs @@ -4,7 +4,7 @@ use api::{BorderRadius, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize}; use api::{DevicePoint, DeviceRect, DeviceSize, LayerPoint, LayerRect, LayerSize}; -use api::{LayerToWorldTransform, LayoutPoint, LayoutRect, LayoutSize, WorldPoint3D}; +use api::{LayerToWorldTransform, LayoutRect, WorldPoint3D}; use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D, TypedTransform2D}; use euclid::TypedTransform3D; use num_traits::Zero; @@ -290,14 +290,6 @@ pub trait ComplexClipRegionHelpers { /// Return the approximately largest aligned rectangle that is fully inside /// the provided clip region. fn get_inner_rect_full(&self) -> Option; - /// Split the clip region into 2 sets of rectangles: opaque and transparent. - /// Guarantees no T-junctions in the produced split. - /// Attempts to cover more space in opaque, where it reasonably makes sense. - fn split_rectangles( - &self, - opaque: &mut Vec, - transparent: &mut Vec, - ); } impl ComplexClipRegionHelpers for ComplexClipRegion { @@ -306,63 +298,6 @@ impl ComplexClipRegionHelpers for ComplexClipRegion { let k = 1.0 - 0.5 * FRAC_1_SQRT_2; // could be nicely approximated to `0.3` extract_inner_rect_impl(&self.rect, &self.radii, k) } - - fn split_rectangles( - &self, - opaque: &mut Vec, - transparent: &mut Vec, - ) { - fn rect(p0: LayoutPoint, p1: LayoutPoint) -> Option { - if p0.x != p1.x && p0.y != p1.y { - Some(LayerRect::new(p0.min(p1), (p1 - p0).abs().to_size())) - } else { - None - } - } - - let inner = match extract_inner_rect_impl(&self.rect, &self.radii, 1.0) { - Some(rect) => rect, - None => { - transparent.push(self.rect); - return - }, - }; - let left_top = inner.origin - self.rect.origin; - let right_bot = self.rect.bottom_right() - inner.bottom_right(); - - // fill in the opaque parts - opaque.push(inner); - if left_top.x > 0.0 { - opaque.push(LayerRect::new( - LayoutPoint::new(self.rect.origin.x, inner.origin.y), - LayoutSize::new(left_top.x, inner.size.height), - )); - } - if right_bot.y > 0.0 { - opaque.push(LayerRect::new( - LayoutPoint::new(inner.origin.x, inner.origin.y + inner.size.height), - LayoutSize::new(inner.size.width, right_bot.y), - )); - } - if right_bot.x > 0.0 { - opaque.push(LayerRect::new( - LayoutPoint::new(inner.origin.x + inner.size.width, inner.origin.y), - LayoutSize::new(right_bot.x, inner.size.height), - )); - } - if left_top.y > 0.0 { - opaque.push(LayerRect::new( - LayoutPoint::new(inner.origin.x, self.rect.origin.y), - LayoutSize::new(inner.size.width, left_top.y), - )); - } - - // fill in the transparent parts - transparent.extend(rect(self.rect.origin, inner.origin)); - transparent.extend(rect(self.rect.bottom_left(), inner.bottom_left())); - transparent.extend(rect(self.rect.bottom_right(), inner.bottom_right())); - transparent.extend(rect(self.rect.top_right(), inner.top_right())); - } } #[inline] diff --git a/webrender/tests/angle_shader_validation.rs b/webrender/tests/angle_shader_validation.rs index a1459e0e4c..274b50cb54 100644 --- a/webrender/tests/angle_shader_validation.rs +++ b/webrender/tests/angle_shader_validation.rs @@ -94,15 +94,15 @@ const SHADERS: &[Shader] = &[ name: "ps_text_run", features: PRIM_FEATURES, }, - Shader { - name: "ps_rectangle", - features: &["", "TRANSFORM", "CLIP_FEATURE", "TRANSFORM,CLIP_FEATURE"], - }, // Brush shaders Shader { name: "brush_mask", features: &[], }, + Shader { + name: "brush_solid", + features: &[], + }, Shader { name: "brush_image", features: &["COLOR_TARGET", "ALPHA_TARGET"], diff --git a/wrench/reftests/transforms/reftest.list b/wrench/reftests/transforms/reftest.list index 176db325d0..a986305e49 100644 --- a/wrench/reftests/transforms/reftest.list +++ b/wrench/reftests/transforms/reftest.list @@ -7,4 +7,4 @@ fuzzy(1,2) == rotated-image.yaml rotated-image.png == singular.yaml singular-ref.yaml fuzzy(1,41) == perspective.yaml perspective.png == prim-suite.yaml prim-suite.png - +== segments-bug.yaml segments-bug-ref.yaml diff --git a/wrench/reftests/transforms/rotated-image.png b/wrench/reftests/transforms/rotated-image.png index fc0958fd801dd8d3bde4cffe0405d9e6623f7028..5aede3284d4d0198f51bb80909a37f2c9fb67618 100644 GIT binary patch literal 7481 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 E14H^nGXMYp literal 7469 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