FrameBuilderTests.cpp revision a82ffc549bd6dbf8cfc6f4d646d0f458dca54014
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include <gtest/gtest.h> 18 19#include <BakedOpState.h> 20#include <DeferredLayerUpdater.h> 21#include <FrameBuilder.h> 22#include <LayerUpdateQueue.h> 23#include <RecordedOp.h> 24#include <RecordingCanvas.h> 25#include <tests/common/TestUtils.h> 26 27#include <unordered_map> 28 29namespace android { 30namespace uirenderer { 31 32const LayerUpdateQueue sEmptyLayerUpdateQueue; 33const std::vector< sp<RenderNode> > sEmptyNodeList; 34const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50}; 35 36 37/** 38 * Virtual class implemented by each test to redirect static operation / state transitions to 39 * virtual methods. 40 * 41 * Virtual dispatch allows for default behaviors to be specified (very common case in below tests), 42 * and allows Renderer vs Dispatching behavior to be merged. 43 * 44 * onXXXOp methods fail by default - tests should override ops they expect 45 * startRepaintLayer fails by default - tests should override if expected 46 * startFrame/endFrame do nothing by default - tests should override to intercept 47 */ 48class TestRendererBase { 49public: 50 virtual ~TestRendererBase() {} 51 virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) { 52 ADD_FAILURE() << "Layer creation not expected in this test"; 53 return nullptr; 54 } 55 virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) { 56 ADD_FAILURE() << "Layer repaint not expected in this test"; 57 } 58 virtual void endLayer() { 59 ADD_FAILURE() << "Layer updates not expected in this test"; 60 } 61 virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {} 62 virtual void endFrame(const Rect& repaintRect) {} 63 64 // define virtual defaults for single draw methods 65#define X(Type) \ 66 virtual void on##Type(const Type&, const BakedOpState&) { \ 67 ADD_FAILURE() << #Type " not expected in this test"; \ 68 } 69 MAP_RENDERABLE_OPS(X) 70#undef X 71 72 // define virtual defaults for merged draw methods 73#define X(Type) \ 74 virtual void onMerged##Type##s(const MergedBakedOpList& opList) { \ 75 ADD_FAILURE() << "Merged " #Type "s not expected in this test"; \ 76 } 77 MAP_MERGEABLE_OPS(X) 78#undef X 79 80 int getIndex() { return mIndex; } 81 82protected: 83 int mIndex = 0; 84}; 85 86/** 87 * Dispatches all static methods to similar formed methods on renderer, which fail by default but 88 * are overridden by subclasses per test. 89 */ 90class TestDispatcher { 91public: 92 // define single op methods, which redirect to TestRendererBase 93#define X(Type) \ 94 static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \ 95 renderer.on##Type(op, state); \ 96 } 97 MAP_RENDERABLE_OPS(X); 98#undef X 99 100 // define merged op methods, which redirect to TestRendererBase 101#define X(Type) \ 102 static void onMerged##Type##s(TestRendererBase& renderer, const MergedBakedOpList& opList) { \ 103 renderer.onMerged##Type##s(opList); \ 104 } 105 MAP_MERGEABLE_OPS(X); 106#undef X 107}; 108 109class FailRenderer : public TestRendererBase {}; 110 111RENDERTHREAD_TEST(FrameBuilder, simple) { 112 class SimpleTestRenderer : public TestRendererBase { 113 public: 114 void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { 115 EXPECT_EQ(0, mIndex++); 116 EXPECT_EQ(100u, width); 117 EXPECT_EQ(200u, height); 118 } 119 void onRectOp(const RectOp& op, const BakedOpState& state) override { 120 EXPECT_EQ(1, mIndex++); 121 } 122 void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { 123 EXPECT_EQ(2, mIndex++); 124 } 125 void endFrame(const Rect& repaintRect) override { 126 EXPECT_EQ(3, mIndex++); 127 } 128 }; 129 130 auto node = TestUtils::createNode(0, 0, 100, 200, 131 [](RenderProperties& props, RecordingCanvas& canvas) { 132 SkBitmap bitmap = TestUtils::createSkBitmap(25, 25); 133 canvas.drawRect(0, 0, 100, 200, SkPaint()); 134 canvas.drawBitmap(bitmap, 10, 10, nullptr); 135 }); 136 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, 137 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 138 SimpleTestRenderer renderer; 139 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 140 EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end 141} 142 143RENDERTHREAD_TEST(FrameBuilder, simpleStroke) { 144 class SimpleStrokeTestRenderer : public TestRendererBase { 145 public: 146 void onPointsOp(const PointsOp& op, const BakedOpState& state) override { 147 EXPECT_EQ(0, mIndex++); 148 // even though initial bounds are empty... 149 EXPECT_TRUE(op.unmappedBounds.isEmpty()) 150 << "initial bounds should be empty, since they're unstroked"; 151 EXPECT_EQ(Rect(45, 45, 55, 55), state.computedState.clippedBounds) 152 << "final bounds should account for stroke"; 153 } 154 }; 155 156 auto node = TestUtils::createNode(0, 0, 100, 200, 157 [](RenderProperties& props, RecordingCanvas& canvas) { 158 SkPaint strokedPaint; 159 strokedPaint.setStrokeWidth(10); 160 canvas.drawPoint(50, 50, strokedPaint); 161 }); 162 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, 163 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 164 SimpleStrokeTestRenderer renderer; 165 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 166 EXPECT_EQ(1, renderer.getIndex()); 167} 168 169RENDERTHREAD_TEST(FrameBuilder, simpleRejection) { 170 auto node = TestUtils::createNode(0, 0, 200, 200, 171 [](RenderProperties& props, RecordingCanvas& canvas) { 172 canvas.save(SaveFlags::MatrixClip); 173 canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty 174 canvas.drawRect(0, 0, 400, 400, SkPaint()); 175 canvas.restore(); 176 }); 177 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 178 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 179 180 FailRenderer renderer; 181 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 182} 183 184RENDERTHREAD_TEST(FrameBuilder, simpleBatching) { 185 const int LOOPS = 5; 186 class SimpleBatchingTestRenderer : public TestRendererBase { 187 public: 188 void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { 189 EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects"; 190 } 191 void onRectOp(const RectOp& op, const BakedOpState& state) override { 192 EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps"; 193 } 194 }; 195 196 auto node = TestUtils::createNode(0, 0, 200, 200, 197 [](RenderProperties& props, RecordingCanvas& canvas) { 198 SkBitmap bitmap = TestUtils::createSkBitmap(10, 10, 199 kAlpha_8_SkColorType); // Disable merging by using alpha 8 bitmap 200 201 // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects. 202 // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group. 203 canvas.save(SaveFlags::MatrixClip); 204 for (int i = 0; i < LOOPS; i++) { 205 canvas.translate(0, 10); 206 canvas.drawRect(0, 0, 10, 10, SkPaint()); 207 canvas.drawBitmap(bitmap, 5, 0, nullptr); 208 } 209 canvas.restore(); 210 }); 211 212 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 213 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 214 SimpleBatchingTestRenderer renderer; 215 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 216 EXPECT_EQ(2 * LOOPS, renderer.getIndex()) 217 << "Expect number of ops = 2 * loop count"; 218} 219 220RENDERTHREAD_TEST(FrameBuilder, empty_noFbo0) { 221 class EmptyNoFbo0TestRenderer : public TestRendererBase { 222 public: 223 void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { 224 ADD_FAILURE() << "Primary frame draw not expected in this test"; 225 } 226 void endFrame(const Rect& repaintRect) override { 227 ADD_FAILURE() << "Primary frame draw not expected in this test"; 228 } 229 }; 230 231 // Pass empty node list, so no work is enqueued for Fbo0 232 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 233 sEmptyNodeList, sLightGeometry, Caches::getInstance()); 234 EmptyNoFbo0TestRenderer renderer; 235 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 236} 237 238RENDERTHREAD_TEST(FrameBuilder, empty_withFbo0) { 239 class EmptyWithFbo0TestRenderer : public TestRendererBase { 240 public: 241 void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { 242 EXPECT_EQ(0, mIndex++); 243 } 244 void endFrame(const Rect& repaintRect) override { 245 EXPECT_EQ(1, mIndex++); 246 } 247 }; 248 auto node = TestUtils::createNode(10, 10, 110, 110, 249 [](RenderProperties& props, RecordingCanvas& canvas) { 250 // no drawn content 251 }); 252 auto syncedNodeList = TestUtils::createSyncedNodeList(node); 253 254 // Draw, but pass empty node list, so no work is done for primary frame 255 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 256 syncedNodeList, sLightGeometry, Caches::getInstance()); 257 EmptyWithFbo0TestRenderer renderer; 258 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 259 EXPECT_EQ(2, renderer.getIndex()) << "No drawing content produced," 260 " but fbo0 update lifecycle should still be observed"; 261} 262 263RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) { 264 class AvoidOverdrawRectsTestRenderer : public TestRendererBase { 265 public: 266 void onRectOp(const RectOp& op, const BakedOpState& state) override { 267 EXPECT_EQ(mIndex++, 0) << "Should be one rect"; 268 EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds) 269 << "Last rect should occlude others."; 270 } 271 }; 272 auto node = TestUtils::createNode(0, 0, 200, 200, 273 [](RenderProperties& props, RecordingCanvas& canvas) { 274 canvas.drawRect(0, 0, 200, 200, SkPaint()); 275 canvas.drawRect(0, 0, 200, 200, SkPaint()); 276 canvas.drawRect(10, 10, 190, 190, SkPaint()); 277 }); 278 279 // Damage (and therefore clip) is same as last draw, subset of renderable area. 280 // This means last op occludes other contents, and they'll be rejected to avoid overdraw. 281 SkRect damageRect = SkRect::MakeLTRB(10, 10, 190, 190); 282 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, damageRect, 200, 200, 283 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 284 285 EXPECT_EQ(3u, node->getDisplayList()->getOps().size()) 286 << "Recording must not have rejected ops, in order for this test to be valid"; 287 288 AvoidOverdrawRectsTestRenderer renderer; 289 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 290 EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op"; 291} 292 293RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) { 294 static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50, 295 SkColorType::kRGB_565_SkColorType); 296 static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50, 297 SkColorType::kAlpha_8_SkColorType); 298 class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase { 299 public: 300 void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { 301 switch(mIndex++) { 302 case 0: 303 EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef()); 304 break; 305 case 1: 306 EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef()); 307 break; 308 default: 309 ADD_FAILURE() << "Only two ops expected."; 310 } 311 } 312 }; 313 314 auto node = TestUtils::createNode(0, 0, 50, 50, 315 [](RenderProperties& props, RecordingCanvas& canvas) { 316 canvas.drawRect(0, 0, 50, 50, SkPaint()); 317 canvas.drawRect(0, 0, 50, 50, SkPaint()); 318 canvas.drawBitmap(transpBitmap, 0, 0, nullptr); 319 320 // only the below draws should remain, since they're 321 canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr); 322 canvas.drawBitmap(transpBitmap, 0, 0, nullptr); 323 }); 324 325 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(50, 50), 50, 50, 326 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 327 328 EXPECT_EQ(5u, node->getDisplayList()->getOps().size()) 329 << "Recording must not have rejected ops, in order for this test to be valid"; 330 331 AvoidOverdrawBitmapsTestRenderer renderer; 332 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 333 EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly two ops"; 334} 335 336RENDERTHREAD_TEST(FrameBuilder, clippedMerging) { 337 class ClippedMergingTestRenderer : public TestRendererBase { 338 public: 339 void onMergedBitmapOps(const MergedBakedOpList& opList) override { 340 EXPECT_EQ(0, mIndex); 341 mIndex += opList.count; 342 EXPECT_EQ(4u, opList.count); 343 EXPECT_EQ(Rect(10, 10, 90, 90), opList.clip); 344 EXPECT_EQ(OpClipSideFlags::Left | OpClipSideFlags::Top | OpClipSideFlags::Right, 345 opList.clipSideFlags); 346 } 347 }; 348 auto node = TestUtils::createNode(0, 0, 100, 100, 349 [](RenderProperties& props, TestCanvas& canvas) { 350 SkBitmap bitmap = TestUtils::createSkBitmap(20, 20); 351 352 // left side clipped (to inset left half) 353 canvas.clipRect(10, 0, 50, 100, SkRegion::kReplace_Op); 354 canvas.drawBitmap(bitmap, 0, 40, nullptr); 355 356 // top side clipped (to inset top half) 357 canvas.clipRect(0, 10, 100, 50, SkRegion::kReplace_Op); 358 canvas.drawBitmap(bitmap, 40, 0, nullptr); 359 360 // right side clipped (to inset right half) 361 canvas.clipRect(50, 0, 90, 100, SkRegion::kReplace_Op); 362 canvas.drawBitmap(bitmap, 80, 40, nullptr); 363 364 // bottom not clipped, just abutting (inset bottom half) 365 canvas.clipRect(0, 50, 100, 90, SkRegion::kReplace_Op); 366 canvas.drawBitmap(bitmap, 40, 70, nullptr); 367 }); 368 369 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, 370 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 371 ClippedMergingTestRenderer renderer; 372 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 373 EXPECT_EQ(4, renderer.getIndex()); 374} 375 376RENDERTHREAD_TEST(FrameBuilder, textMerging) { 377 class TextMergingTestRenderer : public TestRendererBase { 378 public: 379 void onMergedTextOps(const MergedBakedOpList& opList) override { 380 EXPECT_EQ(0, mIndex); 381 mIndex += opList.count; 382 EXPECT_EQ(2u, opList.count); 383 EXPECT_EQ(OpClipSideFlags::Top, opList.clipSideFlags); 384 EXPECT_EQ(OpClipSideFlags::Top, opList.states[0]->computedState.clipSideFlags); 385 EXPECT_EQ(OpClipSideFlags::None, opList.states[1]->computedState.clipSideFlags); 386 } 387 }; 388 auto node = TestUtils::createNode(0, 0, 400, 400, 389 [](RenderProperties& props, TestCanvas& canvas) { 390 SkPaint paint; 391 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 392 paint.setAntiAlias(true); 393 paint.setTextSize(50); 394 TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped 395 TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped 396 }); 397 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400, 398 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 399 TextMergingTestRenderer renderer; 400 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 401 EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops"; 402} 403 404RENDERTHREAD_TEST(FrameBuilder, textStrikethrough) { 405 const int LOOPS = 5; 406 class TextStrikethroughTestRenderer : public TestRendererBase { 407 public: 408 void onRectOp(const RectOp& op, const BakedOpState& state) override { 409 EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text"; 410 } 411 void onMergedTextOps(const MergedBakedOpList& opList) override { 412 EXPECT_EQ(0, mIndex); 413 mIndex += opList.count; 414 EXPECT_EQ(5u, opList.count); 415 } 416 }; 417 auto node = TestUtils::createNode(0, 0, 200, 2000, 418 [](RenderProperties& props, RecordingCanvas& canvas) { 419 SkPaint textPaint; 420 textPaint.setAntiAlias(true); 421 textPaint.setTextSize(20); 422 textPaint.setStrikeThruText(true); 423 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 424 for (int i = 0; i < LOOPS; i++) { 425 TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1)); 426 } 427 }); 428 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000, 429 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 430 TextStrikethroughTestRenderer renderer; 431 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 432 EXPECT_EQ(2 * LOOPS, renderer.getIndex()) 433 << "Expect number of ops = 2 * loop count"; 434} 435 436static auto styles = { 437 SkPaint::kFill_Style, SkPaint::kStroke_Style, SkPaint::kStrokeAndFill_Style }; 438 439RENDERTHREAD_TEST(FrameBuilder, textStyle) { 440 class TextStyleTestRenderer : public TestRendererBase { 441 public: 442 void onMergedTextOps(const MergedBakedOpList& opList) override { 443 ASSERT_EQ(0, mIndex); 444 ASSERT_EQ(3u, opList.count); 445 mIndex += opList.count; 446 447 int index = 0; 448 for (auto style : styles) { 449 auto state = opList.states[index++]; 450 ASSERT_EQ(style, state->op->paint->getStyle()) 451 << "Remainder of validation relies upon stable merged order"; 452 ASSERT_EQ(0, state->computedState.clipSideFlags) 453 << "Clipped bounds validation requires unclipped ops"; 454 } 455 456 Rect fill = opList.states[0]->computedState.clippedBounds; 457 Rect stroke = opList.states[1]->computedState.clippedBounds; 458 EXPECT_EQ(stroke, opList.states[2]->computedState.clippedBounds) 459 << "Stroke+Fill should be same as stroke"; 460 461 EXPECT_TRUE(stroke.contains(fill)); 462 EXPECT_FALSE(fill.contains(stroke)); 463 464 // outset by half the stroke width 465 Rect outsetFill(fill); 466 outsetFill.outset(5); 467 EXPECT_EQ(stroke, outsetFill); 468 } 469 }; 470 auto node = TestUtils::createNode(0, 0, 400, 400, 471 [](RenderProperties& props, TestCanvas& canvas) { 472 SkPaint paint; 473 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 474 paint.setAntiAlias(true); 475 paint.setTextSize(50); 476 paint.setStrokeWidth(10); 477 478 // draw 3 copies of the same text overlapping, each with a different style. 479 // They'll get merged, but with 480 for (auto style : styles) { 481 paint.setStyle(style); 482 TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); 483 } 484 }); 485 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400, 486 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 487 TextStyleTestRenderer renderer; 488 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 489 EXPECT_EQ(3, renderer.getIndex()) << "Expect 3 ops"; 490} 491 492RENDERTHREAD_TEST(FrameBuilder, textureLayer_clipLocalMatrix) { 493 class TextureLayerClipLocalMatrixTestRenderer : public TestRendererBase { 494 public: 495 void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override { 496 EXPECT_EQ(0, mIndex++); 497 EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect()); 498 EXPECT_EQ(Rect(50, 50, 105, 105), state.computedState.clippedBounds); 499 500 Matrix4 expected; 501 expected.loadTranslate(5, 5, 0); 502 EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); 503 } 504 }; 505 506 auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100, 507 SkMatrix::MakeTrans(5, 5)); 508 509 auto node = TestUtils::createNode(0, 0, 200, 200, 510 [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { 511 canvas.save(SaveFlags::MatrixClip); 512 canvas.clipRect(50, 50, 150, 150, SkRegion::kIntersect_Op); 513 canvas.drawLayer(layerUpdater.get()); 514 canvas.restore(); 515 }); 516 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 517 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 518 TextureLayerClipLocalMatrixTestRenderer renderer; 519 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 520 EXPECT_EQ(1, renderer.getIndex()); 521} 522 523RENDERTHREAD_TEST(FrameBuilder, textureLayer_combineMatrices) { 524 class TextureLayerCombineMatricesTestRenderer : public TestRendererBase { 525 public: 526 void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override { 527 EXPECT_EQ(0, mIndex++); 528 529 Matrix4 expected; 530 expected.loadTranslate(35, 45, 0); 531 EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); 532 } 533 }; 534 535 auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100, 536 SkMatrix::MakeTrans(5, 5)); 537 538 auto node = TestUtils::createNode(0, 0, 200, 200, 539 [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { 540 canvas.save(SaveFlags::MatrixClip); 541 canvas.translate(30, 40); 542 canvas.drawLayer(layerUpdater.get()); 543 canvas.restore(); 544 }); 545 546 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 547 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 548 TextureLayerCombineMatricesTestRenderer renderer; 549 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 550 EXPECT_EQ(1, renderer.getIndex()); 551} 552 553RENDERTHREAD_TEST(FrameBuilder, textureLayer_reject) { 554 auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100, 555 SkMatrix::MakeTrans(5, 5)); 556 layerUpdater->backingLayer()->setRenderTarget(GL_NONE); // Should be rejected 557 558 auto node = TestUtils::createNode(0, 0, 200, 200, 559 [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { 560 canvas.drawLayer(layerUpdater.get()); 561 }); 562 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 563 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 564 FailRenderer renderer; 565 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 566} 567 568RENDERTHREAD_TEST(FrameBuilder, functor_reject) { 569 class FunctorTestRenderer : public TestRendererBase { 570 public: 571 void onFunctorOp(const FunctorOp& op, const BakedOpState& state) override { 572 EXPECT_EQ(0, mIndex++); 573 } 574 }; 575 Functor noopFunctor; 576 577 // 1 million pixel tall view, scrolled down 80% 578 auto scrolledFunctorView = TestUtils::createNode(0, 0, 400, 1000000, 579 [&noopFunctor](RenderProperties& props, RecordingCanvas& canvas) { 580 canvas.translate(0, -800000); 581 canvas.callDrawGLFunction(&noopFunctor); 582 }); 583 584 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 585 TestUtils::createSyncedNodeList(scrolledFunctorView), 586 sLightGeometry, Caches::getInstance()); 587 FunctorTestRenderer renderer; 588 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 589 EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected"; 590} 591 592RENDERTHREAD_TEST(FrameBuilder, deferColorOp_unbounded) { 593 class ColorTestRenderer : public TestRendererBase { 594 public: 595 void onColorOp(const ColorOp& op, const BakedOpState& state) override { 596 EXPECT_EQ(0, mIndex++); 597 EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds) 598 << "Color op should be expanded to bounds of surrounding"; 599 } 600 }; 601 602 auto unclippedColorView = TestUtils::createNode(0, 0, 10, 10, 603 [](RenderProperties& props, RecordingCanvas& canvas) { 604 props.setClipToBounds(false); 605 canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode); 606 }); 607 608 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 609 TestUtils::createSyncedNodeList(unclippedColorView), 610 sLightGeometry, Caches::getInstance()); 611 ColorTestRenderer renderer; 612 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 613 EXPECT_EQ(1, renderer.getIndex()) << "ColorOp should not be rejected"; 614} 615 616TEST(FrameBuilder, renderNode) { 617 class RenderNodeTestRenderer : public TestRendererBase { 618 public: 619 void onRectOp(const RectOp& op, const BakedOpState& state) override { 620 switch(mIndex++) { 621 case 0: 622 EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds); 623 EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); 624 break; 625 case 1: 626 EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds); 627 EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); 628 break; 629 default: 630 ADD_FAILURE(); 631 } 632 } 633 }; 634 635 auto child = TestUtils::createNode(10, 10, 110, 110, 636 [](RenderProperties& props, RecordingCanvas& canvas) { 637 SkPaint paint; 638 paint.setColor(SK_ColorWHITE); 639 canvas.drawRect(0, 0, 100, 100, paint); 640 }); 641 642 auto parent = TestUtils::createNode(0, 0, 200, 200, 643 [&child](RenderProperties& props, RecordingCanvas& canvas) { 644 SkPaint paint; 645 paint.setColor(SK_ColorDKGRAY); 646 canvas.drawRect(0, 0, 200, 200, paint); 647 648 canvas.save(SaveFlags::MatrixClip); 649 canvas.translate(40, 40); 650 canvas.drawRenderNode(child.get()); 651 canvas.restore(); 652 }); 653 654 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 655 TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance()); 656 RenderNodeTestRenderer renderer; 657 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 658 EXPECT_EQ(2, renderer.getIndex()); 659} 660 661RENDERTHREAD_TEST(FrameBuilder, clipped) { 662 class ClippedTestRenderer : public TestRendererBase { 663 public: 664 void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { 665 EXPECT_EQ(0, mIndex++); 666 EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds); 667 EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect()); 668 EXPECT_TRUE(state.computedState.transform.isIdentity()); 669 } 670 }; 671 672 auto node = TestUtils::createNode(0, 0, 200, 200, 673 [](RenderProperties& props, RecordingCanvas& canvas) { 674 SkBitmap bitmap = TestUtils::createSkBitmap(200, 200); 675 canvas.drawBitmap(bitmap, 0, 0, nullptr); 676 }); 677 678 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, 679 SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver 680 200, 200, TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 681 ClippedTestRenderer renderer; 682 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 683} 684 685RENDERTHREAD_TEST(FrameBuilder, saveLayer_simple) { 686 class SaveLayerSimpleTestRenderer : public TestRendererBase { 687 public: 688 OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { 689 EXPECT_EQ(0, mIndex++); 690 EXPECT_EQ(180u, width); 691 EXPECT_EQ(180u, height); 692 return nullptr; 693 } 694 void endLayer() override { 695 EXPECT_EQ(2, mIndex++); 696 } 697 void onRectOp(const RectOp& op, const BakedOpState& state) override { 698 EXPECT_EQ(1, mIndex++); 699 EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds); 700 EXPECT_EQ(Rect(180, 180), state.computedState.clippedBounds); 701 EXPECT_EQ(Rect(180, 180), state.computedState.clipRect()); 702 703 Matrix4 expectedTransform; 704 expectedTransform.loadTranslate(-10, -10, 0); 705 EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform); 706 } 707 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 708 EXPECT_EQ(3, mIndex++); 709 EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); 710 EXPECT_EQ(Rect(200, 200), state.computedState.clipRect()); 711 EXPECT_TRUE(state.computedState.transform.isIdentity()); 712 } 713 }; 714 715 auto node = TestUtils::createNode(0, 0, 200, 200, 716 [](RenderProperties& props, RecordingCanvas& canvas) { 717 canvas.saveLayerAlpha(10, 10, 190, 190, 128, SaveFlags::ClipToLayer); 718 canvas.drawRect(10, 10, 190, 190, SkPaint()); 719 canvas.restore(); 720 }); 721 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 722 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 723 SaveLayerSimpleTestRenderer renderer; 724 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 725 EXPECT_EQ(4, renderer.getIndex()); 726} 727 728RENDERTHREAD_TEST(FrameBuilder, saveLayer_nested) { 729 /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as: 730 * - startTemporaryLayer2, rect2 endLayer2 731 * - startTemporaryLayer1, rect1, drawLayer2, endLayer1 732 * - startFrame, layerOp1, endFrame 733 */ 734 class SaveLayerNestedTestRenderer : public TestRendererBase { 735 public: 736 OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { 737 const int index = mIndex++; 738 if (index == 0) { 739 EXPECT_EQ(400u, width); 740 EXPECT_EQ(400u, height); 741 return (OffscreenBuffer*) 0x400; 742 } else if (index == 3) { 743 EXPECT_EQ(800u, width); 744 EXPECT_EQ(800u, height); 745 return (OffscreenBuffer*) 0x800; 746 } else { ADD_FAILURE(); } 747 return (OffscreenBuffer*) nullptr; 748 } 749 void endLayer() override { 750 int index = mIndex++; 751 EXPECT_TRUE(index == 2 || index == 6); 752 } 753 void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { 754 EXPECT_EQ(7, mIndex++); 755 } 756 void endFrame(const Rect& repaintRect) override { 757 EXPECT_EQ(9, mIndex++); 758 } 759 void onRectOp(const RectOp& op, const BakedOpState& state) override { 760 const int index = mIndex++; 761 if (index == 1) { 762 EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner rect 763 } else if (index == 4) { 764 EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer rect 765 } else { ADD_FAILURE(); } 766 } 767 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 768 const int index = mIndex++; 769 if (index == 5) { 770 EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle); 771 EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner layer 772 } else if (index == 8) { 773 EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle); 774 EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer layer 775 } else { ADD_FAILURE(); } 776 } 777 }; 778 779 auto node = TestUtils::createNode(0, 0, 800, 800, 780 [](RenderProperties& props, RecordingCanvas& canvas) { 781 canvas.saveLayerAlpha(0, 0, 800, 800, 128, SaveFlags::ClipToLayer); 782 { 783 canvas.drawRect(0, 0, 800, 800, SkPaint()); 784 canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer); 785 { 786 canvas.drawRect(0, 0, 400, 400, SkPaint()); 787 } 788 canvas.restore(); 789 } 790 canvas.restore(); 791 }); 792 793 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800, 794 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 795 SaveLayerNestedTestRenderer renderer; 796 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 797 EXPECT_EQ(10, renderer.getIndex()); 798} 799 800RENDERTHREAD_TEST(FrameBuilder, saveLayer_contentRejection) { 801 auto node = TestUtils::createNode(0, 0, 200, 200, 802 [](RenderProperties& props, RecordingCanvas& canvas) { 803 canvas.save(SaveFlags::MatrixClip); 804 canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); 805 canvas.saveLayerAlpha(200, 200, 400, 400, 128, SaveFlags::ClipToLayer); 806 807 // draw within save layer may still be recorded, but shouldn't be drawn 808 canvas.drawRect(200, 200, 400, 400, SkPaint()); 809 810 canvas.restore(); 811 canvas.restore(); 812 }); 813 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 814 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 815 816 FailRenderer renderer; 817 // should see no ops, even within the layer, since the layer should be rejected 818 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 819} 820 821RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_simple) { 822 class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase { 823 public: 824 void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { 825 EXPECT_EQ(0, mIndex++); 826 EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); 827 EXPECT_CLIP_RECT(Rect(200, 200), state.computedState.clipState); 828 EXPECT_TRUE(state.computedState.transform.isIdentity()); 829 } 830 void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { 831 EXPECT_EQ(1, mIndex++); 832 ASSERT_NE(nullptr, op.paint); 833 ASSERT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint)); 834 } 835 void onRectOp(const RectOp& op, const BakedOpState& state) override { 836 EXPECT_EQ(2, mIndex++); 837 EXPECT_EQ(Rect(200, 200), op.unmappedBounds); 838 EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds); 839 EXPECT_EQ(Rect(200, 200), state.computedState.clipRect()); 840 EXPECT_TRUE(state.computedState.transform.isIdentity()); 841 } 842 void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { 843 EXPECT_EQ(3, mIndex++); 844 EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); 845 EXPECT_CLIP_RECT(Rect(200, 200), state.computedState.clipState); 846 EXPECT_TRUE(state.computedState.transform.isIdentity()); 847 } 848 }; 849 850 auto node = TestUtils::createNode(0, 0, 200, 200, 851 [](RenderProperties& props, RecordingCanvas& canvas) { 852 canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0)); 853 canvas.drawRect(0, 0, 200, 200, SkPaint()); 854 canvas.restore(); 855 }); 856 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 857 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 858 SaveLayerUnclippedSimpleTestRenderer renderer; 859 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 860 EXPECT_EQ(4, renderer.getIndex()); 861} 862 863RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_mergedClears) { 864 class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase { 865 public: 866 void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { 867 int index = mIndex++; 868 EXPECT_GT(4, index); 869 EXPECT_EQ(5, op.unmappedBounds.getWidth()); 870 EXPECT_EQ(5, op.unmappedBounds.getHeight()); 871 if (index == 0) { 872 EXPECT_EQ(Rect(10, 10), state.computedState.clippedBounds); 873 } else if (index == 1) { 874 EXPECT_EQ(Rect(190, 0, 200, 10), state.computedState.clippedBounds); 875 } else if (index == 2) { 876 EXPECT_EQ(Rect(0, 190, 10, 200), state.computedState.clippedBounds); 877 } else if (index == 3) { 878 EXPECT_EQ(Rect(190, 190, 200, 200), state.computedState.clippedBounds); 879 } 880 } 881 void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { 882 EXPECT_EQ(4, mIndex++); 883 ASSERT_EQ(op.vertexCount, 16u); 884 for (size_t i = 0; i < op.vertexCount; i++) { 885 auto v = op.vertices[i]; 886 EXPECT_TRUE(v.x == 0 || v.x == 10 || v.x == 190 || v.x == 200); 887 EXPECT_TRUE(v.y == 0 || v.y == 10 || v.y == 190 || v.y == 200); 888 } 889 } 890 void onRectOp(const RectOp& op, const BakedOpState& state) override { 891 EXPECT_EQ(5, mIndex++); 892 } 893 void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { 894 EXPECT_LT(5, mIndex++); 895 } 896 }; 897 898 auto node = TestUtils::createNode(0, 0, 200, 200, 899 [](RenderProperties& props, RecordingCanvas& canvas) { 900 901 int restoreTo = canvas.save(SaveFlags::MatrixClip); 902 canvas.scale(2, 2); 903 canvas.saveLayerAlpha(0, 0, 5, 5, 128, SaveFlags::MatrixClip); 904 canvas.saveLayerAlpha(95, 0, 100, 5, 128, SaveFlags::MatrixClip); 905 canvas.saveLayerAlpha(0, 95, 5, 100, 128, SaveFlags::MatrixClip); 906 canvas.saveLayerAlpha(95, 95, 100, 100, 128, SaveFlags::MatrixClip); 907 canvas.drawRect(0, 0, 100, 100, SkPaint()); 908 canvas.restoreToCount(restoreTo); 909 }); 910 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 911 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 912 SaveLayerUnclippedMergedClearsTestRenderer renderer; 913 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 914 EXPECT_EQ(10, renderer.getIndex()) 915 << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect."; 916} 917 918RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_clearClip) { 919 class SaveLayerUnclippedClearClipTestRenderer : public TestRendererBase { 920 public: 921 void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { 922 EXPECT_EQ(0, mIndex++); 923 } 924 void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { 925 EXPECT_EQ(1, mIndex++); 926 ASSERT_NE(nullptr, op.paint); 927 EXPECT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint)); 928 EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds) 929 << "Expect dirty rect as clip"; 930 ASSERT_NE(nullptr, state.computedState.clipState); 931 EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipState->rect); 932 EXPECT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode); 933 } 934 void onRectOp(const RectOp& op, const BakedOpState& state) override { 935 EXPECT_EQ(2, mIndex++); 936 } 937 void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { 938 EXPECT_EQ(3, mIndex++); 939 } 940 }; 941 942 auto node = TestUtils::createNode(0, 0, 200, 200, 943 [](RenderProperties& props, RecordingCanvas& canvas) { 944 // save smaller than clip, so we get unclipped behavior 945 canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0)); 946 canvas.drawRect(0, 0, 200, 200, SkPaint()); 947 canvas.restore(); 948 }); 949 950 // draw with partial screen dirty, and assert we see that rect later 951 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeLTRB(50, 50, 150, 150), 200, 200, 952 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 953 SaveLayerUnclippedClearClipTestRenderer renderer; 954 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 955 EXPECT_EQ(4, renderer.getIndex()); 956} 957 958RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_reject) { 959 auto node = TestUtils::createNode(0, 0, 200, 200, 960 [](RenderProperties& props, RecordingCanvas& canvas) { 961 // unclipped savelayer + rect both in area that won't intersect with dirty 962 canvas.saveLayerAlpha(100, 100, 200, 200, 128, (SaveFlags::Flags)(0)); 963 canvas.drawRect(100, 100, 200, 200, SkPaint()); 964 canvas.restore(); 965 }); 966 967 // draw with partial screen dirty that doesn't intersect with savelayer 968 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200, 969 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 970 FailRenderer renderer; 971 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 972} 973 974/* saveLayerUnclipped { saveLayer { saveLayerUnclipped { rect } } } will play back as: 975 * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer 976 * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe 977 */ 978RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_complex) { 979 class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase { 980 public: 981 OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) { 982 EXPECT_EQ(0, mIndex++); // savelayer first 983 return (OffscreenBuffer*)0xabcd; 984 } 985 void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { 986 int index = mIndex++; 987 EXPECT_TRUE(index == 1 || index == 7); 988 } 989 void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { 990 int index = mIndex++; 991 EXPECT_TRUE(index == 2 || index == 8); 992 } 993 void onRectOp(const RectOp& op, const BakedOpState& state) override { 994 EXPECT_EQ(3, mIndex++); 995 Matrix4 expected; 996 expected.loadTranslate(-100, -100, 0); 997 EXPECT_EQ(Rect(100, 100, 200, 200), state.computedState.clippedBounds); 998 EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); 999 } 1000 void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { 1001 int index = mIndex++; 1002 EXPECT_TRUE(index == 4 || index == 10); 1003 } 1004 void endLayer() override { 1005 EXPECT_EQ(5, mIndex++); 1006 } 1007 void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { 1008 EXPECT_EQ(6, mIndex++); 1009 } 1010 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 1011 EXPECT_EQ(9, mIndex++); 1012 } 1013 void endFrame(const Rect& repaintRect) override { 1014 EXPECT_EQ(11, mIndex++); 1015 } 1016 }; 1017 1018 auto node = TestUtils::createNode(0, 0, 600, 600, // 500x500 triggers clipping 1019 [](RenderProperties& props, RecordingCanvas& canvas) { 1020 canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SaveFlags::Flags)0); // unclipped 1021 canvas.saveLayerAlpha(100, 100, 400, 400, 128, SaveFlags::ClipToLayer); // clipped 1022 canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SaveFlags::Flags)0); // unclipped 1023 canvas.drawRect(200, 200, 300, 300, SkPaint()); 1024 canvas.restore(); 1025 canvas.restore(); 1026 canvas.restore(); 1027 }); 1028 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600, 1029 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 1030 SaveLayerUnclippedComplexTestRenderer renderer; 1031 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1032 EXPECT_EQ(12, renderer.getIndex()); 1033} 1034 1035RENDERTHREAD_TEST(FrameBuilder, hwLayer_simple) { 1036 class HwLayerSimpleTestRenderer : public TestRendererBase { 1037 public: 1038 void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { 1039 EXPECT_EQ(0, mIndex++); 1040 EXPECT_EQ(100u, offscreenBuffer->viewportWidth); 1041 EXPECT_EQ(100u, offscreenBuffer->viewportHeight); 1042 EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect); 1043 } 1044 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1045 EXPECT_EQ(1, mIndex++); 1046 1047 EXPECT_TRUE(state.computedState.transform.isIdentity()) 1048 << "Transform should be reset within layer"; 1049 1050 EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect()) 1051 << "Damage rect should be used to clip layer content"; 1052 } 1053 void endLayer() override { 1054 EXPECT_EQ(2, mIndex++); 1055 } 1056 void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { 1057 EXPECT_EQ(3, mIndex++); 1058 } 1059 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 1060 EXPECT_EQ(4, mIndex++); 1061 } 1062 void endFrame(const Rect& repaintRect) override { 1063 EXPECT_EQ(5, mIndex++); 1064 } 1065 }; 1066 1067 auto node = TestUtils::createNode(10, 10, 110, 110, 1068 [](RenderProperties& props, RecordingCanvas& canvas) { 1069 props.mutateLayerProperties().setType(LayerType::RenderLayer); 1070 SkPaint paint; 1071 paint.setColor(SK_ColorWHITE); 1072 canvas.drawRect(0, 0, 100, 100, paint); 1073 }); 1074 OffscreenBuffer** layerHandle = node->getLayerHandle(); 1075 1076 // create RenderNode's layer here in same way prepareTree would 1077 OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); 1078 *layerHandle = &layer; 1079 1080 auto syncedNodeList = TestUtils::createSyncedNodeList(node); 1081 1082 // only enqueue partial damage 1083 LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid 1084 layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); 1085 1086 FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 1087 syncedNodeList, sLightGeometry, Caches::getInstance()); 1088 HwLayerSimpleTestRenderer renderer; 1089 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1090 EXPECT_EQ(6, renderer.getIndex()); 1091 1092 // clean up layer pointer, so we can safely destruct RenderNode 1093 *layerHandle = nullptr; 1094} 1095 1096RENDERTHREAD_TEST(FrameBuilder, hwLayer_complex) { 1097 /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as: 1098 * - startRepaintLayer(child), rect(grey), endLayer 1099 * - startTemporaryLayer, drawLayer(child), endLayer 1100 * - startRepaintLayer(parent), rect(white), drawLayer(saveLayer), endLayer 1101 * - startFrame, drawLayer(parent), endLayerb 1102 */ 1103 class HwLayerComplexTestRenderer : public TestRendererBase { 1104 public: 1105 OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) { 1106 EXPECT_EQ(3, mIndex++); // savelayer first 1107 return (OffscreenBuffer*)0xabcd; 1108 } 1109 void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { 1110 int index = mIndex++; 1111 if (index == 0) { 1112 // starting inner layer 1113 EXPECT_EQ(100u, offscreenBuffer->viewportWidth); 1114 EXPECT_EQ(100u, offscreenBuffer->viewportHeight); 1115 } else if (index == 6) { 1116 // starting outer layer 1117 EXPECT_EQ(200u, offscreenBuffer->viewportWidth); 1118 EXPECT_EQ(200u, offscreenBuffer->viewportHeight); 1119 } else { ADD_FAILURE(); } 1120 } 1121 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1122 int index = mIndex++; 1123 if (index == 1) { 1124 // inner layer's rect (white) 1125 EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); 1126 } else if (index == 7) { 1127 // outer layer's rect (grey) 1128 EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); 1129 } else { ADD_FAILURE(); } 1130 } 1131 void endLayer() override { 1132 int index = mIndex++; 1133 EXPECT_TRUE(index == 2 || index == 5 || index == 9); 1134 } 1135 void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { 1136 EXPECT_EQ(10, mIndex++); 1137 } 1138 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 1139 OffscreenBuffer* layer = *op.layerHandle; 1140 int index = mIndex++; 1141 if (index == 4) { 1142 EXPECT_EQ(100u, layer->viewportWidth); 1143 EXPECT_EQ(100u, layer->viewportHeight); 1144 } else if (index == 8) { 1145 EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle); 1146 } else if (index == 11) { 1147 EXPECT_EQ(200u, layer->viewportWidth); 1148 EXPECT_EQ(200u, layer->viewportHeight); 1149 } else { ADD_FAILURE(); } 1150 } 1151 void endFrame(const Rect& repaintRect) override { 1152 EXPECT_EQ(12, mIndex++); 1153 } 1154 }; 1155 1156 auto child = TestUtils::createNode(50, 50, 150, 150, 1157 [](RenderProperties& props, RecordingCanvas& canvas) { 1158 props.mutateLayerProperties().setType(LayerType::RenderLayer); 1159 SkPaint paint; 1160 paint.setColor(SK_ColorWHITE); 1161 canvas.drawRect(0, 0, 100, 100, paint); 1162 }); 1163 OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100); 1164 *(child->getLayerHandle()) = &childLayer; 1165 1166 RenderNode* childPtr = child.get(); 1167 auto parent = TestUtils::createNode(0, 0, 200, 200, 1168 [childPtr](RenderProperties& props, RecordingCanvas& canvas) { 1169 props.mutateLayerProperties().setType(LayerType::RenderLayer); 1170 SkPaint paint; 1171 paint.setColor(SK_ColorDKGRAY); 1172 canvas.drawRect(0, 0, 200, 200, paint); 1173 1174 canvas.saveLayerAlpha(50, 50, 150, 150, 128, SaveFlags::ClipToLayer); 1175 canvas.drawRenderNode(childPtr); 1176 canvas.restore(); 1177 }); 1178 OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200); 1179 *(parent->getLayerHandle()) = &parentLayer; 1180 1181 auto syncedList = TestUtils::createSyncedNodeList(parent); 1182 1183 LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid 1184 layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100)); 1185 layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200)); 1186 1187 FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 1188 syncedList, sLightGeometry, Caches::getInstance()); 1189 HwLayerComplexTestRenderer renderer; 1190 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1191 EXPECT_EQ(13, renderer.getIndex()); 1192 1193 // clean up layer pointers, so we can safely destruct RenderNodes 1194 *(child->getLayerHandle()) = nullptr; 1195 *(parent->getLayerHandle()) = nullptr; 1196} 1197 1198 1199RENDERTHREAD_TEST(FrameBuilder, buildLayer) { 1200 class BuildLayerTestRenderer : public TestRendererBase { 1201 public: 1202 void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { 1203 EXPECT_EQ(0, mIndex++); 1204 EXPECT_EQ(100u, offscreenBuffer->viewportWidth); 1205 EXPECT_EQ(100u, offscreenBuffer->viewportHeight); 1206 EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect); 1207 } 1208 void onColorOp(const ColorOp& op, const BakedOpState& state) override { 1209 EXPECT_EQ(1, mIndex++); 1210 1211 EXPECT_TRUE(state.computedState.transform.isIdentity()) 1212 << "Transform should be reset within layer"; 1213 1214 EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect()) 1215 << "Damage rect should be used to clip layer content"; 1216 } 1217 void endLayer() override { 1218 EXPECT_EQ(2, mIndex++); 1219 } 1220 void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { 1221 ADD_FAILURE() << "Primary frame draw not expected in this test"; 1222 } 1223 void endFrame(const Rect& repaintRect) override { 1224 ADD_FAILURE() << "Primary frame draw not expected in this test"; 1225 } 1226 }; 1227 1228 auto node = TestUtils::createNode(10, 10, 110, 110, 1229 [](RenderProperties& props, RecordingCanvas& canvas) { 1230 props.mutateLayerProperties().setType(LayerType::RenderLayer); 1231 canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode); 1232 }); 1233 OffscreenBuffer** layerHandle = node->getLayerHandle(); 1234 1235 // create RenderNode's layer here in same way prepareTree would 1236 OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); 1237 *layerHandle = &layer; 1238 1239 auto syncedNodeList = TestUtils::createSyncedNodeList(node); 1240 1241 // only enqueue partial damage 1242 LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid 1243 layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); 1244 1245 // Draw, but pass empty node list, so no work is done for primary frame 1246 FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(1, 1), 1, 1, 1247 sEmptyNodeList, sLightGeometry, Caches::getInstance()); 1248 BuildLayerTestRenderer renderer; 1249 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1250 EXPECT_EQ(3, renderer.getIndex()); 1251 1252 // clean up layer pointer, so we can safely destruct RenderNode 1253 *layerHandle = nullptr; 1254} 1255 1256static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) { 1257 SkPaint paint; 1258 paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel 1259 canvas->drawRect(0, 0, 100, 100, paint); 1260} 1261static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) { 1262 auto node = TestUtils::createNode(0, 0, 100, 100, 1263 [expectedDrawOrder](RenderProperties& props, RecordingCanvas& canvas) { 1264 drawOrderedRect(&canvas, expectedDrawOrder); 1265 }); 1266 node->mutateStagingProperties().setTranslationZ(z); 1267 node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z); 1268 canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership 1269} 1270RENDERTHREAD_TEST(FrameBuilder, zReorder) { 1271 class ZReorderTestRenderer : public TestRendererBase { 1272 public: 1273 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1274 int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel 1275 EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order"; 1276 } 1277 }; 1278 1279 auto parent = TestUtils::createNode(0, 0, 100, 100, 1280 [](RenderProperties& props, RecordingCanvas& canvas) { 1281 drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder 1282 drawOrderedRect(&canvas, 1); 1283 canvas.insertReorderBarrier(true); 1284 drawOrderedNode(&canvas, 6, 2.0f); 1285 drawOrderedRect(&canvas, 3); 1286 drawOrderedNode(&canvas, 4, 0.0f); 1287 drawOrderedRect(&canvas, 5); 1288 drawOrderedNode(&canvas, 2, -2.0f); 1289 drawOrderedNode(&canvas, 7, 2.0f); 1290 canvas.insertReorderBarrier(false); 1291 drawOrderedRect(&canvas, 8); 1292 drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder 1293 }); 1294 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, 1295 TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance()); 1296 ZReorderTestRenderer renderer; 1297 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1298 EXPECT_EQ(10, renderer.getIndex()); 1299}; 1300 1301RENDERTHREAD_TEST(FrameBuilder, projectionReorder) { 1302 static const int scrollX = 5; 1303 static const int scrollY = 10; 1304 class ProjectionReorderTestRenderer : public TestRendererBase { 1305 public: 1306 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1307 const int index = mIndex++; 1308 1309 Matrix4 expectedMatrix; 1310 switch (index) { 1311 case 0: 1312 EXPECT_EQ(Rect(100, 100), op.unmappedBounds); 1313 EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); 1314 expectedMatrix.loadIdentity(); 1315 EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask); 1316 break; 1317 case 1: 1318 EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds); 1319 EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); 1320 expectedMatrix.loadTranslate(50 - scrollX, 50 - scrollY, 0); 1321 ASSERT_NE(nullptr, state.computedState.localProjectionPathMask); 1322 EXPECT_EQ(Rect(-35, -30, 45, 50), 1323 Rect(state.computedState.localProjectionPathMask->getBounds())); 1324 break; 1325 case 2: 1326 EXPECT_EQ(Rect(100, 50), op.unmappedBounds); 1327 EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); 1328 expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0); 1329 EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask); 1330 break; 1331 default: 1332 ADD_FAILURE(); 1333 } 1334 EXPECT_EQ(expectedMatrix, state.computedState.transform); 1335 } 1336 }; 1337 1338 /** 1339 * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C) 1340 * with a projecting child (P) of its own. P would normally draw between B and C's "background" 1341 * draw, but because it is projected backwards, it's drawn in between B and C. 1342 * 1343 * The parent is scrolled by scrollX/scrollY, but this does not affect the background 1344 * (which isn't affected by scroll). 1345 */ 1346 auto receiverBackground = TestUtils::createNode(0, 0, 100, 100, 1347 [](RenderProperties& properties, RecordingCanvas& canvas) { 1348 properties.setProjectionReceiver(true); 1349 // scroll doesn't apply to background, so undone via translationX/Y 1350 // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver! 1351 properties.setTranslationX(scrollX); 1352 properties.setTranslationY(scrollY); 1353 1354 SkPaint paint; 1355 paint.setColor(SK_ColorWHITE); 1356 canvas.drawRect(0, 0, 100, 100, paint); 1357 }); 1358 auto projectingRipple = TestUtils::createNode(50, 0, 100, 50, 1359 [](RenderProperties& properties, RecordingCanvas& canvas) { 1360 properties.setProjectBackwards(true); 1361 properties.setClipToBounds(false); 1362 SkPaint paint; 1363 paint.setColor(SK_ColorDKGRAY); 1364 canvas.drawRect(-10, -10, 60, 60, paint); 1365 }); 1366 auto child = TestUtils::createNode(0, 50, 100, 100, 1367 [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { 1368 SkPaint paint; 1369 paint.setColor(SK_ColorBLUE); 1370 canvas.drawRect(0, 0, 100, 50, paint); 1371 canvas.drawRenderNode(projectingRipple.get()); 1372 }); 1373 auto parent = TestUtils::createNode(0, 0, 100, 100, 1374 [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { 1375 // Set a rect outline for the projecting ripple to be masked against. 1376 properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f); 1377 1378 canvas.save(SaveFlags::MatrixClip); 1379 canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally) 1380 canvas.drawRenderNode(receiverBackground.get()); 1381 canvas.drawRenderNode(child.get()); 1382 canvas.restore(); 1383 }); 1384 1385 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, 1386 TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance()); 1387 ProjectionReorderTestRenderer renderer; 1388 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1389 EXPECT_EQ(3, renderer.getIndex()); 1390} 1391 1392RENDERTHREAD_TEST(FrameBuilder, projectionHwLayer) { 1393 static const int scrollX = 5; 1394 static const int scrollY = 10; 1395 class ProjectionHwLayerTestRenderer : public TestRendererBase { 1396 public: 1397 void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { 1398 EXPECT_EQ(0, mIndex++); 1399 } 1400 void onArcOp(const ArcOp& op, const BakedOpState& state) override { 1401 EXPECT_EQ(1, mIndex++); 1402 ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); 1403 } 1404 void endLayer() override { 1405 EXPECT_EQ(2, mIndex++); 1406 } 1407 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1408 EXPECT_EQ(3, mIndex++); 1409 ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); 1410 } 1411 void onOvalOp(const OvalOp& op, const BakedOpState& state) override { 1412 EXPECT_EQ(4, mIndex++); 1413 ASSERT_NE(nullptr, state.computedState.localProjectionPathMask); 1414 Matrix4 expected; 1415 expected.loadTranslate(100 - scrollX, 100 - scrollY, 0); 1416 EXPECT_EQ(expected, state.computedState.transform); 1417 EXPECT_EQ(Rect(-85, -80, 295, 300), 1418 Rect(state.computedState.localProjectionPathMask->getBounds())); 1419 } 1420 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 1421 EXPECT_EQ(5, mIndex++); 1422 ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); 1423 } 1424 }; 1425 auto receiverBackground = TestUtils::createNode(0, 0, 400, 400, 1426 [](RenderProperties& properties, RecordingCanvas& canvas) { 1427 properties.setProjectionReceiver(true); 1428 // scroll doesn't apply to background, so undone via translationX/Y 1429 // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver! 1430 properties.setTranslationX(scrollX); 1431 properties.setTranslationY(scrollY); 1432 1433 canvas.drawRect(0, 0, 400, 400, SkPaint()); 1434 }); 1435 auto projectingRipple = TestUtils::createNode(0, 0, 200, 200, 1436 [](RenderProperties& properties, RecordingCanvas& canvas) { 1437 properties.setProjectBackwards(true); 1438 properties.setClipToBounds(false); 1439 canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds 1440 }); 1441 auto child = TestUtils::createNode(100, 100, 300, 300, 1442 [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { 1443 properties.mutateLayerProperties().setType(LayerType::RenderLayer); 1444 canvas.drawRenderNode(projectingRipple.get()); 1445 canvas.drawArc(0, 0, 200, 200, 0.0f, 280.0f, true, SkPaint()); 1446 }); 1447 auto parent = TestUtils::createNode(0, 0, 400, 400, 1448 [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { 1449 // Set a rect outline for the projecting ripple to be masked against. 1450 properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f); 1451 canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally) 1452 canvas.drawRenderNode(receiverBackground.get()); 1453 canvas.drawRenderNode(child.get()); 1454 }); 1455 1456 OffscreenBuffer** layerHandle = child->getLayerHandle(); 1457 1458 // create RenderNode's layer here in same way prepareTree would, setting windowTransform 1459 OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200, 200); 1460 Matrix4 windowTransform; 1461 windowTransform.loadTranslate(100, 100, 0); // total transform of layer's origin 1462 layer.setWindowTransform(windowTransform); 1463 *layerHandle = &layer; 1464 1465 auto syncedList = TestUtils::createSyncedNodeList(parent); 1466 LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid 1467 layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200)); 1468 FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400, 1469 syncedList, sLightGeometry, Caches::getInstance()); 1470 ProjectionHwLayerTestRenderer renderer; 1471 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1472 EXPECT_EQ(6, renderer.getIndex()); 1473 1474 // clean up layer pointer, so we can safely destruct RenderNode 1475 *layerHandle = nullptr; 1476} 1477 1478RENDERTHREAD_TEST(FrameBuilder, projectionChildScroll) { 1479 static const int scrollX = 500000; 1480 static const int scrollY = 0; 1481 class ProjectionChildScrollTestRenderer : public TestRendererBase { 1482 public: 1483 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1484 EXPECT_EQ(0, mIndex++); 1485 EXPECT_TRUE(state.computedState.transform.isIdentity()); 1486 } 1487 void onOvalOp(const OvalOp& op, const BakedOpState& state) override { 1488 EXPECT_EQ(1, mIndex++); 1489 ASSERT_NE(nullptr, state.computedState.clipState); 1490 ASSERT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode); 1491 ASSERT_EQ(Rect(400, 400), state.computedState.clipState->rect); 1492 EXPECT_TRUE(state.computedState.transform.isIdentity()); 1493 } 1494 }; 1495 auto receiverBackground = TestUtils::createNode(0, 0, 400, 400, 1496 [](RenderProperties& properties, RecordingCanvas& canvas) { 1497 properties.setProjectionReceiver(true); 1498 canvas.drawRect(0, 0, 400, 400, SkPaint()); 1499 }); 1500 auto projectingRipple = TestUtils::createNode(0, 0, 200, 200, 1501 [](RenderProperties& properties, RecordingCanvas& canvas) { 1502 // scroll doesn't apply to background, so undone via translationX/Y 1503 // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver! 1504 properties.setTranslationX(scrollX); 1505 properties.setTranslationY(scrollY); 1506 properties.setProjectBackwards(true); 1507 properties.setClipToBounds(false); 1508 canvas.drawOval(0, 0, 200, 200, SkPaint()); 1509 }); 1510 auto child = TestUtils::createNode(0, 0, 400, 400, 1511 [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { 1512 // Record time clip will be ignored by projectee 1513 canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op); 1514 1515 canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally) 1516 canvas.drawRenderNode(projectingRipple.get()); 1517 }); 1518 auto parent = TestUtils::createNode(0, 0, 400, 400, 1519 [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { 1520 canvas.drawRenderNode(receiverBackground.get()); 1521 canvas.drawRenderNode(child.get()); 1522 }); 1523 1524 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400, 1525 TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance()); 1526 ProjectionChildScrollTestRenderer renderer; 1527 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1528 EXPECT_EQ(2, renderer.getIndex()); 1529} 1530 1531// creates a 100x100 shadow casting node with provided translationZ 1532static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) { 1533 return TestUtils::createNode(0, 0, 100, 100, 1534 [translationZ](RenderProperties& properties, RecordingCanvas& canvas) { 1535 properties.setTranslationZ(translationZ); 1536 properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f); 1537 SkPaint paint; 1538 paint.setColor(SK_ColorWHITE); 1539 canvas.drawRect(0, 0, 100, 100, paint); 1540 }); 1541} 1542 1543RENDERTHREAD_TEST(FrameBuilder, shadow) { 1544 class ShadowTestRenderer : public TestRendererBase { 1545 public: 1546 void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { 1547 EXPECT_EQ(0, mIndex++); 1548 EXPECT_FLOAT_EQ(1.0f, op.casterAlpha); 1549 EXPECT_TRUE(op.shadowTask->casterPerimeter.isRect(nullptr)); 1550 EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowTask->transformXY); 1551 1552 Matrix4 expectedZ; 1553 expectedZ.loadTranslate(0, 0, 5); 1554 EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowTask->transformZ); 1555 } 1556 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1557 EXPECT_EQ(1, mIndex++); 1558 } 1559 }; 1560 1561 auto parent = TestUtils::createNode(0, 0, 200, 200, 1562 [](RenderProperties& props, RecordingCanvas& canvas) { 1563 canvas.insertReorderBarrier(true); 1564 canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); 1565 }); 1566 1567 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 1568 TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance()); 1569 ShadowTestRenderer renderer; 1570 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1571 EXPECT_EQ(2, renderer.getIndex()); 1572} 1573 1574RENDERTHREAD_TEST(FrameBuilder, shadowSaveLayer) { 1575 class ShadowSaveLayerTestRenderer : public TestRendererBase { 1576 public: 1577 OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { 1578 EXPECT_EQ(0, mIndex++); 1579 return nullptr; 1580 } 1581 void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { 1582 EXPECT_EQ(1, mIndex++); 1583 EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x); 1584 EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y); 1585 } 1586 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1587 EXPECT_EQ(2, mIndex++); 1588 } 1589 void endLayer() override { 1590 EXPECT_EQ(3, mIndex++); 1591 } 1592 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 1593 EXPECT_EQ(4, mIndex++); 1594 } 1595 }; 1596 1597 auto parent = TestUtils::createNode(0, 0, 200, 200, 1598 [](RenderProperties& props, RecordingCanvas& canvas) { 1599 // save/restore outside of reorderBarrier, so they don't get moved out of place 1600 canvas.translate(20, 10); 1601 int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SaveFlags::ClipToLayer); 1602 canvas.insertReorderBarrier(true); 1603 canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); 1604 canvas.insertReorderBarrier(false); 1605 canvas.restoreToCount(count); 1606 }); 1607 1608 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 1609 TestUtils::createSyncedNodeList(parent), 1610 (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance()); 1611 ShadowSaveLayerTestRenderer renderer; 1612 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1613 EXPECT_EQ(5, renderer.getIndex()); 1614} 1615 1616RENDERTHREAD_TEST(FrameBuilder, shadowHwLayer) { 1617 class ShadowHwLayerTestRenderer : public TestRendererBase { 1618 public: 1619 void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { 1620 EXPECT_EQ(0, mIndex++); 1621 } 1622 void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { 1623 EXPECT_EQ(1, mIndex++); 1624 EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x); 1625 EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y); 1626 EXPECT_FLOAT_EQ(30, op.shadowTask->lightRadius); 1627 } 1628 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1629 EXPECT_EQ(2, mIndex++); 1630 } 1631 void endLayer() override { 1632 EXPECT_EQ(3, mIndex++); 1633 } 1634 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 1635 EXPECT_EQ(4, mIndex++); 1636 } 1637 }; 1638 1639 auto parent = TestUtils::createNode(50, 60, 150, 160, 1640 [](RenderProperties& props, RecordingCanvas& canvas) { 1641 props.mutateLayerProperties().setType(LayerType::RenderLayer); 1642 canvas.insertReorderBarrier(true); 1643 canvas.save(SaveFlags::MatrixClip); 1644 canvas.translate(20, 10); 1645 canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); 1646 canvas.restore(); 1647 }); 1648 OffscreenBuffer** layerHandle = parent->getLayerHandle(); 1649 1650 // create RenderNode's layer here in same way prepareTree would, setting windowTransform 1651 OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); 1652 Matrix4 windowTransform; 1653 windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin 1654 layer.setWindowTransform(windowTransform); 1655 *layerHandle = &layer; 1656 1657 auto syncedList = TestUtils::createSyncedNodeList(parent); 1658 LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid 1659 layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100)); 1660 FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 1661 syncedList, 1662 (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 30}, Caches::getInstance()); 1663 ShadowHwLayerTestRenderer renderer; 1664 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1665 EXPECT_EQ(5, renderer.getIndex()); 1666 1667 // clean up layer pointer, so we can safely destruct RenderNode 1668 *layerHandle = nullptr; 1669} 1670 1671RENDERTHREAD_TEST(FrameBuilder, shadowLayering) { 1672 class ShadowLayeringTestRenderer : public TestRendererBase { 1673 public: 1674 void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { 1675 int index = mIndex++; 1676 EXPECT_TRUE(index == 0 || index == 1); 1677 } 1678 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1679 int index = mIndex++; 1680 EXPECT_TRUE(index == 2 || index == 3); 1681 } 1682 }; 1683 auto parent = TestUtils::createNode(0, 0, 200, 200, 1684 [](RenderProperties& props, RecordingCanvas& canvas) { 1685 canvas.insertReorderBarrier(true); 1686 canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); 1687 canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get()); 1688 }); 1689 1690 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 1691 TestUtils::createSyncedNodeList(parent), 1692 (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance()); 1693 ShadowLayeringTestRenderer renderer; 1694 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1695 EXPECT_EQ(4, renderer.getIndex()); 1696} 1697 1698static void testProperty(std::function<void(RenderProperties&)> propSetupCallback, 1699 std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) { 1700 class PropertyTestRenderer : public TestRendererBase { 1701 public: 1702 PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback) 1703 : mCallback(callback) {} 1704 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1705 EXPECT_EQ(mIndex++, 0); 1706 mCallback(op, state); 1707 } 1708 std::function<void(const RectOp&, const BakedOpState&)> mCallback; 1709 }; 1710 1711 auto node = TestUtils::createNode(0, 0, 100, 100, 1712 [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) { 1713 propSetupCallback(props); 1714 SkPaint paint; 1715 paint.setColor(SK_ColorWHITE); 1716 canvas.drawRect(0, 0, 100, 100, paint); 1717 }); 1718 1719 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200, 1720 TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); 1721 PropertyTestRenderer renderer(opValidateCallback); 1722 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1723 EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op"; 1724} 1725 1726RENDERTHREAD_TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) { 1727 testProperty([](RenderProperties& properties) { 1728 properties.setAlpha(0.5f); 1729 properties.setHasOverlappingRendering(false); 1730 }, [](const RectOp& op, const BakedOpState& state) { 1731 EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op"; 1732 }); 1733} 1734 1735RENDERTHREAD_TEST(FrameBuilder, renderPropClipping) { 1736 testProperty([](RenderProperties& properties) { 1737 properties.setClipToBounds(true); 1738 properties.setClipBounds(Rect(10, 20, 300, 400)); 1739 }, [](const RectOp& op, const BakedOpState& state) { 1740 EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds) 1741 << "Clip rect should be intersection of node bounds and clip bounds"; 1742 }); 1743} 1744 1745RENDERTHREAD_TEST(FrameBuilder, renderPropRevealClip) { 1746 testProperty([](RenderProperties& properties) { 1747 properties.mutableRevealClip().set(true, 50, 50, 25); 1748 }, [](const RectOp& op, const BakedOpState& state) { 1749 ASSERT_NE(nullptr, state.roundRectClipState); 1750 EXPECT_TRUE(state.roundRectClipState->highPriority); 1751 EXPECT_EQ(25, state.roundRectClipState->radius); 1752 EXPECT_EQ(Rect(50, 50, 50, 50), state.roundRectClipState->innerRect); 1753 }); 1754} 1755 1756RENDERTHREAD_TEST(FrameBuilder, renderPropOutlineClip) { 1757 testProperty([](RenderProperties& properties) { 1758 properties.mutableOutline().setShouldClip(true); 1759 properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f); 1760 }, [](const RectOp& op, const BakedOpState& state) { 1761 ASSERT_NE(nullptr, state.roundRectClipState); 1762 EXPECT_FALSE(state.roundRectClipState->highPriority); 1763 EXPECT_EQ(5, state.roundRectClipState->radius); 1764 EXPECT_EQ(Rect(15, 25, 25, 35), state.roundRectClipState->innerRect); 1765 }); 1766} 1767 1768RENDERTHREAD_TEST(FrameBuilder, renderPropTransform) { 1769 testProperty([](RenderProperties& properties) { 1770 properties.setLeftTopRightBottom(10, 10, 110, 110); 1771 1772 SkMatrix staticMatrix = SkMatrix::MakeScale(1.2f, 1.2f); 1773 properties.setStaticMatrix(&staticMatrix); 1774 1775 // ignored, since static overrides animation 1776 SkMatrix animationMatrix = SkMatrix::MakeTrans(15, 15); 1777 properties.setAnimationMatrix(&animationMatrix); 1778 1779 properties.setTranslationX(10); 1780 properties.setTranslationY(20); 1781 properties.setScaleX(0.5f); 1782 properties.setScaleY(0.7f); 1783 }, [](const RectOp& op, const BakedOpState& state) { 1784 Matrix4 matrix; 1785 matrix.loadTranslate(10, 10, 0); // left, top 1786 matrix.scale(1.2f, 1.2f, 1); // static matrix 1787 // ignore animation matrix, since static overrides it 1788 1789 // translation xy 1790 matrix.translate(10, 20); 1791 1792 // scale xy (from default pivot - center) 1793 matrix.translate(50, 50); 1794 matrix.scale(0.5f, 0.7f, 1); 1795 matrix.translate(-50, -50); 1796 EXPECT_MATRIX_APPROX_EQ(matrix, state.computedState.transform) 1797 << "Op draw matrix must match expected combination of transformation properties"; 1798 }); 1799} 1800 1801struct SaveLayerAlphaData { 1802 uint32_t layerWidth = 0; 1803 uint32_t layerHeight = 0; 1804 Rect rectClippedBounds; 1805 Matrix4 rectMatrix; 1806}; 1807/** 1808 * Constructs a view to hit the temporary layer alpha property implementation: 1809 * a) 0 < alpha < 1 1810 * b) too big for layer (larger than maxTextureSize) 1811 * c) overlapping rendering content 1812 * returning observed data about layer size and content clip/transform. 1813 * 1814 * Used to validate clipping behavior of temporary layer, where requested layer size is reduced 1815 * (for efficiency, and to fit in layer size constraints) based on parent clip. 1816 */ 1817void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData, 1818 std::function<void(RenderProperties&)> propSetupCallback) { 1819 class SaveLayerAlphaClipTestRenderer : public TestRendererBase { 1820 public: 1821 SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData) 1822 : mOutData(outData) {} 1823 1824 OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { 1825 EXPECT_EQ(0, mIndex++); 1826 mOutData->layerWidth = width; 1827 mOutData->layerHeight = height; 1828 return nullptr; 1829 } 1830 void onRectOp(const RectOp& op, const BakedOpState& state) override { 1831 EXPECT_EQ(1, mIndex++); 1832 1833 mOutData->rectClippedBounds = state.computedState.clippedBounds; 1834 mOutData->rectMatrix = state.computedState.transform; 1835 } 1836 void endLayer() override { 1837 EXPECT_EQ(2, mIndex++); 1838 } 1839 void onLayerOp(const LayerOp& op, const BakedOpState& state) override { 1840 EXPECT_EQ(3, mIndex++); 1841 } 1842 private: 1843 SaveLayerAlphaData* mOutData; 1844 }; 1845 1846 ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize()) 1847 << "Node must be bigger than max texture size to exercise saveLayer codepath"; 1848 auto node = TestUtils::createNode(0, 0, 10000, 10000, 1849 [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) { 1850 properties.setHasOverlappingRendering(true); 1851 properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer 1852 // apply other properties 1853 propSetupCallback(properties); 1854 1855 SkPaint paint; 1856 paint.setColor(SK_ColorWHITE); 1857 canvas.drawRect(0, 0, 10000, 10000, paint); 1858 }); 1859 auto nodes = TestUtils::createSyncedNodeList(node); // sync before querying height 1860 1861 FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, 1862 nodes, sLightGeometry, Caches::getInstance()); 1863 SaveLayerAlphaClipTestRenderer renderer(outObservedData); 1864 frameBuilder.replayBakedOps<TestDispatcher>(renderer); 1865 1866 // assert, since output won't be valid if we haven't seen a save layer triggered 1867 ASSERT_EQ(4, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior."; 1868} 1869 1870RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) { 1871 SaveLayerAlphaData observedData; 1872 testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { 1873 properties.setTranslationX(10); // offset rendering content 1874 properties.setTranslationY(-2000); // offset rendering content 1875 }); 1876 EXPECT_EQ(190u, observedData.layerWidth); 1877 EXPECT_EQ(200u, observedData.layerHeight); 1878 EXPECT_EQ(Rect(190, 200), observedData.rectClippedBounds) 1879 << "expect content to be clipped to screen area"; 1880 Matrix4 expected; 1881 expected.loadTranslate(0, -2000, 0); 1882 EXPECT_MATRIX_APPROX_EQ(expected, observedData.rectMatrix) 1883 << "expect content to be translated as part of being clipped"; 1884} 1885 1886RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) { 1887 SaveLayerAlphaData observedData; 1888 testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { 1889 // Translate and rotate the view so that the only visible part is the top left corner of 1890 // the view. It will form an isosceles right triangle with a long side length of 200 at the 1891 // bottom of the viewport. 1892 properties.setTranslationX(100); 1893 properties.setTranslationY(100); 1894 properties.setPivotX(0); 1895 properties.setPivotY(0); 1896 properties.setRotation(45); 1897 }); 1898 // ceil(sqrt(2) / 2 * 200) = 142 1899 EXPECT_EQ(142u, observedData.layerWidth); 1900 EXPECT_EQ(142u, observedData.layerHeight); 1901 EXPECT_EQ(Rect(142, 142), observedData.rectClippedBounds); 1902 EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); 1903} 1904 1905RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaScale) { 1906 SaveLayerAlphaData observedData; 1907 testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { 1908 properties.setPivotX(0); 1909 properties.setPivotY(0); 1910 properties.setScaleX(2); 1911 properties.setScaleY(0.5f); 1912 }); 1913 EXPECT_EQ(100u, observedData.layerWidth); 1914 EXPECT_EQ(400u, observedData.layerHeight); 1915 EXPECT_EQ(Rect(100, 400), observedData.rectClippedBounds); 1916 EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); 1917} 1918 1919} // namespace uirenderer 1920} // namespace android 1921