skia2/tests/RuntimeBlendTest.cpp
Brian Osman 4008acdc7c Fix a subtle bug with blend-mode blenders used from runtime effects
The FP factory would apply optimizations based on the mode, and return
one of the passed-in FPs. We use 'nullptr' as the 'src' FP when
constructing the blender's FP in make_effect_fp. This means that we get
back 'nullptr' from a src-mode blender. Later, we interpret that as
src-over (the default for an unset blender). Oops.

The new test variant would previously fail, before the fix to the FP.
The tweak to the FP technically eliminates an optimization, but it's one
that only applies to blending happening in the shader (eg, compose
shader, or runtime effects using a blender), and only when the blender
is one of the trivial modes. The resulting shader will still optimize
down, it just involves a bit of extra work before that happens. This
shouldn't have any long-term performance impact, particularly on
important scenarios.

Change-Id: Id5c6a6ca8a263b35c2dca3c41171748cffd41adb
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/556599
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2022-07-07 19:46:38 +00:00

104 lines
4.5 KiB
C++

/*
* Copyright 2021 Google LLC
*
* 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/SkPaint.h"
#include "include/core/SkSize.h"
#include "include/core/SkSurface.h"
#include "include/effects/SkRuntimeEffect.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include "tools/RuntimeBlendUtils.h"
#include "tools/ToolUtils.h"
static bool nearly_equal(const SkColor& x, const SkColor& y) {
const int kTolerance = 1;
return abs((int)SkColorGetA(x) - (int)SkColorGetA(y)) <= kTolerance &&
abs((int)SkColorGetR(x) - (int)SkColorGetR(y)) <= kTolerance &&
abs((int)SkColorGetG(x) - (int)SkColorGetG(y)) <= kTolerance &&
abs((int)SkColorGetB(x) - (int)SkColorGetB(y)) <= kTolerance;
}
static void test_blend(skiatest::Reporter* r, SkSurface* surface) {
SkBitmap bitmap;
REPORTER_ASSERT(r, bitmap.tryAllocPixels(surface->imageInfo()));
for (int m = 0; m < kSkBlendModeCount; ++m) {
SkBlendMode mode = (SkBlendMode)m;
for (int alpha : {0x80, 0xFF}) {
for (bool useShader : {false, true}) {
std::vector<SkColor> colors;
for (bool useRuntimeBlend : {false, true}) {
// Draw a solid red pixel.
SkPaint paint;
paint.setColor(SK_ColorRED);
paint.setBlendMode(SkBlendMode::kSrc);
surface->getCanvas()->drawRect(SkRect::MakeWH(1, 1), paint);
// Draw a blue pixel on top of it, using the passed-in blend mode.
if (useShader) {
// Install a different color in the paint, to ensure we're using the shader
paint.setColor(SK_ColorGREEN);
paint.setShader(SkShaders::Color(SkColorSetARGB(alpha, 0x00, 0x00, 0xFF)));
} else {
paint.setColor(SkColorSetARGB(alpha, 0x00, 0x00, 0xFF));
}
if (useRuntimeBlend) {
paint.setBlender(GetRuntimeBlendForBlendMode(mode));
} else {
paint.setBlendMode(mode);
}
surface->getCanvas()->drawRect(SkRect::MakeWH(1, 1), paint);
// Read back the red/blue blended pixel.
REPORTER_ASSERT(r,
surface->readPixels(bitmap.info(),
bitmap.getPixels(),
bitmap.rowBytes(),
/*srcX=*/0,
/*srcY=*/0));
colors.push_back(bitmap.getColor(/*x=*/0, /*y=*/0));
}
REPORTER_ASSERT(r,
nearly_equal(colors[0], colors[1]),
"Expected: %s %s %s blend matches. Actual: Built-in "
"A=%02X R=%02X G=%02X B=%02X, Runtime A=%02X R=%02X G=%02X B=%02X",
SkBlendMode_Name(mode),
(alpha == 0xFF) ? "solid" : "transparent",
useShader ? "shader" : "paint",
SkColorGetA(colors[0]),
SkColorGetR(colors[0]),
SkColorGetG(colors[0]),
SkColorGetB(colors[0]),
SkColorGetA(colors[1]),
SkColorGetR(colors[1]),
SkColorGetG(colors[1]),
SkColorGetB(colors[1]));
}
}
}
}
DEF_TEST(SkRuntimeBlender_CPU, r) {
const SkImageInfo info = SkImageInfo::MakeN32Premul(/*width=*/1, /*height=*/1);
sk_sp<SkSurface> surface(SkSurface::MakeRaster(info));
test_blend(r, surface.get());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRuntimeBlender_GPU, r, ctxInfo) {
const SkImageInfo info = SkImageInfo::MakeN32Premul(/*width=*/1, /*height=*/1);
sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctxInfo.directContext(),
SkBudgeted::kNo, info));
test_blend(r, surface.get());
}