Replace mask blur FPs with GrSkSLFP
Change-Id: Ie257eca4357b7ab66845af046329ad89857cdaa5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/423025
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 4767ca7..f2d7a9c 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -308,16 +308,10 @@
"$_src/gpu/effects/GrTextureEffect.h",
"$_src/gpu/effects/GrYUVtoRGBEffect.cpp",
"$_src/gpu/effects/GrYUVtoRGBEffect.h",
- "$_src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp",
- "$_src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h",
"$_src/gpu/effects/generated/GrConfigConversionEffect.cpp",
"$_src/gpu/effects/generated/GrConfigConversionEffect.h",
"$_src/gpu/effects/generated/GrDitherEffect.cpp",
"$_src/gpu/effects/generated/GrDitherEffect.h",
- "$_src/gpu/effects/generated/GrRRectBlurEffect.cpp",
- "$_src/gpu/effects/generated/GrRRectBlurEffect.h",
- "$_src/gpu/effects/generated/GrRectBlurEffect.cpp",
- "$_src/gpu/effects/generated/GrRectBlurEffect.h",
"$_src/gpu/geometry/GrPathUtils.cpp",
"$_src/gpu/geometry/GrPathUtils.h",
"$_src/gpu/geometry/GrQuad.cpp",
diff --git a/gn/sksl.gni b/gn/sksl.gni
index 2784c4f..7ab85fd 100644
--- a/gn/sksl.gni
+++ b/gn/sksl.gni
@@ -208,10 +208,7 @@
]
skia_gpu_processor_sources = [
- "$_src/gpu/effects/GrCircleBlurFragmentProcessor.fp",
"$_src/gpu/effects/GrConfigConversionEffect.fp",
"$_src/gpu/effects/GrDitherEffect.fp",
- "$_src/gpu/effects/GrRRectBlurEffect.fp",
- "$_src/gpu/effects/GrRectBlurEffect.fp",
"$_src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp",
]
diff --git a/src/core/SkBlurMF.cpp b/src/core/SkBlurMF.cpp
index 953f5d1..05b1ab8 100644
--- a/src/core/SkBlurMF.cpp
+++ b/src/core/SkBlurMF.cpp
@@ -23,6 +23,7 @@
#if SK_SUPPORT_GPU
#include "include/gpu/GrRecordingContext.h"
+#include "src/core/SkRuntimeEffectPriv.h"
#include "src/gpu/GrFragmentProcessor.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrResourceProvider.h"
@@ -30,10 +31,10 @@
#include "src/gpu/GrStyle.h"
#include "src/gpu/GrSurfaceDrawContext.h"
#include "src/gpu/GrTextureProxy.h"
+#include "src/gpu/GrThreadSafeCache.h"
+#include "src/gpu/SkGr.h"
+#include "src/gpu/effects/GrSkSLFP.h"
#include "src/gpu/effects/GrTextureEffect.h"
-#include "src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h"
-#include "src/gpu/effects/generated/GrRRectBlurEffect.h"
-#include "src/gpu/effects/generated/GrRectBlurEffect.h"
#include "src/gpu/geometry/GrStyledShape.h"
#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
@@ -575,6 +576,872 @@
#if SK_SUPPORT_GPU && GR_OGA
+///////////////////////////////////////////////////////////////////////////////
+// Circle Blur
+///////////////////////////////////////////////////////////////////////////////
+
+// Computes an unnormalized half kernel (right side). Returns the summation of all the half
+// kernel values.
+static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) {
+ const float invSigma = 1.f / sigma;
+ const float b = -0.5f * invSigma * invSigma;
+ float tot = 0.0f;
+ // Compute half kernel values at half pixel steps out from the center.
+ float t = 0.5f;
+ for (int i = 0; i < halfKernelSize; ++i) {
+ float value = expf(t * t * b);
+ tot += value;
+ halfKernel[i] = value;
+ t += 1.f;
+ }
+ return tot;
+}
+
+// Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number
+// of discrete steps. The half kernel is normalized to sum to 0.5.
+static void make_half_kernel_and_summed_table(float* halfKernel,
+ float* summedHalfKernel,
+ int halfKernelSize,
+ float sigma) {
+ // The half kernel should sum to 0.5 not 1.0.
+ const float tot = 2.f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma);
+ float sum = 0.f;
+ for (int i = 0; i < halfKernelSize; ++i) {
+ halfKernel[i] /= tot;
+ sum += halfKernel[i];
+ summedHalfKernel[i] = sum;
+ }
+}
+
+// Applies the 1D half kernel vertically at points along the x axis to a circle centered at the
+// origin with radius circleR.
+void apply_kernel_in_y(float* results,
+ int numSteps,
+ float firstX,
+ float circleR,
+ int halfKernelSize,
+ const float* summedHalfKernelTable) {
+ float x = firstX;
+ for (int i = 0; i < numSteps; ++i, x += 1.f) {
+ if (x < -circleR || x > circleR) {
+ results[i] = 0;
+ continue;
+ }
+ float y = sqrtf(circleR * circleR - x * x);
+ // In the column at x we exit the circle at +y and -y
+ // The summed table entry j is actually reflects an offset of j + 0.5.
+ y -= 0.5f;
+ int yInt = SkScalarFloorToInt(y);
+ SkASSERT(yInt >= -1);
+ if (y < 0) {
+ results[i] = (y + 0.5f) * summedHalfKernelTable[0];
+ } else if (yInt >= halfKernelSize - 1) {
+ results[i] = 0.5f;
+ } else {
+ float yFrac = y - yInt;
+ results[i] = (1.f - yFrac) * summedHalfKernelTable[yInt] +
+ yFrac * summedHalfKernelTable[yInt + 1];
+ }
+ }
+}
+
+// Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR.
+// This relies on having a half kernel computed for the Gaussian and a table of applications of
+// the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX +
+// halfKernel) passed in as yKernelEvaluations.
+static uint8_t eval_at(float evalX,
+ float circleR,
+ const float* halfKernel,
+ int halfKernelSize,
+ const float* yKernelEvaluations) {
+ float acc = 0;
+
+ float x = evalX - halfKernelSize;
+ for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
+ if (x < -circleR || x > circleR) {
+ continue;
+ }
+ float verticalEval = yKernelEvaluations[i];
+ acc += verticalEval * halfKernel[halfKernelSize - i - 1];
+ }
+ for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
+ if (x < -circleR || x > circleR) {
+ continue;
+ }
+ float verticalEval = yKernelEvaluations[i + halfKernelSize];
+ acc += verticalEval * halfKernel[i];
+ }
+ // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about
+ // the x axis).
+ return SkUnitScalarClampToByte(2.f * acc);
+}
+
+// This function creates a profile of a blurred circle. It does this by computing a kernel for
+// half the Gaussian and a matching summed area table. The summed area table is used to compute
+// an array of vertical applications of the half kernel to the circle along the x axis. The
+// table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is
+// the size of the profile being computed. Then for each of the n profile entries we walk out k
+// steps in each horizontal direction multiplying the corresponding y evaluation by the half
+// kernel entry and sum these values to compute the profile entry.
+static void create_circle_profile(uint8_t* weights,
+ float sigma,
+ float circleR,
+ int profileTextureWidth) {
+ const int numSteps = profileTextureWidth;
+
+ // The full kernel is 6 sigmas wide.
+ int halfKernelSize = SkScalarCeilToInt(6.0f * sigma);
+ // round up to next multiple of 2 and then divide by 2
+ halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
+
+ // Number of x steps at which to apply kernel in y to cover all the profile samples in x.
+ int numYSteps = numSteps + 2 * halfKernelSize;
+
+ SkAutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps);
+ float* halfKernel = bulkAlloc.get();
+ float* summedKernel = bulkAlloc.get() + halfKernelSize;
+ float* yEvals = bulkAlloc.get() + 2 * halfKernelSize;
+ make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma);
+
+ float firstX = -halfKernelSize + 0.5f;
+ apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summedKernel);
+
+ for (int i = 0; i < numSteps - 1; ++i) {
+ float evalX = i + 0.5f;
+ weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i);
+ }
+ // Ensure the tail of the Gaussian goes to zero.
+ weights[numSteps - 1] = 0;
+}
+
+static void create_half_plane_profile(uint8_t* profile, int profileWidth) {
+ SkASSERT(!(profileWidth & 0x1));
+ // The full kernel is 6 sigmas wide.
+ float sigma = profileWidth / 6.f;
+ int halfKernelSize = profileWidth / 2;
+
+ SkAutoTArray<float> halfKernel(halfKernelSize);
+
+ // The half kernel should sum to 0.5.
+ const float tot = 2.f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, sigma);
+ float sum = 0.f;
+ // Populate the profile from the right edge to the middle.
+ for (int i = 0; i < halfKernelSize; ++i) {
+ halfKernel[halfKernelSize - i - 1] /= tot;
+ sum += halfKernel[halfKernelSize - i - 1];
+ profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum);
+ }
+ // Populate the profile from the middle to the left edge (by flipping the half kernel and
+ // continuing the summation).
+ for (int i = 0; i < halfKernelSize; ++i) {
+ sum += halfKernel[i];
+ profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum);
+ }
+ // Ensure tail goes to 0.
+ profile[profileWidth - 1] = 0;
+}
+
+static std::unique_ptr<GrFragmentProcessor> create_profile_effect(GrRecordingContext* rContext,
+ const SkRect& circle,
+ float sigma,
+ float* solidRadius,
+ float* textureRadius) {
+ float circleR = circle.width() / 2.0f;
+ if (!sk_float_isfinite(circleR) || circleR < SK_ScalarNearlyZero) {
+ return nullptr;
+ }
+
+ auto threadSafeCache = rContext->priv().threadSafeCache();
+
+ // Profile textures are cached by the ratio of sigma to circle radius and by the size of the
+ // profile texture (binned by powers of 2).
+ SkScalar sigmaToCircleRRatio = sigma / circleR;
+ // When sigma is really small this becomes a equivalent to convolving a Gaussian with a
+ // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the
+ // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet
+ // implemented this latter optimization.
+ sigmaToCircleRRatio = std::min(sigmaToCircleRRatio, 8.f);
+ SkFixed sigmaToCircleRRatioFixed;
+ static const SkScalar kHalfPlaneThreshold = 0.1f;
+ bool useHalfPlaneApprox = false;
+ if (sigmaToCircleRRatio <= kHalfPlaneThreshold) {
+ useHalfPlaneApprox = true;
+ sigmaToCircleRRatioFixed = 0;
+ *solidRadius = circleR - 3 * sigma;
+ *textureRadius = 6 * sigma;
+ } else {
+ // Convert to fixed point for the key.
+ sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio);
+ // We shave off some bits to reduce the number of unique entries. We could probably
+ // shave off more than we do.
+ sigmaToCircleRRatioFixed &= ~0xff;
+ sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed);
+ sigma = circleR * sigmaToCircleRRatio;
+ *solidRadius = 0;
+ *textureRadius = circleR + 3 * sigma;
+ }
+
+ static constexpr int kProfileTextureWidth = 512;
+ // This would be kProfileTextureWidth/textureRadius if it weren't for the fact that we do
+ // the calculation of the profile coord in a coord space that has already been scaled by
+ // 1 / textureRadius. This is done to avoid overflow in length().
+ SkMatrix texM = SkMatrix::Scale(kProfileTextureWidth, 1.f);
+
+ static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+ GrUniqueKey key;
+ GrUniqueKey::Builder builder(&key, kDomain, 1, "1-D Circular Blur");
+ builder[0] = sigmaToCircleRRatioFixed;
+ builder.finish();
+
+ GrSurfaceProxyView profileView = threadSafeCache->find(key);
+ if (profileView) {
+ SkASSERT(profileView.asTextureProxy());
+ SkASSERT(profileView.origin() == kTopLeft_GrSurfaceOrigin);
+ return GrTextureEffect::Make(std::move(profileView), kPremul_SkAlphaType, texM);
+ }
+
+ SkBitmap bm;
+ if (!bm.tryAllocPixels(SkImageInfo::MakeA8(kProfileTextureWidth, 1))) {
+ return nullptr;
+ }
+
+ if (useHalfPlaneApprox) {
+ create_half_plane_profile(bm.getAddr8(0, 0), kProfileTextureWidth);
+ } else {
+ // Rescale params to the size of the texture we're creating.
+ SkScalar scale = kProfileTextureWidth / *textureRadius;
+ create_circle_profile(
+ bm.getAddr8(0, 0), sigma * scale, circleR * scale, kProfileTextureWidth);
+ }
+ bm.setImmutable();
+
+ profileView = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bm));
+ if (!profileView) {
+ return nullptr;
+ }
+
+ profileView = threadSafeCache->add(key, profileView);
+ return GrTextureEffect::Make(std::move(profileView), kPremul_SkAlphaType, texM);
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_circle_blur(GrRecordingContext* context,
+ const SkRect& circle,
+ float sigma) {
+ if (SkGpuBlurUtils::IsEffectivelyZeroSigma(sigma)) {
+ return nullptr;
+ }
+
+ float solidRadius;
+ float textureRadius;
+ std::unique_ptr<GrFragmentProcessor> profile =
+ create_profile_effect(context, circle, sigma, &solidRadius, &textureRadius);
+ if (!profile) {
+ return nullptr;
+ }
+
+ static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
+ uniform shader blurProfile;
+ uniform half4 circleData;
+
+ half4 main(float2 xy, half4 inColor) {
+ // We just want to compute "(length(vec) - circleData.z + 0.5) * circleData.w" but need
+ // to rearrange to avoid passing large values to length() that would overflow.
+ half2 vec = half2((sk_FragCoord.xy - circleData.xy) * circleData.w);
+ half dist = length(vec) + (0.5 - circleData.z) * circleData.w;
+ return inColor * sample(blurProfile, half2(dist, 0.5)).a;
+ }
+ )");
+
+ SkV4 circleData = {circle.centerX(), circle.centerY(), solidRadius, 1.f / textureRadius};
+ return GrSkSLFP::Make(effect, "CircleBlur", /*inputFP=*/nullptr,
+ GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha,
+ "blurProfile", GrSkSLFP::IgnoreOptFlags(std::move(profile)),
+ "circleData", circleData);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Rect Blur
+///////////////////////////////////////////////////////////////////////////////
+
+static std::unique_ptr<GrFragmentProcessor> make_rect_integral_fp(GrRecordingContext* rContext,
+ float sixSigma) {
+ SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(sixSigma / 6.f));
+ auto threadSafeCache = rContext->priv().threadSafeCache();
+
+ int width = SkGpuBlurUtils::CreateIntegralTable(sixSigma, nullptr);
+
+ static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+ GrUniqueKey key;
+ GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask");
+ builder[0] = width;
+ builder.finish();
+
+ SkMatrix m = SkMatrix::Scale(width / sixSigma, 1.f);
+
+ GrSurfaceProxyView view = threadSafeCache->find(key);
+
+ if (view) {
+ SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
+ return GrTextureEffect::Make(
+ std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
+ }
+
+ SkBitmap bitmap;
+ if (!SkGpuBlurUtils::CreateIntegralTable(sixSigma, &bitmap)) {
+ return {};
+ }
+
+ view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bitmap));
+ if (!view) {
+ return {};
+ }
+
+ view = threadSafeCache->add(key, view);
+
+ SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
+ return GrTextureEffect::Make(
+ std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_rect_blur(GrRecordingContext* context,
+ const GrShaderCaps& caps,
+ const SkRect& srcRect,
+ const SkMatrix& viewMatrix,
+ float transformedSigma) {
+ SkASSERT(viewMatrix.preservesRightAngles());
+ SkASSERT(srcRect.isSorted());
+
+ if (SkGpuBlurUtils::IsEffectivelyZeroSigma(transformedSigma)) {
+ // No need to blur the rect
+ return nullptr;
+ }
+
+ SkMatrix invM;
+ SkRect rect;
+ if (viewMatrix.rectStaysRect()) {
+ invM = SkMatrix::I();
+ // We can do everything in device space when the src rect projects to a rect in device space
+ SkAssertResult(viewMatrix.mapRect(&rect, srcRect));
+ } else {
+ // The view matrix may scale, perhaps anisotropically. But we want to apply our device space
+ // "transformedSigma" to the delta of frag coord from the rect edges. Factor out the scaling
+ // to define a space that is purely rotation/translation from device space (and scale from
+ // src space) We'll meet in the middle: pre-scale the src rect to be in this space and then
+ // apply the inverse of the rotation/translation portion to the frag coord.
+ SkMatrix m;
+ SkSize scale;
+ if (!viewMatrix.decomposeScale(&scale, &m)) {
+ return nullptr;
+ }
+ if (!m.invert(&invM)) {
+ return nullptr;
+ }
+ rect = {srcRect.left() * scale.width(),
+ srcRect.top() * scale.height(),
+ srcRect.right() * scale.width(),
+ srcRect.bottom() * scale.height()};
+ }
+
+ if (!caps.floatIs32Bits()) {
+ // We promote the math that gets us into the Gaussian space to full float when the rect
+ // coords are large. If we don't have full float then fail. We could probably clip the rect
+ // to an outset device bounds instead.
+ if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f ||
+ SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f) {
+ return nullptr;
+ }
+ }
+
+ const float sixSigma = 6 * transformedSigma;
+ std::unique_ptr<GrFragmentProcessor> integral = make_rect_integral_fp(context, sixSigma);
+ if (!integral) {
+ return nullptr;
+ }
+
+ // In the fast variant we think of the midpoint of the integral texture as aligning with the
+ // closest rect edge both in x and y. To simplify texture coord calculation we inset the rect so
+ // that the edge of the inset rect corresponds to t = 0 in the texture. It actually simplifies
+ // things a bit in the !isFast case, too.
+ float threeSigma = sixSigma / 2;
+ SkRect insetRect = {rect.left() + threeSigma,
+ rect.top() + threeSigma,
+ rect.right() - threeSigma,
+ rect.bottom() - threeSigma};
+
+ // In our fast variant we find the nearest horizontal and vertical edges and for each do a
+ // lookup in the integral texture for each and multiply them. When the rect is less than 6 sigma
+ // wide then things aren't so simple and we have to consider both the left and right edge of the
+ // rectangle (and similar in y).
+ bool isFast = insetRect.isSorted();
+
+ static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
+ // Effect that is a LUT for integral of normal distribution. The value at x:[0,6*sigma] is
+ // the integral from -inf to (3*sigma - x). I.e. x is mapped from [0, 6*sigma] to
+ // [3*sigma to -3*sigma]. The flip saves a reversal in the shader.
+ uniform shader integral;
+
+ uniform float4 rect;
+ uniform int isFast; // specialized
+
+ half4 main(float2 pos, half4 inColor) {
+ half xCoverage, yCoverage;
+ if (bool(isFast)) {
+ // Get the smaller of the signed distance from the frag coord to the left and right
+ // edges and similar for y.
+ // The integral texture goes "backwards" (from 3*sigma to -3*sigma), So, the below
+ // computations align the left edge of the integral texture with the inset rect's
+ // edge extending outward 6 * sigma from the inset rect.
+ half2 xy = max(half2(rect.LT - pos), half2(pos - rect.RB));
+ xCoverage = sample(integral, half2(xy.x, 0.5)).a;
+ yCoverage = sample(integral, half2(xy.y, 0.5)).a;
+ } else {
+ // We just consider just the x direction here. In practice we compute x and y
+ // separately and multiply them together.
+ // We define our coord system so that the point at which we're evaluating a kernel
+ // defined by the normal distribution (K) at 0. In this coord system let L be left
+ // edge and R be the right edge of the rectangle.
+ // We can calculate C by integrating K with the half infinite ranges outside the
+ // L to R range and subtracting from 1:
+ // C = 1 - <integral of K from from -inf to L> - <integral of K from R to inf>
+ // K is symmetric about x=0 so:
+ // C = 1 - <integral of K from from -inf to L> - <integral of K from -inf to -R>
+
+ // The integral texture goes "backwards" (from 3*sigma to -3*sigma) which is
+ // factored in to the below calculations.
+ // Also, our rect uniform was pre-inset by 3 sigma from the actual rect being
+ // blurred, also factored in.
+ half4 rect = half4(half2(rect.LT - pos), half2(pos - rect.RB));
+ xCoverage = 1 - sample(integral, half2(rect.L, 0.5)).a
+ - sample(integral, half2(rect.R, 0.5)).a;
+ yCoverage = 1 - sample(integral, half2(rect.T, 0.5)).a
+ - sample(integral, half2(rect.B, 0.5)).a;
+ }
+ return inColor * xCoverage * yCoverage;
+ }
+ )");
+
+ std::unique_ptr<GrFragmentProcessor> fp =
+ GrSkSLFP::Make(effect, "RectBlur", /*inputFP=*/nullptr,
+ GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha,
+ "integral", GrSkSLFP::IgnoreOptFlags(std::move(integral)),
+ "rect", insetRect,
+ "isFast", GrSkSLFP::Specialize<int>(isFast));
+ if (!invM.isIdentity()) {
+ fp = GrMatrixEffect::Make(invM, std::move(fp));
+ }
+ return GrFragmentProcessor::DeviceSpace(std::move(fp));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RRect Blur
+///////////////////////////////////////////////////////////////////////////////
+
+static constexpr auto kBlurredRRectMaskOrigin = kTopLeft_GrSurfaceOrigin;
+
+static void make_blurred_rrect_key(GrUniqueKey* key,
+ const SkRRect& rrectToDraw,
+ float xformedSigma) {
+ SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
+ static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+
+ GrUniqueKey::Builder builder(key, kDomain, 9, "RoundRect Blur Mask");
+ builder[0] = SkScalarCeilToInt(xformedSigma - 1 / 6.0f);
+
+ int index = 1;
+ // TODO: this is overkill for _simple_ circular rrects
+ for (auto c : {SkRRect::kUpperLeft_Corner,
+ SkRRect::kUpperRight_Corner,
+ SkRRect::kLowerRight_Corner,
+ SkRRect::kLowerLeft_Corner}) {
+ SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) && SkScalarIsInt(rrectToDraw.radii(c).fY));
+ builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX);
+ builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY);
+ }
+ builder.finish();
+}
+
+static bool fillin_view_on_gpu(GrDirectContext* dContext,
+ const GrSurfaceProxyView& lazyView,
+ sk_sp<GrThreadSafeCache::Trampoline> trampoline,
+ const SkRRect& rrectToDraw,
+ const SkISize& dimensions,
+ float xformedSigma) {
+#if GR_OGA
+ SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
+
+ // We cache blur masks. Use default surface props here so we can use the same cached mask
+ // regardless of the final dst surface.
+ SkSurfaceProps defaultSurfaceProps;
+
+ std::unique_ptr<GrSurfaceDrawContext> rtc =
+ GrSurfaceDrawContext::MakeWithFallback(dContext,
+ GrColorType::kAlpha_8,
+ nullptr,
+ SkBackingFit::kExact,
+ dimensions,
+ defaultSurfaceProps,
+ 1,
+ GrMipmapped::kNo,
+ GrProtected::kNo,
+ kBlurredRRectMaskOrigin);
+ if (!rtc) {
+ return false;
+ }
+
+ GrPaint paint;
+
+ rtc->clear(SK_PMColor4fTRANSPARENT);
+ rtc->drawRRect(nullptr,
+ std::move(paint),
+ GrAA::kYes,
+ SkMatrix::I(),
+ rrectToDraw,
+ GrStyle::SimpleFill());
+
+ GrSurfaceProxyView srcView = rtc->readSurfaceView();
+ SkASSERT(srcView.asTextureProxy());
+ auto rtc2 = SkGpuBlurUtils::GaussianBlur(dContext,
+ std::move(srcView),
+ rtc->colorInfo().colorType(),
+ rtc->colorInfo().alphaType(),
+ nullptr,
+ SkIRect::MakeSize(dimensions),
+ SkIRect::MakeSize(dimensions),
+ xformedSigma,
+ xformedSigma,
+ SkTileMode::kClamp,
+ SkBackingFit::kExact);
+ if (!rtc2 || !rtc2->readSurfaceView()) {
+ return false;
+ }
+
+ auto view = rtc2->readSurfaceView();
+ SkASSERT(view.swizzle() == lazyView.swizzle());
+ SkASSERT(view.origin() == lazyView.origin());
+ trampoline->fProxy = view.asTextureProxyRef();
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+// Evaluate the vertical blur at the specified 'y' value given the location of the top of the
+// rrect.
+static uint8_t eval_V(float top, int y, const uint8_t* integral, int integralSize, float sixSigma) {
+ if (top < 0) {
+ return 0; // an empty column
+ }
+
+ float fT = (top - y - 0.5f) * (integralSize / sixSigma);
+ if (fT < 0) {
+ return 255;
+ } else if (fT >= integralSize - 1) {
+ return 0;
+ }
+
+ int lower = (int)fT;
+ float frac = fT - lower;
+
+ SkASSERT(lower + 1 < integralSize);
+
+ return integral[lower] * (1.0f - frac) + integral[lower + 1] * frac;
+}
+
+// Apply a gaussian 'kernel' horizontally at the specified 'x', 'y' location.
+static uint8_t eval_H(int x,
+ int y,
+ const std::vector<float>& topVec,
+ const float* kernel,
+ int kernelSize,
+ const uint8_t* integral,
+ int integralSize,
+ float sixSigma) {
+ SkASSERT(0 <= x && x < (int)topVec.size());
+ SkASSERT(kernelSize % 2);
+
+ float accum = 0.0f;
+
+ int xSampleLoc = x - (kernelSize / 2);
+ for (int i = 0; i < kernelSize; ++i, ++xSampleLoc) {
+ if (xSampleLoc < 0 || xSampleLoc >= (int)topVec.size()) {
+ continue;
+ }
+
+ accum += kernel[i] * eval_V(topVec[xSampleLoc], y, integral, integralSize, sixSigma);
+ }
+
+ return accum + 0.5f;
+}
+
+// Create a cpu-side blurred-rrect mask that is close to the version the gpu would've produced.
+// The match needs to be close bc the cpu- and gpu-generated version must be interchangeable.
+static GrSurfaceProxyView create_mask_on_cpu(GrRecordingContext* rContext,
+ const SkRRect& rrectToDraw,
+ const SkISize& dimensions,
+ float xformedSigma) {
+ SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
+ int radius = SkGpuBlurUtils::SigmaRadius(xformedSigma);
+ int kernelSize = 2 * radius + 1;
+
+ SkASSERT(kernelSize % 2);
+ SkASSERT(dimensions.width() % 2);
+ SkASSERT(dimensions.height() % 2);
+
+ SkVector radii = rrectToDraw.getSimpleRadii();
+ SkASSERT(SkScalarNearlyEqual(radii.fX, radii.fY));
+
+ const int halfWidthPlus1 = (dimensions.width() / 2) + 1;
+ const int halfHeightPlus1 = (dimensions.height() / 2) + 1;
+
+ std::unique_ptr<float[]> kernel(new float[kernelSize]);
+
+ SkGpuBlurUtils::Compute1DGaussianKernel(kernel.get(), xformedSigma, radius);
+
+ SkBitmap integral;
+ if (!SkGpuBlurUtils::CreateIntegralTable(6 * xformedSigma, &integral)) {
+ return {};
+ }
+
+ SkBitmap result;
+ if (!result.tryAllocPixels(SkImageInfo::MakeA8(dimensions.width(), dimensions.height()))) {
+ return {};
+ }
+
+ std::vector<float> topVec;
+ topVec.reserve(dimensions.width());
+ for (int x = 0; x < dimensions.width(); ++x) {
+ if (x < rrectToDraw.rect().fLeft || x > rrectToDraw.rect().fRight) {
+ topVec.push_back(-1);
+ } else {
+ if (x + 0.5f < rrectToDraw.rect().fLeft + radii.fX) { // in the circular section
+ float xDist = rrectToDraw.rect().fLeft + radii.fX - x - 0.5f;
+ float h = sqrtf(radii.fX * radii.fX - xDist * xDist);
+ SkASSERT(0 <= h && h < radii.fY);
+ topVec.push_back(rrectToDraw.rect().fTop + radii.fX - h + 3 * xformedSigma);
+ } else {
+ topVec.push_back(rrectToDraw.rect().fTop + 3 * xformedSigma);
+ }
+ }
+ }
+
+ for (int y = 0; y < halfHeightPlus1; ++y) {
+ uint8_t* scanline = result.getAddr8(0, y);
+
+ for (int x = 0; x < halfWidthPlus1; ++x) {
+ scanline[x] = eval_H(x,
+ y,
+ topVec,
+ kernel.get(),
+ kernelSize,
+ integral.getAddr8(0, 0),
+ integral.width(),
+ 6 * xformedSigma);
+ scanline[dimensions.width() - x - 1] = scanline[x];
+ }
+
+ memcpy(result.getAddr8(0, dimensions.height() - y - 1), scanline, result.rowBytes());
+ }
+
+ result.setImmutable();
+
+ auto view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, result));
+ if (!view) {
+ return {};
+ }
+
+ SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
+ return view;
+}
+
+static std::unique_ptr<GrFragmentProcessor> find_or_create_rrect_blur_mask_fp(
+ GrRecordingContext* rContext,
+ const SkRRect& rrectToDraw,
+ const SkISize& dimensions,
+ float xformedSigma) {
+ SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
+ GrUniqueKey key;
+ make_blurred_rrect_key(&key, rrectToDraw, xformedSigma);
+
+ auto threadSafeCache = rContext->priv().threadSafeCache();
+
+ // It seems like we could omit this matrix and modify the shader code to not normalize
+ // the coords used to sample the texture effect. However, the "proxyDims" value in the
+ // shader is not always the actual the proxy dimensions. This is because 'dimensions' here
+ // was computed using integer corner radii as determined in
+ // SkComputeBlurredRRectParams whereas the shader code uses the float radius to compute
+ // 'proxyDims'. Why it draws correctly with these unequal values is a mystery for the ages.
+ auto m = SkMatrix::Scale(dimensions.width(), dimensions.height());
+
+ GrSurfaceProxyView view;
+
+ if (GrDirectContext* dContext = rContext->asDirectContext()) {
+ // The gpu thread gets priority over the recording threads. If the gpu thread is first,
+ // it crams a lazy proxy into the cache and then fills it in later.
+ auto [lazyView, trampoline] = GrThreadSafeCache::CreateLazyView(dContext,
+ GrColorType::kAlpha_8,
+ dimensions,
+ kBlurredRRectMaskOrigin,
+ SkBackingFit::kExact);
+ if (!lazyView) {
+ return nullptr;
+ }
+
+ view = threadSafeCache->findOrAdd(key, lazyView);
+ if (view != lazyView) {
+ SkASSERT(view.asTextureProxy());
+ SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
+ return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
+ }
+
+ if (!fillin_view_on_gpu(dContext,
+ lazyView,
+ std::move(trampoline),
+ rrectToDraw,
+ dimensions,
+ xformedSigma)) {
+ // In this case something has gone disastrously wrong so set up to drop the draw
+ // that needed this resource and reduce future pollution of the cache.
+ threadSafeCache->remove(key);
+ return nullptr;
+ }
+ } else {
+ view = threadSafeCache->find(key);
+ if (view) {
+ SkASSERT(view.asTextureProxy());
+ SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
+ return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
+ }
+
+ view = create_mask_on_cpu(rContext, rrectToDraw, dimensions, xformedSigma);
+ if (!view) {
+ return nullptr;
+ }
+
+ view = threadSafeCache->add(key, view);
+ }
+
+ SkASSERT(view.asTextureProxy());
+ SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
+ return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_rrect_blur(GrRecordingContext* context,
+ float sigma,
+ float xformedSigma,
+ const SkRRect& srcRRect,
+ const SkRRect& devRRect) {
+ // Should've been caught up-stream
+#ifdef SK_DEBUG
+ SkASSERTF(!SkRRectPriv::IsCircle(devRRect),
+ "Unexpected circle. %d\n\t%s\n\t%s",
+ SkRRectPriv::IsCircle(srcRRect),
+ srcRRect.dumpToString(true).c_str(),
+ devRRect.dumpToString(true).c_str());
+ SkASSERTF(!devRRect.isRect(),
+ "Unexpected rect. %d\n\t%s\n\t%s",
+ srcRRect.isRect(),
+ srcRRect.dumpToString(true).c_str(),
+ devRRect.dumpToString(true).c_str());
+#endif
+
+ // TODO: loosen this up
+ if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
+ return nullptr;
+ }
+
+ if (SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma)) {
+ return nullptr;
+ }
+
+ // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be sufficiently
+ // small relative to both the size of the corner radius and the width (and height) of the rrect.
+ SkRRect rrectToDraw;
+ SkISize dimensions;
+ SkScalar ignored[SkGpuBlurUtils::kBlurRRectMaxDivisions];
+
+ bool ninePatchable = SkGpuBlurUtils::ComputeBlurredRRectParams(srcRRect,
+ devRRect,
+ sigma,
+ xformedSigma,
+ &rrectToDraw,
+ &dimensions,
+ ignored,
+ ignored,
+ ignored,
+ ignored);
+ if (!ninePatchable) {
+ return nullptr;
+ }
+
+ std::unique_ptr<GrFragmentProcessor> maskFP =
+ find_or_create_rrect_blur_mask_fp(context, rrectToDraw, dimensions, xformedSigma);
+ if (!maskFP) {
+ return nullptr;
+ }
+
+ static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
+ uniform shader ninePatchFP;
+
+ uniform half cornerRadius;
+ uniform float4 proxyRect;
+ uniform half blurRadius;
+
+ half4 main(float2 xy, half4 inColor) {
+ // Warp the fragment position to the appropriate part of the 9-patch blur texture by
+ // snipping out the middle section of the proxy rect.
+ float2 translatedFragPosFloat = sk_FragCoord.xy - proxyRect.LT;
+ float2 proxyCenter = (proxyRect.RB - proxyRect.LT) * 0.5;
+ half edgeSize = 2.0 * blurRadius + cornerRadius + 0.5;
+
+ // Position the fragment so that (0, 0) marks the center of the proxy rectangle.
+ // Negative coordinates are on the left/top side and positive numbers are on the
+ // right/bottom.
+ translatedFragPosFloat -= proxyCenter;
+
+ // Temporarily strip off the fragment's sign. x/y are now strictly increasing as we
+ // move away from the center.
+ half2 fragDirection = half2(sign(translatedFragPosFloat));
+ translatedFragPosFloat = abs(translatedFragPosFloat);
+
+ // Our goal is to snip out the "middle section" of the proxy rect (everything but the
+ // edge). We've repositioned our fragment position so that (0, 0) is the centerpoint
+ // and x/y are always positive, so we can subtract here and interpret negative results
+ // as being within the middle section.
+ half2 translatedFragPosHalf = half2(translatedFragPosFloat - (proxyCenter - edgeSize));
+
+ // Remove the middle section by clamping to zero.
+ translatedFragPosHalf = max(translatedFragPosHalf, 0);
+
+ // Reapply the fragment's sign, so that negative coordinates once again mean left/top
+ // side and positive means bottom/right side.
+ translatedFragPosHalf *= fragDirection;
+
+ // Offset the fragment so that (0, 0) marks the upper-left again, instead of the center
+ // point.
+ translatedFragPosHalf += half2(edgeSize);
+
+ half2 proxyDims = half2(2.0 * edgeSize);
+ half2 texCoord = translatedFragPosHalf / proxyDims;
+
+ return inColor * sample(ninePatchFP, texCoord).a;
+ }
+ )");
+
+ float cornerRadius = SkRRectPriv::GetSimpleRadii(devRRect).fX;
+ float blurRadius = 3.f * SkScalarCeilToScalar(xformedSigma - 1 / 6.0f);
+ SkRect proxyRect = devRRect.getBounds().makeOutset(blurRadius, blurRadius);
+
+ return GrSkSLFP::Make(effect, "RRectBlur", /*inputFP=*/nullptr,
+ GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha,
+ "ninePatchFP", GrSkSLFP::IgnoreOptFlags(std::move(maskFP)),
+ "cornerRadius", cornerRadius,
+ "proxyRect", proxyRect,
+ "blurRadius", blurRadius);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrRecordingContext* context,
GrSurfaceDrawContext* surfaceDrawContext,
GrPaint&& paint,
@@ -618,9 +1485,8 @@
if (canBeRect || canBeCircle) {
if (canBeRect) {
- fp = GrRectBlurEffect::Make(
- /*inputFP=*/nullptr, context, *context->priv().caps()->shaderCaps(),
- srcRRect.rect(), viewMatrix, xformedSigma);
+ fp = make_rect_blur(context, *context->priv().caps()->shaderCaps(),
+ srcRRect.rect(), viewMatrix, xformedSigma);
} else {
SkRect devBounds;
if (devRRectIsCircle) {
@@ -634,8 +1500,7 @@
center.x() + radius,
center.y() + radius};
}
- fp = GrCircleBlurFragmentProcessor::Make(/*inputFP=*/nullptr, context, devBounds,
- xformedSigma);
+ fp = make_circle_blur(context, devBounds, xformedSigma);
}
if (!fp) {
@@ -670,8 +1535,7 @@
return false;
}
- fp = GrRRectBlurEffect::Make(/*inputFP=*/nullptr, context, fSigma, xformedSigma,
- srcRRect, devRRect);
+ fp = make_rrect_blur(context, fSigma, xformedSigma, srcRRect, devRRect);
if (!fp) {
return false;
}
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index e3214bd..d216ffbe 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -55,7 +55,6 @@
kVertexColorSpaceBenchGP_ClassID,
kGrBicubicEffect_ClassID,
kGrBitmapTextGeoProc_ClassID,
- kGrCircleBlurFragmentProcessor_ClassID,
kGrColorSpaceXformEffect_ClassID,
kGrConfigConversionEffect_ClassID,
kGrConicEffect_ClassID,
@@ -82,8 +81,6 @@
kGrPerlinNoise2Effect_ClassID,
kGrPipelineDynamicStateTestProcessor_ClassID,
kGrQuadEffect_ClassID,
- kGrRectBlurEffect_ClassID,
- kGrRRectBlurEffect_ClassID,
kGrRRectShadowGeoProc_ClassID,
kGrSkSLFP_ClassID,
kGrSpecularLightingEffect_ClassID,
diff --git a/src/gpu/GrProcessorUnitTest.cpp b/src/gpu/GrProcessorUnitTest.cpp
index 3df9cea..3a6f0d1 100644
--- a/src/gpu/GrProcessorUnitTest.cpp
+++ b/src/gpu/GrProcessorUnitTest.cpp
@@ -146,7 +146,7 @@
* we verify the count is as expected. If a new factory is added, then these numbers must be
* manually adjusted.
*/
-static constexpr int kFPFactoryCount = 21;
+static constexpr int kFPFactoryCount = 18;
static constexpr int kGPFactoryCount = 14;
static constexpr int kXPFactoryCount = 4;
diff --git a/src/gpu/effects/GrCircleBlurFragmentProcessor.fp b/src/gpu/effects/GrCircleBlurFragmentProcessor.fp
deleted file mode 100644
index 86d7cf6..0000000
--- a/src/gpu/effects/GrCircleBlurFragmentProcessor.fp
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-in fragmentProcessor inputFP;
-in half4 circleRect;
-in half solidRadius;
-in half textureRadius;
-in fragmentProcessor blurProfile;
-
-@header {
- #include "src/gpu/effects/GrTextureEffect.h"
-}
-
-// The data is formatted as:
-// x, y - the center of the circle
-// z - inner radius that should map to 0th entry in the texture.
-// w - the inverse of the distance over which the texture is stretched.
-uniform half4 circleData;
-
-@optimizationFlags {
- ProcessorOptimizationFlags(inputFP.get()) & kCompatibleWithCoverageAsAlpha_OptimizationFlag
-}
-
-@make {
- static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext*, const SkRect& circle,
- float sigma);
-}
-
-@setData(data) {
- data.set4f(circleData, circleRect.centerX(), circleRect.centerY(), solidRadius,
- 1.f / textureRadius);
-}
-
-@cpp {
- #include "include/gpu/GrRecordingContext.h"
- #include "src/core/SkGpuBlurUtils.h"
- #include "src/gpu/GrProxyProvider.h"
- #include "src/gpu/GrRecordingContextPriv.h"
- #include "src/gpu/GrThreadSafeCache.h"
- #include "src/gpu/SkGr.h"
-
- // Computes an unnormalized half kernel (right side). Returns the summation of all the half
- // kernel values.
- static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) {
- const float invSigma = 1.f / sigma;
- const float b = -0.5f * invSigma * invSigma;
- float tot = 0.0f;
- // Compute half kernel values at half pixel steps out from the center.
- float t = 0.5f;
- for (int i = 0; i < halfKernelSize; ++i) {
- float value = expf(t * t * b);
- tot += value;
- halfKernel[i] = value;
- t += 1.f;
- }
- return tot;
- }
-
- // Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number
- // of discrete steps. The half kernel is normalized to sum to 0.5.
- static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel,
- int halfKernelSize, float sigma) {
- // The half kernel should sum to 0.5 not 1.0.
- const float tot = 2.f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma);
- float sum = 0.f;
- for (int i = 0; i < halfKernelSize; ++i) {
- halfKernel[i] /= tot;
- sum += halfKernel[i];
- summedHalfKernel[i] = sum;
- }
- }
-
- // Applies the 1D half kernel vertically at points along the x axis to a circle centered at the
- // origin with radius circleR.
- void apply_kernel_in_y(float* results, int numSteps, float firstX, float circleR,
- int halfKernelSize, const float* summedHalfKernelTable) {
- float x = firstX;
- for (int i = 0; i < numSteps; ++i, x += 1.f) {
- if (x < -circleR || x > circleR) {
- results[i] = 0;
- continue;
- }
- float y = sqrtf(circleR * circleR - x * x);
- // In the column at x we exit the circle at +y and -y
- // The summed table entry j is actually reflects an offset of j + 0.5.
- y -= 0.5f;
- int yInt = SkScalarFloorToInt(y);
- SkASSERT(yInt >= -1);
- if (y < 0) {
- results[i] = (y + 0.5f) * summedHalfKernelTable[0];
- } else if (yInt >= halfKernelSize - 1) {
- results[i] = 0.5f;
- } else {
- float yFrac = y - yInt;
- results[i] = (1.f - yFrac) * summedHalfKernelTable[yInt] +
- yFrac * summedHalfKernelTable[yInt + 1];
- }
- }
- }
-
- // Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR.
- // This relies on having a half kernel computed for the Gaussian and a table of applications of
- // the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX +
- // halfKernel) passed in as yKernelEvaluations.
- static uint8_t eval_at(float evalX, float circleR, const float* halfKernel, int halfKernelSize,
- const float* yKernelEvaluations) {
- float acc = 0;
-
- float x = evalX - halfKernelSize;
- for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
- if (x < -circleR || x > circleR) {
- continue;
- }
- float verticalEval = yKernelEvaluations[i];
- acc += verticalEval * halfKernel[halfKernelSize - i - 1];
- }
- for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
- if (x < -circleR || x > circleR) {
- continue;
- }
- float verticalEval = yKernelEvaluations[i + halfKernelSize];
- acc += verticalEval * halfKernel[i];
- }
- // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about
- // the x axis).
- return SkUnitScalarClampToByte(2.f * acc);
- }
-
- // This function creates a profile of a blurred circle. It does this by computing a kernel for
- // half the Gaussian and a matching summed area table. The summed area table is used to compute
- // an array of vertical applications of the half kernel to the circle along the x axis. The
- // table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is
- // the size of the profile being computed. Then for each of the n profile entries we walk out k
- // steps in each horizontal direction multiplying the corresponding y evaluation by the half
- // kernel entry and sum these values to compute the profile entry.
- static void create_circle_profile(uint8_t* weights, float sigma, float circleR,
- int profileTextureWidth) {
- const int numSteps = profileTextureWidth;
-
- // The full kernel is 6 sigmas wide.
- int halfKernelSize = SkScalarCeilToInt(6.0f * sigma);
- // round up to next multiple of 2 and then divide by 2
- halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
-
- // Number of x steps at which to apply kernel in y to cover all the profile samples in x.
- int numYSteps = numSteps + 2 * halfKernelSize;
-
- SkAutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps);
- float* halfKernel = bulkAlloc.get();
- float* summedKernel = bulkAlloc.get() + halfKernelSize;
- float* yEvals = bulkAlloc.get() + 2 * halfKernelSize;
- make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma);
-
- float firstX = -halfKernelSize + 0.5f;
- apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summedKernel);
-
- for (int i = 0; i < numSteps - 1; ++i) {
- float evalX = i + 0.5f;
- weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i);
- }
- // Ensure the tail of the Gaussian goes to zero.
- weights[numSteps - 1] = 0;
- }
-
- static void create_half_plane_profile(uint8_t* profile, int profileWidth) {
- SkASSERT(!(profileWidth & 0x1));
- // The full kernel is 6 sigmas wide.
- float sigma = profileWidth / 6.f;
- int halfKernelSize = profileWidth / 2;
-
- SkAutoTArray<float> halfKernel(halfKernelSize);
-
- // The half kernel should sum to 0.5.
- const float tot = 2.f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize,
- sigma);
- float sum = 0.f;
- // Populate the profile from the right edge to the middle.
- for (int i = 0; i < halfKernelSize; ++i) {
- halfKernel[halfKernelSize - i - 1] /= tot;
- sum += halfKernel[halfKernelSize - i - 1];
- profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum);
- }
- // Populate the profile from the middle to the left edge (by flipping the half kernel and
- // continuing the summation).
- for (int i = 0; i < halfKernelSize; ++i) {
- sum += halfKernel[i];
- profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum);
- }
- // Ensure tail goes to 0.
- profile[profileWidth - 1] = 0;
- }
-
- static std::unique_ptr<GrFragmentProcessor> create_profile_effect(GrRecordingContext* rContext,
- const SkRect& circle,
- float sigma,
- float* solidRadius,
- float* textureRadius) {
- float circleR = circle.width() / 2.0f;
- if (!sk_float_isfinite(circleR) || circleR < SK_ScalarNearlyZero) {
- return nullptr;
- }
-
- auto threadSafeCache = rContext->priv().threadSafeCache();
-
- // Profile textures are cached by the ratio of sigma to circle radius and by the size of the
- // profile texture (binned by powers of 2).
- SkScalar sigmaToCircleRRatio = sigma / circleR;
- // When sigma is really small this becomes a equivalent to convolving a Gaussian with a
- // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the
- // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet
- // implemented this latter optimization.
- sigmaToCircleRRatio = std::min(sigmaToCircleRRatio, 8.f);
- SkFixed sigmaToCircleRRatioFixed;
- static const SkScalar kHalfPlaneThreshold = 0.1f;
- bool useHalfPlaneApprox = false;
- if (sigmaToCircleRRatio <= kHalfPlaneThreshold) {
- useHalfPlaneApprox = true;
- sigmaToCircleRRatioFixed = 0;
- *solidRadius = circleR - 3 * sigma;
- *textureRadius = 6 * sigma;
- } else {
- // Convert to fixed point for the key.
- sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio);
- // We shave off some bits to reduce the number of unique entries. We could probably
- // shave off more than we do.
- sigmaToCircleRRatioFixed &= ~0xff;
- sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed);
- sigma = circleR * sigmaToCircleRRatio;
- *solidRadius = 0;
- *textureRadius = circleR + 3 * sigma;
- }
-
- static constexpr int kProfileTextureWidth = 512;
- // This would be kProfileTextureWidth/textureRadius if it weren't for the fact that we do
- // the calculation of the profile coord in a coord space that has already been scaled by
- // 1 / textureRadius. This is done to avoid overflow in length().
- SkMatrix texM = SkMatrix::Scale(kProfileTextureWidth, 1.f);
-
- static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
- GrUniqueKey key;
- GrUniqueKey::Builder builder(&key, kDomain, 1, "1-D Circular Blur");
- builder[0] = sigmaToCircleRRatioFixed;
- builder.finish();
-
- GrSurfaceProxyView profileView = threadSafeCache->find(key);
- if (profileView) {
- SkASSERT(profileView.asTextureProxy());
- SkASSERT(profileView.origin() == kTopLeft_GrSurfaceOrigin);
- return GrTextureEffect::Make(std::move(profileView), kPremul_SkAlphaType, texM);
- }
-
- SkBitmap bm;
- if (!bm.tryAllocPixels(SkImageInfo::MakeA8(kProfileTextureWidth, 1))) {
- return nullptr;
- }
-
- if (useHalfPlaneApprox) {
- create_half_plane_profile(bm.getAddr8(0, 0), kProfileTextureWidth);
- } else {
- // Rescale params to the size of the texture we're creating.
- SkScalar scale = kProfileTextureWidth / *textureRadius;
- create_circle_profile(bm.getAddr8(0, 0), sigma * scale, circleR * scale,
- kProfileTextureWidth);
- }
- bm.setImmutable();
-
- profileView = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bm));
- if (!profileView) {
- return nullptr;
- }
-
- profileView = threadSafeCache->add(key, profileView);
- return GrTextureEffect::Make(std::move(profileView), kPremul_SkAlphaType, texM);
- }
-
- std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::Make(
- std::unique_ptr<GrFragmentProcessor> inputFP, GrRecordingContext* context,
- const SkRect& circle, float sigma) {
- if (SkGpuBlurUtils::IsEffectivelyZeroSigma(sigma)) {
- return inputFP;
- }
-
- float solidRadius;
- float textureRadius;
- std::unique_ptr<GrFragmentProcessor> profile = create_profile_effect(context, circle, sigma,
- &solidRadius, &textureRadius);
- if (!profile) {
- return nullptr;
- }
- return std::unique_ptr<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(
- std::move(inputFP), circle, solidRadius, textureRadius, std::move(profile)));
- }
-}
-
-half4 main() {
- // We just want to compute "(length(vec) - circleData.z + 0.5) * circleData.w" but need to
- // rearrange to avoid passing large values to length() that would overflow.
- half2 vec = half2((sk_FragCoord.xy - circleData.xy) * circleData.w);
- half dist = length(vec) + (0.5 - circleData.z) * circleData.w;
- half4 inputColor = sample(inputFP);
- return inputColor * sample(blurProfile, half2(dist, 0.5)).a;
-}
-
-@test(testData) {
- SkScalar wh = testData->fRandom->nextRangeScalar(100.f, 1000.f);
- SkScalar sigma = testData->fRandom->nextRangeF(1.f, 10.f);
- SkRect circle = SkRect::MakeWH(wh, wh);
- return GrCircleBlurFragmentProcessor::Make(testData->inputFP(), testData->context(),
- circle, sigma);
-}
diff --git a/src/gpu/effects/GrRRectBlurEffect.fp b/src/gpu/effects/GrRRectBlurEffect.fp
deleted file mode 100644
index 7edf8b8..0000000
--- a/src/gpu/effects/GrRRectBlurEffect.fp
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-in fragmentProcessor inputFP;
-in float sigma;
-in float4 rect;
-in uniform half cornerRadius;
-in fragmentProcessor ninePatchFP;
-uniform float4 proxyRect;
-uniform half blurRadius;
-
-@header {
- #include "include/core/SkRect.h"
- class GrRecordingContext;
- class SkRRect;
-}
-
-@optimizationFlags {
- ProcessorOptimizationFlags(inputFP.get()) & kCompatibleWithCoverageAsAlpha_OptimizationFlag
-}
-
-@make {
- static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext* context,
- float sigma,
- float xformedSigma,
- const SkRRect& srcRRect,
- const SkRRect& devRRect);
-}
-
-@cpp {
- #include "include/gpu/GrDirectContext.h"
- #include "include/gpu/GrRecordingContext.h"
- #include "src/core/SkAutoMalloc.h"
- #include "src/core/SkGpuBlurUtils.h"
- #include "src/core/SkRRectPriv.h"
- #include "src/gpu/GrCaps.h"
- #include "src/gpu/GrDirectContextPriv.h"
- #include "src/gpu/GrPaint.h"
- #include "src/gpu/GrProxyProvider.h"
- #include "src/gpu/GrRecordingContextPriv.h"
- #include "src/gpu/GrStyle.h"
- #include "src/gpu/GrSurfaceDrawContext.h"
- #include "src/gpu/GrThreadSafeCache.h"
- #include "src/gpu/SkGr.h"
- #include "src/gpu/effects/GrTextureEffect.h"
-
- static constexpr auto kBlurredRRectMaskOrigin = kTopLeft_GrSurfaceOrigin;
-
- static void make_blurred_rrect_key(GrUniqueKey* key,
- const SkRRect& rrectToDraw,
- float xformedSigma) {
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
- static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
-
- GrUniqueKey::Builder builder(key, kDomain, 9, "RoundRect Blur Mask");
- builder[0] = SkScalarCeilToInt(xformedSigma-1/6.0f);
-
- int index = 1;
- // TODO: this is overkill for _simple_ circular rrects
- for (auto c : { SkRRect::kUpperLeft_Corner, SkRRect::kUpperRight_Corner,
- SkRRect::kLowerRight_Corner, SkRRect::kLowerLeft_Corner }) {
- SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) &&
- SkScalarIsInt(rrectToDraw.radii(c).fY));
- builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX);
- builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY);
- }
- builder.finish();
- }
-
- static bool fillin_view_on_gpu(
- GrDirectContext* dContext,
- const GrSurfaceProxyView& lazyView,
- sk_sp<GrThreadSafeCache::Trampoline> trampoline,
- const SkRRect& rrectToDraw,
- const SkISize& dimensions,
- float xformedSigma) {
-#if GR_OGA
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
-
- // We cache blur masks. Use default surface props here so we can use the same cached mask
- // regardless of the final dst surface.
- SkSurfaceProps defaultSurfaceProps;
-
- std::unique_ptr<GrSurfaceDrawContext> rtc = GrSurfaceDrawContext::MakeWithFallback(
- dContext, GrColorType::kAlpha_8, nullptr, SkBackingFit::kExact, dimensions,
- defaultSurfaceProps, 1, GrMipmapped::kNo, GrProtected::kNo,
- kBlurredRRectMaskOrigin);
- if (!rtc) {
- return false;
- }
-
- GrPaint paint;
-
- rtc->clear(SK_PMColor4fTRANSPARENT);
- rtc->drawRRect(nullptr, std::move(paint), GrAA::kYes, SkMatrix::I(), rrectToDraw,
- GrStyle::SimpleFill());
-
- GrSurfaceProxyView srcView = rtc->readSurfaceView();
- SkASSERT(srcView.asTextureProxy());
- auto rtc2 = SkGpuBlurUtils::GaussianBlur(dContext,
- std::move(srcView),
- rtc->colorInfo().colorType(),
- rtc->colorInfo().alphaType(),
- nullptr,
- SkIRect::MakeSize(dimensions),
- SkIRect::MakeSize(dimensions),
- xformedSigma,
- xformedSigma,
- SkTileMode::kClamp,
- SkBackingFit::kExact);
- if (!rtc2 || !rtc2->readSurfaceView()) {
- return false;
- }
-
- auto view = rtc2->readSurfaceView();
- SkASSERT(view.swizzle() == lazyView.swizzle());
- SkASSERT(view.origin() == lazyView.origin());
- trampoline->fProxy = view.asTextureProxyRef();
-
- return true;
-#else
- return false;
-#endif
- }
-
- // Evaluate the vertical blur at the specified 'y' value given the location of the top of the
- // rrect.
- static uint8_t eval_V(float top, int y,
- const uint8_t* integral, int integralSize, float sixSigma) {
- if (top < 0) {
- return 0; // an empty column
- }
-
- float fT = (top - y - 0.5f) * (integralSize/sixSigma);
- if (fT < 0) {
- return 255;
- } else if (fT >= integralSize-1) {
- return 0;
- }
-
- int lower = (int) fT;
- float frac = fT - lower;
-
- SkASSERT(lower+1 < integralSize);
-
- return integral[lower] * (1.0f-frac) + integral[lower+1] * frac;
- }
-
- // Apply a gaussian 'kernel' horizontally at the specified 'x', 'y' location.
- static uint8_t eval_H(int x, int y, const std::vector<float>& topVec,
- const float* kernel, int kernelSize,
- const uint8_t* integral, int integralSize, float sixSigma) {
- SkASSERT(0 <= x && x < (int) topVec.size());
- SkASSERT(kernelSize % 2);
-
- float accum = 0.0f;
-
- int xSampleLoc = x - (kernelSize / 2);
- for (int i = 0; i < kernelSize; ++i, ++xSampleLoc) {
- if (xSampleLoc < 0 || xSampleLoc >= (int) topVec.size()) {
- continue;
- }
-
- accum += kernel[i] * eval_V(topVec[xSampleLoc], y, integral, integralSize, sixSigma);
- }
-
- return accum + 0.5f;
- }
-
- // Create a cpu-side blurred-rrect mask that is close to the version the gpu would've produced.
- // The match needs to be close bc the cpu- and gpu-generated version must be interchangeable.
- static GrSurfaceProxyView create_mask_on_cpu(GrRecordingContext* rContext,
- const SkRRect& rrectToDraw,
- const SkISize& dimensions,
- float xformedSigma) {
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
- int radius = SkGpuBlurUtils::SigmaRadius(xformedSigma);
- int kernelSize = 2*radius + 1;
-
- SkASSERT(kernelSize %2);
- SkASSERT(dimensions.width() % 2);
- SkASSERT(dimensions.height() % 2);
-
- SkVector radii = rrectToDraw.getSimpleRadii();
- SkASSERT(SkScalarNearlyEqual(radii.fX, radii.fY));
-
- const int halfWidthPlus1 = (dimensions.width() / 2) + 1;
- const int halfHeightPlus1 = (dimensions.height() / 2) + 1;
-
- std::unique_ptr<float[]> kernel(new float[kernelSize]);
-
- SkGpuBlurUtils::Compute1DGaussianKernel(kernel.get(), xformedSigma, radius);
-
- SkBitmap integral;
- if (!SkGpuBlurUtils::CreateIntegralTable(6*xformedSigma, &integral)) {
- return {};
- }
-
- SkBitmap result;
- if (!result.tryAllocPixels(SkImageInfo::MakeA8(dimensions.width(), dimensions.height()))) {
- return {};
- }
-
- std::vector<float> topVec;
- topVec.reserve(dimensions.width());
- for (int x = 0; x < dimensions.width(); ++x) {
- if (x < rrectToDraw.rect().fLeft || x > rrectToDraw.rect().fRight) {
- topVec.push_back(-1);
- } else {
- if (x+0.5f < rrectToDraw.rect().fLeft + radii.fX) { // in the circular section
- float xDist = rrectToDraw.rect().fLeft + radii.fX - x - 0.5f;
- float h = sqrtf(radii.fX * radii.fX - xDist * xDist);
- SkASSERT(0 <= h && h < radii.fY);
- topVec.push_back(rrectToDraw.rect().fTop+radii.fX-h + 3*xformedSigma);
- } else {
- topVec.push_back(rrectToDraw.rect().fTop + 3*xformedSigma);
- }
- }
- }
-
- for (int y = 0; y < halfHeightPlus1; ++y) {
- uint8_t* scanline = result.getAddr8(0, y);
-
- for (int x = 0; x < halfWidthPlus1; ++x) {
- scanline[x] = eval_H(x, y, topVec,
- kernel.get(), kernelSize,
- integral.getAddr8(0, 0), integral.width(), 6*xformedSigma);
- scanline[dimensions.width()-x-1] = scanline[x];
- }
-
- memcpy(result.getAddr8(0, dimensions.height()-y-1), scanline, result.rowBytes());
- }
-
- result.setImmutable();
-
- auto view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, result));
- if (!view) {
- return {};
- }
-
- SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
- return view;
- }
-
- static std::unique_ptr<GrFragmentProcessor> find_or_create_rrect_blur_mask_fp(
- GrRecordingContext* rContext,
- const SkRRect& rrectToDraw,
- const SkISize& dimensions,
- float xformedSigma) {
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
- GrUniqueKey key;
- make_blurred_rrect_key(&key, rrectToDraw, xformedSigma);
-
- auto threadSafeCache = rContext->priv().threadSafeCache();
-
- // It seems like we could omit this matrix and modify the shader code to not normalize
- // the coords used to sample the texture effect. However, the "proxyDims" value in the
- // shader is not always the actual the proxy dimensions. This is because 'dimensions' here
- // was computed using integer corner radii as determined in
- // SkComputeBlurredRRectParams whereas the shader code uses the float radius to compute
- // 'proxyDims'. Why it draws correctly with these unequal values is a mystery for the ages.
- auto m = SkMatrix::Scale(dimensions.width(), dimensions.height());
-
- GrSurfaceProxyView view;
-
- if (GrDirectContext* dContext = rContext->asDirectContext()) {
- // The gpu thread gets priority over the recording threads. If the gpu thread is first,
- // it crams a lazy proxy into the cache and then fills it in later.
- auto[lazyView, trampoline] = GrThreadSafeCache::CreateLazyView(
- dContext, GrColorType::kAlpha_8, dimensions,
- kBlurredRRectMaskOrigin, SkBackingFit::kExact);
- if (!lazyView) {
- return nullptr;
- }
-
- view = threadSafeCache->findOrAdd(key, lazyView);
- if (view != lazyView) {
- SkASSERT(view.asTextureProxy());
- SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
- return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
- }
-
- if (!fillin_view_on_gpu(dContext, lazyView, std::move(trampoline),
- rrectToDraw, dimensions, xformedSigma)) {
- // In this case something has gone disastrously wrong so set up to drop the draw
- // that needed this resource and reduce future pollution of the cache.
- threadSafeCache->remove(key);
- return nullptr;
- }
- } else {
- view = threadSafeCache->find(key);
- if (view) {
- SkASSERT(view.asTextureProxy());
- SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
- return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
- }
-
- view = create_mask_on_cpu(rContext, rrectToDraw, dimensions, xformedSigma);
- if (!view) {
- return nullptr;
- }
-
- view = threadSafeCache->add(key, view);
- }
-
- SkASSERT(view.asTextureProxy());
- SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
- return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
- }
-
- std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(
- std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext* context,
- float sigma,
- float xformedSigma,
- const SkRRect& srcRRect,
- const SkRRect& devRRect) {
- // Should've been caught up-stream
-#ifdef SK_DEBUG
- SkASSERTF(!SkRRectPriv::IsCircle(devRRect), "Unexpected circle. %d\n\t%s\n\t%s",
- SkRRectPriv::IsCircle(srcRRect),
- srcRRect.dumpToString(true).c_str(), devRRect.dumpToString(true).c_str());
- SkASSERTF(!devRRect.isRect(), "Unexpected rect. %d\n\t%s\n\t%s",
- srcRRect.isRect(),
- srcRRect.dumpToString(true).c_str(), devRRect.dumpToString(true).c_str());
-#endif
- // TODO: loosen this up
- if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
- return nullptr;
- }
-
- if (SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma)) {
- return inputFP;
- }
-
- // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
- // sufficiently small relative to both the size of the corner radius and the
- // width (and height) of the rrect.
- SkRRect rrectToDraw;
- SkISize dimensions;
- SkScalar ignored[SkGpuBlurUtils::kBlurRRectMaxDivisions];
-
- bool ninePatchable = SkGpuBlurUtils::ComputeBlurredRRectParams(srcRRect, devRRect,
- sigma, xformedSigma,
- &rrectToDraw, &dimensions,
- ignored, ignored,
- ignored, ignored);
- if (!ninePatchable) {
- return nullptr;
- }
-
- std::unique_ptr<GrFragmentProcessor> maskFP = find_or_create_rrect_blur_mask_fp(
- context, rrectToDraw, dimensions, xformedSigma);
- if (!maskFP) {
- return nullptr;
- }
-
- return std::unique_ptr<GrFragmentProcessor>(
- new GrRRectBlurEffect(std::move(inputFP), xformedSigma, devRRect.getBounds(),
- SkRRectPriv::GetSimpleRadii(devRRect).fX, std::move(maskFP)));
- }
-}
-
-@test(d) {
- SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f);
- SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f);
- SkScalar r = d->fRandom->nextRangeF(1.f, 9.f);
- SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f);
- SkRRect rrect;
- rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
- return GrRRectBlurEffect::Make(d->inputFP(), d->context(), sigma, sigma, rrect, rrect);
-}
-
-half4 main() {
- // Warp the fragment position to the appropriate part of the 9-patch blur texture by snipping
- // out the middle section of the proxy rect.
- float2 translatedFragPosFloat = sk_FragCoord.xy - proxyRect.LT;
- float2 proxyCenter = (proxyRect.RB - proxyRect.LT) * 0.5;
- half edgeSize = 2.0 * blurRadius + cornerRadius + 0.5;
-
- // Position the fragment so that (0, 0) marks the center of the proxy rectangle.
- // Negative coordinates are on the left/top side and positive numbers are on the right/bottom.
- translatedFragPosFloat -= proxyCenter;
-
- // Temporarily strip off the fragment's sign. x/y are now strictly increasing as we move away
- // from the center.
- half2 fragDirection = half2(sign(translatedFragPosFloat));
- translatedFragPosFloat = abs(translatedFragPosFloat);
-
- // Our goal is to snip out the "middle section" of the proxy rect (everything but the edge).
- // We've repositioned our fragment position so that (0, 0) is the centerpoint and x/y are always
- // positive, so we can subtract here and interpret negative results as being within the middle
- // section.
- half2 translatedFragPosHalf = half2(translatedFragPosFloat - (proxyCenter - edgeSize));
-
- // Remove the middle section by clamping to zero.
- translatedFragPosHalf = max(translatedFragPosHalf, 0);
-
- // Reapply the fragment's sign, so that negative coordinates once again mean left/top side and
- // positive means bottom/right side.
- translatedFragPosHalf *= fragDirection;
-
- // Offset the fragment so that (0, 0) marks the upper-left again, instead of the center point.
- translatedFragPosHalf += half2(edgeSize);
-
- half2 proxyDims = half2(2.0 * edgeSize);
- half2 texCoord = translatedFragPosHalf / proxyDims;
-
- return sample(inputFP) * sample(ninePatchFP, texCoord).a;
-}
-
-@setData(pdman) {
- float blurRadiusValue = 3.f * SkScalarCeilToScalar(sigma - 1 / 6.0f);
- pdman.set1f(blurRadius, blurRadiusValue);
-
- SkRect outset = rect;
- outset.outset(blurRadiusValue, blurRadiusValue);
- pdman.set4f(proxyRect, outset.fLeft, outset.fTop, outset.fRight, outset.fBottom);
-}
diff --git a/src/gpu/effects/GrRectBlurEffect.fp b/src/gpu/effects/GrRectBlurEffect.fp
deleted file mode 100644
index fc4d632..0000000
--- a/src/gpu/effects/GrRectBlurEffect.fp
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-@header {
-#include <cmath>
-#include "include/core/SkRect.h"
-#include "include/core/SkScalar.h"
-#include "include/gpu/GrRecordingContext.h"
-#include "src/core/SkBlurMask.h"
-#include "src/core/SkGpuBlurUtils.h"
-#include "src/core/SkMathPriv.h"
-#include "src/gpu/GrProxyProvider.h"
-#include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/GrShaderCaps.h"
-#include "src/gpu/GrThreadSafeCache.h"
-#include "src/gpu/SkGr.h"
-#include "src/gpu/effects/GrMatrixEffect.h"
-#include "src/gpu/effects/GrTextureEffect.h"
-}
-
-in uniform float4 rect;
-
-// Effect that is a LUT for integral of normal distribution. The value at x:[0,6*sigma] is the
-// integral from -inf to (3*sigma - x). I.e. x is mapped from [0, 6*sigma] to [3*sigma to -3*sigma].
-// The flip saves a reversal in the shader.
-in fragmentProcessor integral;
-
-// There is a fast variant of the effect that does 2 texture lookups and a more general one for
-// wider blurs relative to rect sizes that does 4.
-layout(key) in bool isFast;
-
-@optimizationFlags {
- kCompatibleWithCoverageAsAlpha_OptimizationFlag
-}
-
-@class {
-static std::unique_ptr<GrFragmentProcessor> MakeIntegralFP(GrRecordingContext* rContext,
- float sixSigma) {
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(sixSigma / 6.f));
- auto threadSafeCache = rContext->priv().threadSafeCache();
-
- int width = SkGpuBlurUtils::CreateIntegralTable(sixSigma, nullptr);
-
- static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
- GrUniqueKey key;
- GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask");
- builder[0] = width;
- builder.finish();
-
- SkMatrix m = SkMatrix::Scale(width/sixSigma, 1.f);
-
- GrSurfaceProxyView view = threadSafeCache->find(key);
-
- if (view) {
- SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
- return GrTextureEffect::Make(
- std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
- }
-
- SkBitmap bitmap;
- if (!SkGpuBlurUtils::CreateIntegralTable(sixSigma, &bitmap)) {
- return {};
- }
-
- view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bitmap));
- if (!view) {
- return {};
- }
-
- view = threadSafeCache->add(key, view);
-
- SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
- return GrTextureEffect::Make(
- std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
-}
-}
-
-@make {
- static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext* context,
- const GrShaderCaps& caps,
- const SkRect& srcRect,
- const SkMatrix& viewMatrix,
- float transformedSigma) {
- SkASSERT(viewMatrix.preservesRightAngles());
- SkASSERT(srcRect.isSorted());
-
- if (SkGpuBlurUtils::IsEffectivelyZeroSigma(transformedSigma)) {
- // No need to blur the rect
- return inputFP;
- }
-
- SkMatrix invM;
- SkRect rect;
- if (viewMatrix.rectStaysRect()) {
- invM = SkMatrix::I();
- // We can do everything in device space when the src rect projects to a rect in device
- // space.
- SkAssertResult(viewMatrix.mapRect(&rect, srcRect));
- } else {
- // The view matrix may scale, perhaps anisotropically. But we want to apply our device
- // space "transformedSigma" to the delta of frag coord from the rect edges. Factor out
- // the scaling to define a space that is purely rotation/translation from device space
- // (and scale from src space) We'll meet in the middle: pre-scale the src rect to be in
- // this space and then apply the inverse of the rotation/translation portion to the
- // frag coord.
- SkMatrix m;
- SkSize scale;
- if (!viewMatrix.decomposeScale(&scale, &m)) {
- return nullptr;
- }
- if (!m.invert(&invM)) {
- return nullptr;
- }
- rect = {srcRect.left() * scale.width(),
- srcRect.top() * scale.height(),
- srcRect.right() * scale.width(),
- srcRect.bottom() * scale.height()};
- }
-
- if (!caps.floatIs32Bits()) {
- // We promote the math that gets us into the Gaussian space to full float when the rect
- // coords are large. If we don't have full float then fail. We could probably clip the
- // rect to an outset device bounds instead.
- if (SkScalarAbs(rect.fLeft) > 16000.f ||
- SkScalarAbs(rect.fTop) > 16000.f ||
- SkScalarAbs(rect.fRight) > 16000.f ||
- SkScalarAbs(rect.fBottom) > 16000.f) {
- return nullptr;
- }
- }
-
- const float sixSigma = 6 * transformedSigma;
- std::unique_ptr<GrFragmentProcessor> integral = MakeIntegralFP(context, sixSigma);
- if (!integral) {
- return nullptr;
- }
-
- // In the fast variant we think of the midpoint of the integral texture as aligning
- // with the closest rect edge both in x and y. To simplify texture coord calculation we
- // inset the rect so that the edge of the inset rect corresponds to t = 0 in the texture.
- // It actually simplifies things a bit in the !isFast case, too.
- float threeSigma = sixSigma / 2;
- SkRect insetRect = {rect.left() + threeSigma,
- rect.top() + threeSigma,
- rect.right() - threeSigma,
- rect.bottom() - threeSigma};
-
- // In our fast variant we find the nearest horizontal and vertical edges and for each
- // do a lookup in the integral texture for each and multiply them. When the rect is
- // less than 6 sigma wide then things aren't so simple and we have to consider both the
- // left and right edge of the rectangle (and similar in y).
- bool isFast = insetRect.isSorted();
- std::unique_ptr<GrFragmentProcessor> fp(new GrRectBlurEffect(insetRect,
- std::move(integral),
- isFast));
- if (!invM.isIdentity()) {
- fp = GrMatrixEffect::Make(invM, std::move(fp));
- }
- fp = GrFragmentProcessor::DeviceSpace(std::move(fp));
- return GrFragmentProcessor::MulInputByChildAlpha(std::move(fp));
- }
-}
-
-half4 main(float2 pos) {
- half xCoverage, yCoverage;
- @if (isFast) {
- // Get the smaller of the signed distance from the frag coord to the left and right
- // edges and similar for y.
- // The integral texture goes "backwards" (from 3*sigma to -3*sigma), So, the below
- // computations align the left edge of the integral texture with the inset rect's edge
- // extending outward 6 * sigma from the inset rect.
- half2 xy = max(half2(rect.LT - pos), half2(pos - rect.RB));
- xCoverage = sample(integral, half2(xy.x, 0.5)).a;
- yCoverage = sample(integral, half2(xy.y, 0.5)).a;
- } else {
- // We just consider just the x direction here. In practice we compute x and y separately
- // and multiply them together.
- // We define our coord system so that the point at which we're evaluating a kernel
- // defined by the normal distribution (K) at 0. In this coord system let L be left
- // edge and R be the right edge of the rectangle.
- // We can calculate C by integrating K with the half infinite ranges outside the L to R
- // range and subtracting from 1:
- // C = 1 - <integral of K from from -inf to L> - <integral of K from R to inf>
- // K is symmetric about x=0 so:
- // C = 1 - <integral of K from from -inf to L> - <integral of K from -inf to -R>
-
- // The integral texture goes "backwards" (from 3*sigma to -3*sigma) which is factored
- // in to the below calculations.
- // Also, our rect uniform was pre-inset by 3 sigma from the actual rect being blurred,
- // also factored in.
- half4 rect = half4(half2(rect.LT - pos), half2(pos - rect.RB));
- xCoverage = 1 - sample(integral, half2(rect.L, 0.5)).a
- - sample(integral, half2(rect.R, 0.5)).a;
- yCoverage = 1 - sample(integral, half2(rect.T, 0.5)).a
- - sample(integral, half2(rect.B, 0.5)).a;
- }
- return half4(xCoverage * yCoverage);
-}
-
-@test(data) {
- float sigma = data->fRandom->nextRangeF(3, 8);
- int x = data->fRandom->nextRangeF(1, 200);
- int y = data->fRandom->nextRangeF(1, 200);
- float width = data->fRandom->nextRangeF(200, 300);
- float height = data->fRandom->nextRangeF(200, 300);
- SkMatrix vm = GrTest::TestMatrixPreservesRightAngles(data->fRandom);
- auto rect = SkRect::MakeXYWH(x, y, width, height);
- return GrRectBlurEffect::Make(data->inputFP(), data->context(), *data->caps()->shaderCaps(),
- rect, vm, sigma);
-}
diff --git a/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp b/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp
deleted file mode 100644
index 81c4527..0000000
--- a/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrCircleBlurFragmentProcessor.fp; do not modify.
- **************************************************************************************************/
-#include "GrCircleBlurFragmentProcessor.h"
-
-#include "include/gpu/GrRecordingContext.h"
-#include "src/core/SkGpuBlurUtils.h"
-#include "src/gpu/GrProxyProvider.h"
-#include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/GrThreadSafeCache.h"
-#include "src/gpu/SkGr.h"
-
-// Computes an unnormalized half kernel (right side). Returns the summation of all the half
-// kernel values.
-static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) {
- const float invSigma = 1.f / sigma;
- const float b = -0.5f * invSigma * invSigma;
- float tot = 0.0f;
- // Compute half kernel values at half pixel steps out from the center.
- float t = 0.5f;
- for (int i = 0; i < halfKernelSize; ++i) {
- float value = expf(t * t * b);
- tot += value;
- halfKernel[i] = value;
- t += 1.f;
- }
- return tot;
-}
-
-// Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number
-// of discrete steps. The half kernel is normalized to sum to 0.5.
-static void make_half_kernel_and_summed_table(float* halfKernel,
- float* summedHalfKernel,
- int halfKernelSize,
- float sigma) {
- // The half kernel should sum to 0.5 not 1.0.
- const float tot = 2.f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma);
- float sum = 0.f;
- for (int i = 0; i < halfKernelSize; ++i) {
- halfKernel[i] /= tot;
- sum += halfKernel[i];
- summedHalfKernel[i] = sum;
- }
-}
-
-// Applies the 1D half kernel vertically at points along the x axis to a circle centered at the
-// origin with radius circleR.
-void apply_kernel_in_y(float* results,
- int numSteps,
- float firstX,
- float circleR,
- int halfKernelSize,
- const float* summedHalfKernelTable) {
- float x = firstX;
- for (int i = 0; i < numSteps; ++i, x += 1.f) {
- if (x < -circleR || x > circleR) {
- results[i] = 0;
- continue;
- }
- float y = sqrtf(circleR * circleR - x * x);
- // In the column at x we exit the circle at +y and -y
- // The summed table entry j is actually reflects an offset of j + 0.5.
- y -= 0.5f;
- int yInt = SkScalarFloorToInt(y);
- SkASSERT(yInt >= -1);
- if (y < 0) {
- results[i] = (y + 0.5f) * summedHalfKernelTable[0];
- } else if (yInt >= halfKernelSize - 1) {
- results[i] = 0.5f;
- } else {
- float yFrac = y - yInt;
- results[i] = (1.f - yFrac) * summedHalfKernelTable[yInt] +
- yFrac * summedHalfKernelTable[yInt + 1];
- }
- }
-}
-
-// Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR.
-// This relies on having a half kernel computed for the Gaussian and a table of applications of
-// the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX +
-// halfKernel) passed in as yKernelEvaluations.
-static uint8_t eval_at(float evalX,
- float circleR,
- const float* halfKernel,
- int halfKernelSize,
- const float* yKernelEvaluations) {
- float acc = 0;
-
- float x = evalX - halfKernelSize;
- for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
- if (x < -circleR || x > circleR) {
- continue;
- }
- float verticalEval = yKernelEvaluations[i];
- acc += verticalEval * halfKernel[halfKernelSize - i - 1];
- }
- for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
- if (x < -circleR || x > circleR) {
- continue;
- }
- float verticalEval = yKernelEvaluations[i + halfKernelSize];
- acc += verticalEval * halfKernel[i];
- }
- // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about
- // the x axis).
- return SkUnitScalarClampToByte(2.f * acc);
-}
-
-// This function creates a profile of a blurred circle. It does this by computing a kernel for
-// half the Gaussian and a matching summed area table. The summed area table is used to compute
-// an array of vertical applications of the half kernel to the circle along the x axis. The
-// table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is
-// the size of the profile being computed. Then for each of the n profile entries we walk out k
-// steps in each horizontal direction multiplying the corresponding y evaluation by the half
-// kernel entry and sum these values to compute the profile entry.
-static void create_circle_profile(uint8_t* weights,
- float sigma,
- float circleR,
- int profileTextureWidth) {
- const int numSteps = profileTextureWidth;
-
- // The full kernel is 6 sigmas wide.
- int halfKernelSize = SkScalarCeilToInt(6.0f * sigma);
- // round up to next multiple of 2 and then divide by 2
- halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
-
- // Number of x steps at which to apply kernel in y to cover all the profile samples in x.
- int numYSteps = numSteps + 2 * halfKernelSize;
-
- SkAutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps);
- float* halfKernel = bulkAlloc.get();
- float* summedKernel = bulkAlloc.get() + halfKernelSize;
- float* yEvals = bulkAlloc.get() + 2 * halfKernelSize;
- make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma);
-
- float firstX = -halfKernelSize + 0.5f;
- apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summedKernel);
-
- for (int i = 0; i < numSteps - 1; ++i) {
- float evalX = i + 0.5f;
- weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i);
- }
- // Ensure the tail of the Gaussian goes to zero.
- weights[numSteps - 1] = 0;
-}
-
-static void create_half_plane_profile(uint8_t* profile, int profileWidth) {
- SkASSERT(!(profileWidth & 0x1));
- // The full kernel is 6 sigmas wide.
- float sigma = profileWidth / 6.f;
- int halfKernelSize = profileWidth / 2;
-
- SkAutoTArray<float> halfKernel(halfKernelSize);
-
- // The half kernel should sum to 0.5.
- const float tot = 2.f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, sigma);
- float sum = 0.f;
- // Populate the profile from the right edge to the middle.
- for (int i = 0; i < halfKernelSize; ++i) {
- halfKernel[halfKernelSize - i - 1] /= tot;
- sum += halfKernel[halfKernelSize - i - 1];
- profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum);
- }
- // Populate the profile from the middle to the left edge (by flipping the half kernel and
- // continuing the summation).
- for (int i = 0; i < halfKernelSize; ++i) {
- sum += halfKernel[i];
- profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum);
- }
- // Ensure tail goes to 0.
- profile[profileWidth - 1] = 0;
-}
-
-static std::unique_ptr<GrFragmentProcessor> create_profile_effect(GrRecordingContext* rContext,
- const SkRect& circle,
- float sigma,
- float* solidRadius,
- float* textureRadius) {
- float circleR = circle.width() / 2.0f;
- if (!sk_float_isfinite(circleR) || circleR < SK_ScalarNearlyZero) {
- return nullptr;
- }
-
- auto threadSafeCache = rContext->priv().threadSafeCache();
-
- // Profile textures are cached by the ratio of sigma to circle radius and by the size of the
- // profile texture (binned by powers of 2).
- SkScalar sigmaToCircleRRatio = sigma / circleR;
- // When sigma is really small this becomes a equivalent to convolving a Gaussian with a
- // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the
- // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet
- // implemented this latter optimization.
- sigmaToCircleRRatio = std::min(sigmaToCircleRRatio, 8.f);
- SkFixed sigmaToCircleRRatioFixed;
- static const SkScalar kHalfPlaneThreshold = 0.1f;
- bool useHalfPlaneApprox = false;
- if (sigmaToCircleRRatio <= kHalfPlaneThreshold) {
- useHalfPlaneApprox = true;
- sigmaToCircleRRatioFixed = 0;
- *solidRadius = circleR - 3 * sigma;
- *textureRadius = 6 * sigma;
- } else {
- // Convert to fixed point for the key.
- sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio);
- // We shave off some bits to reduce the number of unique entries. We could probably
- // shave off more than we do.
- sigmaToCircleRRatioFixed &= ~0xff;
- sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed);
- sigma = circleR * sigmaToCircleRRatio;
- *solidRadius = 0;
- *textureRadius = circleR + 3 * sigma;
- }
-
- static constexpr int kProfileTextureWidth = 512;
- // This would be kProfileTextureWidth/textureRadius if it weren't for the fact that we do
- // the calculation of the profile coord in a coord space that has already been scaled by
- // 1 / textureRadius. This is done to avoid overflow in length().
- SkMatrix texM = SkMatrix::Scale(kProfileTextureWidth, 1.f);
-
- static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
- GrUniqueKey key;
- GrUniqueKey::Builder builder(&key, kDomain, 1, "1-D Circular Blur");
- builder[0] = sigmaToCircleRRatioFixed;
- builder.finish();
-
- GrSurfaceProxyView profileView = threadSafeCache->find(key);
- if (profileView) {
- SkASSERT(profileView.asTextureProxy());
- SkASSERT(profileView.origin() == kTopLeft_GrSurfaceOrigin);
- return GrTextureEffect::Make(std::move(profileView), kPremul_SkAlphaType, texM);
- }
-
- SkBitmap bm;
- if (!bm.tryAllocPixels(SkImageInfo::MakeA8(kProfileTextureWidth, 1))) {
- return nullptr;
- }
-
- if (useHalfPlaneApprox) {
- create_half_plane_profile(bm.getAddr8(0, 0), kProfileTextureWidth);
- } else {
- // Rescale params to the size of the texture we're creating.
- SkScalar scale = kProfileTextureWidth / *textureRadius;
- create_circle_profile(
- bm.getAddr8(0, 0), sigma * scale, circleR * scale, kProfileTextureWidth);
- }
- bm.setImmutable();
-
- profileView = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bm));
- if (!profileView) {
- return nullptr;
- }
-
- profileView = threadSafeCache->add(key, profileView);
- return GrTextureEffect::Make(std::move(profileView), kPremul_SkAlphaType, texM);
-}
-
-std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::Make(
- std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext* context,
- const SkRect& circle,
- float sigma) {
- if (SkGpuBlurUtils::IsEffectivelyZeroSigma(sigma)) {
- return inputFP;
- }
-
- float solidRadius;
- float textureRadius;
- std::unique_ptr<GrFragmentProcessor> profile =
- create_profile_effect(context, circle, sigma, &solidRadius, &textureRadius);
- if (!profile) {
- return nullptr;
- }
- return std::unique_ptr<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(
- std::move(inputFP), circle, solidRadius, textureRadius, std::move(profile)));
-}
-#include "src/core/SkUtils.h"
-#include "src/gpu/GrTexture.h"
-#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
-#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
-#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
-#include "src/sksl/SkSLCPP.h"
-#include "src/sksl/SkSLUtil.h"
-class GrGLSLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor {
-public:
- GrGLSLCircleBlurFragmentProcessor() {}
- void emitCode(EmitArgs& args) override {
- GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
- const GrCircleBlurFragmentProcessor& _outer =
- args.fFp.cast<GrCircleBlurFragmentProcessor>();
- (void)_outer;
- auto circleRect = _outer.circleRect;
- (void)circleRect;
- auto solidRadius = _outer.solidRadius;
- (void)solidRadius;
- auto textureRadius = _outer.textureRadius;
- (void)textureRadius;
- circleDataVar = args.fUniformHandler->addUniform(
- &_outer, kFragment_GrShaderFlag, kHalf4_GrSLType, "circleData");
- fragBuilder->codeAppendf(
- R"SkSL(half2 vec = half2((sk_FragCoord.xy - float2(%s.xy)) * float(%s.w));
-half dist = length(vec) + (0.5 - %s.z) * %s.w;)SkSL",
- args.fUniformHandler->getUniformCStr(circleDataVar),
- args.fUniformHandler->getUniformCStr(circleDataVar),
- args.fUniformHandler->getUniformCStr(circleDataVar),
- args.fUniformHandler->getUniformCStr(circleDataVar));
- SkString _sample0 = this->invokeChild(0, args);
- fragBuilder->codeAppendf(
- R"SkSL(
-half4 inputColor = %s;)SkSL",
- _sample0.c_str());
- SkString _coords1("float2(half2(dist, 0.5))");
- SkString _sample1 = this->invokeChild(1, args, _coords1.c_str());
- fragBuilder->codeAppendf(
- R"SkSL(
-return inputColor * %s.w;
-)SkSL",
- _sample1.c_str());
- }
-
-private:
- void onSetData(const GrGLSLProgramDataManager& data,
- const GrFragmentProcessor& _proc) override {
- const GrCircleBlurFragmentProcessor& _outer = _proc.cast<GrCircleBlurFragmentProcessor>();
- auto circleRect = _outer.circleRect;
- (void)circleRect;
- auto solidRadius = _outer.solidRadius;
- (void)solidRadius;
- auto textureRadius = _outer.textureRadius;
- (void)textureRadius;
- UniformHandle& circleData = circleDataVar;
- (void)circleData;
-
- data.set4f(circleData,
- circleRect.centerX(),
- circleRect.centerY(),
- solidRadius,
- 1.f / textureRadius);
- }
- UniformHandle circleDataVar;
-};
-std::unique_ptr<GrGLSLFragmentProcessor> GrCircleBlurFragmentProcessor::onMakeProgramImpl() const {
- return std::make_unique<GrGLSLCircleBlurFragmentProcessor>();
-}
-void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
- GrProcessorKeyBuilder* b) const {}
-bool GrCircleBlurFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const {
- const GrCircleBlurFragmentProcessor& that = other.cast<GrCircleBlurFragmentProcessor>();
- (void)that;
- if (circleRect != that.circleRect) return false;
- if (solidRadius != that.solidRadius) return false;
- if (textureRadius != that.textureRadius) return false;
- return true;
-}
-GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(
- const GrCircleBlurFragmentProcessor& src)
- : INHERITED(kGrCircleBlurFragmentProcessor_ClassID, src.optimizationFlags())
- , circleRect(src.circleRect)
- , solidRadius(src.solidRadius)
- , textureRadius(src.textureRadius) {
- this->cloneAndRegisterAllChildProcessors(src);
-}
-std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::clone() const {
- return std::make_unique<GrCircleBlurFragmentProcessor>(*this);
-}
-#if GR_TEST_UTILS
-SkString GrCircleBlurFragmentProcessor::onDumpInfo() const {
- return SkStringPrintf("(circleRect=half4(%f, %f, %f, %f), solidRadius=%f, textureRadius=%f)",
- circleRect.left(),
- circleRect.top(),
- circleRect.right(),
- circleRect.bottom(),
- solidRadius,
- textureRadius);
-}
-#endif
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::TestCreate(
- GrProcessorTestData* testData) {
- SkScalar wh = testData->fRandom->nextRangeScalar(100.f, 1000.f);
- SkScalar sigma = testData->fRandom->nextRangeF(1.f, 10.f);
- SkRect circle = SkRect::MakeWH(wh, wh);
- return GrCircleBlurFragmentProcessor::Make(
- testData->inputFP(), testData->context(), circle, sigma);
-}
-#endif
diff --git a/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h b/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h
deleted file mode 100644
index b3cce4c..0000000
--- a/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrCircleBlurFragmentProcessor.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrCircleBlurFragmentProcessor_DEFINED
-#define GrCircleBlurFragmentProcessor_DEFINED
-
-#include "include/core/SkM44.h"
-#include "include/core/SkTypes.h"
-
-#include "src/gpu/effects/GrTextureEffect.h"
-
-#include "src/gpu/GrFragmentProcessor.h"
-
-class GrCircleBlurFragmentProcessor : public GrFragmentProcessor {
-public:
- static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext*,
- const SkRect& circle,
- float sigma);
- GrCircleBlurFragmentProcessor(const GrCircleBlurFragmentProcessor& src);
- std::unique_ptr<GrFragmentProcessor> clone() const override;
- const char* name() const override { return "CircleBlurFragmentProcessor"; }
- SkRect circleRect;
- float solidRadius;
- float textureRadius;
-
-private:
- GrCircleBlurFragmentProcessor(std::unique_ptr<GrFragmentProcessor> inputFP,
- SkRect circleRect,
- float solidRadius,
- float textureRadius,
- std::unique_ptr<GrFragmentProcessor> blurProfile)
- : INHERITED(kGrCircleBlurFragmentProcessor_ClassID,
- (OptimizationFlags)ProcessorOptimizationFlags(inputFP.get()) &
- kCompatibleWithCoverageAsAlpha_OptimizationFlag)
- , circleRect(circleRect)
- , solidRadius(solidRadius)
- , textureRadius(textureRadius) {
- this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
- this->registerChild(std::move(blurProfile), SkSL::SampleUsage::Explicit());
- }
- std::unique_ptr<GrGLSLFragmentProcessor> onMakeProgramImpl() const override;
- void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
- bool onIsEqual(const GrFragmentProcessor&) const override;
-#if GR_TEST_UTILS
- SkString onDumpInfo() const override;
-#endif
- GR_DECLARE_FRAGMENT_PROCESSOR_TEST
- using INHERITED = GrFragmentProcessor;
-};
-#endif
diff --git a/src/gpu/effects/generated/GrRRectBlurEffect.cpp b/src/gpu/effects/generated/GrRRectBlurEffect.cpp
deleted file mode 100644
index 5f74cca..0000000
--- a/src/gpu/effects/generated/GrRRectBlurEffect.cpp
+++ /dev/null
@@ -1,509 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRRectBlurEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrRRectBlurEffect.h"
-
-#include "include/gpu/GrDirectContext.h"
-#include "include/gpu/GrRecordingContext.h"
-#include "src/core/SkAutoMalloc.h"
-#include "src/core/SkGpuBlurUtils.h"
-#include "src/core/SkRRectPriv.h"
-#include "src/gpu/GrCaps.h"
-#include "src/gpu/GrDirectContextPriv.h"
-#include "src/gpu/GrPaint.h"
-#include "src/gpu/GrProxyProvider.h"
-#include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/GrStyle.h"
-#include "src/gpu/GrSurfaceDrawContext.h"
-#include "src/gpu/GrThreadSafeCache.h"
-#include "src/gpu/SkGr.h"
-#include "src/gpu/effects/GrTextureEffect.h"
-
-static constexpr auto kBlurredRRectMaskOrigin = kTopLeft_GrSurfaceOrigin;
-
-static void make_blurred_rrect_key(GrUniqueKey* key,
- const SkRRect& rrectToDraw,
- float xformedSigma) {
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
- static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
-
- GrUniqueKey::Builder builder(key, kDomain, 9, "RoundRect Blur Mask");
- builder[0] = SkScalarCeilToInt(xformedSigma - 1 / 6.0f);
-
- int index = 1;
- // TODO: this is overkill for _simple_ circular rrects
- for (auto c : {SkRRect::kUpperLeft_Corner,
- SkRRect::kUpperRight_Corner,
- SkRRect::kLowerRight_Corner,
- SkRRect::kLowerLeft_Corner}) {
- SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) && SkScalarIsInt(rrectToDraw.radii(c).fY));
- builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX);
- builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY);
- }
- builder.finish();
-}
-
-static bool fillin_view_on_gpu(GrDirectContext* dContext,
- const GrSurfaceProxyView& lazyView,
- sk_sp<GrThreadSafeCache::Trampoline> trampoline,
- const SkRRect& rrectToDraw,
- const SkISize& dimensions,
- float xformedSigma) {
-#if GR_OGA
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
-
- // We cache blur masks. Use default surface props here so we can use the same cached mask
- // regardless of the final dst surface.
- SkSurfaceProps defaultSurfaceProps;
-
- std::unique_ptr<GrSurfaceDrawContext> rtc =
- GrSurfaceDrawContext::MakeWithFallback(dContext,
- GrColorType::kAlpha_8,
- nullptr,
- SkBackingFit::kExact,
- dimensions,
- defaultSurfaceProps,
- 1,
- GrMipmapped::kNo,
- GrProtected::kNo,
- kBlurredRRectMaskOrigin);
- if (!rtc) {
- return false;
- }
-
- GrPaint paint;
-
- rtc->clear(SK_PMColor4fTRANSPARENT);
- rtc->drawRRect(nullptr,
- std::move(paint),
- GrAA::kYes,
- SkMatrix::I(),
- rrectToDraw,
- GrStyle::SimpleFill());
-
- GrSurfaceProxyView srcView = rtc->readSurfaceView();
- SkASSERT(srcView.asTextureProxy());
- auto rtc2 = SkGpuBlurUtils::GaussianBlur(dContext,
- std::move(srcView),
- rtc->colorInfo().colorType(),
- rtc->colorInfo().alphaType(),
- nullptr,
- SkIRect::MakeSize(dimensions),
- SkIRect::MakeSize(dimensions),
- xformedSigma,
- xformedSigma,
- SkTileMode::kClamp,
- SkBackingFit::kExact);
- if (!rtc2 || !rtc2->readSurfaceView()) {
- return false;
- }
-
- auto view = rtc2->readSurfaceView();
- SkASSERT(view.swizzle() == lazyView.swizzle());
- SkASSERT(view.origin() == lazyView.origin());
- trampoline->fProxy = view.asTextureProxyRef();
-
- return true;
-#else
- return false;
-#endif
-}
-
-// Evaluate the vertical blur at the specified 'y' value given the location of the top of the
-// rrect.
-static uint8_t eval_V(float top, int y, const uint8_t* integral, int integralSize, float sixSigma) {
- if (top < 0) {
- return 0; // an empty column
- }
-
- float fT = (top - y - 0.5f) * (integralSize / sixSigma);
- if (fT < 0) {
- return 255;
- } else if (fT >= integralSize - 1) {
- return 0;
- }
-
- int lower = (int)fT;
- float frac = fT - lower;
-
- SkASSERT(lower + 1 < integralSize);
-
- return integral[lower] * (1.0f - frac) + integral[lower + 1] * frac;
-}
-
-// Apply a gaussian 'kernel' horizontally at the specified 'x', 'y' location.
-static uint8_t eval_H(int x,
- int y,
- const std::vector<float>& topVec,
- const float* kernel,
- int kernelSize,
- const uint8_t* integral,
- int integralSize,
- float sixSigma) {
- SkASSERT(0 <= x && x < (int)topVec.size());
- SkASSERT(kernelSize % 2);
-
- float accum = 0.0f;
-
- int xSampleLoc = x - (kernelSize / 2);
- for (int i = 0; i < kernelSize; ++i, ++xSampleLoc) {
- if (xSampleLoc < 0 || xSampleLoc >= (int)topVec.size()) {
- continue;
- }
-
- accum += kernel[i] * eval_V(topVec[xSampleLoc], y, integral, integralSize, sixSigma);
- }
-
- return accum + 0.5f;
-}
-
-// Create a cpu-side blurred-rrect mask that is close to the version the gpu would've produced.
-// The match needs to be close bc the cpu- and gpu-generated version must be interchangeable.
-static GrSurfaceProxyView create_mask_on_cpu(GrRecordingContext* rContext,
- const SkRRect& rrectToDraw,
- const SkISize& dimensions,
- float xformedSigma) {
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
- int radius = SkGpuBlurUtils::SigmaRadius(xformedSigma);
- int kernelSize = 2 * radius + 1;
-
- SkASSERT(kernelSize % 2);
- SkASSERT(dimensions.width() % 2);
- SkASSERT(dimensions.height() % 2);
-
- SkVector radii = rrectToDraw.getSimpleRadii();
- SkASSERT(SkScalarNearlyEqual(radii.fX, radii.fY));
-
- const int halfWidthPlus1 = (dimensions.width() / 2) + 1;
- const int halfHeightPlus1 = (dimensions.height() / 2) + 1;
-
- std::unique_ptr<float[]> kernel(new float[kernelSize]);
-
- SkGpuBlurUtils::Compute1DGaussianKernel(kernel.get(), xformedSigma, radius);
-
- SkBitmap integral;
- if (!SkGpuBlurUtils::CreateIntegralTable(6 * xformedSigma, &integral)) {
- return {};
- }
-
- SkBitmap result;
- if (!result.tryAllocPixels(SkImageInfo::MakeA8(dimensions.width(), dimensions.height()))) {
- return {};
- }
-
- std::vector<float> topVec;
- topVec.reserve(dimensions.width());
- for (int x = 0; x < dimensions.width(); ++x) {
- if (x < rrectToDraw.rect().fLeft || x > rrectToDraw.rect().fRight) {
- topVec.push_back(-1);
- } else {
- if (x + 0.5f < rrectToDraw.rect().fLeft + radii.fX) { // in the circular section
- float xDist = rrectToDraw.rect().fLeft + radii.fX - x - 0.5f;
- float h = sqrtf(radii.fX * radii.fX - xDist * xDist);
- SkASSERT(0 <= h && h < radii.fY);
- topVec.push_back(rrectToDraw.rect().fTop + radii.fX - h + 3 * xformedSigma);
- } else {
- topVec.push_back(rrectToDraw.rect().fTop + 3 * xformedSigma);
- }
- }
- }
-
- for (int y = 0; y < halfHeightPlus1; ++y) {
- uint8_t* scanline = result.getAddr8(0, y);
-
- for (int x = 0; x < halfWidthPlus1; ++x) {
- scanline[x] = eval_H(x,
- y,
- topVec,
- kernel.get(),
- kernelSize,
- integral.getAddr8(0, 0),
- integral.width(),
- 6 * xformedSigma);
- scanline[dimensions.width() - x - 1] = scanline[x];
- }
-
- memcpy(result.getAddr8(0, dimensions.height() - y - 1), scanline, result.rowBytes());
- }
-
- result.setImmutable();
-
- auto view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, result));
- if (!view) {
- return {};
- }
-
- SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
- return view;
-}
-
-static std::unique_ptr<GrFragmentProcessor> find_or_create_rrect_blur_mask_fp(
- GrRecordingContext* rContext,
- const SkRRect& rrectToDraw,
- const SkISize& dimensions,
- float xformedSigma) {
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
- GrUniqueKey key;
- make_blurred_rrect_key(&key, rrectToDraw, xformedSigma);
-
- auto threadSafeCache = rContext->priv().threadSafeCache();
-
- // It seems like we could omit this matrix and modify the shader code to not normalize
- // the coords used to sample the texture effect. However, the "proxyDims" value in the
- // shader is not always the actual the proxy dimensions. This is because 'dimensions' here
- // was computed using integer corner radii as determined in
- // SkComputeBlurredRRectParams whereas the shader code uses the float radius to compute
- // 'proxyDims'. Why it draws correctly with these unequal values is a mystery for the ages.
- auto m = SkMatrix::Scale(dimensions.width(), dimensions.height());
-
- GrSurfaceProxyView view;
-
- if (GrDirectContext* dContext = rContext->asDirectContext()) {
- // The gpu thread gets priority over the recording threads. If the gpu thread is first,
- // it crams a lazy proxy into the cache and then fills it in later.
- auto[lazyView, trampoline] = GrThreadSafeCache::CreateLazyView(dContext,
- GrColorType::kAlpha_8,
- dimensions,
- kBlurredRRectMaskOrigin,
- SkBackingFit::kExact);
- if (!lazyView) {
- return nullptr;
- }
-
- view = threadSafeCache->findOrAdd(key, lazyView);
- if (view != lazyView) {
- SkASSERT(view.asTextureProxy());
- SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
- return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
- }
-
- if (!fillin_view_on_gpu(dContext,
- lazyView,
- std::move(trampoline),
- rrectToDraw,
- dimensions,
- xformedSigma)) {
- // In this case something has gone disastrously wrong so set up to drop the draw
- // that needed this resource and reduce future pollution of the cache.
- threadSafeCache->remove(key);
- return nullptr;
- }
- } else {
- view = threadSafeCache->find(key);
- if (view) {
- SkASSERT(view.asTextureProxy());
- SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
- return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
- }
-
- view = create_mask_on_cpu(rContext, rrectToDraw, dimensions, xformedSigma);
- if (!view) {
- return nullptr;
- }
-
- view = threadSafeCache->add(key, view);
- }
-
- SkASSERT(view.asTextureProxy());
- SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
- return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
-}
-
-std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(
- std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext* context,
- float sigma,
- float xformedSigma,
- const SkRRect& srcRRect,
- const SkRRect& devRRect) {
-// Should've been caught up-stream
-#ifdef SK_DEBUG
- SkASSERTF(!SkRRectPriv::IsCircle(devRRect),
- "Unexpected circle. %d\n\t%s\n\t%s",
- SkRRectPriv::IsCircle(srcRRect),
- srcRRect.dumpToString(true).c_str(),
- devRRect.dumpToString(true).c_str());
- SkASSERTF(!devRRect.isRect(),
- "Unexpected rect. %d\n\t%s\n\t%s",
- srcRRect.isRect(),
- srcRRect.dumpToString(true).c_str(),
- devRRect.dumpToString(true).c_str());
-#endif
- // TODO: loosen this up
- if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
- return nullptr;
- }
-
- if (SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma)) {
- return inputFP;
- }
-
- // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
- // sufficiently small relative to both the size of the corner radius and the
- // width (and height) of the rrect.
- SkRRect rrectToDraw;
- SkISize dimensions;
- SkScalar ignored[SkGpuBlurUtils::kBlurRRectMaxDivisions];
-
- bool ninePatchable = SkGpuBlurUtils::ComputeBlurredRRectParams(srcRRect,
- devRRect,
- sigma,
- xformedSigma,
- &rrectToDraw,
- &dimensions,
- ignored,
- ignored,
- ignored,
- ignored);
- if (!ninePatchable) {
- return nullptr;
- }
-
- std::unique_ptr<GrFragmentProcessor> maskFP =
- find_or_create_rrect_blur_mask_fp(context, rrectToDraw, dimensions, xformedSigma);
- if (!maskFP) {
- return nullptr;
- }
-
- return std::unique_ptr<GrFragmentProcessor>(
- new GrRRectBlurEffect(std::move(inputFP),
- xformedSigma,
- devRRect.getBounds(),
- SkRRectPriv::GetSimpleRadii(devRRect).fX,
- std::move(maskFP)));
-}
-#include "src/core/SkUtils.h"
-#include "src/gpu/GrTexture.h"
-#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
-#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
-#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
-#include "src/sksl/SkSLCPP.h"
-#include "src/sksl/SkSLUtil.h"
-class GrGLSLRRectBlurEffect : public GrGLSLFragmentProcessor {
-public:
- GrGLSLRRectBlurEffect() {}
- void emitCode(EmitArgs& args) override {
- GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
- const GrRRectBlurEffect& _outer = args.fFp.cast<GrRRectBlurEffect>();
- (void)_outer;
- auto sigma = _outer.sigma;
- (void)sigma;
- auto rect = _outer.rect;
- (void)rect;
- auto cornerRadius = _outer.cornerRadius;
- (void)cornerRadius;
- cornerRadiusVar = args.fUniformHandler->addUniform(
- &_outer, kFragment_GrShaderFlag, kHalf_GrSLType, "cornerRadius");
- proxyRectVar = args.fUniformHandler->addUniform(
- &_outer, kFragment_GrShaderFlag, kFloat4_GrSLType, "proxyRect");
- blurRadiusVar = args.fUniformHandler->addUniform(
- &_outer, kFragment_GrShaderFlag, kHalf_GrSLType, "blurRadius");
- fragBuilder->codeAppendf(
- R"SkSL(float2 translatedFragPosFloat = sk_FragCoord.xy - %s.xy;
-float2 proxyCenter = (%s.zw - %s.xy) * 0.5;
-half edgeSize = (2.0 * %s + %s) + 0.5;
-translatedFragPosFloat -= proxyCenter;
-half2 fragDirection = half2(sign(translatedFragPosFloat));
-translatedFragPosFloat = abs(translatedFragPosFloat);
-half2 translatedFragPosHalf = half2(translatedFragPosFloat - (proxyCenter - float(edgeSize)));
-translatedFragPosHalf = max(translatedFragPosHalf, 0.0);
-translatedFragPosHalf *= fragDirection;
-translatedFragPosHalf += half2(edgeSize);
-half2 proxyDims = half2(2.0 * edgeSize);
-half2 texCoord = translatedFragPosHalf / proxyDims;)SkSL",
- args.fUniformHandler->getUniformCStr(proxyRectVar),
- args.fUniformHandler->getUniformCStr(proxyRectVar),
- args.fUniformHandler->getUniformCStr(proxyRectVar),
- args.fUniformHandler->getUniformCStr(blurRadiusVar),
- args.fUniformHandler->getUniformCStr(cornerRadiusVar));
- SkString _sample0 = this->invokeChild(0, args);
- SkString _coords1("float2(texCoord)");
- SkString _sample1 = this->invokeChild(1, args, _coords1.c_str());
- fragBuilder->codeAppendf(
- R"SkSL(
-return %s * %s.w;
-)SkSL",
- _sample0.c_str(),
- _sample1.c_str());
- }
-
-private:
- void onSetData(const GrGLSLProgramDataManager& pdman,
- const GrFragmentProcessor& _proc) override {
- const GrRRectBlurEffect& _outer = _proc.cast<GrRRectBlurEffect>();
- { pdman.set1f(cornerRadiusVar, _outer.cornerRadius); }
- auto sigma = _outer.sigma;
- (void)sigma;
- auto rect = _outer.rect;
- (void)rect;
- UniformHandle& cornerRadius = cornerRadiusVar;
- (void)cornerRadius;
- UniformHandle& proxyRect = proxyRectVar;
- (void)proxyRect;
- UniformHandle& blurRadius = blurRadiusVar;
- (void)blurRadius;
-
- float blurRadiusValue = 3.f * SkScalarCeilToScalar(sigma - 1 / 6.0f);
- pdman.set1f(blurRadius, blurRadiusValue);
-
- SkRect outset = rect;
- outset.outset(blurRadiusValue, blurRadiusValue);
- pdman.set4f(proxyRect, outset.fLeft, outset.fTop, outset.fRight, outset.fBottom);
- }
- UniformHandle proxyRectVar;
- UniformHandle blurRadiusVar;
- UniformHandle cornerRadiusVar;
-};
-std::unique_ptr<GrGLSLFragmentProcessor> GrRRectBlurEffect::onMakeProgramImpl() const {
- return std::make_unique<GrGLSLRRectBlurEffect>();
-}
-void GrRRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
- GrProcessorKeyBuilder* b) const {}
-bool GrRRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
- const GrRRectBlurEffect& that = other.cast<GrRRectBlurEffect>();
- (void)that;
- if (sigma != that.sigma) return false;
- if (rect != that.rect) return false;
- if (cornerRadius != that.cornerRadius) return false;
- return true;
-}
-GrRRectBlurEffect::GrRRectBlurEffect(const GrRRectBlurEffect& src)
- : INHERITED(kGrRRectBlurEffect_ClassID, src.optimizationFlags())
- , sigma(src.sigma)
- , rect(src.rect)
- , cornerRadius(src.cornerRadius) {
- this->cloneAndRegisterAllChildProcessors(src);
-}
-std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::clone() const {
- return std::make_unique<GrRRectBlurEffect>(*this);
-}
-#if GR_TEST_UTILS
-SkString GrRRectBlurEffect::onDumpInfo() const {
- return SkStringPrintf("(sigma=%f, rect=float4(%f, %f, %f, %f), cornerRadius=%f)",
- sigma,
- rect.left(),
- rect.top(),
- rect.right(),
- rect.bottom(),
- cornerRadius);
-}
-#endif
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRRectBlurEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::TestCreate(GrProcessorTestData* d) {
- SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f);
- SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f);
- SkScalar r = d->fRandom->nextRangeF(1.f, 9.f);
- SkScalar sigma = d->fRandom->nextRangeF(1.f, 10.f);
- SkRRect rrect;
- rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
- return GrRRectBlurEffect::Make(d->inputFP(), d->context(), sigma, sigma, rrect, rrect);
-}
-#endif
diff --git a/src/gpu/effects/generated/GrRRectBlurEffect.h b/src/gpu/effects/generated/GrRRectBlurEffect.h
deleted file mode 100644
index bad6fcf..0000000
--- a/src/gpu/effects/generated/GrRRectBlurEffect.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRRectBlurEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrRRectBlurEffect_DEFINED
-#define GrRRectBlurEffect_DEFINED
-
-#include "include/core/SkM44.h"
-#include "include/core/SkTypes.h"
-
-#include "include/core/SkRect.h"
-class GrRecordingContext;
-class SkRRect;
-
-#include "src/gpu/GrFragmentProcessor.h"
-
-class GrRRectBlurEffect : public GrFragmentProcessor {
-public:
- static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext* context,
- float sigma,
- float xformedSigma,
- const SkRRect& srcRRect,
- const SkRRect& devRRect);
- GrRRectBlurEffect(const GrRRectBlurEffect& src);
- std::unique_ptr<GrFragmentProcessor> clone() const override;
- const char* name() const override { return "RRectBlurEffect"; }
- float sigma;
- SkRect rect;
- float cornerRadius;
-
-private:
- GrRRectBlurEffect(std::unique_ptr<GrFragmentProcessor> inputFP,
- float sigma,
- SkRect rect,
- float cornerRadius,
- std::unique_ptr<GrFragmentProcessor> ninePatchFP)
- : INHERITED(kGrRRectBlurEffect_ClassID,
- (OptimizationFlags)ProcessorOptimizationFlags(inputFP.get()) &
- kCompatibleWithCoverageAsAlpha_OptimizationFlag)
- , sigma(sigma)
- , rect(rect)
- , cornerRadius(cornerRadius) {
- this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
- this->registerChild(std::move(ninePatchFP), SkSL::SampleUsage::Explicit());
- }
- std::unique_ptr<GrGLSLFragmentProcessor> onMakeProgramImpl() const override;
- void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
- bool onIsEqual(const GrFragmentProcessor&) const override;
-#if GR_TEST_UTILS
- SkString onDumpInfo() const override;
-#endif
- GR_DECLARE_FRAGMENT_PROCESSOR_TEST
- using INHERITED = GrFragmentProcessor;
-};
-#endif
diff --git a/src/gpu/effects/generated/GrRectBlurEffect.cpp b/src/gpu/effects/generated/GrRectBlurEffect.cpp
deleted file mode 100644
index 3444d03..0000000
--- a/src/gpu/effects/generated/GrRectBlurEffect.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRectBlurEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrRectBlurEffect.h"
-
-#include "src/core/SkUtils.h"
-#include "src/gpu/GrTexture.h"
-#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
-#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
-#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
-#include "src/sksl/SkSLCPP.h"
-#include "src/sksl/SkSLUtil.h"
-class GrGLSLRectBlurEffect : public GrGLSLFragmentProcessor {
-public:
- GrGLSLRectBlurEffect() {}
- void emitCode(EmitArgs& args) override {
- GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
- const GrRectBlurEffect& _outer = args.fFp.cast<GrRectBlurEffect>();
- (void)_outer;
- auto rect = _outer.rect;
- (void)rect;
- auto isFast = _outer.isFast;
- (void)isFast;
- rectVar = args.fUniformHandler->addUniform(
- &_outer, kFragment_GrShaderFlag, kFloat4_GrSLType, "rect");
- fragBuilder->codeAppendf(
- R"SkSL(half xCoverage;
-half yCoverage;
-
-@if (%s) {
- half2 xy = max(half2(%s.xy - %s), half2(%s - %s.zw));)SkSL",
- (_outer.isFast ? "true" : "false"),
- args.fUniformHandler->getUniformCStr(rectVar),
- args.fSampleCoord,
- args.fSampleCoord,
- args.fUniformHandler->getUniformCStr(rectVar));
- SkString _coords0("float2(half2(xy.x, 0.5))");
- SkString _sample0 = this->invokeChild(0, args, _coords0.c_str());
- fragBuilder->codeAppendf(
- R"SkSL(
- xCoverage = %s.w;)SkSL",
- _sample0.c_str());
- SkString _coords1("float2(half2(xy.y, 0.5))");
- SkString _sample1 = this->invokeChild(0, args, _coords1.c_str());
- fragBuilder->codeAppendf(
- R"SkSL(
- yCoverage = %s.w;
-} else {
- half4 rect = half4(half2(%s.xy - %s), half2(%s - %s.zw));)SkSL",
- _sample1.c_str(),
- args.fUniformHandler->getUniformCStr(rectVar),
- args.fSampleCoord,
- args.fSampleCoord,
- args.fUniformHandler->getUniformCStr(rectVar));
- SkString _coords2("float2(half2(rect.x, 0.5))");
- SkString _sample2 = this->invokeChild(0, args, _coords2.c_str());
- SkString _coords3("float2(half2(rect.z, 0.5))");
- SkString _sample3 = this->invokeChild(0, args, _coords3.c_str());
- fragBuilder->codeAppendf(
- R"SkSL(
- xCoverage = (1.0 - %s.w) - %s.w;)SkSL",
- _sample2.c_str(),
- _sample3.c_str());
- SkString _coords4("float2(half2(rect.y, 0.5))");
- SkString _sample4 = this->invokeChild(0, args, _coords4.c_str());
- SkString _coords5("float2(half2(rect.w, 0.5))");
- SkString _sample5 = this->invokeChild(0, args, _coords5.c_str());
- fragBuilder->codeAppendf(
- R"SkSL(
- yCoverage = (1.0 - %s.w) - %s.w;
-}
-return half4(xCoverage * yCoverage);
-)SkSL",
- _sample4.c_str(),
- _sample5.c_str());
- }
-
-private:
- void onSetData(const GrGLSLProgramDataManager& pdman,
- const GrFragmentProcessor& _proc) override {
- const GrRectBlurEffect& _outer = _proc.cast<GrRectBlurEffect>();
- { pdman.set4fv(rectVar, 1, reinterpret_cast<const float*>(&_outer.rect)); }
- }
- UniformHandle rectVar;
-};
-std::unique_ptr<GrGLSLFragmentProcessor> GrRectBlurEffect::onMakeProgramImpl() const {
- return std::make_unique<GrGLSLRectBlurEffect>();
-}
-void GrRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
- GrProcessorKeyBuilder* b) const {
- b->addBool(isFast, "isFast");
-}
-bool GrRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
- const GrRectBlurEffect& that = other.cast<GrRectBlurEffect>();
- (void)that;
- if (rect != that.rect) return false;
- if (isFast != that.isFast) return false;
- return true;
-}
-GrRectBlurEffect::GrRectBlurEffect(const GrRectBlurEffect& src)
- : INHERITED(kGrRectBlurEffect_ClassID, src.optimizationFlags())
- , rect(src.rect)
- , isFast(src.isFast) {
- this->cloneAndRegisterAllChildProcessors(src);
- this->setUsesSampleCoordsDirectly();
-}
-std::unique_ptr<GrFragmentProcessor> GrRectBlurEffect::clone() const {
- return std::make_unique<GrRectBlurEffect>(*this);
-}
-#if GR_TEST_UTILS
-SkString GrRectBlurEffect::onDumpInfo() const {
- return SkStringPrintf("(rect=float4(%f, %f, %f, %f), isFast=%s)",
- rect.left(),
- rect.top(),
- rect.right(),
- rect.bottom(),
- (isFast ? "true" : "false"));
-}
-#endif
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRectBlurEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrRectBlurEffect::TestCreate(GrProcessorTestData* data) {
- float sigma = data->fRandom->nextRangeF(3, 8);
- int x = data->fRandom->nextRangeF(1, 200);
- int y = data->fRandom->nextRangeF(1, 200);
- float width = data->fRandom->nextRangeF(200, 300);
- float height = data->fRandom->nextRangeF(200, 300);
- SkMatrix vm = GrTest::TestMatrixPreservesRightAngles(data->fRandom);
- auto rect = SkRect::MakeXYWH(x, y, width, height);
- return GrRectBlurEffect::Make(
- data->inputFP(), data->context(), *data->caps()->shaderCaps(), rect, vm, sigma);
-}
-#endif
diff --git a/src/gpu/effects/generated/GrRectBlurEffect.h b/src/gpu/effects/generated/GrRectBlurEffect.h
deleted file mode 100644
index 279c924..0000000
--- a/src/gpu/effects/generated/GrRectBlurEffect.h
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRectBlurEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrRectBlurEffect_DEFINED
-#define GrRectBlurEffect_DEFINED
-
-#include "include/core/SkM44.h"
-#include "include/core/SkTypes.h"
-
-#include <cmath>
-#include "include/core/SkRect.h"
-#include "include/core/SkScalar.h"
-#include "include/gpu/GrRecordingContext.h"
-#include "src/core/SkBlurMask.h"
-#include "src/core/SkGpuBlurUtils.h"
-#include "src/core/SkMathPriv.h"
-#include "src/gpu/GrProxyProvider.h"
-#include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/GrShaderCaps.h"
-#include "src/gpu/GrThreadSafeCache.h"
-#include "src/gpu/SkGr.h"
-#include "src/gpu/effects/GrMatrixEffect.h"
-#include "src/gpu/effects/GrTextureEffect.h"
-
-#include "src/gpu/GrFragmentProcessor.h"
-
-class GrRectBlurEffect : public GrFragmentProcessor {
-public:
- static std::unique_ptr<GrFragmentProcessor> MakeIntegralFP(GrRecordingContext* rContext,
- float sixSigma) {
- SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(sixSigma / 6.f));
- auto threadSafeCache = rContext->priv().threadSafeCache();
-
- int width = SkGpuBlurUtils::CreateIntegralTable(sixSigma, nullptr);
-
- static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
- GrUniqueKey key;
- GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask");
- builder[0] = width;
- builder.finish();
-
- SkMatrix m = SkMatrix::Scale(width / sixSigma, 1.f);
-
- GrSurfaceProxyView view = threadSafeCache->find(key);
-
- if (view) {
- SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
- return GrTextureEffect::Make(
- std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
- }
-
- SkBitmap bitmap;
- if (!SkGpuBlurUtils::CreateIntegralTable(sixSigma, &bitmap)) {
- return {};
- }
-
- view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bitmap));
- if (!view) {
- return {};
- }
-
- view = threadSafeCache->add(key, view);
-
- SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
- return GrTextureEffect::Make(
- std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
- }
-
- static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
- GrRecordingContext* context,
- const GrShaderCaps& caps,
- const SkRect& srcRect,
- const SkMatrix& viewMatrix,
- float transformedSigma) {
- SkASSERT(viewMatrix.preservesRightAngles());
- SkASSERT(srcRect.isSorted());
-
- if (SkGpuBlurUtils::IsEffectivelyZeroSigma(transformedSigma)) {
- // No need to blur the rect
- return inputFP;
- }
-
- SkMatrix invM;
- SkRect rect;
- if (viewMatrix.rectStaysRect()) {
- invM = SkMatrix::I();
- // We can do everything in device space when the src rect projects to a rect in device
- // space.
- SkAssertResult(viewMatrix.mapRect(&rect, srcRect));
- } else {
- // The view matrix may scale, perhaps anisotropically. But we want to apply our device
- // space "transformedSigma" to the delta of frag coord from the rect edges. Factor out
- // the scaling to define a space that is purely rotation/translation from device space
- // (and scale from src space) We'll meet in the middle: pre-scale the src rect to be in
- // this space and then apply the inverse of the rotation/translation portion to the
- // frag coord.
- SkMatrix m;
- SkSize scale;
- if (!viewMatrix.decomposeScale(&scale, &m)) {
- return nullptr;
- }
- if (!m.invert(&invM)) {
- return nullptr;
- }
- rect = {srcRect.left() * scale.width(),
- srcRect.top() * scale.height(),
- srcRect.right() * scale.width(),
- srcRect.bottom() * scale.height()};
- }
-
- if (!caps.floatIs32Bits()) {
- // We promote the math that gets us into the Gaussian space to full float when the rect
- // coords are large. If we don't have full float then fail. We could probably clip the
- // rect to an outset device bounds instead.
- if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f ||
- SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f) {
- return nullptr;
- }
- }
-
- const float sixSigma = 6 * transformedSigma;
- std::unique_ptr<GrFragmentProcessor> integral = MakeIntegralFP(context, sixSigma);
- if (!integral) {
- return nullptr;
- }
-
- // In the fast variant we think of the midpoint of the integral texture as aligning
- // with the closest rect edge both in x and y. To simplify texture coord calculation we
- // inset the rect so that the edge of the inset rect corresponds to t = 0 in the texture.
- // It actually simplifies things a bit in the !isFast case, too.
- float threeSigma = sixSigma / 2;
- SkRect insetRect = {rect.left() + threeSigma,
- rect.top() + threeSigma,
- rect.right() - threeSigma,
- rect.bottom() - threeSigma};
-
- // In our fast variant we find the nearest horizontal and vertical edges and for each
- // do a lookup in the integral texture for each and multiply them. When the rect is
- // less than 6 sigma wide then things aren't so simple and we have to consider both the
- // left and right edge of the rectangle (and similar in y).
- bool isFast = insetRect.isSorted();
- std::unique_ptr<GrFragmentProcessor> fp(
- new GrRectBlurEffect(insetRect, std::move(integral), isFast));
- if (!invM.isIdentity()) {
- fp = GrMatrixEffect::Make(invM, std::move(fp));
- }
- fp = GrFragmentProcessor::DeviceSpace(std::move(fp));
- return GrFragmentProcessor::MulInputByChildAlpha(std::move(fp));
- }
- GrRectBlurEffect(const GrRectBlurEffect& src);
- std::unique_ptr<GrFragmentProcessor> clone() const override;
- const char* name() const override { return "RectBlurEffect"; }
- SkRect rect;
- bool isFast;
-
-private:
- GrRectBlurEffect(SkRect rect, std::unique_ptr<GrFragmentProcessor> integral, bool isFast)
- : INHERITED(kGrRectBlurEffect_ClassID,
- (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
- , rect(rect)
- , isFast(isFast) {
- this->setUsesSampleCoordsDirectly();
- this->registerChild(std::move(integral), SkSL::SampleUsage::Explicit());
- }
- std::unique_ptr<GrGLSLFragmentProcessor> onMakeProgramImpl() const override;
- void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
- bool onIsEqual(const GrFragmentProcessor&) const override;
-#if GR_TEST_UTILS
- SkString onDumpInfo() const override;
-#endif
- GR_DECLARE_FRAGMENT_PROCESSOR_TEST
- using INHERITED = GrFragmentProcessor;
-};
-#endif