|  | /* | 
|  | * 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 "GrConfigConversionEffect.h" | 
|  | #include "GrContext.h" | 
|  | #include "GrDrawContext.h" | 
|  | #include "GrInvariantOutput.h" | 
|  | #include "GrSimpleTextureEffect.h" | 
|  | #include "SkMatrix.h" | 
|  | #include "glsl/GrGLSLFragmentProcessor.h" | 
|  | #include "glsl/GrGLSLFragmentShaderBuilder.h" | 
|  |  | 
|  | class GrGLConfigConversionEffect : public GrGLSLFragmentProcessor { | 
|  | public: | 
|  | void emitCode(EmitArgs& args) override { | 
|  | const GrConfigConversionEffect& cce = args.fFp.cast<GrConfigConversionEffect>(); | 
|  | const GrSwizzle& swizzle = cce.swizzle(); | 
|  | GrConfigConversionEffect::PMConversion pmConversion = cce.pmConversion(); | 
|  |  | 
|  | // Using highp for GLES here in order to avoid some precision issues on specific GPUs. | 
|  | GrGLSLShaderVar tmpVar("tmpColor", kVec4f_GrSLType, 0, kHigh_GrSLPrecision); | 
|  | SkString tmpDecl; | 
|  | tmpVar.appendDecl(args.fGLSLCaps, &tmpDecl); | 
|  |  | 
|  | GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder; | 
|  |  | 
|  | fragBuilder->codeAppendf("%s;", tmpDecl.c_str()); | 
|  |  | 
|  | fragBuilder->codeAppendf("%s = ", tmpVar.c_str()); | 
|  | fragBuilder->appendTextureLookup(args.fSamplers[0], args.fCoords[0].c_str(), | 
|  | args.fCoords[0].getType()); | 
|  | fragBuilder->codeAppend(";"); | 
|  |  | 
|  | if (GrConfigConversionEffect::kNone_PMConversion == pmConversion) { | 
|  | SkASSERT(GrSwizzle::RGBA() != swizzle); | 
|  | fragBuilder->codeAppendf("%s = %s.%s;", args.fOutputColor, tmpVar.c_str(), | 
|  | swizzle.c_str()); | 
|  | } else { | 
|  | switch (pmConversion) { | 
|  | case GrConfigConversionEffect::kMulByAlpha_RoundUp_PMConversion: | 
|  | fragBuilder->codeAppendf( | 
|  | "%s = vec4(ceil(%s.rgb * %s.a * 255.0) / 255.0, %s.a);", | 
|  | tmpVar.c_str(), tmpVar.c_str(), tmpVar.c_str(), tmpVar.c_str()); | 
|  | break; | 
|  | case GrConfigConversionEffect::kMulByAlpha_RoundDown_PMConversion: | 
|  | // Add a compensation(0.001) here to avoid the side effect of the floor operation. | 
|  | // In Intel GPUs, the integer value converted from floor(%s.r * 255.0) / 255.0 | 
|  | // is less than the integer value converted from  %s.r by 1 when the %s.r is | 
|  | // converted from the integer value 2^n, such as 1, 2, 4, 8, etc. | 
|  | fragBuilder->codeAppendf( | 
|  | "%s = vec4(floor(%s.rgb * %s.a * 255.0 + 0.001) / 255.0, %s.a);", | 
|  | tmpVar.c_str(), tmpVar.c_str(), tmpVar.c_str(), tmpVar.c_str()); | 
|  |  | 
|  | break; | 
|  | case GrConfigConversionEffect::kDivByAlpha_RoundUp_PMConversion: | 
|  | fragBuilder->codeAppendf( | 
|  | "%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(ceil(%s.rgb / %s.a * 255.0) / 255.0, %s.a);", | 
|  | tmpVar.c_str(), tmpVar.c_str(), tmpVar.c_str(), tmpVar.c_str(), | 
|  | tmpVar.c_str()); | 
|  | break; | 
|  | case GrConfigConversionEffect::kDivByAlpha_RoundDown_PMConversion: | 
|  | fragBuilder->codeAppendf( | 
|  | "%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(floor(%s.rgb / %s.a * 255.0) / 255.0, %s.a);", | 
|  | tmpVar.c_str(), tmpVar.c_str(), tmpVar.c_str(), tmpVar.c_str(), | 
|  | tmpVar.c_str()); | 
|  | break; | 
|  | default: | 
|  | SkFAIL("Unknown conversion op."); | 
|  | break; | 
|  | } | 
|  | fragBuilder->codeAppendf("%s = %s.%s;", args.fOutputColor, tmpVar.c_str(), | 
|  | swizzle.c_str()); | 
|  | } | 
|  | SkString modulate; | 
|  | GrGLSLMulVarBy4f(&modulate, args.fOutputColor, args.fInputColor); | 
|  | fragBuilder->codeAppend(modulate.c_str()); | 
|  | } | 
|  |  | 
|  | static inline void GenKey(const GrProcessor& processor, const GrGLSLCaps&, | 
|  | GrProcessorKeyBuilder* b) { | 
|  | const GrConfigConversionEffect& cce = processor.cast<GrConfigConversionEffect>(); | 
|  | uint32_t key = (cce.swizzle().asKey()) | (cce.pmConversion() << 16); | 
|  | b->add32(key); | 
|  | } | 
|  |  | 
|  | private: | 
|  | typedef GrGLSLFragmentProcessor INHERITED; | 
|  |  | 
|  | }; | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | GrConfigConversionEffect::GrConfigConversionEffect(GrTexture* texture, | 
|  | const GrSwizzle& swizzle, | 
|  | PMConversion pmConversion, | 
|  | const SkMatrix& matrix) | 
|  | : INHERITED(texture, matrix) | 
|  | , fSwizzle(swizzle) | 
|  | , fPMConversion(pmConversion) { | 
|  | this->initClassID<GrConfigConversionEffect>(); | 
|  | // We expect to get here with non-BGRA/RGBA only if we're doing not doing a premul/unpremul | 
|  | // conversion. | 
|  | SkASSERT((kRGBA_8888_GrPixelConfig == texture->config() || | 
|  | kBGRA_8888_GrPixelConfig == texture->config()) || | 
|  | kNone_PMConversion == pmConversion); | 
|  | // Why did we pollute our texture cache instead of using a GrSingleTextureEffect? | 
|  | SkASSERT(swizzle != GrSwizzle::RGBA() || kNone_PMConversion != pmConversion); | 
|  | } | 
|  |  | 
|  | bool GrConfigConversionEffect::onIsEqual(const GrFragmentProcessor& s) const { | 
|  | const GrConfigConversionEffect& other = s.cast<GrConfigConversionEffect>(); | 
|  | return other.fSwizzle == fSwizzle && | 
|  | other.fPMConversion == fPMConversion; | 
|  | } | 
|  |  | 
|  | void GrConfigConversionEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const { | 
|  | this->updateInvariantOutputForModulation(inout); | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConfigConversionEffect); | 
|  |  | 
|  | const GrFragmentProcessor* GrConfigConversionEffect::TestCreate(GrProcessorTestData* d) { | 
|  | PMConversion pmConv = static_cast<PMConversion>(d->fRandom->nextULessThan(kPMConversionCnt)); | 
|  | GrSwizzle swizzle; | 
|  | do { | 
|  | swizzle = GrSwizzle::CreateRandom(d->fRandom); | 
|  | } while (pmConv == kNone_PMConversion && swizzle == GrSwizzle::RGBA()); | 
|  | return new GrConfigConversionEffect(d->fTextures[GrProcessorUnitTest::kSkiaPMTextureIdx], | 
|  | swizzle, pmConv, GrTest::TestMatrix(d->fRandom)); | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | void GrConfigConversionEffect::onGetGLSLProcessorKey(const GrGLSLCaps& caps, | 
|  | GrProcessorKeyBuilder* b) const { | 
|  | GrGLConfigConversionEffect::GenKey(*this, caps, b); | 
|  | } | 
|  |  | 
|  | GrGLSLFragmentProcessor* GrConfigConversionEffect::onCreateGLSLInstance() const { | 
|  | return new GrGLConfigConversionEffect(); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | void GrConfigConversionEffect::TestForPreservingPMConversions(GrContext* context, | 
|  | PMConversion* pmToUPMRule, | 
|  | PMConversion* upmToPMRule) { | 
|  | *pmToUPMRule = kNone_PMConversion; | 
|  | *upmToPMRule = kNone_PMConversion; | 
|  | SkAutoTMalloc<uint32_t> data(256 * 256 * 3); | 
|  | uint32_t* srcData = data.get(); | 
|  | uint32_t* firstRead = data.get() + 256 * 256; | 
|  | uint32_t* secondRead = data.get() + 2 * 256 * 256; | 
|  |  | 
|  | // Fill with every possible premultiplied A, color channel value. There will be 256-y duplicate | 
|  | // values in row y. We set r,g, and b to the same value since they are handled identically. | 
|  | for (int y = 0; y < 256; ++y) { | 
|  | for (int x = 0; x < 256; ++x) { | 
|  | uint8_t* color = reinterpret_cast<uint8_t*>(&srcData[256*y + x]); | 
|  | color[3] = y; | 
|  | color[2] = SkTMin(x, y); | 
|  | color[1] = SkTMin(x, y); | 
|  | color[0] = SkTMin(x, y); | 
|  | } | 
|  | } | 
|  |  | 
|  | GrSurfaceDesc desc; | 
|  | desc.fFlags = kRenderTarget_GrSurfaceFlag; | 
|  | desc.fWidth = 256; | 
|  | desc.fHeight = 256; | 
|  | desc.fConfig = kRGBA_8888_GrPixelConfig; | 
|  |  | 
|  | SkAutoTUnref<GrTexture> readTex(context->textureProvider()->createTexture(desc, true, nullptr, 0)); | 
|  | if (!readTex.get()) { | 
|  | return; | 
|  | } | 
|  | SkAutoTUnref<GrTexture> tempTex(context->textureProvider()->createTexture(desc, true, nullptr, 0)); | 
|  | if (!tempTex.get()) { | 
|  | return; | 
|  | } | 
|  | desc.fFlags = kNone_GrSurfaceFlags; | 
|  | SkAutoTUnref<GrTexture> dataTex(context->textureProvider()->createTexture(desc, true, data, 0)); | 
|  | if (!dataTex.get()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | static const PMConversion kConversionRules[][2] = { | 
|  | {kDivByAlpha_RoundDown_PMConversion, kMulByAlpha_RoundUp_PMConversion}, | 
|  | {kDivByAlpha_RoundUp_PMConversion, kMulByAlpha_RoundDown_PMConversion}, | 
|  | }; | 
|  |  | 
|  | bool failed = true; | 
|  |  | 
|  | for (size_t i = 0; i < SK_ARRAY_COUNT(kConversionRules) && failed; ++i) { | 
|  | *pmToUPMRule = kConversionRules[i][0]; | 
|  | *upmToPMRule = kConversionRules[i][1]; | 
|  |  | 
|  | static const SkRect kDstRect = SkRect::MakeWH(SkIntToScalar(256), SkIntToScalar(256)); | 
|  | static const SkRect kSrcRect = SkRect::MakeWH(SK_Scalar1, SK_Scalar1); | 
|  | // We do a PM->UPM draw from dataTex to readTex and read the data. Then we do a UPM->PM draw | 
|  | // from readTex to tempTex followed by a PM->UPM draw to readTex and finally read the data. | 
|  | // We then verify that two reads produced the same values. | 
|  |  | 
|  | GrPaint paint1; | 
|  | GrPaint paint2; | 
|  | GrPaint paint3; | 
|  | SkAutoTUnref<GrFragmentProcessor> pmToUPM1(new GrConfigConversionEffect( | 
|  | dataTex, GrSwizzle::RGBA(), *pmToUPMRule, SkMatrix::I())); | 
|  | SkAutoTUnref<GrFragmentProcessor> upmToPM(new GrConfigConversionEffect( | 
|  | readTex, GrSwizzle::RGBA(), *upmToPMRule, SkMatrix::I())); | 
|  | SkAutoTUnref<GrFragmentProcessor> pmToUPM2(new GrConfigConversionEffect( | 
|  | tempTex, GrSwizzle::RGBA(), *pmToUPMRule, SkMatrix::I())); | 
|  |  | 
|  | paint1.addColorFragmentProcessor(pmToUPM1); | 
|  | paint1.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); | 
|  |  | 
|  |  | 
|  | SkAutoTUnref<GrDrawContext> readDrawContext( | 
|  | context->drawContext(readTex->asRenderTarget())); | 
|  | if (!readDrawContext) { | 
|  | failed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | readDrawContext->fillRectToRect(GrClip::WideOpen(), | 
|  | paint1, | 
|  | SkMatrix::I(), | 
|  | kDstRect, | 
|  | kSrcRect); | 
|  |  | 
|  | readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, firstRead); | 
|  |  | 
|  | paint2.addColorFragmentProcessor(upmToPM); | 
|  | paint2.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); | 
|  |  | 
|  | SkAutoTUnref<GrDrawContext> tempDrawContext( | 
|  | context->drawContext(tempTex->asRenderTarget())); | 
|  | if (!tempDrawContext) { | 
|  | failed = true; | 
|  | break; | 
|  | } | 
|  | tempDrawContext->fillRectToRect(GrClip::WideOpen(), | 
|  | paint2, | 
|  | SkMatrix::I(), | 
|  | kDstRect, | 
|  | kSrcRect); | 
|  |  | 
|  | paint3.addColorFragmentProcessor(pmToUPM2); | 
|  | paint3.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); | 
|  |  | 
|  | readDrawContext.reset(context->drawContext(readTex->asRenderTarget())); | 
|  | if (!readDrawContext) { | 
|  | failed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | readDrawContext->fillRectToRect(GrClip::WideOpen(), | 
|  | paint3, | 
|  | SkMatrix::I(), | 
|  | kDstRect, | 
|  | kSrcRect); | 
|  |  | 
|  | readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, secondRead); | 
|  |  | 
|  | failed = false; | 
|  | for (int y = 0; y < 256 && !failed; ++y) { | 
|  | for (int x = 0; x <= y; ++x) { | 
|  | if (firstRead[256 * y + x] != secondRead[256 * y + x]) { | 
|  | failed = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (failed) { | 
|  | *pmToUPMRule = kNone_PMConversion; | 
|  | *upmToPMRule = kNone_PMConversion; | 
|  | } | 
|  | } | 
|  |  | 
|  | const GrFragmentProcessor* GrConfigConversionEffect::Create(GrTexture* texture, | 
|  | const GrSwizzle& swizzle, | 
|  | PMConversion pmConversion, | 
|  | const SkMatrix& matrix) { | 
|  | if (swizzle == GrSwizzle::RGBA() && kNone_PMConversion == pmConversion) { | 
|  | // If we returned a GrConfigConversionEffect that was equivalent to a GrSimpleTextureEffect | 
|  | // then we may pollute our texture cache with redundant shaders. So in the case that no | 
|  | // conversions were requested we instead return a GrSimpleTextureEffect. | 
|  | return GrSimpleTextureEffect::Create(texture, matrix); | 
|  | } else { | 
|  | if (kRGBA_8888_GrPixelConfig != texture->config() && | 
|  | kBGRA_8888_GrPixelConfig != texture->config() && | 
|  | kNone_PMConversion != pmConversion) { | 
|  | // The PM conversions assume colors are 0..255 | 
|  | return nullptr; | 
|  | } | 
|  | return new GrConfigConversionEffect(texture, swizzle, pmConversion, matrix); | 
|  | } | 
|  | } |