| /* | |
| * 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 "include/core/SkBlendMode.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkColorFilter.h" | |
| #include "include/core/SkImageFilter.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPicture.h" | |
| #include "include/core/SkPictureRecorder.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.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" | |
| #include "tests/RecordTestUtils.h" | |
| #include "tests/Test.h" | |
| #include <array> | |
| #include <cstddef> | |
| 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<SkRecords::NoOp>(1); // NoOps should be allowed. | |
| SkRecordNoopSaveRestores(&record); | |
| REPORTER_ASSERT(r, 2 == count_instances_of_type<SkRecords::DrawRect>(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<SkRecords::NoOp>(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<SkRecords::NoOp>(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<SkRecords::Save> (r, record, 0); | |
| assert_type<SkRecords::SaveLayer>(r, record, 1); | |
| assert_type<SkRecords::Restore> (r, record, 2); | |
| assert_type<SkRecords::Restore> (r, record, 3); | |
| break; | |
| case 2: | |
| assert_type<SkRecords::SaveLayer>(r, record, 0); | |
| assert_type<SkRecords::Restore> (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<SkRecords::NoOp>(r, *record, i); | |
| assert_type<SkRecords::NoOp>(r, *record, i+1); | |
| } else { | |
| assert_type<SkRecords::SaveLayer>(r, *record, i); | |
| assert_type<SkRecords::Restore>(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<SkRecords::NoOp>(r, *record, i); | |
| assert_type<SkRecords::NoOp>(r, *record, i+2); | |
| } else { | |
| assert_type<SkRecords::SaveLayer>(r, *record, i); | |
| assert_type<SkRecords::Restore>(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<SkRecords::DrawRect>(r, record, 16); | |
| REPORTER_ASSERT(r, drawRect != nullptr); | |
| REPORTER_ASSERT(r, drawRect->paint.getColor() == 0x03020202); | |
| // saveLayer w/ backdrop should NOT go away | |
| sk_sp<SkImageFilter> 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<SkRecords::NoOp>(r, *record, i); | |
| assert_type<SkRecords::NoOp>(r, *record, i + 6); | |
| } else { | |
| assert_type<SkRecords::SaveLayer>(r, *record, i); | |
| assert_type<SkRecords::Restore>(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<SkPicture> shape; | |
| { | |
| SkPictureRecorder picRecorder; | |
| SkCanvas* canvas = picRecorder.beginRecording(SkIntToScalar(100), SkIntToScalar(100)); | |
| SkPaint shapePaint; | |
| shapePaint.setColor(SK_ColorWHITE); | |
| canvas->drawRect(SkRect::MakeWH(SkIntToScalar(50), SkIntToScalar(50)), shapePaint); | |
| shape = picRecorder.finishRecordingAsPicture(); | |
| } | |
| translucentFilterLayerPaint.setImageFilter(SkImageFilters::Picture(shape)); | |
| int index = 0; | |
| { | |
| sk_sp<SkImageFilter> 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 < std::size(firstBounds); ++ i) { | |
| for (size_t j = 0; j < std::size(firstPaints); ++j) { | |
| for (size_t k = 0; k < std::size(secondBounds); ++k) { | |
| for (size_t m = 0; m < std::size(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 < std::size(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<SkRecords::SaveLayer>(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<SkRecords::SaveLayer>(r, record, index); | |
| assert_type<SkRecords::SaveLayer>(r, record, index + 1); | |
| assert_type<SkRecords::Restore>(r, record, index + 2); | |
| assert_type<SkRecords::Restore>(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<SkSurface> surf0 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10)); | |
| sk_sp<SkSurface> surf1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(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); | |
| } | |