Commit 7239114d authored by chrishtr's avatar chrishtr Committed by Commit bot

Implement merging non-composited paint property nodes in the PACompositor.

Currently, the PaintArtifactCompositor naively puts every PaintChunk
into its own composited layer. This patch improves on that by merging
a PaintChunk into the same layer as the PaintChunk which precedes it,
if the new PaintChunk has a compatible PropertyTreeState (no directly
composited property tree nodes between the state of the new PaintChunk
and the old one).

This algorithm is spelled out in a design doc, see crbug.com/668342.

A large amount of this patch is additional tests and testing machinery
to support the change in behavior.

BUG=668342
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2

Review-Url: https://codereview.chromium.org/2581843002
Cr-Commit-Position: refs/heads/master@{#440775}
parent 356cd837
...@@ -538,6 +538,7 @@ Bug(none) fast/canvas/alpha.html [ Failure ] ...@@ -538,6 +538,7 @@ Bug(none) fast/canvas/alpha.html [ Failure ]
Bug(none) fast/canvas/canvas-composite-video.html [ Failure ] Bug(none) fast/canvas/canvas-composite-video.html [ Failure ]
Bug(none) fast/canvas/canvas-css-clip-path.html [ Failure ] Bug(none) fast/canvas/canvas-css-clip-path.html [ Failure ]
Bug(none) fast/canvas/webgl/pixelated.html [ Failure ] Bug(none) fast/canvas/webgl/pixelated.html [ Failure ]
crbug.com/668342 fast/clip/010.html [ Failure ]
Bug(none) fast/clip/nestedTransparencyClip.html [ Failure ] Bug(none) fast/clip/nestedTransparencyClip.html [ Failure ]
Bug(none) fast/clip/outline-overflowClip.html [ Failure ] Bug(none) fast/clip/outline-overflowClip.html [ Failure ]
Bug(none) fast/clip/overflow-border-radius-clip.html [ Failure ] Bug(none) fast/clip/overflow-border-radius-clip.html [ Failure ]
...@@ -1247,6 +1248,7 @@ Bug(none) svg/custom/grayscale-gradient-mask-2.svg [ Failure ] ...@@ -1247,6 +1248,7 @@ Bug(none) svg/custom/grayscale-gradient-mask-2.svg [ Failure ]
Bug(none) svg/custom/grayscale-gradient-mask.svg [ Failure ] Bug(none) svg/custom/grayscale-gradient-mask.svg [ Failure ]
Bug(none) svg/custom/group-opacity.svg [ Failure ] Bug(none) svg/custom/group-opacity.svg [ Failure ]
Bug(none) svg/custom/image-with-preserveAspectRatio-none.html [ Failure ] Bug(none) svg/custom/image-with-preserveAspectRatio-none.html [ Failure ]
Bug(none) svg/custom/image-with-transform-clip-filter.svg [ Failure ]
Bug(none) svg/custom/inline-svg-in-xhtml.xml [ Failure ] Bug(none) svg/custom/inline-svg-in-xhtml.xml [ Failure ]
Bug(none) svg/custom/invalid-css.svg [ Failure ] Bug(none) svg/custom/invalid-css.svg [ Failure ]
Bug(none) svg/custom/invalid-stroke-hex.svg [ Failure ] Bug(none) svg/custom/invalid-stroke-hex.svg [ Failure ]
...@@ -1335,6 +1337,7 @@ Bug(none) svg/custom/use-referencing-nonexisting-symbol.svg [ Failure ] ...@@ -1335,6 +1337,7 @@ Bug(none) svg/custom/use-referencing-nonexisting-symbol.svg [ Failure ]
Bug(none) svg/custom/use-transform.svg [ Failure ] Bug(none) svg/custom/use-transform.svg [ Failure ]
Bug(none) svg/custom/viewBox-hit.svg [ Failure ] Bug(none) svg/custom/viewBox-hit.svg [ Failure ]
Bug(none) svg/custom/viewbox-syntax.svg [ Failure ] Bug(none) svg/custom/viewbox-syntax.svg [ Failure ]
Bug(none) svg/custom/viewport-clippath-invalidation.html [ Failure ]
Bug(none) svg/custom/visibility-override-clip.svg [ Failure ] Bug(none) svg/custom/visibility-override-clip.svg [ Failure ]
Bug(none) svg/custom/visibility-override-mask.svg [ Failure ] Bug(none) svg/custom/visibility-override-mask.svg [ Failure ]
Bug(none) svg/custom/visited-link-color.svg [ Failure ] Bug(none) svg/custom/visited-link-color.svg [ Failure ]
...@@ -2206,3 +2209,12 @@ crbug.com/589279 css3/blending/background-blend-mode-data-uri-svg-image.html [ F ...@@ -2206,3 +2209,12 @@ crbug.com/589279 css3/blending/background-blend-mode-data-uri-svg-image.html [ F
# SPv1 does not understand how to apply transforms properly in PaintLayerClipper (the issue is # SPv1 does not understand how to apply transforms properly in PaintLayerClipper (the issue is
# also tracked in crbug.com/548184). # also tracked in crbug.com/548184).
# transforms/transform-overflow.html # transforms/transform-overflow.html
# Subpixel adjustments due to differences in compositing
crbug.com/668342 fast/pagination/auto-height-with-break.html [ Failure ]
crbug.com/668342 svg/custom/non-scaling-stroke-update.svg [ Failure ]
crbug.com/668342 fast/css/transform-default-parameter.html [ Failure ]
crbug.com/668342 svg/custom/use-css-events.svg [ Failure ]
crbug.com/668342 svg/text/text-layout-crash.html [ Failure ]
crbug.com/668342 fast/multicol/span/invalid-spanner-in-transform.html [ Failure ]
crbug.com/668342 images/color-profile-iframe.html [ Failure ]
...@@ -43,7 +43,9 @@ crbug.com/647831 virtual/spv2/paint/invalidation/margin.html [ Pass ] ...@@ -43,7 +43,9 @@ crbug.com/647831 virtual/spv2/paint/invalidation/margin.html [ Pass ]
crbug.com/596780 virtual/spv2/compositing/framesets/composited-frame-alignment.html [ Pass ] crbug.com/596780 virtual/spv2/compositing/framesets/composited-frame-alignment.html [ Pass ]
crbug.com/596780 virtual/spv2/compositing/geometry/outline-change.html [ Pass ] crbug.com/596780 virtual/spv2/compositing/geometry/outline-change.html [ Pass ]
crbug.com/600618 virtual/spv2/svg/custom/object-current-scale.html [ Pass ] crbug.com/600618 virtual/spv2/svg/custom/object-current-scale.html [ Pass ]
crbug.com/600618 virtual/spv2/svg/custom/object-sizing-explicit-height.xhtml [ Pass ]
# Re-add this once it rebaselines.
# crbug.com/600618 virtual/spv2/svg/custom/object-sizing-explicit-height.xhtml [ Pass ]
# SkiaBitLocker should avoid allocating huge offscreen buffer # SkiaBitLocker should avoid allocating huge offscreen buffer
crbug.com/605812 [ Mac ] virtual/spv2/fast/overflow/overflow-height-float-not-removed-crash.html [ Skip ] crbug.com/605812 [ Mac ] virtual/spv2/fast/overflow/overflow-height-float-not-removed-crash.html [ Skip ]
crbug.com/605812 [ Mac ] virtual/spv2/fast/overflow/overflow-height-float-not-removed-crash2.html [ Skip ] crbug.com/605812 [ Mac ] virtual/spv2/fast/overflow/overflow-height-float-not-removed-crash2.html [ Skip ]
...@@ -698,6 +700,8 @@ crbug.com/520613 virtual/mojo-loading/http/tests/cache/freshness-header.html [ F ...@@ -698,6 +700,8 @@ crbug.com/520613 virtual/mojo-loading/http/tests/cache/freshness-header.html [ F
crbug.com/520194 http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-overridesexpires.html [ Failure Pass ] crbug.com/520194 http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-overridesexpires.html [ Failure Pass ]
crbug.com/520194 virtual/mojo-loading/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-overridesexpires.html [ Failure Pass ] crbug.com/520194 virtual/mojo-loading/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-overridesexpires.html [ Failure Pass ]
crbug.com/668342 virtual/spv2/svg/custom/object-sizing-explicit-height.xhtml [ NeedsRebaseline ]
crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-basic.html [ Pass Failure ] crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-basic.html [ Pass Failure ]
crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-consume-deltas.html [ Pass Failure ] crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-consume-deltas.html [ Pass Failure ]
crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-consume-deltas-throw.html [ Pass Failure ] crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-consume-deltas-throw.html [ Pass Failure ]
......
...@@ -182,7 +182,7 @@ class CORE_EXPORT PaintLayerClipper { ...@@ -182,7 +182,7 @@ class CORE_EXPORT PaintLayerClipper {
LayoutRect localClipRect(const PaintLayer* ancestorLayer) const; LayoutRect localClipRect(const PaintLayer* ancestorLayer) const;
// Computes the same thing as backgroundRect in calculateRects(), but skips // Computes the same thing as backgroundRect in calculateRects(), but skips
// apllying CSS clip and the visualOverflowRect() of |m_layer|. // applying CSS clip and the visualOverflowRect() of |m_layer|.
ClipRect backgroundClipRect(const ClipRectsContext&) const; ClipRect backgroundClipRect(const ClipRectsContext&) const;
// This method figures out our layerBounds in coordinates relative to // This method figures out our layerBounds in coordinates relative to
......
...@@ -319,6 +319,18 @@ static CompositingReasons compositingReasonsForTransform( ...@@ -319,6 +319,18 @@ static CompositingReasons compositingReasonsForTransform(
!object.styleRef().subtreeWillChangeContents()) !object.styleRef().subtreeWillChangeContents())
compositingReasons |= CompositingReasonWillChangeCompositingHint; compositingReasons |= CompositingReasonWillChangeCompositingHint;
if (object.isBoxModelObject()) {
const LayoutBoxModelObject* box = toLayoutBoxModelObject(&object);
if (box->hasLayer()) {
// TODO(chrishtr): move this to the descendant-dependent flags recursion
// PaintLayer::updateDescendantDependentFlags.
box->layer()->update3DTransformedDescendantStatus();
if (box->layer()->has3DTransformedDescendant())
compositingReasons |= CompositingReason3DTransform;
}
}
return compositingReasons; return compositingReasons;
} }
......
...@@ -209,7 +209,8 @@ following during the tree walk: ...@@ -209,7 +209,8 @@ following during the tree walk:
* Building paint property tree: creates paint property tree nodes for special * Building paint property tree: creates paint property tree nodes for special
things in the layout tree, including but not limit to: overflow clip, transform, things in the layout tree, including but not limit to: overflow clip, transform,
fixed-pos, animation, mask, filter, etc. fixed-pos, animation, mask, filter, etc. Also sets direct compositing reasons to be
used later for compositing.
* Paint invalidation: Not implemented yet. TODO(wangxianzhu): add details after * Paint invalidation: Not implemented yet. TODO(wangxianzhu): add details after
it's implemented. it's implemented.
......
...@@ -1024,6 +1024,7 @@ component("platform") { ...@@ -1024,6 +1024,7 @@ component("platform") {
"graphics/paint/PaintChunker.h", "graphics/paint/PaintChunker.h",
"graphics/paint/PaintController.cpp", "graphics/paint/PaintController.cpp",
"graphics/paint/PaintController.h", "graphics/paint/PaintController.h",
"graphics/paint/PropertyTreeState.cpp",
"graphics/paint/PropertyTreeState.h", "graphics/paint/PropertyTreeState.h",
"graphics/paint/RasterInvalidationTracking.cpp", "graphics/paint/RasterInvalidationTracking.cpp",
"graphics/paint/RasterInvalidationTracking.h", "graphics/paint/RasterInvalidationTracking.h",
...@@ -1755,6 +1756,7 @@ test("blink_platform_unittests") { ...@@ -1755,6 +1756,7 @@ test("blink_platform_unittests") {
"graphics/paint/PaintChunkTest.cpp", "graphics/paint/PaintChunkTest.cpp",
"graphics/paint/PaintChunkerTest.cpp", "graphics/paint/PaintChunkerTest.cpp",
"graphics/paint/PaintControllerTest.cpp", "graphics/paint/PaintControllerTest.cpp",
"graphics/paint/PropertyTreeStateTest.cpp",
"image-decoders/FastSharedBufferReaderTest.cpp", "image-decoders/FastSharedBufferReaderTest.cpp",
"image-decoders/ImageDecoderTest.cpp", "image-decoders/ImageDecoderTest.cpp",
"image-decoders/ImageDecoderTestHelpers.cpp", "image-decoders/ImageDecoderTestHelpers.cpp",
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <memory> #include <memory>
namespace cc { namespace cc {
class DisplayItemList;
class Layer; class Layer;
} }
...@@ -25,6 +26,7 @@ class Vector2dF; ...@@ -25,6 +26,7 @@ class Vector2dF;
namespace blink { namespace blink {
class GeometryMapper;
class JSONObject; class JSONObject;
class PaintArtifact; class PaintArtifact;
class WebLayer; class WebLayer;
...@@ -81,24 +83,50 @@ class PLATFORM_EXPORT PaintArtifactCompositor { ...@@ -81,24 +83,50 @@ class PLATFORM_EXPORT PaintArtifactCompositor {
std::unique_ptr<JSONObject> layersAsJSON(LayerTreeFlags) const; std::unique_ptr<JSONObject> layersAsJSON(LayerTreeFlags) const;
#ifndef NDEBUG
void showDebugData();
#endif
private: private:
// A pending layer is a collection of paint chunks that will end up in
// the same cc::Layer.
struct PLATFORM_EXPORT PendingLayer {
PendingLayer(const PaintChunk& firstPaintChunk);
void add(const PaintChunk&, GeometryMapper*);
FloatRect bounds;
Vector<const PaintChunk*> paintChunks;
bool knownToBeOpaque;
bool backfaceHidden;
PropertyTreeState propertyTreeState;
};
PaintArtifactCompositor(); PaintArtifactCompositor();
class ContentLayerClientImpl; class ContentLayerClientImpl;
// Collects the PaintChunks into groups which will end up in the same
// cc layer. This includes testing PaintChunks for "merge" compatibility (e.g.
// directly composited property tree states are separately composited)
// and overlap testing (PaintChunks that overlap existing PaintLayers they
// are not compatible with must be separately composited).
void collectPendingLayers(const PaintArtifact&,
Vector<PendingLayer>& pendingLayers,
GeometryMapper&);
// Builds a leaf layer that represents a single paint chunk. // Builds a leaf layer that represents a single paint chunk.
// Note: cc::Layer API assumes the layer bounds start at (0, 0), but the // Note: cc::Layer API assumes the layer bounds start at (0, 0), but the
// bounding box of a paint chunk does not necessarily start at (0, 0) (and // bounding box of a paint chunk does not necessarily start at (0, 0) (and
// could even be negative). Internally the generated layer translates the // could even be negative). Internally the generated layer translates the
// paint chunk to align the bounding box to (0, 0) and return the actual // paint chunk to align the bounding box to (0, 0) and return the actual
// origin of the paint chunk in the |layerOffset| outparam. // origin of the paint chunk in the |layerOffset| outparam.
scoped_refptr<cc::Layer> layerForPaintChunk( scoped_refptr<cc::Layer> compositedLayerForPendingLayer(
const PaintArtifact&, const PaintArtifact&,
const PaintChunk&, const PendingLayer&,
gfx::Vector2dF& layerOffset, gfx::Vector2dF& layerOffset,
Vector<std::unique_ptr<ContentLayerClientImpl>>& newContentLayerClients, Vector<std::unique_ptr<ContentLayerClientImpl>>& newContentLayerClients,
RasterInvalidationTracking*, RasterInvalidationTrackingMap<const PaintChunk>*,
bool storeDebugInfo); bool storeDebugInfo,
GeometryMapper&);
// Finds a client among the current vector of clients that matches the paint // Finds a client among the current vector of clients that matches the paint
// chunk's id, or otherwise allocates a new one. // chunk's id, or otherwise allocates a new one.
...@@ -106,6 +134,26 @@ class PLATFORM_EXPORT PaintArtifactCompositor { ...@@ -106,6 +134,26 @@ class PLATFORM_EXPORT PaintArtifactCompositor {
const PaintChunk&, const PaintChunk&,
const PaintArtifact&); const PaintArtifact&);
// This method is an implementation of Algorithm step 4 from goo.gl/6xP8Oe.
static scoped_refptr<cc::DisplayItemList> recordPendingLayer(
const PaintArtifact&,
const PendingLayer&,
const gfx::Rect& combinedBounds,
GeometryMapper&);
static bool canMergeInto(const PaintArtifact&,
const PaintChunk& newChunk,
const PendingLayer& candidatePendingLayer);
// Returns true if |newChunk| might overlap |candidatePendingLayer| in the
// root property tree space. If it does overlap, it will always return true.
// If it doesn't overlap, it might return true in cases were we can't
// efficiently determine a false value, or the truth depends on
// compositor animations.
static bool mightOverlap(const PaintChunk& newChunk,
const PendingLayer& candidatePendingLayer,
GeometryMapper&);
scoped_refptr<cc::Layer> m_rootLayer; scoped_refptr<cc::Layer> m_rootLayer;
std::unique_ptr<WebLayer> m_webLayer; std::unique_ptr<WebLayer> m_webLayer;
Vector<std::unique_ptr<ContentLayerClientImpl>> m_contentLayerClients; Vector<std::unique_ptr<ContentLayerClientImpl>> m_contentLayerClients;
...@@ -115,6 +163,35 @@ class PLATFORM_EXPORT PaintArtifactCompositor { ...@@ -115,6 +163,35 @@ class PLATFORM_EXPORT PaintArtifactCompositor {
friend class StubChromeClientForSPv2; friend class StubChromeClientForSPv2;
bool m_isTrackingRasterInvalidations; bool m_isTrackingRasterInvalidations;
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
ForeignLayerPassesThrough);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
MergeSimpleChunks);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
Merge2DTransform);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
MergeClip);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
MergeOpacity);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
MergeNested);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
ClipPushedUp);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
EffectPushedUp);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
EffectAndClipPushedUp);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
ClipAndEffectNoTransform);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
TwoClips);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
TwoEffects);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
PendingLayer);
FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees,
PendingLayerWithGeometry);
}; };
} // namespace blink } // namespace blink
......
# `Source/platform/graphics/compositing`
This directory contains the implementation of the "Blink compositing algorithm".
This code is owned by the [paint team][paint-team-site].
[paint-team-site]: https://www.chromium.org/developers/paint-team
This document explains the SPv2 world as it develops, not the SPv1 world it
replaces.
## Blink compositing algorithm
Design document: goo.gl/6xP8Oe
Inputs: `PaintArtifact`
Outputs: List of `cc::Layer` objects and `cc::PropertyTree`'s.
The algorithm walks through the list of `PaintChunks` in the `PaintArtifact`,
allocating new `c::Layers` if the `PaintChunk` cannot merge into an existing
`cc::Layer`. The reasons why it would not be able to do so are:
1. The `PaintChunk` requires a foreign layer (see below)
2. The `PaintChunk` cannot merge with any existing layer, due incompatible
direct compositing reasons on its `PropertyTreeState`.
3. The `PaintChunk` overlaps with an earlier `cc::Layer` that it can't merge with
due to reason 2, and there is no later-drawn `cc::Layer` for which reasons 1 and
2 do not apply.
In the worst case, this algorithm has an O(n^2) running time, where n is the
number of `PaintChunks`.
All property tree nodes referred to by any `PaintChunk` are currently copied
into their equivalent `cc::PropertyTree` node, regardless of whether they are
required by the above.
### Flattening property tree nodes
When `PaintChunks` can merge into an existing `cc::Layer`, they may have
different `PropertyTreeState`s than the `PropertyTreeState` of the `cc::Layer`.
If so, we need to *flatten* down the nodes that are different between the
`PropertyTreeState` of the `PaintChunk` and the `cc::Layer`. This is done
by iterating over the "innermostNode" of the `PropertyTreeState`, finding
the sequence of transform, clip and effect nodes that need to be flattened,
and turning them into a sequence of paired display items representing the
transforms, clips and effects. The algorithm for this is spelled out in
the designdoc at http://goo.gl/6xP8Oe.
### Foreign layers
Some `PaintChunk` content requires a foreign layer, meaning a layer that is
managed outside of the scope of this code. Examples are composited video, a
and 2D/3D (WebGL) canvas.
### Raster invalidations
Any raster invalidates on a `PaintChunk` are also mapped to the space of the
backing `cc::Layer`.
...@@ -31,9 +31,11 @@ class PLATFORM_EXPORT ClipPaintPropertyNode ...@@ -31,9 +31,11 @@ class PLATFORM_EXPORT ClipPaintPropertyNode
static PassRefPtr<ClipPaintPropertyNode> create( static PassRefPtr<ClipPaintPropertyNode> create(
PassRefPtr<const ClipPaintPropertyNode> parent, PassRefPtr<const ClipPaintPropertyNode> parent,
PassRefPtr<const TransformPaintPropertyNode> localTransformSpace, PassRefPtr<const TransformPaintPropertyNode> localTransformSpace,
const FloatRoundedRect& clipRect) { const FloatRoundedRect& clipRect,
CompositingReasons directCompositingReasons = CompositingReasonNone) {
return adoptRef(new ClipPaintPropertyNode( return adoptRef(new ClipPaintPropertyNode(
std::move(parent), std::move(localTransformSpace), clipRect)); std::move(parent), std::move(localTransformSpace), clipRect,
directCompositingReasons));
} }
void update(PassRefPtr<const ClipPaintPropertyNode> parent, void update(PassRefPtr<const ClipPaintPropertyNode> parent,
...@@ -59,8 +61,9 @@ class PLATFORM_EXPORT ClipPaintPropertyNode ...@@ -59,8 +61,9 @@ class PLATFORM_EXPORT ClipPaintPropertyNode
// The clone function is used by FindPropertiesNeedingUpdate.h for recording // The clone function is used by FindPropertiesNeedingUpdate.h for recording
// a clip node before it has been updated, to later detect changes. // a clip node before it has been updated, to later detect changes.
PassRefPtr<ClipPaintPropertyNode> clone() const { PassRefPtr<ClipPaintPropertyNode> clone() const {
return adoptRef( return adoptRef(new ClipPaintPropertyNode(m_parent, m_localTransformSpace,
new ClipPaintPropertyNode(m_parent, m_localTransformSpace, m_clipRect)); m_clipRect,
m_directCompositingReasons));
} }
// The equality operator is used by FindPropertiesNeedingUpdate.h for checking // The equality operator is used by FindPropertiesNeedingUpdate.h for checking
...@@ -74,18 +77,25 @@ class PLATFORM_EXPORT ClipPaintPropertyNode ...@@ -74,18 +77,25 @@ class PLATFORM_EXPORT ClipPaintPropertyNode
String toString() const; String toString() const;
bool hasDirectCompositingReasons() const {
return m_directCompositingReasons != CompositingReasonNone;
}
private: private:
ClipPaintPropertyNode( ClipPaintPropertyNode(
PassRefPtr<const ClipPaintPropertyNode> parent, PassRefPtr<const ClipPaintPropertyNode> parent,
PassRefPtr<const TransformPaintPropertyNode> localTransformSpace, PassRefPtr<const TransformPaintPropertyNode> localTransformSpace,
const FloatRoundedRect& clipRect) const FloatRoundedRect& clipRect,
CompositingReasons directCompositingReasons)
: m_parent(parent), : m_parent(parent),
m_localTransformSpace(localTransformSpace), m_localTransformSpace(localTransformSpace),
m_clipRect(clipRect) {} m_clipRect(clipRect),
m_directCompositingReasons(directCompositingReasons) {}
RefPtr<const ClipPaintPropertyNode> m_parent; RefPtr<const ClipPaintPropertyNode> m_parent;
RefPtr<const TransformPaintPropertyNode> m_localTransformSpace; RefPtr<const TransformPaintPropertyNode> m_localTransformSpace;
FloatRoundedRect m_clipRect; FloatRoundedRect m_clipRect;
CompositingReasons m_directCompositingReasons;
}; };
// Redeclared here to avoid ODR issues. // Redeclared here to avoid ODR issues.
......
...@@ -117,6 +117,14 @@ class PLATFORM_EXPORT GeometryMapper { ...@@ -117,6 +117,14 @@ class PLATFORM_EXPORT GeometryMapper {
const PropertyTreeState& ancestorState, const PropertyTreeState& ancestorState,
bool& success); bool& success);
// Returns the matrix used in |LocalToAncestorRect|. Sets |success| to false
// iff |localTransformNode| is not equal to or a descendant of
// |ancestorState.transform|.
const TransformationMatrix& localToAncestorMatrix(
const TransformPaintPropertyNode* localTransformNode,
const PropertyTreeState& ancestorState,
bool& success);
private: private:
// Used by mapToVisualRectInDestinationSpace() after fast mapping (assuming // Used by mapToVisualRectInDestinationSpace() after fast mapping (assuming
// destination is an ancestor of source) failed. // destination is an ancestor of source) failed.
...@@ -134,14 +142,6 @@ class PLATFORM_EXPORT GeometryMapper { ...@@ -134,14 +142,6 @@ class PLATFORM_EXPORT GeometryMapper {
const PropertyTreeState& destinationState, const PropertyTreeState& destinationState,
bool& success); bool& success);
// Returns the matrix used in |LocalToAncestorRect|. Sets |success| to false
// iff |localTransformNode| is not equal to or a descendant of
// |ancestorState.transform|.
const TransformationMatrix& localToAncestorMatrix(
const TransformPaintPropertyNode* localTransformNode,
const PropertyTreeState& ancestorState,
bool& success);
// Returns the "clip visual rect" between |localTransformState| and // Returns the "clip visual rect" between |localTransformState| and
// |ancestorState|. See above for the definition of "clip visual rect". // |ancestorState|. See above for the definition of "clip visual rect".
FloatRect localToAncestorClipRect( FloatRect localToAncestorClipRect(
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/graphics/paint/PropertyTreeState.h"
#include "platform/graphics/paint/GeometryMapper.h"
namespace blink {
bool PropertyTreeState::hasDirectCompositingReasons() const {
switch (innermostNode()) {
case Transform:
return transform()->hasDirectCompositingReasons();
case Clip:
return clip()->hasDirectCompositingReasons();
case Effect:
return effect()->hasDirectCompositingReasons();
default:
return false;
}
}
template <typename PropertyNode>
bool isAncestorOf(const PropertyNode* ancestor, const PropertyNode* child) {
while (child && child != ancestor) {
child = child->parent();
}
return child == ancestor;
}
PropertyTreeState::InnermostNode PropertyTreeState::innermostNode() const {
// TODO(chrishtr): this is very inefficient when innermostNode() is called
// repeatedly.
bool clipTransformStrictAncestorOfTransform =
m_clip->localTransformSpace() != m_transform.get() &&
isAncestorOf<TransformPaintPropertyNode>(m_clip->localTransformSpace(),
m_transform.get());
bool effectTransformStrictAncestorOfTransform =
m_effect->localTransformSpace() != m_transform.get() &&
isAncestorOf<TransformPaintPropertyNode>(m_effect->localTransformSpace(),
m_transform.get());
if (!m_transform->isRoot() && clipTransformStrictAncestorOfTransform &&
effectTransformStrictAncestorOfTransform)
return Transform;
bool clipAncestorOfEffect =
isAncestorOf<ClipPaintPropertyNode>(m_clip.get(), m_effect->outputClip());
if (!m_effect->isRoot() && clipAncestorOfEffect) {
return Effect;
}
if (!m_clip->isRoot())
return Clip;
return None;
}
const PropertyTreeState* PropertyTreeStateIterator::next() {
switch (m_properties.innermostNode()) {
case PropertyTreeState::Transform:
m_properties.setTransform(m_properties.transform()->parent());
return &m_properties;
case PropertyTreeState::Clip:
m_properties.setClip(m_properties.clip()->parent());
return &m_properties;
case PropertyTreeState::Effect:
m_properties.setEffect(m_properties.effect()->parent());
return &m_properties;
case PropertyTreeState::None:
return nullptr;
}
return nullptr;
}
} // namespace blink
...@@ -18,7 +18,7 @@ namespace blink { ...@@ -18,7 +18,7 @@ namespace blink {
// other objects. RefPtrs are used to guard against use-after-free bugs and // other objects. RefPtrs are used to guard against use-after-free bugs and
// DCHECKs ensure PropertyTreeState never retains the last reference to a // DCHECKs ensure PropertyTreeState never retains the last reference to a
// property tree node. // property tree node.
class PropertyTreeState { class PLATFORM_EXPORT PropertyTreeState {
public: public:
PropertyTreeState(const TransformPaintPropertyNode* transform, PropertyTreeState(const TransformPaintPropertyNode* transform,
const ClipPaintPropertyNode* clip, const ClipPaintPropertyNode* clip,
...@@ -34,6 +34,8 @@ class PropertyTreeState { ...@@ -34,6 +34,8 @@ class PropertyTreeState {
DCHECK(!m_scroll || !m_scroll->hasOneRef()); DCHECK(!m_scroll || !m_scroll->hasOneRef());
} }
bool hasDirectCompositingReasons() const;
const TransformPaintPropertyNode* transform() const { const TransformPaintPropertyNode* transform() const {
DCHECK(!m_transform || !m_transform->hasOneRef()); DCHECK(!m_transform || !m_transform->hasOneRef());
return m_transform.get(); return m_transform.get();
...@@ -66,6 +68,50 @@ class PropertyTreeState { ...@@ -66,6 +68,50 @@ class PropertyTreeState {
m_scroll = std::move(node); m_scroll = std::move(node);
} }
enum InnermostNode {
None, // None means that all nodes are their root values
Transform,
Clip,
Effect,
};
// There is always a well-defined order in which the transform, clip
// and effects of a PropertyTreeState apply. This method returns which
// of them applies first to content drawn with this PropertyTreeState.
// Note that it may be the case that multiple nodes from the same tree apply
// before any from another tree. This can happen, for example, if multiple
// effects or clips apply to a descendant transform state from the transform
// node.
//
// This method is meant to be used in concert with
// |PropertyTreeStateIterator|, which allows one to iterate over the nodes in
// the order in which they apply.
//
// Example:
//
// Transform tree Clip tree Effect tree
// ~~~~~~~~~~~~~~ ~~~~~~~~~ ~~~~~~~~~~~
// Root Root Root
// | | |
// T C E
//
// Suppose that PropertyTreeState(T, C, E).innerMostNode() is E, and
// PropertytreeState(T, C, Root).innermostNode() is C. Then a PaintChunk
// that has propertyTreeState = PropertyTreeState(T, C, E) can be painted
// with the following display list structure:
//
// [BeginTransform] [BeginClip] [BeginEffect] PaintChunk drawings
// [EndEffect] [EndClip] [EndTransform]
//
// The PropertyTreeStateIterator will behave like this:
//
// PropertyTreeStateIterator iterator(PropertyTreeState(T, C, E));
// DCHECK(iterator.innermostNode() == Effect);
// DCHECK(iterator.next()->innermostNode() == Clip);
// DCHECK(iterator.next()->innermostNode() == Transform);
// DCHECK(iterator.next()->innermostNode() == None);
InnermostNode innermostNode() const;
private: private:
RefPtr<const TransformPaintPropertyNode> m_transform; RefPtr<const TransformPaintPropertyNode> m_transform;
RefPtr<const ClipPaintPropertyNode> m_clip; RefPtr<const ClipPaintPropertyNode> m_clip;
...@@ -78,6 +124,21 @@ inline bool operator==(const PropertyTreeState& a, const PropertyTreeState& b) { ...@@ -78,6 +124,21 @@ inline bool operator==(const PropertyTreeState& a, const PropertyTreeState& b) {
a.effect() == b.effect() && a.scroll() == b.scroll(); a.effect() == b.effect() && a.scroll() == b.scroll();
} }
// Iterates over the sequence transforms, clips and effects for a
// PropertyTreeState between that state and the "root" state (all nodes equal
// to *::Root()), in the order that they apply.
//
// See also PropertyTreeState::innermostNode for a more detailed example.
class PLATFORM_EXPORT PropertyTreeStateIterator {
public:
PropertyTreeStateIterator(const PropertyTreeState& properties)
: m_properties(properties) {}
const PropertyTreeState* next();
private:
PropertyTreeState m_properties;
};
} // namespace blink } // namespace blink
#endif // PropertyTreeState_h #endif // PropertyTreeState_h
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/graphics/paint/PropertyTreeState.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
class PropertyTreeStateTest : public ::testing::Test {};
TEST_F(PropertyTreeStateTest, TrasformOnEffectOnClip) {
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(),
TransformationMatrix(),
FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
ClipPaintPropertyNode::root(), TransformPaintPropertyNode::root(),
FloatRoundedRect());
RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create(
EffectPaintPropertyNode::root(), TransformPaintPropertyNode::root(),
clip.get(), CompositorFilterOperations(), 1.0, SkBlendMode::kSrcOver);
PropertyTreeState state(transform.get(), clip.get(), effect.get(),
ScrollPaintPropertyNode::root());
EXPECT_EQ(PropertyTreeState::Transform, state.innermostNode());
PropertyTreeStateIterator iterator(state);
EXPECT_EQ(PropertyTreeState::Effect, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::Clip, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode());
}
TEST_F(PropertyTreeStateTest, RootState) {
PropertyTreeState state(
TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(),
EffectPaintPropertyNode::root(), ScrollPaintPropertyNode::root());
EXPECT_EQ(PropertyTreeState::None, state.innermostNode());
}
TEST_F(PropertyTreeStateTest, EffectOnClipOnTransform) {
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(),
TransformationMatrix(),
FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
ClipPaintPropertyNode::root(), transform.get(), FloatRoundedRect());
RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create(
EffectPaintPropertyNode::root(), transform.get(), clip.get(),
CompositorFilterOperations(), 1.0, SkBlendMode::kSrcOver);
PropertyTreeState state(transform.get(), clip.get(), effect.get(),
ScrollPaintPropertyNode::root());
EXPECT_EQ(PropertyTreeState::Effect, state.innermostNode());
PropertyTreeStateIterator iterator(state);
EXPECT_EQ(PropertyTreeState::Clip, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::Transform, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode());
}
TEST_F(PropertyTreeStateTest, ClipOnEffectOnTransform) {
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(),
TransformationMatrix(),
FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
ClipPaintPropertyNode::root(), transform.get(), FloatRoundedRect());
RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create(
EffectPaintPropertyNode::root(), transform.get(),
ClipPaintPropertyNode::root(), CompositorFilterOperations(), 1.0,
SkBlendMode::kSrcOver);
PropertyTreeState state(transform.get(), clip.get(), effect.get(),
ScrollPaintPropertyNode::root());
EXPECT_EQ(PropertyTreeState::Clip, state.innermostNode());
PropertyTreeStateIterator iterator(state);
EXPECT_EQ(PropertyTreeState::Effect, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::Transform, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode());
}
TEST_F(PropertyTreeStateTest, ClipDescendantOfTransform) {
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(),
TransformationMatrix(),
FloatPoint3D());
RefPtr<TransformPaintPropertyNode> transform2 =
TransformPaintPropertyNode::create(
transform.get(), TransformationMatrix(), FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
ClipPaintPropertyNode::root(), transform2.get(), FloatRoundedRect());
RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create(
EffectPaintPropertyNode::root(), TransformPaintPropertyNode::root(),
ClipPaintPropertyNode::root(), CompositorFilterOperations(), 1.0,
SkBlendMode::kSrcOver);
// Here the clip is inside of its own transform, but the transform is an
// ancestor of the clip's transform. This models situations such as
// a clip inside a scroller that applies to an absolute-positioned element
// which escapes the scroll transform but not the clip.
PropertyTreeState state(transform.get(), clip.get(), effect.get(),
ScrollPaintPropertyNode::root());
EXPECT_EQ(PropertyTreeState::Clip, state.innermostNode());
PropertyTreeStateIterator iterator(state);
EXPECT_EQ(PropertyTreeState::Transform, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::Effect, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode());
}
TEST_F(PropertyTreeStateTest, EffectDescendantOfTransform) {
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(),
TransformationMatrix(),
FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
ClipPaintPropertyNode::root(), TransformPaintPropertyNode::root(),
FloatRoundedRect());
RefPtr<TransformPaintPropertyNode> transform2 =
TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(),
TransformationMatrix(),
FloatPoint3D());
RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create(
EffectPaintPropertyNode::root(), transform2.get(), clip.get(),
CompositorFilterOperations(), 1.0, SkBlendMode::kSrcOver);
// Here the clip is inside of its own transform, but the transform is an
// ancestor of the clip's transform. This models situations such as
// a clip inside a scroller that applies to an absolute-positioned element
// which escapes the scroll transform but not the clip.
PropertyTreeState state(transform.get(), clip.get(), effect.get(),
ScrollPaintPropertyNode::root());
EXPECT_EQ(PropertyTreeState::Effect, state.innermostNode());
PropertyTreeStateIterator iterator(state);
EXPECT_EQ(PropertyTreeState::Transform, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::Clip, iterator.next()->innermostNode());
EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode());
}
} // namespace blink
...@@ -16,81 +16,165 @@ namespace blink { ...@@ -16,81 +16,165 @@ namespace blink {
namespace { namespace {
struct QuadWithColor {
FloatQuad quad;
Color color;
};
class DrawsRectangleCanvas : public SkCanvas { class DrawsRectangleCanvas : public SkCanvas {
public: public:
DrawsRectangleCanvas() : SkCanvas(800, 600) {} DrawsRectangleCanvas()
const Vector<std::pair<FloatQuad, Color>>& quads() const { return m_quads; } : SkCanvas(800, 600),
m_saveCount(0),
m_alpha(255),
m_alphaSaveLayerCount(-1) {}
const Vector<QuadWithColor>& quadsWithColor() const { return m_quads; }
void onDrawRect(const SkRect& rect, const SkPaint& paint) override { void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
SkRect clippedRect(rect);
for (Vector<ClipAndIndex>::const_reverse_iterator clip = m_clips.rbegin();
clip != m_clips.rend(); clip++) {
if (SkRect::Intersects(rect, clip->rect))
CHECK(clippedRect.intersect(clip->rect));
}
SkPoint quad[4]; SkPoint quad[4];
getTotalMatrix().mapRectToQuad(quad, rect); getTotalMatrix().mapRectToQuad(quad, clippedRect);
FloatQuad floatQuad(quad); QuadWithColor quadWithColor;
m_quads.append(std::make_pair(floatQuad, Color(paint.getColor()))); quadWithColor.quad = FloatQuad(quad);
SkCanvas::onDrawRect(rect, paint);
unsigned paintAlpha = static_cast<unsigned>(paint.getAlpha());
SkPaint paintWithAlpha(paint);
paintWithAlpha.setAlpha(static_cast<U8CPU>(m_alpha * paintAlpha / 255));
quadWithColor.color = Color(paintWithAlpha.getColor());
m_quads.append(quadWithColor);
SkCanvas::onDrawRect(clippedRect, paint);
}
SkCanvas::SaveLayerStrategy getSaveLayerStrategy(
const SaveLayerRec& rec) override {
m_saveCount++;
unsigned layerAlpha = static_cast<unsigned>(rec.fPaint->getAlpha());
if (layerAlpha < 255) {
DCHECK_EQ(m_alphaSaveLayerCount, -1);
m_alphaSaveLayerCount = m_saveCount;
m_alpha = layerAlpha;
}
return SkCanvas::getSaveLayerStrategy(rec);
}
void willSave() override {
m_saveCount++;
SkCanvas::willSave();
}
void willRestore() override {
DCHECK_GT(m_saveCount, 0);
if (m_clips.size() && m_saveCount == m_clips.back().saveCount)
m_clips.pop_back();
if (m_alphaSaveLayerCount == m_saveCount) {
m_alpha = 255;
m_alphaSaveLayerCount = -1;
}
m_saveCount--;
SkCanvas::willRestore();
}
void onClipRect(const SkRect& rect,
SkClipOp op,
ClipEdgeStyle style) override {
ClipAndIndex clipStruct;
clipStruct.rect = rect;
clipStruct.saveCount = m_saveCount;
m_clips.push_back(clipStruct);
SkCanvas::onClipRect(rect, op, style);
} }
struct ClipAndIndex {
SkRect rect;
int saveCount;
};
private: private:
Vector<std::pair<FloatQuad, Color>> m_quads; Vector<QuadWithColor> m_quads;
Vector<ClipAndIndex> m_clips;
int m_saveCount;
unsigned m_alpha;
int m_alphaSaveLayerCount;
}; };
class DrawsRectangleMatcher class DrawsRectanglesMatcher
: public ::testing::MatcherInterface<const SkPicture&> { : public ::testing::MatcherInterface<const SkPicture&> {
public: public:
DrawsRectangleMatcher(const FloatRect& rect, Color color) DrawsRectanglesMatcher(const Vector<RectWithColor>& rectsWithColor)
: m_rect(rect), m_color(color) {} : m_rectsWithColor(rectsWithColor) {}
bool MatchAndExplain( bool MatchAndExplain(
const SkPicture& picture, const SkPicture& picture,
::testing::MatchResultListener* listener) const override { ::testing::MatchResultListener* listener) const override {
DrawsRectangleCanvas canvas; DrawsRectangleCanvas canvas;
picture.playback(&canvas); picture.playback(&canvas);
const auto& quads = canvas.quads(); const auto& quads = canvas.quadsWithColor();
if (quads.size() != m_rectsWithColor.size()) {
if (quads.size() != 1) {
*listener << "which draws " << quads.size() << " quads"; *listener << "which draws " << quads.size() << " quads";
return false; return false;
} }
const FloatQuad& quad = quads[0].first; for (unsigned index = 0; index < quads.size(); index++) {
if (!quad.isRectilinear()) { const auto& quadWithColor = quads[index];
if (listener->IsInterested()) { const auto& rectWithColor = m_rectsWithColor[index];
*listener << "which draws "; if (!quadWithColor.quad.isRectilinear()) {
PrintTo(quad, listener->stream()); if (listener->IsInterested()) {
*listener << " with color " *listener << "at index " << index << " which draws ";
<< quads[0].second.serialized().ascii().data(); PrintTo(quadWithColor.quad, listener->stream());
*listener << " with color "
<< quadWithColor.color.serialized().ascii().data() << "\n";
}
return false;
} }
return false;
}
const FloatRect& rect = quad.boundingBox(); const FloatRect& rect = quadWithColor.quad.boundingBox();
if (rect != m_rect || quads[0].second != m_color) { if (rect != rectWithColor.rect ||
if (listener->IsInterested()) { quadWithColor.color != rectWithColor.color) {
*listener << "which draws "; if (listener->IsInterested()) {
PrintTo(rect, listener->stream()); *listener << "at index " << index << " which draws ";
*listener << " with color " PrintTo(rect, listener->stream());
<< quads[0].second.serialized().ascii().data(); *listener << " with color "
<< quadWithColor.color.serialized().ascii().data() << "\n";
}
return false;
} }
return false;
} }
return true; return true;
} }
void DescribeTo(::std::ostream* os) const override { void DescribeTo(::std::ostream* os) const override {
*os << "draws "; *os << "\n";
PrintTo(m_rect, os); for (unsigned index = 0; index < m_rectsWithColor.size(); index++) {
*os << " with color " << m_color.serialized().ascii().data(); const auto& rectWithColor = m_rectsWithColor[index];
*os << "at index " << index << " rect draws ";
PrintTo(rectWithColor.rect, os);
*os << " with color " << rectWithColor.color.serialized().ascii().data()
<< "\n";
}
} }
private: private:
const FloatRect m_rect; const Vector<RectWithColor> m_rectsWithColor;
const Color m_color;
}; };
} // namespace } // namespace
::testing::Matcher<const SkPicture&> drawsRectangle(const FloatRect& rect, ::testing::Matcher<const SkPicture&> drawsRectangle(const FloatRect& rect,
Color color) { Color color) {
return ::testing::MakeMatcher(new DrawsRectangleMatcher(rect, color)); Vector<RectWithColor> rectsWithColor;
rectsWithColor.push_back(RectWithColor(rect, color));
return ::testing::MakeMatcher(new DrawsRectanglesMatcher(rectsWithColor));
}
::testing::Matcher<const SkPicture&> drawsRectangles(
const Vector<RectWithColor>& rectsWithColor) {
return ::testing::MakeMatcher(new DrawsRectanglesMatcher(rectsWithColor));
} }
} // namespace blink } // namespace blink
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef PictureMatchers_h #ifndef PictureMatchers_h
#define PictureMatchers_h #define PictureMatchers_h
#include "platform/geometry/FloatRect.h"
#include "platform/graphics/Color.h" #include "platform/graphics/Color.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
...@@ -15,10 +16,25 @@ namespace blink { ...@@ -15,10 +16,25 @@ namespace blink {
class FloatRect; class FloatRect;
// Matches if the picture draws exactly one rectangle, which (after accounting // Matches if the picture draws exactly one rectangle, which (after accounting
// for the total transformation matrix) matches the rect provided, and whose // for the total transformation matrix and applying any clips inside that
// paint has the color requested. // transform) matches the rect provided, and whose paint has the color
// requested.
// Note that clips which appear outside of a transform are not currently
// supported.
::testing::Matcher<const SkPicture&> drawsRectangle(const FloatRect&, Color); ::testing::Matcher<const SkPicture&> drawsRectangle(const FloatRect&, Color);
struct RectWithColor {
RectWithColor(const FloatRect& rectArg, const Color& colorArg)
: rect(rectArg), color(colorArg) {}
FloatRect rect;
Color color;
};
// Same as above, but matches a number of rectangles equal to the size of the
// given vector.
::testing::Matcher<const SkPicture&> drawsRectangles(
const Vector<RectWithColor>&);
} // namespace blink } // namespace blink
#endif // PictureMatchers_h #endif // PictureMatchers_h
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment