aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar kkinnunen <kkinnunen@nvidia.com>2015-01-26 00:14:26 -0800
committerGravatar Commit bot <commit-bot@chromium.org>2015-01-26 00:14:26 -0800
commitdc0f408a961b6ce4e7631e06031bbfe1d8d7bcec (patch)
tree0c039bb8fd3ea59d89a3d5fd088af5404f1dca72
parent5c1dad7c751b7fa5a6291a15af3cfabb06808cba (diff)
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: <path style="opacity:0.5 filter:url(#blur_filter)"/> 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
-rw-r--r--gm/recordopts.cpp218
-rw-r--r--gyp/gmslides.gypi1
-rw-r--r--src/core/SkRecordOpts.cpp136
-rw-r--r--src/core/SkRecordOpts.h4
-rw-r--r--src/core/SkRecordPattern.h13
-rw-r--r--tests/RecordOptsTest.cpp139
6 files changed, 482 insertions, 29 deletions
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<SkColorFilter> 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<SkPicture> 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<SkPicture> 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<SkPicture> 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<NoOp>(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<SaveLayer>, Is<Save>, Is<ClipRect>, Is<SaveLayer>,
+ Is<Restore>, Is<Restore>, Is<Restore> > Pattern;
+
+ bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
+ SkPaint* opacityPaint = pattern->first<SaveLayer>()->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<SaveLayer>()->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<NoOp>(saveLayerIndex); // SaveLayer
+ record->replace<NoOp>(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 <typename T> T* first() { return fHead.get(); }
template <typename T> T* second() { return fTail.fHead.get(); }
template <typename T> T* third() { return fTail.fTail.fHead.get(); }
+ template <typename T> 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<A, Pattern1<B> > {};
template <typename A, typename B, typename C>
struct Pattern3 : Cons<A, Pattern2<B, C> > {};
+template <typename A, typename B, typename C, typename D>
+struct Pattern4 : Cons<A, Pattern3<B, C, D> > {};
+
+template <typename A, typename B, typename C, typename D, typename E>
+struct Pattern5 : Cons<A, Pattern4<B, C, D, E> > {};
+
+template <typename A, typename B, typename C, typename D, typename E, typename F>
+struct Pattern6 : Cons<A, Pattern5<B, C, D, E, F> > {};
+
+template <typename A, typename B, typename C, typename D, typename E, typename F, typename G>
+struct Pattern7 : Cons<A, Pattern6<B, C, D, E, F, G> > {};
+
} // 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<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.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<SkPicture> 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<SkRecords::SaveLayer>(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<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;
+}