| // Copyright 2017 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/compositing/PaintChunksToCcLayer.h" |
| |
| #include <initializer_list> |
| |
| #include "cc/paint/display_item_list.h" |
| #include "cc/paint/paint_op_buffer.h" |
| #include "platform/graphics/paint/ClipPaintPropertyNode.h" |
| #include "platform/graphics/paint/DisplayItemList.h" |
| #include "platform/graphics/paint/DrawingDisplayItem.h" |
| #include "platform/graphics/paint/EffectPaintPropertyNode.h" |
| #include "platform/graphics/paint/PaintChunk.h" |
| #include "platform/graphics/paint/TransformPaintPropertyNode.h" |
| #include "platform/testing/FakeDisplayItemClient.h" |
| #include "platform/testing/PaintPropertyTestHelpers.h" |
| #include "platform/testing/RuntimeEnabledFeaturesTestHelpers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace blink { |
| namespace { |
| |
| using testing::CreateOpacityOnlyEffect; |
| |
| class PaintChunksToCcLayerTest : public ::testing::Test, |
| private ScopedSlimmingPaintV2ForTest { |
| protected: |
| PaintChunksToCcLayerTest() : ScopedSlimmingPaintV2ForTest(true) {} |
| }; |
| |
| // A simple matcher that only looks for a few ops, ignoring all else. |
| // Recognized ops: |
| // ClipPath, ClipRect, Concat, DrawRecord, Save, SaveLayer, Restore. |
| class PaintRecordMatcher |
| : public ::testing::MatcherInterface<const cc::PaintOpBuffer&> { |
| public: |
| static ::testing::Matcher<const cc::PaintOpBuffer&> Make( |
| std::initializer_list<cc::PaintOpType> args) { |
| return ::testing::MakeMatcher(new PaintRecordMatcher(args)); |
| } |
| PaintRecordMatcher(std::initializer_list<cc::PaintOpType> args) |
| : expected_ops_(args) {} |
| |
| bool MatchAndExplain( |
| const cc::PaintOpBuffer& buffer, |
| ::testing::MatchResultListener* listener) const override { |
| auto next = expected_ops_.begin(); |
| size_t op_idx = 0; |
| for (cc::PaintOpBuffer::Iterator it(&buffer); it; ++it, ++op_idx) { |
| cc::PaintOpType op = (*it)->GetType(); |
| switch (op) { |
| case cc::PaintOpType::ClipPath: |
| case cc::PaintOpType::ClipRect: |
| case cc::PaintOpType::Concat: |
| case cc::PaintOpType::DrawRecord: |
| case cc::PaintOpType::Save: |
| case cc::PaintOpType::SaveLayer: |
| case cc::PaintOpType::Restore: |
| case cc::PaintOpType::Translate: |
| if (next == expected_ops_.end()) { |
| if (listener->IsInterested()) { |
| *listener << "unexpected op " << cc::PaintOpTypeToString(op) |
| << " at index " << op_idx << ", expecting end of list."; |
| } |
| return false; |
| } |
| if (*next == op) { |
| next++; |
| continue; |
| } |
| if (listener->IsInterested()) { |
| *listener << "unexpected op " << cc::PaintOpTypeToString(op) |
| << " at index " << op_idx << ", expecting " |
| << cc::PaintOpTypeToString(*next) << "(#" |
| << (next - expected_ops_.begin()) << ")."; |
| } |
| return false; |
| default: |
| continue; |
| } |
| } |
| if (next == expected_ops_.end()) |
| return true; |
| if (listener->IsInterested()) { |
| *listener << "unexpected end of list, expecting " |
| << cc::PaintOpTypeToString(*next) << "(#" |
| << (next - expected_ops_.begin()) << ")."; |
| } |
| return false; |
| } |
| void DescribeTo(::std::ostream* os) const override { |
| *os << "["; |
| bool first = true; |
| for (auto op : expected_ops_) { |
| if (first) |
| first = false; |
| else |
| *os << ", "; |
| *os << cc::PaintOpTypeToString(op); |
| } |
| *os << "]"; |
| } |
| |
| private: |
| Vector<cc::PaintOpType> expected_ops_; |
| }; |
| |
| // Convenient shorthands. |
| const TransformPaintPropertyNode* t0() { |
| return TransformPaintPropertyNode::Root(); |
| } |
| const ClipPaintPropertyNode* c0() { |
| return ClipPaintPropertyNode::Root(); |
| } |
| const EffectPaintPropertyNode* e0() { |
| return EffectPaintPropertyNode::Root(); |
| } |
| |
| PaintChunk::Id DefaultId() { |
| DEFINE_STATIC_LOCAL(FakeDisplayItemClient, fake_client, |
| ("FakeDisplayItemClient", LayoutRect(0, 0, 100, 100))); |
| return PaintChunk::Id(fake_client, DisplayItem::kDrawingFirst); |
| } |
| |
| struct TestChunks { |
| Vector<PaintChunk> chunks; |
| DisplayItemList items = DisplayItemList(0); |
| |
| // Add a paint chunk with a non-empty paint record and given property nodes. |
| void AddChunk(const TransformPaintPropertyNode* t, |
| const ClipPaintPropertyNode* c, |
| const EffectPaintPropertyNode* e) { |
| auto record = sk_make_sp<PaintRecord>(); |
| record->push<cc::DrawRectOp>(SkRect::MakeXYWH(0, 0, 100, 100), |
| cc::PaintFlags()); |
| AddChunk(std::move(record), t, c, e); |
| } |
| |
| // Add a paint chunk with a given paint record and property nodes. |
| void AddChunk(sk_sp<PaintRecord> record, |
| const TransformPaintPropertyNode* t, |
| const ClipPaintPropertyNode* c, |
| const EffectPaintPropertyNode* e) { |
| size_t i = items.size(); |
| items.AllocateAndConstruct<DrawingDisplayItem>( |
| DefaultId().client, DefaultId().type, std::move(record)); |
| chunks.emplace_back(i, i + 1, DefaultId(), |
| PaintChunkProperties(PropertyTreeState(t, c, e))); |
| } |
| |
| Vector<const PaintChunk*> GetChunkList() const { |
| Vector<const PaintChunk*> result; |
| for (const PaintChunk& chunk : chunks) |
| result.push_back(&chunk); |
| return result; |
| } |
| }; |
| |
| TEST_F(PaintChunksToCcLayerTest, EffectGroupingSimple) { |
| // This test verifies effects are applied as a group. |
| scoped_refptr<EffectPaintPropertyNode> e1 = |
| CreateOpacityOnlyEffect(e0(), 0.5f); |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c0(), e1.get()); |
| chunks.AddChunk(t0(), c0(), e1.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT( |
| output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e1> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::DrawRecord, // <p1/> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e1> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, EffectGroupingNested) { |
| // This test verifies nested effects are grouped properly. |
| scoped_refptr<EffectPaintPropertyNode> e1 = |
| CreateOpacityOnlyEffect(e0(), 0.5f); |
| scoped_refptr<EffectPaintPropertyNode> e2 = |
| CreateOpacityOnlyEffect(e1.get(), 0.5f); |
| scoped_refptr<EffectPaintPropertyNode> e3 = |
| CreateOpacityOnlyEffect(e1.get(), 0.5f); |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c0(), e2.get()); |
| chunks.AddChunk(t0(), c0(), e3.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT( |
| output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e1> |
| cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e2> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e2> |
| cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e3> |
| cc::PaintOpType::DrawRecord, // <p1/> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e3> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e1> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, InterleavedClipEffect) { |
| // This test verifies effects are enclosed by their output clips. |
| // It is the same as the example made in the class comments of |
| // ConversionContext. |
| // Refer to PaintChunksToCcLayer.cpp for detailed explanation. |
| // (Search "State management example".) |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<ClipPaintPropertyNode> c2 = ClipPaintPropertyNode::Create( |
| c1.get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<ClipPaintPropertyNode> c3 = ClipPaintPropertyNode::Create( |
| c2.get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<ClipPaintPropertyNode> c4 = ClipPaintPropertyNode::Create( |
| c3.get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create( |
| e0(), t0(), c2.get(), ColorFilter(), CompositorFilterOperations(), 0.5f, |
| SkBlendMode::kSrcOver); |
| scoped_refptr<EffectPaintPropertyNode> e2 = EffectPaintPropertyNode::Create( |
| e1.get(), t0(), c4.get(), ColorFilter(), CompositorFilterOperations(), |
| 0.5f, SkBlendMode::kSrcOver); |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c3.get(), e0()); |
| chunks.AddChunk(t0(), c4.get(), e2.get()); |
| chunks.AddChunk(t0(), c3.get(), e1.get()); |
| chunks.AddChunk(t0(), c4.get(), e0()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT( |
| output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c1> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c2> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c3> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, // </c3> |
| cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e1> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c3> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c4> |
| cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e2> |
| cc::PaintOpType::DrawRecord, // <p1/> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e2> |
| cc::PaintOpType::Restore, // </c4> |
| cc::PaintOpType::DrawRecord, // <p2/> |
| cc::PaintOpType::Restore, // </c3> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e1> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c3> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c4> |
| cc::PaintOpType::DrawRecord, // <p3/> |
| cc::PaintOpType::Restore, // </c4> |
| cc::PaintOpType::Restore, // </c3> |
| cc::PaintOpType::Restore, // </c2> |
| cc::PaintOpType::Restore}))); // </c1> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, ClipSpaceInversion) { |
| // This test verifies chunks that have a shallower transform state than |
| // its clip can still be painted. The infamous CSS corner case: |
| // <div style="position:absolute; clip:rect(...)"> |
| // <div style="position:fixed;">Clipped but not scroll along.</div> |
| // </div> |
| scoped_refptr<TransformPaintPropertyNode> t1 = |
| TransformPaintPropertyNode::Create( |
| t0(), TransformationMatrix().Scale(2.f), FloatPoint3D()); |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t1.get(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c1.get(), e0()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT(output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::Save, cc::PaintOpType::Concat, // <t1 |
| cc::PaintOpType::ClipRect, // c1> |
| cc::PaintOpType::Save, cc::PaintOpType::Concat, // <t1^-1> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, // </t1^-1> |
| cc::PaintOpType::Restore}))); // </c1 t1> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, EffectSpaceInversion) { |
| // This test verifies chunks that have a shallower transform state than |
| // its effect can still be painted. The infamous CSS corner case: |
| // <div style="overflow:scroll"> |
| // <div style="opacity:0.5"> |
| // <div style="position:absolute;">Transparent but not scroll |
| // along.</div> |
| // </div> |
| // </div> |
| scoped_refptr<TransformPaintPropertyNode> t1 = |
| TransformPaintPropertyNode::Create( |
| t0(), TransformationMatrix().Scale(2.f), FloatPoint3D()); |
| scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create( |
| e0(), t1.get(), c0(), ColorFilter(), CompositorFilterOperations(), 0.5f, |
| SkBlendMode::kSrcOver); |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c0(), e1.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT( |
| output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::SaveLayer, cc::PaintOpType::Concat, // <t1 |
| cc::PaintOpType::SaveLayer, // e1> |
| cc::PaintOpType::Save, cc::PaintOpType::Concat, // <t1^-1> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, // </t1^-1> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e1 t1> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, NonRootLayerSimple) { |
| // This test verifies a layer with composited property state does not |
| // apply properties again internally. |
| scoped_refptr<TransformPaintPropertyNode> t1 = |
| TransformPaintPropertyNode::Create( |
| t0(), TransformationMatrix().Scale(2.f), FloatPoint3D()); |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<EffectPaintPropertyNode> e1 = |
| CreateOpacityOnlyEffect(e0(), 0.5f); |
| TestChunks chunks; |
| chunks.AddChunk(t1.get(), c1.get(), e1.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), |
| PropertyTreeState(t1.get(), c1.get(), e1.get()), gfx::Vector2dF(), |
| chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT(output, |
| Pointee(PaintRecordMatcher::Make({cc::PaintOpType::DrawRecord}))); |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, NonRootLayerTransformEscape) { |
| // This test verifies chunks that have a shallower transform state than the |
| // layer can still be painted. |
| scoped_refptr<TransformPaintPropertyNode> t1 = |
| TransformPaintPropertyNode::Create( |
| t0(), TransformationMatrix().Scale(2.f), FloatPoint3D()); |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<EffectPaintPropertyNode> e1 = |
| CreateOpacityOnlyEffect(e0(), 0.5f); |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c1.get(), e1.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), |
| PropertyTreeState(t1.get(), c1.get(), e1.get()), gfx::Vector2dF(), |
| chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT(output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::Save, cc::PaintOpType::Concat, // <t1^-1> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore}))); // </t1^-1> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, EffectWithNoOutputClip) { |
| // This test verifies effect with no output clip can be correctly processed. |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<ClipPaintPropertyNode> c2 = ClipPaintPropertyNode::Create( |
| c1.get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create( |
| e0(), t0(), nullptr, kColorFilterNone, CompositorFilterOperations(), 0.5, |
| SkBlendMode::kSrcOver); |
| |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c2.get(), e1.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c1.get(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT( |
| output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e1> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c2> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, // </c2> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e1> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, |
| EffectWithNoOutputClipNestedInDecompositedEffect) { |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create( |
| e0(), t0(), c0(), kColorFilterNone, CompositorFilterOperations(), 0.5, |
| SkBlendMode::kSrcOver); |
| scoped_refptr<EffectPaintPropertyNode> e2 = EffectPaintPropertyNode::Create( |
| e1.get(), t0(), nullptr, kColorFilterNone, CompositorFilterOperations(), |
| 0.5, SkBlendMode::kSrcOver); |
| |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c1.get(), e2.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT( |
| output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e1> |
| cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e2> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c1> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, // </c1> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e2> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e1> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, |
| EffectWithNoOutputClipNestedInCompositedEffect) { |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create( |
| e0(), t0(), c0(), kColorFilterNone, CompositorFilterOperations(), 0.5, |
| SkBlendMode::kSrcOver); |
| scoped_refptr<EffectPaintPropertyNode> e2 = EffectPaintPropertyNode::Create( |
| e1.get(), t0(), nullptr, kColorFilterNone, CompositorFilterOperations(), |
| 0.5, SkBlendMode::kSrcOver); |
| |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c1.get(), e2.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e1.get()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT( |
| output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e2> |
| cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c1> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, // </c1> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e2> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, |
| EffectWithNoOutputClipNestedInCompositedEffectAndClip) { |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create( |
| e0(), t0(), c0(), kColorFilterNone, CompositorFilterOperations(), 0.5, |
| SkBlendMode::kSrcOver); |
| scoped_refptr<EffectPaintPropertyNode> e2 = EffectPaintPropertyNode::Create( |
| e1.get(), t0(), nullptr, kColorFilterNone, CompositorFilterOperations(), |
| 0.5, SkBlendMode::kSrcOver); |
| |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c1.get(), e2.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c1.get(), e1.get()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| EXPECT_THAT( |
| output, |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e2> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e2> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, VisualRect) { |
| auto layer_transform = TransformPaintPropertyNode::Create( |
| t0(), TransformationMatrix().Scale(20), FloatPoint3D()); |
| auto chunk_transform = TransformPaintPropertyNode::Create( |
| layer_transform.get(), TransformationMatrix().Translate(50, 100), |
| FloatPoint3D()); |
| |
| TestChunks chunks; |
| chunks.AddChunk(chunk_transform.get(), c0(), e0()); |
| |
| auto cc_list = base::MakeRefCounted<cc::DisplayItemList>( |
| cc::DisplayItemList::kTopLevelDisplayItemList); |
| PaintChunksToCcLayer::ConvertInto( |
| chunks.GetChunkList(), |
| PropertyTreeState(layer_transform.get(), c0(), e0()), |
| gfx::Vector2dF(100, 200), chunks.items, *cc_list); |
| EXPECT_EQ(gfx::Rect(-50, -100, 100, 100), cc_list->VisualRectForTesting(4)); |
| |
| EXPECT_THAT(cc_list->ReleaseAsRecord(), |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::Save, // |
| cc::PaintOpType::Translate, // <layer_offset> |
| cc::PaintOpType::Save, // |
| cc::PaintOpType::Concat, // <layer_transform> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore, // </layer_transform> |
| cc::PaintOpType::Restore}))); // </layer_offset> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, NoncompositedClipPath) { |
| scoped_refptr<RefCountedPath> clip_path = base::AdoptRef(new RefCountedPath); |
| auto c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(25.f, 25.f, 100.f, 100.f), nullptr, |
| clip_path); |
| |
| TestChunks chunks; |
| chunks.AddChunk(t0(), c1.get(), e0()); |
| |
| auto cc_list = base::MakeRefCounted<cc::DisplayItemList>( |
| cc::DisplayItemList::kTopLevelDisplayItemList); |
| PaintChunksToCcLayer::ConvertInto(chunks.GetChunkList(), |
| PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, *cc_list); |
| |
| EXPECT_THAT(cc_list->ReleaseAsRecord(), |
| Pointee(PaintRecordMatcher::Make( |
| {cc::PaintOpType::Save, // |
| cc::PaintOpType::ClipRect, // |
| cc::PaintOpType::ClipPath, // <clip_path> |
| cc::PaintOpType::DrawRecord, // <p0/> |
| cc::PaintOpType::Restore}))); // </clip_path> |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, EmptyClipsAreElided) { |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<ClipPaintPropertyNode> c1c2 = ClipPaintPropertyNode::Create( |
| c1.get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<ClipPaintPropertyNode> c2 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| |
| TestChunks chunks; |
| chunks.AddChunk(nullptr, t0(), c1.get(), e0()); |
| chunks.AddChunk(nullptr, t0(), c1c2.get(), e0()); |
| chunks.AddChunk(nullptr, t0(), c1c2.get(), e0()); |
| chunks.AddChunk(nullptr, t0(), c1c2.get(), e0()); |
| chunks.AddChunk(nullptr, t0(), c1.get(), e0()); |
| // D1 |
| chunks.AddChunk(t0(), c2.get(), e0()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| |
| // Note that c1 and c1c2 are elided. |
| EXPECT_THAT(output, Pointee(PaintRecordMatcher::Make({ |
| cc::PaintOpType::Save, // |
| cc::PaintOpType::ClipRect, // <c2> |
| cc::PaintOpType::DrawRecord, // D1 |
| cc::PaintOpType::Restore, // </c2> |
| }))); |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, NonEmptyClipsAreStored) { |
| scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<ClipPaintPropertyNode> c1c2 = ClipPaintPropertyNode::Create( |
| c1.get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| scoped_refptr<ClipPaintPropertyNode> c2 = ClipPaintPropertyNode::Create( |
| c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f)); |
| |
| TestChunks chunks; |
| chunks.AddChunk(nullptr, t0(), c1.get(), e0()); |
| chunks.AddChunk(nullptr, t0(), c1c2.get(), e0()); |
| chunks.AddChunk(nullptr, t0(), c1c2.get(), e0()); |
| // D1 |
| chunks.AddChunk(t0(), c1c2.get(), e0()); |
| chunks.AddChunk(nullptr, t0(), c1.get(), e0()); |
| // D2 |
| chunks.AddChunk(t0(), c2.get(), e0()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| |
| EXPECT_THAT(output, Pointee(PaintRecordMatcher::Make({ |
| cc::PaintOpType::Save, // |
| cc::PaintOpType::ClipRect, // <c1> |
| cc::PaintOpType::Save, // |
| cc::PaintOpType::ClipRect, // <c2> |
| cc::PaintOpType::DrawRecord, // D1 |
| cc::PaintOpType::Restore, // </c2> |
| cc::PaintOpType::Restore, // </c1> |
| cc::PaintOpType::Save, // |
| cc::PaintOpType::ClipRect, // <c2> |
| cc::PaintOpType::DrawRecord, // D2 |
| cc::PaintOpType::Restore, // </c2> |
| }))); |
| } |
| |
| TEST_F(PaintChunksToCcLayerTest, EmptyEffectsAreStored) { |
| scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create( |
| e0(), t0(), c0(), kColorFilterNone, CompositorFilterOperations(), 0.5, |
| SkBlendMode::kSrcOver); |
| |
| TestChunks chunks; |
| chunks.AddChunk(nullptr, t0(), c0(), e0()); |
| chunks.AddChunk(nullptr, t0(), c0(), e1.get()); |
| |
| sk_sp<PaintRecord> output = |
| PaintChunksToCcLayer::Convert( |
| chunks.GetChunkList(), PropertyTreeState(t0(), c0(), e0()), |
| gfx::Vector2dF(), chunks.items, |
| cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer) |
| ->ReleaseAsRecord(); |
| |
| // TODO(vmpstr): Ideally this would change to be one save layer if possible. |
| EXPECT_THAT(output, Pointee(PaintRecordMatcher::Make({ |
| cc::PaintOpType::SaveLayer, // <e1> |
| cc::PaintOpType::SaveLayer, // |
| cc::PaintOpType::Restore, // |
| cc::PaintOpType::Restore, // </e1> |
| }))); |
| } |
| |
| } // namespace |
| } // namespace blink |