/* * Copyright 2015 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrCircleBlurFragmentProcessor.h" #if SK_SUPPORT_GPU #include "GrContext.h" #include "GrTextureProvider.h" #include "gl/GrGLFragmentProcessor.h" #include "gl/builders/GrGLProgramBuilder.h" class GrGLCircleBlurFragmentProcessor : public GrGLFragmentProcessor { public: GrGLCircleBlurFragmentProcessor(const GrProcessor&) {} void emitCode(EmitArgs&) override; protected: void onSetData(const GrGLProgramDataManager&, const GrProcessor&) override; private: GrGLProgramDataManager::UniformHandle fDataUniform; typedef GrGLFragmentProcessor INHERITED; }; void GrGLCircleBlurFragmentProcessor::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) // w - the size of the profile texture fDataUniform = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility, kVec4f_GrSLType, kDefault_GrSLPrecision, "data", &dataName); GrGLFragmentBuilder* fsBuilder = args.fBuilder->getFragmentShaderBuilder(); const char *fragmentPos = fsBuilder->fragmentPosition(); if (args.fInputColor) { fsBuilder->codeAppendf("vec4 src=%s;", args.fInputColor); } else { fsBuilder->codeAppendf("vec4 src=vec4(1);"); } fsBuilder->codeAppendf("vec2 vec = %s.xy - %s.xy;", fragmentPos, dataName); fsBuilder->codeAppendf("float dist = (length(vec) - %s.z + 0.5) / %s.w;", dataName, dataName); fsBuilder->codeAppendf("float intensity = "); fsBuilder->appendTextureLookup(args.fSamplers[0], "vec2(dist, 0.5)"); fsBuilder->codeAppend(".a;"); fsBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor ); } void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLProgramDataManager& pdman, const GrProcessor& proc) { const GrCircleBlurFragmentProcessor& cbfp = proc.cast(); const SkRect& circle = cbfp.circle(); // 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) // w - the size of the profile texture pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(), SkIntToScalar(cbfp.profileSize())); } /////////////////////////////////////////////////////////////////////////////// GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle, float sigma, float offset, GrTexture* blurProfile) : fCircle(circle) , fSigma(sigma) , fOffset(offset) , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) { this->initClassID(); this->addTextureAccess(&fBlurProfileAccess); this->setWillReadFragmentPosition(); } GrGLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLInstance() const { return new GrGLCircleBlurFragmentProcessor(*this); } void GrCircleBlurFragmentProcessor::onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const { GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b); } void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const { inout->mulByUnknownSingleComponent(); } // Evaluate an AA circle function centered at the origin with 'radius' at (x,y) static inline float disk(float x, float y, float radius) { float distSq = x*x + y*y; if (distSq <= (radius-0.5f)*(radius-0.5f)) { return 1.0f; } else if (distSq >= (radius+0.5f)*(radius+0.5f)) { return 0.0f; } else { float ramp = radius + 0.5f - sqrtf(distSq); SkASSERT(ramp >= 0.0f && ramp <= 1.0f); return ramp; } } // Create the top half of an even-sized Gaussian kernel static void make_half_kernel(float* kernel, int kernelWH, float sigma) { SkASSERT(!(kernelWH & 1)); const float kernelOff = (kernelWH-1)/2.0f; float b = 1.0f / (2.0f * sigma * sigma); // omit the scale term since we're just going to renormalize float tot = 0.0f; for (int y = 0; y < kernelWH/2; ++y) { for (int x = 0; x < kernelWH/2; ++x) { // TODO: use a cheap approximation of the 2D Guassian? float x2 = (x-kernelOff) * (x-kernelOff); float y2 = (y-kernelOff) * (y-kernelOff); // The kernel is symmetric so only compute it once for both sides kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = expf(-(x2 + y2) * b); tot += 2.0f * kernel[y*kernelWH+x]; } } // Still normalize the half kernel to 1.0 (rather than 0.5) so we don't // have to scale by 2.0 after convolution. for (int y = 0; y < kernelWH/2; ++y) { for (int x = 0; x < kernelWH; ++x) { kernel[y*kernelWH+x] /= tot; } } } // Apply the half-kernel at 't' away from the center of the circle static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH) { SkASSERT(!(kernelWH & 1)); const float kernelOff = (kernelWH-1)/2.0f; float acc = 0; for (int y = 0; y < kernelWH/2; ++y) { if (kernelOff-y > halfWidth+0.5f) { // All disk() samples in this row will be 0.0f continue; } for (int x = 0; x < kernelWH; ++x) { float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth); float kernel = halfKernel[y*kernelWH+x]; acc += kernel * image; } } return SkUnitScalarClampToByte(acc); } static inline void compute_profile_offset_and_size(float halfWH, float sigma, float* offset, int* size) { if (3*sigma <= halfWH) { // The circle is bigger than the Gaussian. In this case we know the interior of the // blurred circle is solid. *offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weights texture. // It should always be 255. *size = SkScalarCeilToInt(6*sigma); } else { // The Gaussian is bigger than the circle. *offset = 0.0f; *size = SkScalarCeilToInt(halfWH + 3*sigma); } } static uint8_t* create_profile(float halfWH, float sigma) { int kernelWH = SkScalarCeilToInt(6.0f*sigma); kernelWH = (kernelWH + 1) & ~1; // make it the next even number up SkAutoTArray halfKernel(kernelWH*kernelWH/2); make_half_kernel(halfKernel.get(), kernelWH, sigma); float offset; int numSteps; compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps); uint8_t* weights = new uint8_t[numSteps]; for (int i = 0; i < numSteps; ++i) { weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH); } return weights; } GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( GrTextureProvider* textureProvider, const SkRect& circle, float sigma, float* offset) { float halfWH = circle.width() / 2.0f; int size; compute_profile_offset_and_size(halfWH, sigma, offset, &size); GrSurfaceDesc texDesc; texDesc.fWidth = size; texDesc.fHeight = 1; texDesc.fConfig = kAlpha_8_GrPixelConfig; static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); GrUniqueKey key; GrUniqueKey::Builder builder(&key, kDomain, 2); // The profile curve varies with both the sigma of the Gaussian and the size of the // disk. Quantizing to 16.16 should be close enough though. builder[0] = SkScalarToFixed(sigma); builder[1] = SkScalarToFixed(halfWH); builder.finish(); GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); if (!blurProfile) { SkAutoTDeleteArray profile(create_profile(halfWH, sigma)); blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0); if (blurProfile) { textureProvider->assignUniqueKeyToTexture(key, blurProfile); } } return blurProfile; } GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) { SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); SkRect circle = SkRect::MakeWH(wh, wh); return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma); } #endif