Home | History | Annotate | Download | only in effects
      1 /*
      2  * Copyright 2012 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "GrGaussianConvolutionFragmentProcessor.h"
      9 
     10 #include "GrTexture.h"
     11 #include "GrTextureProxy.h"
     12 #include "../private/GrGLSL.h"
     13 #include "glsl/GrGLSLFragmentProcessor.h"
     14 #include "glsl/GrGLSLFragmentShaderBuilder.h"
     15 #include "glsl/GrGLSLProgramDataManager.h"
     16 #include "glsl/GrGLSLUniformHandler.h"
     17 
     18 // For brevity
     19 using UniformHandle = GrGLSLProgramDataManager::UniformHandle;
     20 using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
     21 
     22 class GrGLConvolutionEffect : public GrGLSLFragmentProcessor {
     23 public:
     24     void emitCode(EmitArgs&) override;
     25 
     26     static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*);
     27 
     28 protected:
     29     void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
     30 
     31 private:
     32     UniformHandle fKernelUni;
     33     UniformHandle fImageIncrementUni;
     34     UniformHandle fBoundsUni;
     35 
     36     typedef GrGLSLFragmentProcessor INHERITED;
     37 };
     38 
     39 void GrGLConvolutionEffect::emitCode(EmitArgs& args) {
     40     const GrGaussianConvolutionFragmentProcessor& ce =
     41             args.fFp.cast<GrGaussianConvolutionFragmentProcessor>();
     42 
     43     GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
     44     fImageIncrementUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
     45                                                     "ImageIncrement");
     46     if (ce.useBounds()) {
     47         fBoundsUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
     48                                                 "Bounds");
     49     }
     50 
     51     int width = ce.width();
     52 
     53     int arrayCount = (width + 3) / 4;
     54     SkASSERT(4 * arrayCount >= width);
     55 
     56     fKernelUni = uniformHandler->addUniformArray(kFragment_GrShaderFlag, kHalf4_GrSLType,
     57                                                  "Kernel", arrayCount);
     58 
     59     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
     60     SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
     61 
     62     fragBuilder->codeAppendf("%s = half4(0, 0, 0, 0);", args.fOutputColor);
     63 
     64     const GrShaderVar& kernel = uniformHandler->getUniformVariable(fKernelUni);
     65     const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni);
     66 
     67     fragBuilder->codeAppendf("float2 coord = %s - %d.0 * %s;", coords2D.c_str(), ce.radius(), imgInc);
     68     fragBuilder->codeAppend("float2 coordSampled = half2(0, 0);");
     69 
     70     // Manually unroll loop because some drivers don't; yields 20-30% speedup.
     71     const char* kVecSuffix[4] = {".x", ".y", ".z", ".w"};
     72     for (int i = 0; i < width; i++) {
     73         SkString index;
     74         SkString kernelIndex;
     75         index.appendS32(i / 4);
     76         kernel.appendArrayAccess(index.c_str(), &kernelIndex);
     77         kernelIndex.append(kVecSuffix[i & 0x3]);
     78 
     79         fragBuilder->codeAppend("coordSampled = coord;");
     80         if (ce.useBounds()) {
     81             // We used to compute a bool indicating whether we're in bounds or not, cast it to a
     82             // float, and then mul weight*texture_sample by the float. However, the Adreno 430 seems
     83             // to have a bug that caused corruption.
     84             const char* bounds = uniformHandler->getUniformCStr(fBoundsUni);
     85             const char* component = ce.direction() == Direction::kY ? "y" : "x";
     86 
     87             switch (ce.mode()) {
     88                 case GrTextureDomain::kClamp_Mode: {
     89                     fragBuilder->codeAppendf("coordSampled.%s = clamp(coord.%s, %s.x, %s.y);\n",
     90                                              component, component, bounds, bounds);
     91                     break;
     92                 }
     93                 case GrTextureDomain::kRepeat_Mode: {
     94                     fragBuilder->codeAppendf("coordSampled.%s = "
     95                                              "mod(coord.%s - %s.x, %s.y - %s.x) + %s.x;\n",
     96                                              component, component, bounds, bounds, bounds, bounds);
     97                     break;
     98                 }
     99                 case GrTextureDomain::kDecal_Mode: {
    100                     fragBuilder->codeAppendf("if (coord.%s >= %s.x && coord.%s <= %s.y) {",
    101                                              component, bounds, component, bounds);
    102                     break;
    103                 }
    104                 default: {
    105                     SK_ABORT("Unsupported operation.");
    106                 }
    107             }
    108         }
    109         fragBuilder->codeAppendf("%s += ", args.fOutputColor);
    110         fragBuilder->appendTextureLookup(args.fTexSamplers[0], "coordSampled");
    111         fragBuilder->codeAppendf(" * %s;\n", kernelIndex.c_str());
    112         if (GrTextureDomain::kDecal_Mode == ce.mode()) {
    113             fragBuilder->codeAppend("}");
    114         }
    115         fragBuilder->codeAppendf("coord += %s;\n", imgInc);
    116     }
    117     fragBuilder->codeAppendf("%s *= %s;\n", args.fOutputColor, args.fInputColor);
    118 }
    119 
    120 void GrGLConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdman,
    121                                       const GrFragmentProcessor& processor) {
    122     const GrGaussianConvolutionFragmentProcessor& conv =
    123             processor.cast<GrGaussianConvolutionFragmentProcessor>();
    124     GrSurfaceProxy* proxy = conv.textureSampler(0).proxy();
    125     GrTexture& texture = *proxy->priv().peekTexture();
    126 
    127     float imageIncrement[2] = {0};
    128     float ySign = proxy->origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
    129     switch (conv.direction()) {
    130         case Direction::kX:
    131             imageIncrement[0] = 1.0f / texture.width();
    132             break;
    133         case Direction::kY:
    134             imageIncrement[1] = ySign / texture.height();
    135             break;
    136         default:
    137             SK_ABORT("Unknown filter direction.");
    138     }
    139     pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
    140     if (conv.useBounds()) {
    141         float bounds[2] = {0};
    142         bounds[0] = conv.bounds()[0];
    143         bounds[1] = conv.bounds()[1];
    144         if (GrTextureDomain::kClamp_Mode == conv.mode()) {
    145             bounds[0] += SK_ScalarHalf;
    146             bounds[1] -= SK_ScalarHalf;
    147         }
    148         if (Direction::kX == conv.direction()) {
    149             SkScalar inv = SkScalarInvert(SkIntToScalar(texture.width()));
    150             pdman.set2f(fBoundsUni, inv * bounds[0], inv * bounds[1]);
    151         } else {
    152             SkScalar inv = SkScalarInvert(SkIntToScalar(texture.height()));
    153             if (proxy->origin() != kTopLeft_GrSurfaceOrigin) {
    154                 pdman.set2f(fBoundsUni, 1.0f - (inv * bounds[1]), 1.0f - (inv * bounds[0]));
    155             } else {
    156                 pdman.set2f(fBoundsUni, inv * bounds[1], inv * bounds[0]);
    157             }
    158         }
    159     }
    160     int width = conv.width();
    161 
    162     int arrayCount = (width + 3) / 4;
    163     SkASSERT(4 * arrayCount >= width);
    164     pdman.set4fv(fKernelUni, arrayCount, conv.kernel());
    165 }
    166 
    167 void GrGLConvolutionEffect::GenKey(const GrProcessor& processor, const GrShaderCaps&,
    168                                    GrProcessorKeyBuilder* b) {
    169     const GrGaussianConvolutionFragmentProcessor& conv =
    170             processor.cast<GrGaussianConvolutionFragmentProcessor>();
    171     uint32_t key = conv.radius();
    172     key <<= 3;
    173     key |= Direction::kY == conv.direction() ? 0x4 : 0x0;
    174     key |= static_cast<uint32_t>(conv.mode());
    175     b->add32(key);
    176 }
    177 
    178 ///////////////////////////////////////////////////////////////////////////////
    179 static void fill_in_1D_gaussian_kernel(float* kernel, int width, float gaussianSigma, int radius) {
    180     const float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma);
    181 
    182     float sum = 0.0f;
    183     for (int i = 0; i < width; ++i) {
    184         float x = static_cast<float>(i - radius);
    185         // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
    186         // is dropped here, since we renormalize the kernel below.
    187         kernel[i] = sk_float_exp(-x * x * denom);
    188         sum += kernel[i];
    189     }
    190     // Normalize the kernel
    191     float scale = 1.0f / sum;
    192     for (int i = 0; i < width; ++i) {
    193         kernel[i] *= scale;
    194     }
    195 }
    196 
    197 GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
    198                                                             sk_sp<GrTextureProxy> proxy,
    199                                                             Direction direction,
    200                                                             int radius,
    201                                                             float gaussianSigma,
    202                                                             GrTextureDomain::Mode mode,
    203                                                             int bounds[2])
    204         : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID,
    205                     ModulateByConfigOptimizationFlags(proxy->config()))
    206         , fCoordTransform(proxy.get())
    207         , fTextureSampler(std::move(proxy))
    208         , fRadius(radius)
    209         , fDirection(direction)
    210         , fMode(mode) {
    211     this->addCoordTransform(&fCoordTransform);
    212     this->addTextureSampler(&fTextureSampler);
    213     SkASSERT(radius <= kMaxKernelRadius);
    214 
    215     fill_in_1D_gaussian_kernel(fKernel, this->width(), gaussianSigma, this->radius());
    216 
    217     memcpy(fBounds, bounds, sizeof(fBounds));
    218 }
    219 
    220 GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
    221         const GrGaussianConvolutionFragmentProcessor& that)
    222         : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID, that.optimizationFlags())
    223         , fCoordTransform(that.fCoordTransform)
    224         , fTextureSampler(that.fTextureSampler)
    225         , fRadius(that.fRadius)
    226         , fDirection(that.fDirection)
    227         , fMode(that.fMode) {
    228     this->addCoordTransform(&fCoordTransform);
    229     this->addTextureSampler(&fTextureSampler);
    230     memcpy(fKernel, that.fKernel, that.width() * sizeof(float));
    231     memcpy(fBounds, that.fBounds, sizeof(fBounds));
    232 }
    233 
    234 void GrGaussianConvolutionFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
    235                                                                    GrProcessorKeyBuilder* b) const {
    236     GrGLConvolutionEffect::GenKey(*this, caps, b);
    237 }
    238 
    239 GrGLSLFragmentProcessor* GrGaussianConvolutionFragmentProcessor::onCreateGLSLInstance() const {
    240     return new GrGLConvolutionEffect;
    241 }
    242 
    243 bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor& sBase) const {
    244     const GrGaussianConvolutionFragmentProcessor& s =
    245             sBase.cast<GrGaussianConvolutionFragmentProcessor>();
    246     return (this->radius() == s.radius() && this->direction() == s.direction() &&
    247             this->mode() == s.mode() &&
    248             0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) &&
    249             0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
    250 }
    251 
    252 ///////////////////////////////////////////////////////////////////////////////
    253 
    254 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor);
    255 
    256 #if GR_TEST_UTILS
    257 std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate(
    258         GrProcessorTestData* d) {
    259     int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
    260                                         : GrProcessorUnitTest::kAlphaTextureIdx;
    261     sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
    262 
    263     int bounds[2];
    264     int modeIdx = d->fRandom->nextRangeU(0, GrTextureDomain::kModeCount-1);
    265 
    266     Direction dir;
    267     if (d->fRandom->nextBool()) {
    268         dir = Direction::kX;
    269         bounds[0] = d->fRandom->nextRangeU(0, proxy->width()-1);
    270         bounds[1] = d->fRandom->nextRangeU(bounds[0], proxy->width()-1);
    271     } else {
    272         dir = Direction::kY;
    273         bounds[0] = d->fRandom->nextRangeU(0, proxy->height()-1);
    274         bounds[1] = d->fRandom->nextRangeU(bounds[0], proxy->height()-1);
    275     }
    276 
    277     int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius);
    278     float sigma = radius / 3.f;
    279 
    280     return GrGaussianConvolutionFragmentProcessor::Make(
    281             d->textureProxy(texIdx),
    282             dir, radius, sigma, static_cast<GrTextureDomain::Mode>(modeIdx), bounds);
    283 }
    284 #endif
    285