blob: 7718e43f7bb0e5775fa5bebf31c4b5c5d01083b4 [file] [log] [blame]
/*
* 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 "GrInvariantOutput.h"
#include "GrTextureProvider.h"
#include "glsl/GrGLSLFragmentProcessor.h"
#include "glsl/GrGLSLFragmentShaderBuilder.h"
#include "glsl/GrGLSLProgramDataManager.h"
#include "glsl/GrGLSLUniformHandler.h"
#include "SkFixed.h"
class GrGLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor {
public:
void emitCode(EmitArgs&) override;
protected:
void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
private:
GrGLSLProgramDataManager::UniformHandle fDataUniform;
typedef GrGLSLFragmentProcessor 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 inverse of the profile texture size
fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
kVec4f_GrSLType,
kDefault_GrSLPrecision,
"data",
&dataName);
GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
const char *fragmentPos = fragBuilder->fragmentPosition();
if (args.fInputColor) {
fragBuilder->codeAppendf("vec4 src=%s;", args.fInputColor);
} else {
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
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;",
dataName, dataName);
fragBuilder->codeAppendf("float intensity = ");
fragBuilder->appendTextureLookup(args.fTexSamplers[0], "vec2(dist, 0.5)");
fragBuilder->codeAppend(".a;");
fragBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor );
}
void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLSLProgramDataManager& pdman,
const GrProcessor& proc) {
const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentProcessor>();
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 inverse of the profile texture size
pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(),
1.0f / 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<GrCircleBlurFragmentProcessor>();
this->addTextureAccess(&fBlurProfileAccess);
this->setWillReadFragmentPosition();
}
GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const {
return new GrGLCircleBlurFragmentProcessor;
}
void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(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));
// We treat each cell in the half-kernel as a 1x1 window and evaluate it
// at the center. So the evaluations go from -kernelOff to kernelOff in x
// and -kernelOff to -.5 in y (since this is a top-half kernel).
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
float value = expf(-(x2 + y2) * b);
kernel[y * kernelWH + x] = value;
kernel[y * kernelWH + (kernelWH - x - 1)] = value;
tot += 2.0f * value;
}
}
// 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 circleR, float* halfKernel, int kernelWH) {
SkASSERT(!(kernelWH & 1));
float acc = 0;
// We evaluate the kernel application at (x=t, y=0) using halfKernel which represents the top
// half of a 2D Guassian kernel. The full kernel is symmetric so evaluating just the upper half
// is sufficient. The half kernel has been normalized to 1 rather than 0.5 so there is no need
// to double after evaluation.
// The sample positions relative to (t, 0) match the sampling used to create the half kernel.
const float kernelOff = (kernelWH - 1) / 2.0f;
for (int j = 0; j < kernelWH / 2; ++j) {
float y = (kernelOff - j);
if (y > circleR + 0.5f) {
// The entire row is above the circle.
continue;
}
for (int i = 0; i < kernelWH; ++i) {
float x = t - kernelOff + i;
if (x > circleR + 0.5f) {
// Stop evaluation once x crosses outside the circle.
break;
}
float image = disk(x, y, circleR);
float kernel = halfKernel[j * kernelWH + i];
acc += kernel * image;
}
}
return SkUnitScalarClampToByte(acc);
}
static inline void compute_profile_offset_and_size(float circleR, float sigma,
float* offset, int* size) {
if (3*sigma <= circleR) {
// The circle is bigger than the Gaussian. In this case we know the interior of the
// blurred circle is solid.
*offset = circleR - 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(circleR + 3*sigma);
}
}
static uint8_t* create_profile(float circleR, float sigma) {
int kernelWH = SkScalarCeilToInt(6.0f*sigma);
kernelWH = (kernelWH + 1) & ~1; // make it the next even number up
SkAutoTArray<float> halfKernel(kernelWH * kernelWH / 2);
make_half_kernel(halfKernel.get(), kernelWH, sigma);
float offset;
int numSteps;
compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps);
uint8_t* weights = new uint8_t[numSteps];
for (int i = 0; i < numSteps - 1; ++i) {
weights[i] = eval_at(offset+i, circleR, halfKernel.get(), kernelWH);
}
// Ensure the tail of the Gaussian goes to zero.
weights[numSteps - 1] = 0;
return weights;
}
GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture(
GrTextureProvider* textureProvider,
const SkRect& circle,
float sigma,
float* offset) {
float circleR = circle.width() / 2.0f;
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(circleR);
builder.finish();
GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key);
int profileSize;
compute_profile_offset_and_size(circleR, sigma, offset, &profileSize);
if (!blurProfile) {
GrSurfaceDesc texDesc;
texDesc.fWidth = profileSize;
texDesc.fHeight = 1;
texDesc.fConfig = kAlpha_8_GrPixelConfig;
SkAutoTDeleteArray<uint8_t> profile(create_profile(circleR, sigma));
blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes, 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