Index: src/effects/GrCircleBlurFragmentProcessor.cpp |
diff --git a/src/effects/GrCircleBlurFragmentProcessor.cpp b/src/effects/GrCircleBlurFragmentProcessor.cpp |
index f2a6c36f36bd331cf49ce9ba7be61f2bf138ee13..3e80ef517c860b95e88e34fd2a30b04846bdcd74 100644 |
--- a/src/effects/GrCircleBlurFragmentProcessor.cpp |
+++ b/src/effects/GrCircleBlurFragmentProcessor.cpp |
@@ -34,12 +34,11 @@ private: |
}; |
void GrCircleBlurFragmentProcessor::GLSLProcessor::emitCode(EmitArgs& args) { |
- |
const char *dataName; |
// The data is formatted as: |
// x,y - the center of the circle |
- // z - the distance at which the intensity starts falling off (e.g., the start of the table) |
+ // z - inner radius that should map to 0th entry in the texture. |
// w - the inverse of the distance over which the texture is stretched. |
fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, |
kVec4f_GrSLType, |
@@ -56,12 +55,12 @@ void GrCircleBlurFragmentProcessor::GLSLProcessor::emitCode(EmitArgs& args) { |
fragBuilder->codeAppendf("vec4 src=vec4(1);"); |
} |
- // We just want to compute "length(vec) - %s.z + 0.5) * %s.w" but need to rearrange |
- // for precision |
+ // We just want to compute "(length(vec) - %s.z + 0.5) * %s.w" but need to rearrange |
+ // for precision. |
fragBuilder->codeAppendf("vec2 vec = vec2( (%s.x - %s.x) * %s.w , (%s.y - %s.y) * %s.w );", |
fragmentPos, dataName, dataName, |
fragmentPos, dataName, dataName); |
- fragBuilder->codeAppendf("float dist = length(vec) + ( 0.5 - %s.z ) * %s.w;", |
+ fragBuilder->codeAppendf("float dist = length(vec) + (0.5 - %s.z) * %s.w;", |
dataName, dataName); |
fragBuilder->codeAppendf("float intensity = "); |
@@ -78,7 +77,7 @@ void GrCircleBlurFragmentProcessor::GLSLProcessor::onSetData(const GrGLSLProgram |
// The data is formatted as: |
// x,y - the center of the circle |
- // z - the distance at which the intensity starts falling off (e.g., the start of the table) |
+ // z - inner radius that should map to 0th entry in the texture. |
// w - the inverse of the distance over which the profile texture is stretched. |
pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.fSolidRadius, |
1.f / cbfp.fTextureRadius); |
@@ -87,12 +86,10 @@ void GrCircleBlurFragmentProcessor::GLSLProcessor::onSetData(const GrGLSLProgram |
/////////////////////////////////////////////////////////////////////////////// |
GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle, |
- float sigma, |
- float solidRadius, |
float textureRadius, |
+ float solidRadius, |
GrTexture* blurProfile) |
: fCircle(circle) |
- , fSigma(sigma) |
, fSolidRadius(solidRadius) |
, fTextureRadius(textureRadius) |
, fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) { |
@@ -115,10 +112,9 @@ void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* |
inout->mulByUnknownSingleComponent(); |
} |
-// Create a Gaussian half-kernel 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) { |
+// 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; |
@@ -130,9 +126,16 @@ static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHa |
halfKernel[i] = value; |
t += 1.f; |
} |
- float sum = 0.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. |
- tot *= 2.f; |
+ 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]; |
@@ -203,7 +206,7 @@ static uint8_t eval_at(float evalX, float circleR, const float* halfKernel, int |
// 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 uint8_t* create_profile(float sigma, float circleR, float offset, int profileTextureWidth) { |
+static uint8_t* create_circle_profile(float sigma, float circleR, int profileTextureWidth) { |
const int numSteps = profileTextureWidth; |
uint8_t* weights = new uint8_t[numSteps]; |
@@ -221,11 +224,11 @@ static uint8_t* create_profile(float sigma, float circleR, float offset, int pro |
float* yEvals = bulkAlloc.get() + 2 * halfKernelSize; |
make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma); |
- float firstX = offset - halfKernelSize + 0.5f; |
+ 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 = offset + i + 0.5f; |
+ float evalX = i + 0.5f; |
weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i); |
} |
// Ensure the tail of the Gaussian goes to zero. |
@@ -233,75 +236,89 @@ static uint8_t* create_profile(float sigma, float circleR, float offset, int pro |
return weights; |
} |
-static int next_pow2_16bits(int x) { |
- SkASSERT(x > 0); |
- SkASSERT(x <= SK_MaxS16); |
- x--; |
- x |= x >> 1; |
- x |= x >> 2; |
- x |= x >> 4; |
- x |= x >> 8; |
- return x + 1; |
+static uint8_t* create_half_plane_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); |
+ uint8_t* profile = new uint8_t[profileWidth]; |
+ |
+ // 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; |
+ return profile; |
} |
-GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( |
- GrTextureProvider* textureProvider, |
- const SkRect& circle, |
- float sigma, |
- float* solidRadius, |
- float* textureRadius) { |
+static GrTexture* create_profile_texture(GrTextureProvider* textureProvider, const SkRect& circle, |
+ float sigma, float* solidRadius, float* textureRadius) { |
float circleR = circle.width() / 2.0f; |
// 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. We could do that simpler computation. However, right now we're just using a lower |
- // bound off the ratio. 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. |
- sigmaToCircleRRatio = SkTPin(sigmaToCircleRRatio, 0.05f, 8.f); |
- // Convert to fixed point for the key. |
- SkFixed 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; |
- // From the circle center to solidRadius is all 1s and represented by the leftmost pixel (with |
- // value 255) in the profile texture. If it is zero then there is no solid center to the |
- // blurred circle. |
- if (3*sigma <= circleR) { |
- // The circle is bigger than the Gaussian. In this case we know the interior of the |
- // blurred circle is solid. |
- *solidRadius = circleR - 3 * sigma; // This location maps to 0.5f in the weights texture. |
- // It should always be 255. |
- *textureRadius = SkScalarCeilToScalar(6 * sigma); |
+ // 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 = SkTMin(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 { |
- // The Gaussian is bigger than the circle. |
- *solidRadius = 0.0f; |
- *textureRadius = SkScalarCeilToScalar(circleR + 3 * sigma); |
+ // 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; |
} |
- int profileTextureWidth = SkScalarCeilToInt(*textureRadius); |
- profileTextureWidth = (profileTextureWidth >= 1024) ? 1024 : |
- next_pow2_16bits(profileTextureWidth); |
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); |
GrUniqueKey key; |
- GrUniqueKey::Builder builder(&key, kDomain, 2); |
+ GrUniqueKey::Builder builder(&key, kDomain, 1); |
builder[0] = sigmaToCircleRRatioFixed; |
- builder[1] = profileTextureWidth; |
builder.finish(); |
GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); |
- |
if (!blurProfile) { |
+ static constexpr int kProfileTextureWidth = 512; |
GrSurfaceDesc texDesc; |
- texDesc.fWidth = profileTextureWidth; |
+ texDesc.fWidth = kProfileTextureWidth; |
texDesc.fHeight = 1; |
texDesc.fConfig = kAlpha_8_GrPixelConfig; |
- // Rescale params to the size of the texture we're creating. |
- SkScalar scale = profileTextureWidth / *textureRadius; |
- SkAutoTDeleteArray<uint8_t> profile(create_profile(sigma * scale, circleR * scale, |
- *solidRadius * scale, |
- profileTextureWidth)); |
+ SkAutoTDeleteArray<uint8_t> profile(nullptr); |
+ if (useHalfPlaneApprox) { |
+ profile.reset(create_half_plane_profile(kProfileTextureWidth)); |
+ } else { |
+ // Rescale params to the size of the texture we're creating. |
+ SkScalar scale = kProfileTextureWidth / *textureRadius; |
+ profile.reset(create_circle_profile(sigma * scale, circleR * scale, |
+ kProfileTextureWidth)); |
+ } |
blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes, profile.get(), 0); |
if (blurProfile) { |
@@ -312,6 +329,23 @@ GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( |
return blurProfile; |
} |
+////////////////////////////////////////////////////////////////////////////// |
+ |
+sk_sp<GrFragmentProcessor> GrCircleBlurFragmentProcessor::Make(GrTextureProvider*textureProvider, |
+ const SkRect& circle, float sigma) { |
+ float solidRadius; |
+ float textureRadius; |
+ SkAutoTUnref<GrTexture> profile(create_profile_texture(textureProvider, circle, sigma, |
+ &solidRadius, &textureRadius)); |
+ if (!profile) { |
+ return nullptr; |
+ } |
+ return sk_sp<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(circle, textureRadius, |
+ solidRadius, profile)); |
+} |
+ |
+////////////////////////////////////////////////////////////////////////////// |
+ |
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); |
sk_sp<GrFragmentProcessor> GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) { |