1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "platform/graphics/paint/DisplayItemPropertyTreeBuilder.h"
8 #include "platform/graphics/paint/DisplayItem.h"
9 #include "platform/graphics/paint/DisplayItemClient.h"
10 #include "platform/graphics/paint/DisplayItemClipTree.h"
11 #include "platform/graphics/paint/DisplayItemTransformTree.h"
12 #include "platform/graphics/paint/ScrollDisplayItem.h"
13 #include "platform/graphics/paint/Transform3DDisplayItem.h"
14 #include "platform/graphics/paint/TransformDisplayItem.h"
15 #include "platform/transforms/TransformTestHelper.h"
16 #include "platform/transforms/TransformationMatrix.h"
17 #include "public/platform/WebDisplayItemTransformTree.h"
18 #include "wtf/OwnPtr.h"
19 #include <gmock/gmock.h>
20 #include <gtest/gtest.h>
25 using ::testing::AllOf
;
26 using ::testing::ElementsAre
;
28 using RangeRecord
= DisplayItemPropertyTreeBuilder::RangeRecord
;
30 MATCHER_P2(hasRange
, begin
, end
, "")
32 return arg
.displayListBeginIndex
== static_cast<size_t>(begin
)
33 && arg
.displayListEndIndex
== static_cast<size_t>(end
);
36 MATCHER_P(hasTransformNode
, transformNode
, "")
38 return arg
.transformNodeIndex
== static_cast<size_t>(transformNode
);
41 MATCHER_P(hasOffset
, offset
, "")
43 return arg
.offset
== offset
;
47 DisplayItemClient
displayItemClient() const { return toDisplayItemClient(this); }
48 String
debugName() const { return "DummyClient"; }
51 class DummyDisplayItem final
: public DisplayItem
{
53 DummyDisplayItem(const DummyClient
& client
) : DisplayItem(client
, DisplayItem::DrawingFirst
, sizeof(*this)) { }
56 class DisplayItemPropertyTreeBuilderTest
: public ::testing::Test
{
58 DisplayItemPropertyTreeBuilder
& builder() { return m_builder
; }
59 const DisplayItemTransformTree
& transformTree() { return *m_transformTree
; }
60 const DisplayItemClipTree
& clipTree() { return *m_clipTree
; }
61 const Vector
<RangeRecord
>& rangeRecords() { return m_rangeRecords
; }
62 std::vector
<RangeRecord
> rangeRecordsAsStdVector() { return std::vector
<RangeRecord
>(m_rangeRecords
.begin(), m_rangeRecords
.end()); }
64 void processDisplayItem(const DisplayItem
& displayItem
) { m_builder
.processDisplayItem(displayItem
); }
65 void processDisplayItem(PassOwnPtr
<DisplayItem
> displayItem
) { processDisplayItem(*displayItem
); }
66 void processDummyDisplayItem() { processDisplayItem(DummyDisplayItem(newDummyClient())); }
67 const DummyClient
& processBeginTransform3D(const TransformationMatrix
& transform
, const FloatPoint3D
& transformOrigin
= FloatPoint3D())
69 const DummyClient
& client
= newDummyClient();
70 processDisplayItem(BeginTransform3DDisplayItem(client
, DisplayItem::Transform3DElementTransform
, transform
, transformOrigin
));
73 void processEndTransform3D(const DummyClient
& client
)
75 processDisplayItem(EndTransform3DDisplayItem(client
, DisplayItem::transform3DTypeToEndTransform3DType(DisplayItem::Transform3DElementTransform
)));
77 const DummyClient
& processBeginTransform(const AffineTransform
& transform
)
79 const DummyClient
& client
= newDummyClient();
80 processDisplayItem(BeginTransformDisplayItem(client
, transform
));
83 void processEndTransform(const DummyClient
& client
)
85 processDisplayItem(EndTransformDisplayItem(client
));
87 const DummyClient
& processBeginScroll(int offsetX
, int offsetY
)
89 const DummyClient
& client
= newDummyClient();
90 processDisplayItem(BeginScrollDisplayItem(client
, DisplayItem::ScrollFirst
, IntSize(offsetX
, offsetY
)));
93 void processEndScroll(const DummyClient
& client
)
95 processDisplayItem(EndScrollDisplayItem(client
, DisplayItem::EndScrollFirst
));
98 void finishPropertyTrees()
100 m_transformTree
= m_builder
.releaseTransformTree();
101 m_clipTree
= m_builder
.releaseClipTree();
102 m_rangeRecords
= m_builder
.releaseRangeRecords();
106 // This makes empty objects which can be used as display item clients.
107 const DummyClient
& newDummyClient()
109 m_dummyClients
.append(adoptPtr(new DummyClient
));
110 return *m_dummyClients
.last();
113 DisplayItemPropertyTreeBuilder m_builder
;
114 OwnPtr
<DisplayItemTransformTree
> m_transformTree
;
115 OwnPtr
<DisplayItemClipTree
> m_clipTree
;
116 Vector
<RangeRecord
> m_rangeRecords
;
117 Vector
<OwnPtr
<DummyClient
>> m_dummyClients
;
120 TEST_F(DisplayItemPropertyTreeBuilderTest
, NoDisplayItems
)
122 finishPropertyTrees();
124 // There should be a root transform node.
125 ASSERT_EQ(1u, transformTree().nodeCount());
126 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
128 // There should be no range records, because there are no non-empty
129 // transformed ranges.
130 ASSERT_EQ(0u, rangeRecords().size());
133 TEST_F(DisplayItemPropertyTreeBuilderTest
, NoTransforms
)
135 // Three dummy display items.
136 processDummyDisplayItem();
137 processDummyDisplayItem();
138 processDummyDisplayItem();
139 finishPropertyTrees();
141 // There should only be a root transform node.
142 ASSERT_EQ(1u, transformTree().nodeCount());
143 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
145 // There should be one range record, for the entire list.
146 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
147 AllOf(hasRange(0, 3), hasTransformNode(0))));
150 TEST_F(DisplayItemPropertyTreeBuilderTest
, IdentityTransform
)
152 TransformationMatrix identity
;
154 // There's an identity transform here, but we should not make a node for it.
155 processDummyDisplayItem();
156 auto transformClient
= processBeginTransform3D(identity
);
157 processDummyDisplayItem();
158 processEndTransform3D(transformClient
);
159 processDummyDisplayItem();
160 finishPropertyTrees();
162 // There should only be a root transform node.
163 ASSERT_EQ(1u, transformTree().nodeCount());
164 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
166 // There should be three range records.
167 // Since the transform is the identity, these could be combined, but there
168 // is not currently a special path for this case.
169 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
170 AllOf(hasRange(0, 1), hasTransformNode(0)),
171 AllOf(hasRange(2, 3), hasTransformNode(0)),
172 AllOf(hasRange(4, 5), hasTransformNode(0))));
175 TEST_F(DisplayItemPropertyTreeBuilderTest
, Only2DTranslation
)
177 FloatSize
offset(200.5, -100);
178 TransformationMatrix translation
;
179 translation
.translate(offset
.width(), offset
.height());
181 // There's a translation here, but we should not make a node for it.
182 processDummyDisplayItem();
183 auto transformClient
= processBeginTransform3D(translation
);
184 processDummyDisplayItem();
185 processEndTransform3D(transformClient
);
186 processDummyDisplayItem();
187 finishPropertyTrees();
189 // There should only be a root transform node.
190 ASSERT_EQ(1u, transformTree().nodeCount());
191 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
193 // There should be three ranges, even though there's only one node.
194 // The middle one requires an offset.
195 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
196 AllOf(hasRange(0, 1), hasTransformNode(0)),
197 AllOf(hasRange(2, 3), hasTransformNode(0)),
198 AllOf(hasRange(4, 5), hasTransformNode(0))));
201 TEST_F(DisplayItemPropertyTreeBuilderTest
, Nested2DTranslation
)
203 FloatSize
offset1(10, -40);
204 TransformationMatrix translation1
;
205 translation1
.translate(offset1
.width(), offset1
.height());
206 FloatSize
offset2(80, 80);
207 TransformationMatrix translation2
;
208 translation2
.translate(offset2
.width(), offset2
.height());
210 // These drawings should share a transform node but have different range
212 processDummyDisplayItem();
213 auto transform1
= processBeginTransform3D(translation1
);
214 processDummyDisplayItem();
215 auto transform2
= processBeginTransform3D(translation2
);
216 processDummyDisplayItem();
217 processEndTransform3D(transform2
);
218 processEndTransform3D(transform1
);
219 finishPropertyTrees();
221 // There should only be a root transform node.
222 ASSERT_EQ(1u, transformTree().nodeCount());
223 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
225 // Check that the range records have the right offsets.
226 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
227 AllOf(hasRange(0, 1), hasTransformNode(0), hasOffset(FloatSize())),
228 AllOf(hasRange(2, 3), hasTransformNode(0), hasOffset(offset1
)),
229 AllOf(hasRange(4, 5), hasTransformNode(0), hasOffset(offset1
+ offset2
))));
232 TEST_F(DisplayItemPropertyTreeBuilderTest
, ZTranslation
)
234 TransformationMatrix zTranslation
;
235 zTranslation
.translate3d(0, 0, 1);
237 // Z translation: we expect another node.
238 processDummyDisplayItem();
239 auto transformClient
= processBeginTransform3D(zTranslation
);
240 processDummyDisplayItem();
241 processEndTransform3D(transformClient
);
242 processDummyDisplayItem();
243 finishPropertyTrees();
245 // There should be two nodes here.
246 ASSERT_EQ(2u, transformTree().nodeCount());
247 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
248 EXPECT_EQ(0u, transformTree().nodeAt(1).parentNodeIndex
);
250 // There should be three range records.
251 // The middle of these should be transformed, and the others should be
252 // attached to the root node.
253 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
254 AllOf(hasRange(0, 1), hasTransformNode(0)),
255 AllOf(hasRange(2, 3), hasTransformNode(1)),
256 AllOf(hasRange(4, 5), hasTransformNode(0))));
259 template <typename TreeType
, typename NodeType
>
260 size_t nodeDepth(const TreeType
& tree
, const NodeType
& node
)
262 const auto* currentNode
= &node
;
264 while (!currentNode
->isRoot()) {
265 currentNode
= &tree
.nodeAt(currentNode
->parentNodeIndex
);
271 TEST_F(DisplayItemPropertyTreeBuilderTest
, SkipUnnecessaryRangeRecords
)
273 TransformationMatrix rotation
;
274 rotation
.rotate(1 /* degrees */);
276 // The only drawing is in the second transform.
277 auto transform1
= processBeginTransform3D(rotation
);
278 auto transform2
= processBeginTransform3D(rotation
);
279 processDummyDisplayItem();
280 auto transform3
= processBeginTransform3D(rotation
);
281 processEndTransform3D(transform3
);
282 processDummyDisplayItem();
283 processEndTransform3D(transform2
);
284 processEndTransform3D(transform1
);
285 finishPropertyTrees();
287 // There should be only two ranges.
288 // They must both belong to the same grandchild of the root node.
289 ASSERT_EQ(2u, rangeRecords().size());
290 size_t transformNodeIndex
= rangeRecords()[0].transformNodeIndex
;
291 EXPECT_EQ(2u, nodeDepth(transformTree(), transformTree().nodeAt(transformNodeIndex
)));
292 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
293 AllOf(hasRange(2, 3), hasTransformNode(transformNodeIndex
)),
294 AllOf(hasRange(5, 6), hasTransformNode(transformNodeIndex
))));
297 TEST_F(DisplayItemPropertyTreeBuilderTest
, RootTransformNodeHasIdentityTransform
)
299 finishPropertyTrees();
300 ASSERT_EQ(1u, transformTree().nodeCount());
301 EXPECT_TRUE(transformTree().nodeAt(0).matrix
.isIdentity());
302 EXPECT_TRANSFORMS_ALMOST_EQ(TransformationMatrix(), transformTree().nodeAt(0).matrix
);
305 TEST_F(DisplayItemPropertyTreeBuilderTest
, Transform3DMatrix
)
307 TransformationMatrix matrix
;
308 matrix
.rotate3d(45, 45, 45);
310 auto transform1
= processBeginTransform3D(matrix
);
311 processDummyDisplayItem();
312 processEndTransform3D(transform1
);
313 finishPropertyTrees();
315 const auto& transformNode
= transformTree().nodeAt(rangeRecords()[0].transformNodeIndex
);
316 EXPECT_TRANSFORMS_ALMOST_EQ(matrix
, transformNode
.matrix
);
319 TEST_F(DisplayItemPropertyTreeBuilderTest
, NestedTransformsAreNotCombined
)
321 // It's up the consumer of the tree to multiply transformation matrices.
323 TransformationMatrix matrix1
;
324 matrix1
.rotate3d(45, 45, 45);
325 TransformationMatrix matrix2
;
326 matrix2
.translate3d(0, 10, 20);
327 EXPECT_NE(matrix2
, matrix1
* matrix2
);
329 auto transform1
= processBeginTransform3D(matrix1
);
330 auto transform2
= processBeginTransform3D(matrix2
);
331 processDummyDisplayItem();
332 processEndTransform3D(transform2
);
333 processDummyDisplayItem();
334 processEndTransform3D(transform1
);
335 finishPropertyTrees();
337 const auto& transformNode
= transformTree().nodeAt(rangeRecords()[0].transformNodeIndex
);
338 ASSERT_FALSE(transformNode
.isRoot());
339 EXPECT_TRANSFORMS_ALMOST_EQ(matrix2
, transformNode
.matrix
);
340 const auto& parentNode
= transformTree().nodeAt(transformNode
.parentNodeIndex
);
341 EXPECT_TRANSFORMS_ALMOST_EQ(matrix1
, parentNode
.matrix
);
344 TEST_F(DisplayItemPropertyTreeBuilderTest
, TransformDisplayItemCreatesTransformNode
)
346 // 2D transform display items should create a transform node as well,
347 // unless the transform is a 2D translation only.
348 AffineTransform rotation
;
351 processDummyDisplayItem();
352 auto transformClient
= processBeginTransform(rotation
);
353 processDummyDisplayItem();
354 processEndTransform(transformClient
);
355 processDummyDisplayItem();
356 finishPropertyTrees();
358 // There should be two transform nodes.
359 ASSERT_EQ(2u, transformTree().nodeCount());
360 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
361 EXPECT_EQ(0u, transformTree().nodeAt(1).parentNodeIndex
);
362 EXPECT_TRANSFORMS_ALMOST_EQ(TransformationMatrix(rotation
), transformTree().nodeAt(1).matrix
);
364 // There should be three range records, the middle one affected by the
366 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
367 AllOf(hasRange(0, 1), hasTransformNode(0)),
368 AllOf(hasRange(2, 3), hasTransformNode(1)),
369 AllOf(hasRange(4, 5), hasTransformNode(0))));
372 TEST_F(DisplayItemPropertyTreeBuilderTest
, TransformDisplayItemOnly2DTranslation
)
374 // In this case no transform node should be created for the 2D translation.
375 AffineTransform translation
= AffineTransform::translation(10, -40);
377 processDummyDisplayItem();
378 auto transformClient
= processBeginTransform(translation
);
379 processDummyDisplayItem();
380 processEndTransform(transformClient
);
381 processDummyDisplayItem();
382 finishPropertyTrees();
384 // There should be only one transform node.
385 ASSERT_EQ(1u, transformTree().nodeCount());
386 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
388 // There should be three range records, the middle one affected by the
390 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
391 AllOf(hasRange(0, 1), hasTransformNode(0), hasOffset(FloatSize(0, 0))),
392 AllOf(hasRange(2, 3), hasTransformNode(0), hasOffset(FloatSize(10, -40))),
393 AllOf(hasRange(4, 5), hasTransformNode(0), hasOffset(FloatSize(0, 0)))));
396 TEST_F(DisplayItemPropertyTreeBuilderTest
, ScrollDisplayItemIs2DTranslation
)
398 processDummyDisplayItem();
399 auto scrollClient
= processBeginScroll(-90, 400);
400 processDummyDisplayItem();
401 processEndScroll(scrollClient
);
402 processDummyDisplayItem();
403 finishPropertyTrees();
405 // There should be only one transform node.
406 ASSERT_EQ(1u, transformTree().nodeCount());
407 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
409 // There should be three range records, the middle one affected by the
410 // scroll. Note that the translation due to scroll is the negative of the
412 EXPECT_THAT(rangeRecordsAsStdVector(), ElementsAre(
413 AllOf(hasRange(0, 1), hasTransformNode(0), hasOffset(FloatSize(0, 0))),
414 AllOf(hasRange(2, 3), hasTransformNode(0), hasOffset(FloatSize(90, -400))),
415 AllOf(hasRange(4, 5), hasTransformNode(0), hasOffset(FloatSize(0, 0)))));
418 TEST_F(DisplayItemPropertyTreeBuilderTest
, TransformTreeIncludesTransformOrigin
)
420 FloatPoint3D
transformOrigin(1, 2, 3);
421 TransformationMatrix matrix
;
422 matrix
.scale3d(2, 2, 2);
424 auto transformClient
= processBeginTransform3D(matrix
, transformOrigin
);
425 processDummyDisplayItem();
426 processEndTransform3D(transformClient
);
427 finishPropertyTrees();
429 // There should be two transform nodes.
430 ASSERT_EQ(2u, transformTree().nodeCount());
431 EXPECT_TRUE(transformTree().nodeAt(0).isRoot());
433 // And the non-root node should have both the matrix and the origin,
435 const auto& transformNode
= transformTree().nodeAt(1);
436 EXPECT_EQ(0u, transformNode
.parentNodeIndex
);
437 EXPECT_EQ(TransformationMatrix::toSkMatrix44(matrix
), transformNode
.matrix
);
438 EXPECT_EQ(transformOrigin
, transformNode
.transformOrigin
);