michael@0: /* michael@0: * Copyright 2012 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "GrConvolutionEffect.h" michael@0: #include "gl/GrGLEffect.h" michael@0: #include "gl/GrGLSL.h" michael@0: #include "gl/GrGLTexture.h" michael@0: #include "GrTBackendEffectFactory.h" michael@0: michael@0: // For brevity michael@0: typedef GrGLUniformManager::UniformHandle UniformHandle; michael@0: michael@0: class GrGLConvolutionEffect : public GrGLEffect { michael@0: public: michael@0: GrGLConvolutionEffect(const GrBackendEffectFactory&, const GrDrawEffect&); michael@0: michael@0: virtual void emitCode(GrGLShaderBuilder*, michael@0: const GrDrawEffect&, michael@0: EffectKey, michael@0: const char* outputColor, michael@0: const char* inputColor, michael@0: const TransformedCoordsArray&, michael@0: const TextureSamplerArray&) SK_OVERRIDE; michael@0: michael@0: virtual void setData(const GrGLUniformManager& uman, const GrDrawEffect&) SK_OVERRIDE; michael@0: michael@0: static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&); michael@0: michael@0: private: michael@0: int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); } michael@0: bool useBounds() const { return fUseBounds; } michael@0: Gr1DKernelEffect::Direction direction() const { return fDirection; } michael@0: michael@0: int fRadius; michael@0: bool fUseBounds; michael@0: Gr1DKernelEffect::Direction fDirection; michael@0: UniformHandle fKernelUni; michael@0: UniformHandle fImageIncrementUni; michael@0: UniformHandle fBoundsUni; michael@0: michael@0: typedef GrGLEffect INHERITED; michael@0: }; michael@0: michael@0: GrGLConvolutionEffect::GrGLConvolutionEffect(const GrBackendEffectFactory& factory, michael@0: const GrDrawEffect& drawEffect) michael@0: : INHERITED(factory) { michael@0: const GrConvolutionEffect& c = drawEffect.castEffect(); michael@0: fRadius = c.radius(); michael@0: fUseBounds = c.useBounds(); michael@0: fDirection = c.direction(); michael@0: } michael@0: michael@0: void GrGLConvolutionEffect::emitCode(GrGLShaderBuilder* builder, michael@0: const GrDrawEffect&, michael@0: EffectKey key, michael@0: const char* outputColor, michael@0: const char* inputColor, michael@0: const TransformedCoordsArray& coords, michael@0: const TextureSamplerArray& samplers) { michael@0: SkString coords2D = builder->ensureFSCoords2D(coords, 0); michael@0: fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility, michael@0: kVec2f_GrSLType, "ImageIncrement"); michael@0: if (this->useBounds()) { michael@0: fBoundsUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility, michael@0: kVec2f_GrSLType, "Bounds"); michael@0: } michael@0: fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_Visibility, michael@0: kFloat_GrSLType, "Kernel", this->width()); michael@0: michael@0: builder->fsCodeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor); michael@0: michael@0: int width = this->width(); michael@0: const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni); michael@0: const char* imgInc = builder->getUniformCStr(fImageIncrementUni); michael@0: michael@0: builder->fsCodeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords2D.c_str(), fRadius, imgInc); michael@0: michael@0: // Manually unroll loop because some drivers don't; yields 20-30% speedup. michael@0: for (int i = 0; i < width; i++) { michael@0: SkString index; michael@0: SkString kernelIndex; michael@0: index.appendS32(i); michael@0: kernel.appendArrayAccess(index.c_str(), &kernelIndex); michael@0: builder->fsCodeAppendf("\t\t%s += ", outputColor); michael@0: builder->fsAppendTextureLookup(samplers[0], "coord"); michael@0: if (this->useBounds()) { michael@0: const char* bounds = builder->getUniformCStr(fBoundsUni); michael@0: const char* component = this->direction() == Gr1DKernelEffect::kY_Direction ? "y" : "x"; michael@0: builder->fsCodeAppendf(" * float(coord.%s >= %s.x && coord.%s <= %s.y)", michael@0: component, bounds, component, bounds); michael@0: } michael@0: builder->fsCodeAppendf(" * %s;\n", kernelIndex.c_str()); michael@0: builder->fsCodeAppendf("\t\tcoord += %s;\n", imgInc); michael@0: } michael@0: michael@0: SkString modulate; michael@0: GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor); michael@0: builder->fsCodeAppend(modulate.c_str()); michael@0: } michael@0: michael@0: void GrGLConvolutionEffect::setData(const GrGLUniformManager& uman, michael@0: const GrDrawEffect& drawEffect) { michael@0: const GrConvolutionEffect& conv = drawEffect.castEffect(); michael@0: GrTexture& texture = *conv.texture(0); michael@0: // the code we generated was for a specific kernel radius michael@0: SkASSERT(conv.radius() == fRadius); michael@0: float imageIncrement[2] = { 0 }; michael@0: float ySign = texture.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f; michael@0: switch (conv.direction()) { michael@0: case Gr1DKernelEffect::kX_Direction: michael@0: imageIncrement[0] = 1.0f / texture.width(); michael@0: break; michael@0: case Gr1DKernelEffect::kY_Direction: michael@0: imageIncrement[1] = ySign / texture.height(); michael@0: break; michael@0: default: michael@0: GrCrash("Unknown filter direction."); michael@0: } michael@0: uman.set2fv(fImageIncrementUni, 1, imageIncrement); michael@0: if (conv.useBounds()) { michael@0: const float* bounds = conv.bounds(); michael@0: if (Gr1DKernelEffect::kY_Direction == conv.direction() && michael@0: texture.origin() != kTopLeft_GrSurfaceOrigin) { michael@0: uman.set2f(fBoundsUni, 1.0f - bounds[1], 1.0f - bounds[0]); michael@0: } else { michael@0: uman.set2f(fBoundsUni, bounds[0], bounds[1]); michael@0: } michael@0: } michael@0: uman.set1fv(fKernelUni, this->width(), conv.kernel()); michael@0: } michael@0: michael@0: GrGLEffect::EffectKey GrGLConvolutionEffect::GenKey(const GrDrawEffect& drawEffect, michael@0: const GrGLCaps&) { michael@0: const GrConvolutionEffect& conv = drawEffect.castEffect(); michael@0: EffectKey key = conv.radius(); michael@0: key <<= 2; michael@0: if (conv.useBounds()) { michael@0: key |= 0x2; michael@0: key |= GrConvolutionEffect::kY_Direction == conv.direction() ? 0x1 : 0x0; michael@0: } michael@0: return key; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture, michael@0: Direction direction, michael@0: int radius, michael@0: const float* kernel, michael@0: bool useBounds, michael@0: float bounds[2]) michael@0: : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) { michael@0: SkASSERT(radius <= kMaxKernelRadius); michael@0: SkASSERT(NULL != kernel); michael@0: int width = this->width(); michael@0: for (int i = 0; i < width; i++) { michael@0: fKernel[i] = kernel[i]; michael@0: } michael@0: memcpy(fBounds, bounds, sizeof(fBounds)); michael@0: } michael@0: michael@0: GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture, michael@0: Direction direction, michael@0: int radius, michael@0: float gaussianSigma, michael@0: bool useBounds, michael@0: float bounds[2]) michael@0: : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) { michael@0: SkASSERT(radius <= kMaxKernelRadius); michael@0: int width = this->width(); michael@0: michael@0: float sum = 0.0f; michael@0: float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma); michael@0: for (int i = 0; i < width; ++i) { michael@0: float x = static_cast(i - this->radius()); michael@0: // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian michael@0: // is dropped here, since we renormalize the kernel below. michael@0: fKernel[i] = sk_float_exp(- x * x * denom); michael@0: sum += fKernel[i]; michael@0: } michael@0: // Normalize the kernel michael@0: float scale = 1.0f / sum; michael@0: for (int i = 0; i < width; ++i) { michael@0: fKernel[i] *= scale; michael@0: } michael@0: memcpy(fBounds, bounds, sizeof(fBounds)); michael@0: } michael@0: michael@0: GrConvolutionEffect::~GrConvolutionEffect() { michael@0: } michael@0: michael@0: const GrBackendEffectFactory& GrConvolutionEffect::getFactory() const { michael@0: return GrTBackendEffectFactory::getInstance(); michael@0: } michael@0: michael@0: bool GrConvolutionEffect::onIsEqual(const GrEffect& sBase) const { michael@0: const GrConvolutionEffect& s = CastEffect(sBase); michael@0: return (this->texture(0) == s.texture(0) && michael@0: this->radius() == s.radius() && michael@0: this->direction() == s.direction() && michael@0: this->useBounds() == s.useBounds() && michael@0: 0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) && michael@0: 0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float))); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: GR_DEFINE_EFFECT_TEST(GrConvolutionEffect); michael@0: michael@0: GrEffectRef* GrConvolutionEffect::TestCreate(SkRandom* random, michael@0: GrContext*, michael@0: const GrDrawTargetCaps&, michael@0: GrTexture* textures[]) { michael@0: int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx : michael@0: GrEffectUnitTest::kAlphaTextureIdx; michael@0: Direction dir = random->nextBool() ? kX_Direction : kY_Direction; michael@0: int radius = random->nextRangeU(1, kMaxKernelRadius); michael@0: float kernel[kMaxKernelWidth]; michael@0: for (size_t i = 0; i < SK_ARRAY_COUNT(kernel); ++i) { michael@0: kernel[i] = random->nextSScalar1(); michael@0: } michael@0: float bounds[2]; michael@0: for (size_t i = 0; i < SK_ARRAY_COUNT(bounds); ++i) { michael@0: bounds[i] = random->nextF(); michael@0: } michael@0: michael@0: bool useBounds = random->nextBool(); michael@0: return GrConvolutionEffect::Create(textures[texIdx], michael@0: dir, michael@0: radius, michael@0: kernel, michael@0: useBounds, michael@0: bounds); michael@0: }