/* * Copyright 2014 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "tests/RecordTestUtils.h" #include "tests/Test.h" #include "include/core/SkColorFilter.h" #include "include/core/SkPictureRecorder.h" #include "include/core/SkSurface.h" #include "include/effects/SkImageFilters.h" #include "src/core/SkRecord.h" #include "src/core/SkRecordOpts.h" #include "src/core/SkRecorder.h" #include "src/core/SkRecords.h" static const int W = 1920, H = 1080; DEF_TEST(RecordOpts_NoopDraw, r) { SkRecord record; SkRecorder recorder(&record, W, H); recorder.drawRect(SkRect::MakeWH(200, 200), SkPaint()); recorder.drawRect(SkRect::MakeWH(300, 300), SkPaint()); recorder.drawRect(SkRect::MakeWH(100, 100), SkPaint()); record.replace(1); // NoOps should be allowed. SkRecordNoopSaveRestores(&record); REPORTER_ASSERT(r, 2 == count_instances_of_type(record)); } DEF_TEST(RecordOpts_SingleNoopSaveRestore, r) { SkRecord record; SkRecorder recorder(&record, W, H); recorder.save(); recorder.clipRect(SkRect::MakeWH(200, 200)); recorder.restore(); SkRecordNoopSaveRestores(&record); for (int i = 0; i < 3; i++) { assert_type(r, record, i); } } DEF_TEST(RecordOpts_NoopSaveRestores, r) { SkRecord record; SkRecorder recorder(&record, W, H); // The second pass will clean up this pair after the first pass noops all the innards. recorder.save(); // A simple pointless pair of save/restore. recorder.save(); recorder.restore(); // As long as we don't draw in there, everything is a noop. recorder.save(); recorder.clipRect(SkRect::MakeWH(200, 200)); recorder.clipRect(SkRect::MakeWH(100, 100)); recorder.restore(); recorder.restore(); SkRecordNoopSaveRestores(&record); for (int index = 0; index < record.count(); index++) { assert_type(r, record, index); } } DEF_TEST(RecordOpts_SaveSaveLayerRestoreRestore, r) { SkRecord record; SkRecorder recorder(&record, W, H); // A previous bug NoOp'd away the first 3 commands. recorder.save(); recorder.saveLayer(nullptr, nullptr); recorder.restore(); recorder.restore(); SkRecordNoopSaveRestores(&record); switch (record.count()) { case 4: assert_type (r, record, 0); assert_type(r, record, 1); assert_type (r, record, 2); assert_type (r, record, 3); break; case 2: assert_type(r, record, 0); assert_type (r, record, 1); break; case 0: break; default: REPORTER_ASSERT(r, false); } } #ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK static void assert_savelayer_restore(skiatest::Reporter* r, SkRecord* record, int i, bool shouldBeNoOped) { SkRecordNoopSaveLayerDrawRestores(record); if (shouldBeNoOped) { assert_type(r, *record, i); assert_type(r, *record, i+1); } else { assert_type(r, *record, i); assert_type(r, *record, i+1); } } static void assert_savelayer_draw_restore(skiatest::Reporter* r, SkRecord* record, int i, bool shouldBeNoOped) { SkRecordNoopSaveLayerDrawRestores(record); if (shouldBeNoOped) { assert_type(r, *record, i); assert_type(r, *record, i+2); } else { assert_type(r, *record, i); assert_type(r, *record, i+2); } } DEF_TEST(RecordOpts_NoopSaveLayerDrawRestore, r) { SkRecord record; SkRecorder recorder(&record, W, H); SkRect bounds = SkRect::MakeWH(100, 200); SkRect draw = SkRect::MakeWH(50, 60); SkPaint alphaOnlyLayerPaint, translucentLayerPaint, xfermodeLayerPaint; alphaOnlyLayerPaint.setColor(0x03000000); // Only alpha. translucentLayerPaint.setColor(0x03040506); // Not only alpha. xfermodeLayerPaint.setBlendMode(SkBlendMode::kDstIn); // Any effect will do. SkPaint opaqueDrawPaint, translucentDrawPaint; opaqueDrawPaint.setColor(0xFF020202); // Opaque. translucentDrawPaint.setColor(0x0F020202); // Not opaque. // SaveLayer/Restore removed: No paint = no point. recorder.saveLayer(nullptr, nullptr); recorder.drawRect(draw, opaqueDrawPaint); recorder.restore(); assert_savelayer_draw_restore(r, &record, 0, true); // Bounds don't matter. recorder.saveLayer(&bounds, nullptr); recorder.drawRect(draw, opaqueDrawPaint); recorder.restore(); assert_savelayer_draw_restore(r, &record, 3, true); // TODO(mtklein): test case with null draw paint // No change: layer paint isn't alpha-only. recorder.saveLayer(nullptr, &translucentLayerPaint); recorder.drawRect(draw, opaqueDrawPaint); recorder.restore(); assert_savelayer_draw_restore(r, &record, 6, false); // No change: layer paint has an effect. recorder.saveLayer(nullptr, &xfermodeLayerPaint); recorder.drawRect(draw, opaqueDrawPaint); recorder.restore(); assert_savelayer_draw_restore(r, &record, 9, false); // SaveLayer/Restore removed: we can fold in the alpha! recorder.saveLayer(nullptr, &alphaOnlyLayerPaint); recorder.drawRect(draw, translucentDrawPaint); recorder.restore(); assert_savelayer_draw_restore(r, &record, 12, true); // SaveLayer/Restore removed: we can fold in the alpha! recorder.saveLayer(nullptr, &alphaOnlyLayerPaint); recorder.drawRect(draw, opaqueDrawPaint); recorder.restore(); assert_savelayer_draw_restore(r, &record, 15, true); const SkRecords::DrawRect* drawRect = assert_type(r, record, 16); REPORTER_ASSERT(r, drawRect != nullptr); REPORTER_ASSERT(r, drawRect->paint.getColor() == 0x03020202); // saveLayer w/ backdrop should NOT go away sk_sp filter(SkImageFilters::Blur(3, 3, nullptr)); recorder.saveLayer({ nullptr, nullptr, filter.get(), 0}); recorder.drawRect(draw, opaqueDrawPaint); recorder.restore(); assert_savelayer_draw_restore(r, &record, 18, false); } #endif static void assert_merge_svg_opacity_and_filter_layers(skiatest::Reporter* r, SkRecord* record, int i, bool shouldBeNoOped) { SkRecordMergeSvgOpacityAndFilterLayers(record); if (shouldBeNoOped) { assert_type(r, *record, i); assert_type(r, *record, i + 6); } else { assert_type(r, *record, i); assert_type(r, *record, i + 6); } } DEF_TEST(RecordOpts_MergeSvgOpacityAndFilterLayers, r) { SkRecord record; SkRecorder recorder(&record, W, H); SkRect bounds = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(200)); SkRect clip = SkRect::MakeWH(SkIntToScalar(50), SkIntToScalar(60)); SkPaint alphaOnlyLayerPaint; alphaOnlyLayerPaint.setColor(0x03000000); // Only alpha. SkPaint translucentLayerPaint; translucentLayerPaint.setColor(0x03040506); // Not only alpha. SkPaint xfermodePaint; xfermodePaint.setBlendMode(SkBlendMode::kDstIn); SkPaint colorFilterPaint; colorFilterPaint.setColorFilter( SkColorFilters::Blend(SK_ColorLTGRAY, SkBlendMode::kSrcIn)); SkPaint opaqueFilterLayerPaint; opaqueFilterLayerPaint.setColor(0xFF020202); // Opaque. SkPaint translucentFilterLayerPaint; translucentFilterLayerPaint.setColor(0x0F020202); // Not opaque. sk_sp shape; { SkPictureRecorder recorder; SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(100), SkIntToScalar(100)); SkPaint shapePaint; shapePaint.setColor(SK_ColorWHITE); canvas->drawRect(SkRect::MakeWH(SkIntToScalar(50), SkIntToScalar(50)), shapePaint); shape = recorder.finishRecordingAsPicture(); } translucentFilterLayerPaint.setImageFilter(SkImageFilters::Picture(shape)); int index = 0; { sk_sp filter(SkImageFilters::Blur(3, 3, nullptr)); // first (null) should be optimized, 2nd should not SkImageFilter* filters[] = { nullptr, filter.get() }; // Any combination of these should cause the pattern to be optimized. SkRect* firstBounds[] = { nullptr, &bounds }; SkPaint* firstPaints[] = { nullptr, &alphaOnlyLayerPaint }; SkRect* secondBounds[] = { nullptr, &bounds }; SkPaint* secondPaints[] = { &opaqueFilterLayerPaint, &translucentFilterLayerPaint }; for (auto outerF : filters) { bool outerNoOped = !outerF; for (auto innerF : filters) { for (size_t i = 0; i < SK_ARRAY_COUNT(firstBounds); ++ i) { for (size_t j = 0; j < SK_ARRAY_COUNT(firstPaints); ++j) { for (size_t k = 0; k < SK_ARRAY_COUNT(secondBounds); ++k) { for (size_t m = 0; m < SK_ARRAY_COUNT(secondPaints); ++m) { bool innerNoOped = !secondBounds[k] && !secondPaints[m] && !innerF; recorder.saveLayer({firstBounds[i], firstPaints[j], outerF, 0}); recorder.save(); recorder.clipRect(clip); recorder.saveLayer({secondBounds[k], secondPaints[m], innerF, 0}); recorder.restore(); recorder.restore(); recorder.restore(); assert_merge_svg_opacity_and_filter_layers(r, &record, index, outerNoOped); #ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK assert_savelayer_restore(r, &record, index + 3, innerNoOped); #endif index += 7; } } } } } } } // These should cause the pattern to stay unoptimized: struct { SkPaint* firstPaint; SkPaint* secondPaint; } noChangeTests[] = { // No change: nullptr filter layer paint not implemented. { &alphaOnlyLayerPaint, nullptr }, // No change: layer paint is not alpha-only. { &translucentLayerPaint, &opaqueFilterLayerPaint }, // No change: layer paint has an xfereffect. { &xfermodePaint, &opaqueFilterLayerPaint }, // No change: filter layer paint has an xfereffect. { &alphaOnlyLayerPaint, &xfermodePaint }, // No change: layer paint has a color filter. { &colorFilterPaint, &opaqueFilterLayerPaint }, // No change: filter layer paint has a color filter (until the optimization accounts for // constant color draws that can filter the color). { &alphaOnlyLayerPaint, &colorFilterPaint } }; for (size_t i = 0; i < SK_ARRAY_COUNT(noChangeTests); ++i) { recorder.saveLayer(nullptr, noChangeTests[i].firstPaint); recorder.save(); recorder.clipRect(clip); recorder.saveLayer(nullptr, noChangeTests[i].secondPaint); recorder.restore(); recorder.restore(); recorder.restore(); assert_merge_svg_opacity_and_filter_layers(r, &record, index, false); index += 7; } // Test the folded alpha value. recorder.saveLayer(nullptr, &alphaOnlyLayerPaint); recorder.save(); recorder.clipRect(clip); recorder.saveLayer(nullptr, &opaqueFilterLayerPaint); recorder.restore(); recorder.restore(); recorder.restore(); assert_merge_svg_opacity_and_filter_layers(r, &record, index, true); const SkRecords::SaveLayer* saveLayer = assert_type(r, record, index + 3); REPORTER_ASSERT(r, saveLayer != nullptr); REPORTER_ASSERT(r, saveLayer->paint->getColor() == 0x03020202); index += 7; // Test that currently we do not fold alphas for patterns without the clip. This is just not // implemented. recorder.saveLayer(nullptr, &alphaOnlyLayerPaint); recorder.saveLayer(nullptr, &opaqueFilterLayerPaint); recorder.restore(); recorder.restore(); SkRecordMergeSvgOpacityAndFilterLayers(&record); assert_type(r, record, index); assert_type(r, record, index + 1); assert_type(r, record, index + 2); assert_type(r, record, index + 3); index += 4; } static void do_draw(SkCanvas* canvas, SkColor color, bool doLayer) { canvas->drawColor(SK_ColorWHITE); SkPaint p; p.setColor(color); if (doLayer) { canvas->saveLayer(nullptr, nullptr); p.setBlendMode(SkBlendMode::kSrc); canvas->drawPaint(p); canvas->restore(); } else { canvas->drawPaint(p); } } static bool is_equal(SkSurface* a, SkSurface* b) { const SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1); SkPMColor ca, cb; a->readPixels(info, &ca, sizeof(SkPMColor), 0, 0); b->readPixels(info, &cb, sizeof(SkPMColor), 0, 0); return ca == cb; } // Test drawing w/ and w/o a simple layer (no bounds or paint), so see that drawing ops // that *should* draw the same in fact do. // // Perform this test twice : once directly, and once via a picture // static void do_savelayer_srcmode(skiatest::Reporter* r, SkColor color) { for (int doPicture = 0; doPicture <= 1; ++doPicture) { sk_sp surf0 = SkSurface::MakeRasterN32Premul(10, 10); sk_sp surf1 = SkSurface::MakeRasterN32Premul(10, 10); SkCanvas* c0 = surf0->getCanvas(); SkCanvas* c1 = surf1->getCanvas(); SkPictureRecorder rec0, rec1; if (doPicture) { c0 = rec0.beginRecording(10, 10); c1 = rec1.beginRecording(10, 10); } do_draw(c0, color, false); do_draw(c1, color, true); if (doPicture) { surf0->getCanvas()->drawPicture(rec0.finishRecordingAsPicture()); surf1->getCanvas()->drawPicture(rec1.finishRecordingAsPicture()); } // we replicate the assert so we can see which line is reported if there is a failure if (doPicture) { REPORTER_ASSERT(r, is_equal(surf0.get(), surf1.get())); } else { REPORTER_ASSERT(r, is_equal(surf0.get(), surf1.get())); } } } DEF_TEST(savelayer_srcmode_opaque, r) { do_savelayer_srcmode(r, SK_ColorRED); } DEF_TEST(savelayer_srcmode_alpha, r) { do_savelayer_srcmode(r, 0x80FF0000); }