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 "gl/builders/GrGLProgramBuilder.h"
      9 #include "GrConvolutionEffect.h"
     10 #include "gl/GrGLProcessor.h"
     11 #include "gl/GrGLSL.h"
     12 #include "gl/GrGLTexture.h"
     13 #include "GrTBackendProcessorFactory.h"
     14 
     15 // For brevity
     16 typedef GrGLProgramDataManager::UniformHandle UniformHandle;
     17 
     18 class GrGLConvolutionEffect : public GrGLFragmentProcessor {
     19 public:
     20     GrGLConvolutionEffect(const GrBackendProcessorFactory&, const GrProcessor&);
     21 
     22     virtual void emitCode(GrGLProgramBuilder*,
     23                           const GrFragmentProcessor&,
     24                           const GrProcessorKey&,
     25                           const char* outputColor,
     26                           const char* inputColor,
     27                           const TransformedCoordsArray&,
     28                           const TextureSamplerArray&) SK_OVERRIDE;
     29 
     30     virtual void setData(const GrGLProgramDataManager& pdman, const GrProcessor&) SK_OVERRIDE;
     31 
     32     static inline void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder*);
     33 
     34 private:
     35     int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); }
     36     bool useBounds() const { return fUseBounds; }
     37     Gr1DKernelEffect::Direction direction() const { return fDirection; }
     38 
     39     int                 fRadius;
     40     bool                fUseBounds;
     41     Gr1DKernelEffect::Direction    fDirection;
     42     UniformHandle       fKernelUni;
     43     UniformHandle       fImageIncrementUni;
     44     UniformHandle       fBoundsUni;
     45 
     46     typedef GrGLFragmentProcessor INHERITED;
     47 };
     48 
     49 GrGLConvolutionEffect::GrGLConvolutionEffect(const GrBackendProcessorFactory& factory,
     50                                              const GrProcessor& processor)
     51     : INHERITED(factory) {
     52     const GrConvolutionEffect& c = processor.cast<GrConvolutionEffect>();
     53     fRadius = c.radius();
     54     fUseBounds = c.useBounds();
     55     fDirection = c.direction();
     56 }
     57 
     58 void GrGLConvolutionEffect::emitCode(GrGLProgramBuilder* builder,
     59                                      const GrFragmentProcessor&,
     60                                      const GrProcessorKey& key,
     61                                      const char* outputColor,
     62                                      const char* inputColor,
     63                                      const TransformedCoordsArray& coords,
     64                                      const TextureSamplerArray& samplers) {
     65     fImageIncrementUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
     66                                              kVec2f_GrSLType, "ImageIncrement");
     67     if (this->useBounds()) {
     68         fBoundsUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
     69                                          kVec2f_GrSLType, "Bounds");
     70     }
     71     fKernelUni = builder->addUniformArray(GrGLProgramBuilder::kFragment_Visibility,
     72                                           kFloat_GrSLType, "Kernel", this->width());
     73 
     74     GrGLFragmentShaderBuilder* fsBuilder = builder->getFragmentShaderBuilder();
     75     SkString coords2D = fsBuilder->ensureFSCoords2D(coords, 0);
     76 
     77     fsBuilder->codeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
     78 
     79     int width = this->width();
     80     const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni);
     81     const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
     82 
     83     fsBuilder->codeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords2D.c_str(), fRadius, imgInc);
     84 
     85     // Manually unroll loop because some drivers don't; yields 20-30% speedup.
     86     for (int i = 0; i < width; i++) {
     87         SkString index;
     88         SkString kernelIndex;
     89         index.appendS32(i);
     90         kernel.appendArrayAccess(index.c_str(), &kernelIndex);
     91         fsBuilder->codeAppendf("\t\t%s += ", outputColor);
     92         fsBuilder->appendTextureLookup(samplers[0], "coord");
     93         if (this->useBounds()) {
     94             const char* bounds = builder->getUniformCStr(fBoundsUni);
     95             const char* component = this->direction() == Gr1DKernelEffect::kY_Direction ? "y" : "x";
     96             fsBuilder->codeAppendf(" * float(coord.%s >= %s.x && coord.%s <= %s.y)",
     97                 component, bounds, component, bounds);
     98         }
     99         fsBuilder->codeAppendf(" * %s;\n", kernelIndex.c_str());
    100         fsBuilder->codeAppendf("\t\tcoord += %s;\n", imgInc);
    101     }
    102 
    103     SkString modulate;
    104     GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
    105     fsBuilder->codeAppend(modulate.c_str());
    106 }
    107 
    108 void GrGLConvolutionEffect::setData(const GrGLProgramDataManager& pdman,
    109                                     const GrProcessor& processor) {
    110     const GrConvolutionEffect& conv = processor.cast<GrConvolutionEffect>();
    111     GrTexture& texture = *conv.texture(0);
    112     // the code we generated was for a specific kernel radius
    113     SkASSERT(conv.radius() == fRadius);
    114     float imageIncrement[2] = { 0 };
    115     float ySign = texture.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
    116     switch (conv.direction()) {
    117         case Gr1DKernelEffect::kX_Direction:
    118             imageIncrement[0] = 1.0f / texture.width();
    119             break;
    120         case Gr1DKernelEffect::kY_Direction:
    121             imageIncrement[1] = ySign / texture.height();
    122             break;
    123         default:
    124             SkFAIL("Unknown filter direction.");
    125     }
    126     pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
    127     if (conv.useBounds()) {
    128         const float* bounds = conv.bounds();
    129         if (Gr1DKernelEffect::kY_Direction == conv.direction() &&
    130             texture.origin() != kTopLeft_GrSurfaceOrigin) {
    131             pdman.set2f(fBoundsUni, 1.0f - bounds[1], 1.0f - bounds[0]);
    132         } else {
    133             pdman.set2f(fBoundsUni, bounds[0], bounds[1]);
    134         }
    135     }
    136     pdman.set1fv(fKernelUni, this->width(), conv.kernel());
    137 }
    138 
    139 void GrGLConvolutionEffect::GenKey(const GrProcessor& processor, const GrGLCaps&,
    140                                    GrProcessorKeyBuilder* b) {
    141     const GrConvolutionEffect& conv = processor.cast<GrConvolutionEffect>();
    142     uint32_t key = conv.radius();
    143     key <<= 2;
    144     if (conv.useBounds()) {
    145         key |= 0x2;
    146         key |= GrConvolutionEffect::kY_Direction == conv.direction() ? 0x1 : 0x0;
    147     }
    148     b->add32(key);
    149 }
    150 
    151 ///////////////////////////////////////////////////////////////////////////////
    152 
    153 GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
    154                                          Direction direction,
    155                                          int radius,
    156                                          const float* kernel,
    157                                          bool useBounds,
    158                                          float bounds[2])
    159     : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) {
    160     SkASSERT(radius <= kMaxKernelRadius);
    161     SkASSERT(kernel);
    162     int width = this->width();
    163     for (int i = 0; i < width; i++) {
    164         fKernel[i] = kernel[i];
    165     }
    166     memcpy(fBounds, bounds, sizeof(fBounds));
    167 }
    168 
    169 GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
    170                                          Direction direction,
    171                                          int radius,
    172                                          float gaussianSigma,
    173                                          bool useBounds,
    174                                          float bounds[2])
    175     : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) {
    176     SkASSERT(radius <= kMaxKernelRadius);
    177     int width = this->width();
    178 
    179     float sum = 0.0f;
    180     float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma);
    181     for (int i = 0; i < width; ++i) {
    182         float x = static_cast<float>(i - this->radius());
    183         // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
    184         // is dropped here, since we renormalize the kernel below.
    185         fKernel[i] = sk_float_exp(- x * x * denom);
    186         sum += fKernel[i];
    187     }
    188     // Normalize the kernel
    189     float scale = 1.0f / sum;
    190     for (int i = 0; i < width; ++i) {
    191         fKernel[i] *= scale;
    192     }
    193     memcpy(fBounds, bounds, sizeof(fBounds));
    194 }
    195 
    196 GrConvolutionEffect::~GrConvolutionEffect() {
    197 }
    198 
    199 const GrBackendFragmentProcessorFactory& GrConvolutionEffect::getFactory() const {
    200     return GrTBackendFragmentProcessorFactory<GrConvolutionEffect>::getInstance();
    201 }
    202 
    203 bool GrConvolutionEffect::onIsEqual(const GrProcessor& sBase) const {
    204     const GrConvolutionEffect& s = sBase.cast<GrConvolutionEffect>();
    205     return (this->texture(0) == s.texture(0) &&
    206             this->radius() == s.radius() &&
    207             this->direction() == s.direction() &&
    208             this->useBounds() == s.useBounds() &&
    209             0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) &&
    210             0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
    211 }
    212 
    213 ///////////////////////////////////////////////////////////////////////////////
    214 
    215 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConvolutionEffect);
    216 
    217 GrFragmentProcessor* GrConvolutionEffect::TestCreate(SkRandom* random,
    218                                                      GrContext*,
    219                                                      const GrDrawTargetCaps&,
    220                                                      GrTexture* textures[]) {
    221     int texIdx = random->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx :
    222                                       GrProcessorUnitTest::kAlphaTextureIdx;
    223     Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
    224     int radius = random->nextRangeU(1, kMaxKernelRadius);
    225     float kernel[kMaxKernelWidth];
    226     for (size_t i = 0; i < SK_ARRAY_COUNT(kernel); ++i) {
    227         kernel[i] = random->nextSScalar1();
    228     }
    229     float bounds[2];
    230     for (size_t i = 0; i < SK_ARRAY_COUNT(bounds); ++i) {
    231         bounds[i] = random->nextF();
    232     }
    233 
    234     bool useBounds = random->nextBool();
    235     return GrConvolutionEffect::Create(textures[texIdx],
    236                                        dir,
    237                                        radius,
    238                                        kernel,
    239                                        useBounds,
    240                                        bounds);
    241 }
    242