From dc0f408a961b6ce4e7631e06031bbfe1d8d7bcec Mon Sep 17 00:00:00 2001 From: kkinnunen Date: Mon, 26 Jan 2015 00:14:26 -0800 Subject: Fold alpha to the inner savelayer in savelayer-savelayer-restore patterns Fold alpha to the inner savelayer in savelayer-savelayer-restore patterns such as this: SaveLayer (non-opaque) Save ClipRect SaveLayer Restore Restore Restore Current blink generates these for example for SVG content such as this: The outer save layer is due to the opacity and the inner one is due to blur filter being implemented with picture image filter. Reduces layers in desk_carsvg.skp testcase from 115 to 78. BUG=skia:3119 Review URL: https://codereview.chromium.org/835973005 --- gm/recordopts.cpp | 218 +++++++++++++++++++++++++++++++++++++++++++++ gyp/gmslides.gypi | 1 + src/core/SkRecordOpts.cpp | 136 ++++++++++++++++++++++------ src/core/SkRecordOpts.h | 4 + src/core/SkRecordPattern.h | 13 +++ tests/RecordOptsTest.cpp | 139 +++++++++++++++++++++++++++++ 6 files changed, 482 insertions(+), 29 deletions(-) create mode 100644 gm/recordopts.cpp diff --git a/gm/recordopts.cpp b/gm/recordopts.cpp new file mode 100644 index 0000000000..08202111f5 --- /dev/null +++ b/gm/recordopts.cpp @@ -0,0 +1,218 @@ +/* + * 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 "gm.h" +#include "SkCanvas.h" +#include "SkPath.h" +#include "SkPictureRecorder.h" +#include "SkTableColorFilter.h" +#include "SkColorFilterImageFilter.h" +#include "SkPictureImageFilter.h" + +static const int kTestRectSize = 50; +static const int kDetectorGreenValue = 50; + +// Below are few functions to install "detector" color filters. The filter is there to assert that +// the color value it sees is the expected. It will trigger only with kDetectorGreenValue, and +// turn that value into full green. The idea is that if an optimization incorrectly changes +// kDetectorGreenValue and then the incorrect value is observable by some part of the drawing +// pipeline, that pixel will remain empty. + +static SkColorFilter* make_detector_color_filter() { + uint8_t tableA[256] = { 0, }; + uint8_t tableR[256] = { 0, }; + uint8_t tableG[256] = { 0, }; + uint8_t tableB[256] = { 0, }; + tableA[255] = 255; + tableG[kDetectorGreenValue] = 255; + return SkTableColorFilter::CreateARGB(tableA, tableR, tableG, tableB); +} + +// This detector detects that color filter phase of the pixel pipeline receives the correct value. +static void install_detector_color_filter(SkPaint* drawPaint) { + drawPaint->setColorFilter(make_detector_color_filter())->unref(); +} + +// This detector detects that image filter phase of the pixel pipeline receives the correct value. +static void install_detector_image_filter(SkPaint* drawPaint) { + SkAutoTUnref colorFilter(make_detector_color_filter()); + SkImageFilter* imageFilter = + SkColorFilterImageFilter::Create(colorFilter, drawPaint->getImageFilter()); + drawPaint->setImageFilter(imageFilter)->unref(); +} + +static void no_detector_install(SkPaint*) { +} + +typedef void(*InstallDetectorFunc)(SkPaint*); + + +// Draws an pattern that can be optimized by alpha folding outer savelayer alpha value to +// inner draw. Since we know that folding will happen to the inner draw, install a detector +// to make sure that optimization does not change anything observable. +static void draw_save_layer_draw_rect_restore_sequence(SkCanvas* canvas, SkColor shapeColor, + InstallDetectorFunc installDetector) { + SkRect targetRect(SkRect::MakeWH(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize))); + SkPaint layerPaint; + layerPaint.setColor(SkColorSetARGB(128, 0, 0, 0)); + canvas->saveLayer(&targetRect, &layerPaint); + SkPaint drawPaint; + drawPaint.setColor(shapeColor); + installDetector(&drawPaint); + canvas->drawRect(targetRect, drawPaint); + canvas->restore(); +} + +// Draws an pattern that can be optimized by alpha folding outer savelayer alpha value to +// inner draw. A variant where the draw is not uniform color. +static void draw_save_layer_draw_bitmap_restore_sequence(SkCanvas* canvas, SkColor shapeColor, + InstallDetectorFunc installDetector) { + SkBitmap bitmap; + bitmap.allocN32Pixels(kTestRectSize, kTestRectSize); + bitmap.eraseColor(shapeColor); + { + // Make the bitmap non-uniform color, so that it can not be optimized as uniform drawRect. + SkCanvas canvas(bitmap); + SkPaint p; + p.setColor(SK_ColorWHITE); + SkASSERT(shapeColor != SK_ColorWHITE); + canvas.drawRect(SkRect::MakeWH(SkIntToScalar(7), SkIntToScalar(7)), p); + canvas.flush(); + } + + SkRect targetRect(SkRect::MakeWH(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize))); + SkPaint layerPaint; + layerPaint.setColor(SkColorSetARGB(129, 0, 0, 0)); + canvas->saveLayer(&targetRect, &layerPaint); + SkPaint drawPaint; + installDetector(&drawPaint); + canvas->drawBitmap(bitmap, SkIntToScalar(0), SkIntToScalar(0), &drawPaint); + canvas->restore(); +} + +// Draws an pattern that can be optimized by alpha folding outer savelayer alpha value to +// inner savelayer. We know that alpha folding happens to inner savelayer, so add detector there. +static void draw_svg_opacity_and_filter_layer_sequence(SkCanvas* canvas, SkColor shapeColor, + InstallDetectorFunc installDetector) { + + SkRect targetRect(SkRect::MakeWH(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize))); + SkAutoTUnref shape; + { + SkPictureRecorder recorder; + SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kTestRectSize + 2), + SkIntToScalar(kTestRectSize + 2)); + SkPaint shapePaint; + shapePaint.setColor(shapeColor); + canvas->drawRect(targetRect, shapePaint); + shape.reset(recorder.endRecordingAsPicture()); + } + + SkPaint layerPaint; + layerPaint.setColor(SkColorSetARGB(130, 0, 0, 0)); + canvas->saveLayer(&targetRect, &layerPaint); + canvas->save(); + canvas->clipRect(targetRect); + SkPaint drawPaint; + drawPaint.setImageFilter(SkPictureImageFilter::Create(shape))->unref(); + installDetector(&drawPaint); + canvas->saveLayer(&targetRect, &drawPaint); + canvas->restore(); + canvas->restore(); + canvas->restore(); +} + +// Draws two columns of rectangles. The test is correct when: +// - Left and right columns always identical +// - First 3 rows are green, with a white dent in the middle row +// - Next 6 rows are green, with a grey dent in the middle row +// (the grey dent is from the color filter removing everything but the "good" green, see below) +// - Last 6 rows are grey +DEF_SIMPLE_GM(recordopts, canvas, (kTestRectSize+1)*2, (kTestRectSize+1)*15) { + canvas->clear(SK_ColorTRANSPARENT); + + typedef void (*TestVariantSequence)(SkCanvas*, SkColor, InstallDetectorFunc); + TestVariantSequence funcs[] = { + draw_save_layer_draw_rect_restore_sequence, + draw_save_layer_draw_bitmap_restore_sequence, + draw_svg_opacity_and_filter_layer_sequence, + }; + + // Draw layer-related sequences that can be optimized by folding the opacity layer alpha to + // the inner draw operation. This tries to trigger the optimization, and relies on gm diffs + // to keep the color value correct over time. + + // Draws two green rects side by side: one is without the optimization, the other is with + // the optimization applied. + + SkColor shapeColor = SkColorSetARGB(255, 0, 255, 0); + for (size_t k = 0; k < SK_ARRAY_COUNT(funcs); ++k) { + canvas->save(); + + TestVariantSequence drawTestSequence = funcs[k]; + drawTestSequence(canvas, shapeColor, no_detector_install); + canvas->flush(); + canvas->translate(SkIntToScalar(kTestRectSize) + SkIntToScalar(1), SkIntToScalar(0)); + { + SkPictureRecorder recorder; + drawTestSequence(recorder.beginRecording(SkIntToScalar(kTestRectSize), + SkIntToScalar(kTestRectSize)), + shapeColor, no_detector_install); + SkAutoTUnref optimizedPicture(recorder.endRecordingAsPicture()); + optimizedPicture->playback(canvas); + canvas->flush(); + } + canvas->restore(); + canvas->translate(SkIntToScalar(0), SkIntToScalar(kTestRectSize) + SkIntToScalar(1)); + } + + // Draw the same layer related sequences, but manipulate the sequences so that the result is + // incorrect if the alpha is folded or folded incorrectly. These test the observable state + // throughout the pixel pipeline, and thus may turn off the optimizations (this is why we + // trigger the optimizations above). + + // Draws two green rects side by side: one is without the optimization, the other is with + // the possibility that optimization is applied. + // At the end, draws the same patterns in translucent black. This tests that the detectors + // work, eg. that if the value the detector sees is wrong, the resulting image shows this. + SkColor shapeColors[] = { + SkColorSetARGB(255, 0, kDetectorGreenValue, 0), + SkColorSetARGB(255, 0, kDetectorGreenValue + 1, 0) // This tests that detectors work. + }; + + InstallDetectorFunc detectorInstallFuncs[] = { + install_detector_image_filter, + install_detector_color_filter + }; + + for (size_t i = 0; i < SK_ARRAY_COUNT(shapeColors); ++i) { + shapeColor = shapeColors[i]; + for (size_t j = 0; j < SK_ARRAY_COUNT(shapeColors); ++j) { + InstallDetectorFunc detectorInstallFunc = detectorInstallFuncs[j]; + for (size_t k = 0; k < SK_ARRAY_COUNT(funcs); ++k) { + TestVariantSequence drawTestSequence = funcs[k]; + canvas->save(); + drawTestSequence(canvas, shapeColor, detectorInstallFunc); + canvas->flush(); + canvas->translate(SkIntToScalar(kTestRectSize) + SkIntToScalar(1), SkIntToScalar(0)); + { + SkPictureRecorder recorder; + drawTestSequence(recorder.beginRecording(SkIntToScalar(kTestRectSize), + SkIntToScalar(kTestRectSize)), + shapeColor, detectorInstallFunc); + SkAutoTUnref optimizedPicture(recorder.endRecordingAsPicture()); + optimizedPicture->playback(canvas); + canvas->flush(); + } + + canvas->restore(); + canvas->translate(SkIntToScalar(0), SkIntToScalar(kTestRectSize) + SkIntToScalar(1)); + } + + } + } +} + diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi index 6ee6bd7d5a..abcb86767b 100644 --- a/gyp/gmslides.gypi +++ b/gyp/gmslides.gypi @@ -156,6 +156,7 @@ '../gm/poly2poly.cpp', '../gm/polygons.cpp', '../gm/quadpaths.cpp', + '../gm/recordopts.cpp', '../gm/rects.cpp', '../gm/resizeimagefilter.cpp', '../gm/rrect.cpp', diff --git a/src/core/SkRecordOpts.cpp b/src/core/SkRecordOpts.cpp index df29b1baba..83512926da 100644 --- a/src/core/SkRecordOpts.cpp +++ b/src/core/SkRecordOpts.cpp @@ -21,6 +21,7 @@ void SkRecordOptimize(SkRecord* record) { //SkRecordNoopSaveRestores(record); SkRecordNoopSaveLayerDrawRestores(record); + SkRecordMergeSvgOpacityAndFilterLayers(record); } // Most of the optimizations in this file are pattern-based. These are all defined as structs with: @@ -56,6 +57,64 @@ struct SaveOnlyDrawsRestoreNooper { return true; } }; + +static bool fold_opacity_layer_color_to_paint(const SkPaint& layerPaint, + bool isSaveLayer, + SkPaint* paint) { + // We assume layerPaint is always from a saveLayer. If isSaveLayer is + // true, we assume paint is too. + + // The alpha folding can proceed if the filter layer paint does not have properties which cause + // the resulting filter layer to be "blended" in complex ways to the parent layer. For example, + // looper drawing unmodulated filter layer twice and then modulating the result produces + // different image to drawing modulated filter layer twice. + // TODO: most likely the looper and only some xfer modes are the hard constraints + if (paint->getXfermode() || paint->getLooper()) { + return false; + } + + if (!isSaveLayer && paint->getImageFilter()) { + // For normal draws, the paint color is used as one input for the color for the draw. Image + // filter will operate on the result, and thus we can not change the input. + // For layer saves, the image filter is applied to the layer contents. The layer is then + // modulated with the paint color, so it's fine to proceed with the fold for saveLayer + // paints with image filters. + return false; + } + + if (paint->getColorFilter()) { + // Filter input depends on the paint color. + + // Here we could filter the color if we knew the draw is going to be uniform color. This + // should be detectable as drawPath/drawRect/.. without a shader being uniform, while + // drawBitmap/drawSprite or a shader being non-uniform. However, current matchers don't + // give the type out easily, so just do not optimize that at the moment. + return false; + } + + const uint32_t layerColor = layerPaint.getColor(); + // The layer paint color must have only alpha component. + if (SK_ColorTRANSPARENT != SkColorSetA(layerColor, SK_AlphaTRANSPARENT)) { + return false; + } + + // The layer paint can not have any effects. + if (layerPaint.getPathEffect() || + layerPaint.getShader() || + layerPaint.getXfermode() || + layerPaint.getMaskFilter() || + layerPaint.getColorFilter() || + layerPaint.getRasterizer() || + layerPaint.getLooper() || + layerPaint.getImageFilter()) { + return false; + } + + paint->setAlpha(SkMulDiv255Round(paint->getAlpha(), SkColorGetA(layerColor))); + + return true; +} + // Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops. struct SaveNoDrawsRestoreNooper { // Star matches greedily, so we also have to exclude Save and Restore. @@ -104,14 +163,10 @@ struct SaveLayerDrawRestoreNooper { return false; } - const uint32_t layerColor = layerPaint->getColor(); - const uint32_t drawColor = drawPaint->getColor(); - if (!IsOnlyAlpha(layerColor) || HasAnyEffect(*layerPaint) || CantFoldAlpha(*drawPaint)) { - // Too fancy for us. + if (!fold_opacity_layer_color_to_paint(*layerPaint, false /*isSaveLayer*/, drawPaint)) { return false; } - drawPaint->setAlpha(SkMulDiv255Round(SkColorGetA(drawColor), SkColorGetA(layerColor))); return KillSaveLayerAndRestore(record, begin); } @@ -120,35 +175,58 @@ struct SaveLayerDrawRestoreNooper { record->replace(saveLayerIndex+2); // Restore return true; } +}; +void SkRecordNoopSaveLayerDrawRestores(SkRecord* record) { + SaveLayerDrawRestoreNooper pass; + apply(&pass, record); +} - static bool HasAnyEffect(const SkPaint& paint) { - return paint.getPathEffect() || - paint.getShader() || - paint.getXfermode() || - paint.getMaskFilter() || - paint.getColorFilter() || - paint.getRasterizer() || - paint.getLooper() || - paint.getImageFilter(); - } - // The alpha folding can proceed if the single draw's paint has a shader, - // path effect, mask filter and/or rasterizer. - // TODO: most likely the looper and only some xfer modes are the hard - // constraints - static bool CantFoldAlpha(const SkPaint& paint) { - return paint.getXfermode() || - paint.getColorFilter() || - paint.getLooper() || - paint.getImageFilter(); +/* For SVG generated: + SaveLayer (non-opaque, typically for CSS opacity) + Save + ClipRect + SaveLayer (typically for SVG filter) + Restore + Restore + Restore +*/ +struct SvgOpacityAndFilterLayerMergePass { + typedef Pattern7, Is, Is, Is, + Is, Is, Is > Pattern; + + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + SkPaint* opacityPaint = pattern->first()->paint; + if (NULL == opacityPaint) { + // There wasn't really any point to this SaveLayer at all. + return KillSaveLayerAndRestore(record, begin); + } + + // This layer typically contains a filter, but this should work for layers with for other + // purposes too. + SkPaint* filterLayerPaint = pattern->fourth()->paint; + if (filterLayerPaint == NULL) { + // We can just give the inner SaveLayer the paint of the outer SaveLayer. + // TODO(mtklein): figure out how to do this clearly + return false; + } + + if (!fold_opacity_layer_color_to_paint(*opacityPaint, true /*isSaveLayer*/, + filterLayerPaint)) { + return false; + } + + return KillSaveLayerAndRestore(record, begin); } - static bool IsOnlyAlpha(SkColor color) { - return SK_ColorTRANSPARENT == SkColorSetA(color, SK_AlphaTRANSPARENT); + static bool KillSaveLayerAndRestore(SkRecord* record, unsigned saveLayerIndex) { + record->replace(saveLayerIndex); // SaveLayer + record->replace(saveLayerIndex + 6); // Restore + return true; } }; -void SkRecordNoopSaveLayerDrawRestores(SkRecord* record) { - SaveLayerDrawRestoreNooper pass; + +void SkRecordMergeSvgOpacityAndFilterLayers(SkRecord* record) { + SvgOpacityAndFilterLayerMergePass pass; apply(&pass, record); } - diff --git a/src/core/SkRecordOpts.h b/src/core/SkRecordOpts.h index 936eeffd05..a560d00661 100644 --- a/src/core/SkRecordOpts.h +++ b/src/core/SkRecordOpts.h @@ -20,4 +20,8 @@ void SkRecordNoopSaveRestores(SkRecord*); // draw, and no-op the SaveLayer and Restore. void SkRecordNoopSaveLayerDrawRestores(SkRecord*); +// For SVG generated SaveLayer-Save-ClipRect-SaveLayer-3xRestore patterns, merge +// the alpha of the first SaveLayer to the second SaveLayer. +void SkRecordMergeSvgOpacityAndFilterLayers(SkRecord*); + #endif//SkRecordOpts_DEFINED diff --git a/src/core/SkRecordPattern.h b/src/core/SkRecordPattern.h index 68a3aa315e..85c38ac5fe 100644 --- a/src/core/SkRecordPattern.h +++ b/src/core/SkRecordPattern.h @@ -132,6 +132,7 @@ public: template T* first() { return fHead.get(); } template T* second() { return fTail.fHead.get(); } template T* third() { return fTail.fTail.fHead.get(); } + template T* fourth() { return fTail.fTail.fTail.fHead.get(); } private: // If head isn't a Star, try to match at i once. @@ -182,6 +183,18 @@ struct Pattern2 : Cons > {}; template struct Pattern3 : Cons > {}; +template +struct Pattern4 : Cons > {}; + +template +struct Pattern5 : Cons > {}; + +template +struct Pattern6 : Cons > {}; + +template +struct Pattern7 : Cons > {}; + } // namespace SkRecords #endif//SkRecordPattern_DEFINED diff --git a/tests/RecordOptsTest.cpp b/tests/RecordOptsTest.cpp index b6bfba4cae..e550244004 100644 --- a/tests/RecordOptsTest.cpp +++ b/tests/RecordOptsTest.cpp @@ -8,11 +8,14 @@ #include "Test.h" #include "RecordTestUtils.h" +#include "SkColorFilter.h" #include "SkRecord.h" #include "SkRecordOpts.h" #include "SkRecorder.h" #include "SkRecords.h" #include "SkXfermode.h" +#include "SkPictureRecorder.h" +#include "SkPictureImageFilter.h" static const int W = 1920, H = 1080; @@ -169,3 +172,139 @@ DEF_TEST(RecordOpts_NoopSaveLayerDrawRestore, r) { REPORTER_ASSERT(r, drawRect != NULL); REPORTER_ASSERT(r, drawRect->paint.getColor() == 0x03020202); } + +static void assert_merge_svg_opacity_and_filter_layers(skiatest::Reporter* r, + SkRecord* record, + unsigned 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.setXfermodeMode(SkXfermode::kDstIn_Mode); + SkPaint colorFilterPaint; + colorFilterPaint.setColorFilter( + SkColorFilter::CreateModeFilter(SK_ColorLTGRAY, SkXfermode::kSrcIn_Mode))->unref(); + + SkPaint opaqueFilterLayerPaint; + opaqueFilterLayerPaint.setColor(0xFF020202); // Opaque. + SkPaint translucentFilterLayerPaint; + translucentFilterLayerPaint.setColor(0x0F020202); // Not opaque. + SkAutoTUnref 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.reset(recorder.endRecordingAsPicture()); + } + translucentFilterLayerPaint.setImageFilter(SkPictureImageFilter::Create(shape))->unref(); + + int index = 0; + + { + // Any combination of these should cause the pattern to be optimized. + SkRect* firstBounds[] = { NULL, &bounds }; + SkPaint* firstPaints[] = { NULL, &alphaOnlyLayerPaint }; + SkRect* secondBounds[] = { NULL, &bounds }; + SkPaint* secondPaints[] = { &opaqueFilterLayerPaint, &translucentFilterLayerPaint }; + + 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) { + recorder.saveLayer(firstBounds[i], firstPaints[j]); + recorder.save(); + recorder.clipRect(clip); + recorder.saveLayer(secondBounds[k], secondPaints[m]); + recorder.restore(); + recorder.restore(); + recorder.restore(); + assert_merge_svg_opacity_and_filter_layers(r, &record, index, true); + index += 7; + } + } + } + } + } + + // These should cause the pattern to stay unoptimized: + struct { + SkPaint* firstPaint; + SkPaint* secondPaint; + } noChangeTests[] = { + // No change: NULL filter layer paint not implemented. + { &alphaOnlyLayerPaint, NULL }, + // 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(NULL, noChangeTests[i].firstPaint); + recorder.save(); + recorder.clipRect(clip); + recorder.saveLayer(NULL, 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(NULL, &alphaOnlyLayerPaint); + recorder.save(); + recorder.clipRect(clip); + recorder.saveLayer(NULL, &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 != NULL); + 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(NULL, &alphaOnlyLayerPaint); + recorder.saveLayer(NULL, &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; +} -- cgit v1.2.3