/* * 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/gm.h" #include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkColorFilter.h" #include "include/core/SkImageFilter.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/SkTypes.h" #include "include/effects/SkImageFilters.h" #include "include/effects/SkTableColorFilter.h" #include "include/gpu/GrContext.h" constexpr int kTestRectSize = 50; constexpr 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 sk_sp<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::MakeARGB(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()); } // This detector detects that image filter phase of the pixel pipeline receives the correct value. static void install_detector_image_filter(SkPaint* drawPaint) { drawPaint->setImageFilter(SkImageFilters::ColorFilter( make_detector_color_filter(), drawPaint->refImageFilter())); } 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); } 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))); sk_sp<SkPicture> shape; { SkPictureRecorder recorder; SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kTestRectSize + 2), SkIntToScalar(kTestRectSize + 2)); SkPaint shapePaint; shapePaint.setColor(shapeColor); canvas->drawRect(targetRect, shapePaint); shape = recorder.finishRecordingAsPicture(); } SkPaint layerPaint; layerPaint.setColor(SkColorSetARGB(130, 0, 0, 0)); canvas->saveLayer(&targetRect, &layerPaint); canvas->save(); canvas->clipRect(targetRect); SkPaint drawPaint; drawPaint.setImageFilter(SkImageFilters::Picture(shape)); 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) { GrContext* context = canvas->getGrContext(); 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); if (context) { context->flush(); } canvas->translate(SkIntToScalar(kTestRectSize) + SkIntToScalar(1), SkIntToScalar(0)); { SkPictureRecorder recorder; drawTestSequence(recorder.beginRecording(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize)), shapeColor, no_detector_install); recorder.finishRecordingAsPicture()->playback(canvas); if (context) { context->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(detectorInstallFuncs); ++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); if (context) { context->flush(); } canvas->translate(SkIntToScalar(kTestRectSize) + SkIntToScalar(1), SkIntToScalar(0)); { SkPictureRecorder recorder; drawTestSequence(recorder.beginRecording(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize)), shapeColor, detectorInstallFunc); recorder.finishRecordingAsPicture()->playback(canvas); if (context) { context->flush(); } } canvas->restore(); canvas->translate(SkIntToScalar(0), SkIntToScalar(kTestRectSize) + SkIntToScalar(1)); } } } }