| /* | |
| * Copyright 2012 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "GrTextureDomain.h" | |
| #include "GrSimpleTextureEffect.h" | |
| #include "GrTBackendEffectFactory.h" | |
| #include "gl/GrGLEffect.h" | |
| #include "gl/GrGLShaderBuilder.h" | |
| #include "SkFloatingPoint.h" | |
| GrTextureDomain::GrTextureDomain(const SkRect& domain, Mode mode, int index) | |
| : fIndex(index) { | |
| static const SkRect kFullRect = {0, 0, SK_Scalar1, SK_Scalar1}; | |
| if (domain.contains(kFullRect) && kClamp_Mode == mode) { | |
| fMode = kIgnore_Mode; | |
| } else { | |
| fMode = mode; | |
| } | |
| if (fMode != kIgnore_Mode) { | |
| // We don't currently handle domains that are empty or don't intersect the texture. | |
| // It is OK if the domain rect is a line or point, but it should not be inverted. We do not | |
| // handle rects that do not intersect the [0..1]x[0..1] rect. | |
| SkASSERT(domain.fLeft <= domain.fRight); | |
| SkASSERT(domain.fTop <= domain.fBottom); | |
| fDomain.fLeft = SkMaxScalar(domain.fLeft, kFullRect.fLeft); | |
| fDomain.fRight = SkMinScalar(domain.fRight, kFullRect.fRight); | |
| fDomain.fTop = SkMaxScalar(domain.fTop, kFullRect.fTop); | |
| fDomain.fBottom = SkMinScalar(domain.fBottom, kFullRect.fBottom); | |
| SkASSERT(fDomain.fLeft <= fDomain.fRight); | |
| SkASSERT(fDomain.fTop <= fDomain.fBottom); | |
| } | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| void GrTextureDomain::GLDomain::sampleTexture(GrGLShaderBuilder* builder, | |
| const GrTextureDomain& textureDomain, | |
| const char* outColor, | |
| const SkString& inCoords, | |
| const GrGLEffect::TextureSampler sampler, | |
| const char* inModulateColor) { | |
| SkASSERT((Mode)-1 == fMode || textureDomain.mode() == fMode); | |
| SkDEBUGCODE(fMode = textureDomain.mode();) | |
| if (textureDomain.mode() != kIgnore_Mode && !fDomainUni.isValid()) { | |
| const char* name; | |
| SkString uniName("TexDom"); | |
| if (textureDomain.fIndex >= 0) { | |
| uniName.appendS32(textureDomain.fIndex); | |
| } | |
| fDomainUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility, | |
| kVec4f_GrSLType, uniName.c_str(), &name); | |
| fDomainName = name; | |
| } | |
| switch (textureDomain.mode()) { | |
| case kIgnore_Mode: { | |
| builder->fsCodeAppendf("\t%s = ", outColor); | |
| builder->fsAppendTextureLookupAndModulate(inModulateColor, sampler, | |
| inCoords.c_str()); | |
| builder->fsCodeAppend(";\n"); | |
| break; | |
| } | |
| case kClamp_Mode: { | |
| SkString clampedCoords; | |
| clampedCoords.appendf("\tclamp(%s, %s.xy, %s.zw)", | |
| inCoords.c_str(), fDomainName.c_str(), fDomainName.c_str()); | |
| builder->fsCodeAppendf("\t%s = ", outColor); | |
| builder->fsAppendTextureLookupAndModulate(inModulateColor, sampler, | |
| clampedCoords.c_str()); | |
| builder->fsCodeAppend(";\n"); | |
| break; | |
| } | |
| case kDecal_Mode: { | |
| // Add a block since we're going to declare variables. | |
| GrGLShaderBuilder::FSBlock block(builder); | |
| const char* domain = fDomainName.c_str(); | |
| if (kImagination_GrGLVendor == builder->ctxInfo().vendor()) { | |
| // On the NexusS and GalaxyNexus, the other path (with the 'any' | |
| // call) causes the compilation error "Calls to any function that | |
| // may require a gradient calculation inside a conditional block | |
| // may return undefined results". This appears to be an issue with | |
| // the 'any' call since even the simple "result=black; if (any()) | |
| // result=white;" code fails to compile. | |
| builder->fsCodeAppend("\tvec4 outside = vec4(0.0, 0.0, 0.0, 0.0);\n"); | |
| builder->fsCodeAppend("\tvec4 inside = "); | |
| builder->fsAppendTextureLookupAndModulate(inModulateColor, sampler, | |
| inCoords.c_str()); | |
| builder->fsCodeAppend(";\n"); | |
| builder->fsCodeAppendf("\tfloat x = (%s).x;\n", inCoords.c_str()); | |
| builder->fsCodeAppendf("\tfloat y = (%s).y;\n", inCoords.c_str()); | |
| builder->fsCodeAppendf("\tx = abs(2.0*(x - %s.x)/(%s.z - %s.x) - 1.0);\n", | |
| domain, domain, domain); | |
| builder->fsCodeAppendf("\ty = abs(2.0*(y - %s.y)/(%s.w - %s.y) - 1.0);\n", | |
| domain, domain, domain); | |
| builder->fsCodeAppend("\tfloat blend = step(1.0, max(x, y));\n"); | |
| builder->fsCodeAppendf("\t%s = mix(inside, outside, blend);\n", outColor); | |
| } else { | |
| builder->fsCodeAppend("\tbvec4 outside;\n"); | |
| builder->fsCodeAppendf("\toutside.xy = lessThan(%s, %s.xy);\n", inCoords.c_str(), | |
| domain); | |
| builder->fsCodeAppendf("\toutside.zw = greaterThan(%s, %s.zw);\n", inCoords.c_str(), | |
| domain); | |
| builder->fsCodeAppendf("\t%s = any(outside) ? vec4(0.0, 0.0, 0.0, 0.0) : ", | |
| outColor); | |
| builder->fsAppendTextureLookupAndModulate(inModulateColor, sampler, | |
| inCoords.c_str()); | |
| builder->fsCodeAppend(";\n"); | |
| } | |
| break; | |
| } | |
| case kRepeat_Mode: { | |
| SkString clampedCoords; | |
| clampedCoords.printf("\tmod(%s - %s.xy, %s.zw - %s.xy) + %s.xy", | |
| inCoords.c_str(), fDomainName.c_str(), fDomainName.c_str(), | |
| fDomainName.c_str(), fDomainName.c_str()); | |
| builder->fsCodeAppendf("\t%s = ", outColor); | |
| builder->fsAppendTextureLookupAndModulate(inModulateColor, sampler, | |
| clampedCoords.c_str()); | |
| builder->fsCodeAppend(";\n"); | |
| break; | |
| } | |
| } | |
| } | |
| void GrTextureDomain::GLDomain::setData(const GrGLProgramDataManager& pdman, | |
| const GrTextureDomain& textureDomain, | |
| GrSurfaceOrigin textureOrigin) { | |
| SkASSERT(textureDomain.mode() == fMode); | |
| if (kIgnore_Mode != textureDomain.mode()) { | |
| GrGLfloat values[4] = { | |
| SkScalarToFloat(textureDomain.domain().left()), | |
| SkScalarToFloat(textureDomain.domain().top()), | |
| SkScalarToFloat(textureDomain.domain().right()), | |
| SkScalarToFloat(textureDomain.domain().bottom()) | |
| }; | |
| // vertical flip if necessary | |
| if (kBottomLeft_GrSurfaceOrigin == textureOrigin) { | |
| values[1] = 1.0f - values[1]; | |
| values[3] = 1.0f - values[3]; | |
| // The top and bottom were just flipped, so correct the ordering | |
| // of elements so that values = (l, t, r, b). | |
| SkTSwap(values[1], values[3]); | |
| } | |
| if (0 != memcmp(values, fPrevDomain, 4 * sizeof(GrGLfloat))) { | |
| pdman.set4fv(fDomainUni, 1, values); | |
| memcpy(fPrevDomain, values, 4 * sizeof(GrGLfloat)); | |
| } | |
| } | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| class GrGLTextureDomainEffect : public GrGLEffect { | |
| public: | |
| GrGLTextureDomainEffect(const GrBackendEffectFactory&, const GrDrawEffect&); | |
| virtual void emitCode(GrGLShaderBuilder*, | |
| const GrDrawEffect&, | |
| const GrEffectKey&, | |
| const char* outputColor, | |
| const char* inputColor, | |
| const TransformedCoordsArray&, | |
| const TextureSamplerArray&) SK_OVERRIDE; | |
| virtual void setData(const GrGLProgramDataManager&, const GrDrawEffect&) SK_OVERRIDE; | |
| static inline void GenKey(const GrDrawEffect&, const GrGLCaps&, GrEffectKeyBuilder*); | |
| private: | |
| GrTextureDomain::GLDomain fGLDomain; | |
| typedef GrGLEffect INHERITED; | |
| }; | |
| GrGLTextureDomainEffect::GrGLTextureDomainEffect(const GrBackendEffectFactory& factory, | |
| const GrDrawEffect&) | |
| : INHERITED(factory) { | |
| } | |
| void GrGLTextureDomainEffect::emitCode(GrGLShaderBuilder* builder, | |
| const GrDrawEffect& drawEffect, | |
| const GrEffectKey& key, | |
| const char* outputColor, | |
| const char* inputColor, | |
| const TransformedCoordsArray& coords, | |
| const TextureSamplerArray& samplers) { | |
| const GrTextureDomainEffect& effect = drawEffect.castEffect<GrTextureDomainEffect>(); | |
| const GrTextureDomain& domain = effect.textureDomain(); | |
| SkString coords2D = builder->ensureFSCoords2D(coords, 0); | |
| fGLDomain.sampleTexture(builder, domain, outputColor, coords2D, samplers[0], inputColor); | |
| } | |
| void GrGLTextureDomainEffect::setData(const GrGLProgramDataManager& pdman, | |
| const GrDrawEffect& drawEffect) { | |
| const GrTextureDomainEffect& effect = drawEffect.castEffect<GrTextureDomainEffect>(); | |
| const GrTextureDomain& domain = effect.textureDomain(); | |
| fGLDomain.setData(pdman, domain, effect.texture(0)->origin()); | |
| } | |
| void GrGLTextureDomainEffect::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&, | |
| GrEffectKeyBuilder* b) { | |
| const GrTextureDomain& domain = drawEffect.castEffect<GrTextureDomainEffect>().textureDomain(); | |
| b->add32(GrTextureDomain::GLDomain::DomainKey(domain)); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| GrEffect* GrTextureDomainEffect::Create(GrTexture* texture, | |
| const SkMatrix& matrix, | |
| const SkRect& domain, | |
| GrTextureDomain::Mode mode, | |
| GrTextureParams::FilterMode filterMode, | |
| GrCoordSet coordSet) { | |
| static const SkRect kFullRect = {0, 0, SK_Scalar1, SK_Scalar1}; | |
| if (GrTextureDomain::kIgnore_Mode == mode || | |
| (GrTextureDomain::kClamp_Mode == mode && domain.contains(kFullRect))) { | |
| return GrSimpleTextureEffect::Create(texture, matrix, filterMode); | |
| } else { | |
| return SkNEW_ARGS(GrTextureDomainEffect, (texture, | |
| matrix, | |
| domain, | |
| mode, | |
| filterMode, | |
| coordSet)); | |
| } | |
| } | |
| GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture, | |
| const SkMatrix& matrix, | |
| const SkRect& domain, | |
| GrTextureDomain::Mode mode, | |
| GrTextureParams::FilterMode filterMode, | |
| GrCoordSet coordSet) | |
| : GrSingleTextureEffect(texture, matrix, filterMode, coordSet) | |
| , fTextureDomain(domain, mode) { | |
| SkASSERT(mode != GrTextureDomain::kRepeat_Mode || | |
| filterMode == GrTextureParams::kNone_FilterMode); | |
| } | |
| GrTextureDomainEffect::~GrTextureDomainEffect() { | |
| } | |
| const GrBackendEffectFactory& GrTextureDomainEffect::getFactory() const { | |
| return GrTBackendEffectFactory<GrTextureDomainEffect>::getInstance(); | |
| } | |
| bool GrTextureDomainEffect::onIsEqual(const GrEffect& sBase) const { | |
| const GrTextureDomainEffect& s = CastEffect<GrTextureDomainEffect>(sBase); | |
| return this->hasSameTextureParamsMatrixAndSourceCoords(s) && | |
| this->fTextureDomain == s.fTextureDomain; | |
| } | |
| void GrTextureDomainEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const { | |
| if (GrTextureDomain::kDecal_Mode == fTextureDomain.mode()) { // TODO: helper | |
| *validFlags = 0; | |
| } else { | |
| this->updateConstantColorComponentsForModulation(color, validFlags); | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| GR_DEFINE_EFFECT_TEST(GrTextureDomainEffect); | |
| GrEffect* GrTextureDomainEffect::TestCreate(SkRandom* random, | |
| GrContext*, | |
| const GrDrawTargetCaps&, | |
| GrTexture* textures[]) { | |
| int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx : | |
| GrEffectUnitTest::kAlphaTextureIdx; | |
| SkRect domain; | |
| domain.fLeft = random->nextUScalar1(); | |
| domain.fRight = random->nextRangeScalar(domain.fLeft, SK_Scalar1); | |
| domain.fTop = random->nextUScalar1(); | |
| domain.fBottom = random->nextRangeScalar(domain.fTop, SK_Scalar1); | |
| GrTextureDomain::Mode mode = | |
| (GrTextureDomain::Mode) random->nextULessThan(GrTextureDomain::kModeCount); | |
| const SkMatrix& matrix = GrEffectUnitTest::TestMatrix(random); | |
| bool bilerp = mode != GrTextureDomain::kRepeat_Mode ? random->nextBool() : false; | |
| GrCoordSet coords = random->nextBool() ? kLocal_GrCoordSet : kPosition_GrCoordSet; | |
| return GrTextureDomainEffect::Create(textures[texIdx], | |
| matrix, | |
| domain, | |
| mode, | |
| bilerp ? GrTextureParams::kBilerp_FilterMode : GrTextureParams::kNone_FilterMode, | |
| coords); | |
| } |