GrConfigConversionEffect.cpp revision 74a3a2135ca82ab9324b7e499caa3280348a4fda
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 "GrConfigConversionEffect.h"
9#include "GrContext.h"
10#include "GrTBackendEffectFactory.h"
11#include "GrSimpleTextureEffect.h"
12#include "gl/GrGLEffect.h"
13#include "gl/GrGLEffectMatrix.h"
14#include "SkMatrix.h"
15
16class GrGLConfigConversionEffect : public GrGLEffect {
17public:
18    GrGLConfigConversionEffect(const GrBackendEffectFactory& factory,
19                               const GrDrawEffect& drawEffect)
20    : INHERITED (factory)
21    , fEffectMatrix(drawEffect.castEffect<GrConfigConversionEffect>().coordsType()) {
22        const GrConfigConversionEffect& effect = drawEffect.castEffect<GrConfigConversionEffect>();
23        fSwapRedAndBlue = effect.swapsRedAndBlue();
24        fPMConversion = effect.pmConversion();
25    }
26
27    virtual void emitCode(GrGLShaderBuilder* builder,
28                          const GrDrawEffect&,
29                          EffectKey key,
30                          const char* outputColor,
31                          const char* inputColor,
32                          const TextureSamplerArray& samplers) SK_OVERRIDE {
33        SkString coords;
34        GrSLType coordsType = fEffectMatrix.emitCode(builder, key, &coords);
35        builder->fsCodeAppendf("\t\t%s = ", outputColor);
36        builder->fsAppendTextureLookup(samplers[0], coords.c_str(), coordsType);
37        builder->fsCodeAppend(";\n");
38        if (GrConfigConversionEffect::kNone_PMConversion == fPMConversion) {
39            SkASSERT(fSwapRedAndBlue);
40            builder->fsCodeAppendf("\t%s = %s.bgra;\n", outputColor, outputColor);
41        } else {
42            const char* swiz = fSwapRedAndBlue ? "bgr" : "rgb";
43            switch (fPMConversion) {
44                case GrConfigConversionEffect::kMulByAlpha_RoundUp_PMConversion:
45                    builder->fsCodeAppendf(
46                        "\t\t%s = vec4(ceil(%s.%s * %s.a * 255.0) / 255.0, %s.a);\n",
47                        outputColor, outputColor, swiz, outputColor, outputColor);
48                    break;
49                case GrConfigConversionEffect::kMulByAlpha_RoundDown_PMConversion:
50                    // Add a compensation(0.001) here to avoid the side effect of the floor operation.
51                    // In Intel GPUs, the integer value converted from floor(%s.r * 255.0) / 255.0
52                    // is less than the integer value converted from  %s.r by 1 when the %s.r is
53                    // converted from the integer value 2^n, such as 1, 2, 4, 8, etc.
54                    builder->fsCodeAppendf(
55                        "\t\t%s = vec4(floor(%s.%s * %s.a * 255.0 + 0.001) / 255.0, %s.a);\n",
56                        outputColor, outputColor, swiz, outputColor, outputColor);
57                    break;
58                case GrConfigConversionEffect::kDivByAlpha_RoundUp_PMConversion:
59                    builder->fsCodeAppendf("\t\t%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(ceil(%s.%s / %s.a * 255.0) / 255.0, %s.a);\n",
60                        outputColor, outputColor, outputColor, swiz, outputColor, outputColor);
61                    break;
62                case GrConfigConversionEffect::kDivByAlpha_RoundDown_PMConversion:
63                    builder->fsCodeAppendf("\t\t%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(floor(%s.%s / %s.a * 255.0) / 255.0, %s.a);\n",
64                        outputColor, outputColor, outputColor, swiz, outputColor, outputColor);
65                    break;
66                default:
67                    GrCrash("Unknown conversion op.");
68                    break;
69            }
70        }
71        SkString modulate;
72        GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
73        builder->fsCodeAppend(modulate.c_str());
74    }
75
76    void setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
77        const GrConfigConversionEffect& conv = drawEffect.castEffect<GrConfigConversionEffect>();
78        fEffectMatrix.setData(uman, conv.getMatrix(), drawEffect, conv.texture(0));
79    }
80
81    static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
82        const GrConfigConversionEffect& conv = drawEffect.castEffect<GrConfigConversionEffect>();
83        EffectKey key = static_cast<EffectKey>(conv.swapsRedAndBlue()) | (conv.pmConversion() << 1);
84        key <<= GrGLEffectMatrix::kKeyBits;
85        EffectKey matrixKey =  GrGLEffectMatrix::GenKey(conv.getMatrix(),
86                                                        drawEffect,
87                                                        conv.coordsType(),
88                                                        conv.texture(0));
89        SkASSERT(!(matrixKey & key));
90        return matrixKey | key;
91    }
92
93private:
94    bool                                    fSwapRedAndBlue;
95    GrConfigConversionEffect::PMConversion  fPMConversion;
96    GrGLEffectMatrix                        fEffectMatrix;
97
98    typedef GrGLEffect INHERITED;
99
100};
101
102///////////////////////////////////////////////////////////////////////////////
103
104GrConfigConversionEffect::GrConfigConversionEffect(GrTexture* texture,
105                                                   bool swapRedAndBlue,
106                                                   PMConversion pmConversion,
107                                                   const SkMatrix& matrix)
108    : GrSingleTextureEffect(texture, matrix)
109    , fSwapRedAndBlue(swapRedAndBlue)
110    , fPMConversion(pmConversion) {
111    SkASSERT(kRGBA_8888_GrPixelConfig == texture->config() ||
112             kBGRA_8888_GrPixelConfig == texture->config());
113    // Why did we pollute our texture cache instead of using a GrSingleTextureEffect?
114    SkASSERT(swapRedAndBlue || kNone_PMConversion != pmConversion);
115}
116
117const GrBackendEffectFactory& GrConfigConversionEffect::getFactory() const {
118    return GrTBackendEffectFactory<GrConfigConversionEffect>::getInstance();
119}
120
121bool GrConfigConversionEffect::onIsEqual(const GrEffect& s) const {
122    const GrConfigConversionEffect& other = CastEffect<GrConfigConversionEffect>(s);
123    return this->texture(0) == s.texture(0) &&
124           other.fSwapRedAndBlue == fSwapRedAndBlue &&
125           other.fPMConversion == fPMConversion;
126}
127
128void GrConfigConversionEffect::getConstantColorComponents(GrColor* color,
129                                                          uint32_t* validFlags) const {
130    this->updateConstantColorComponentsForModulation(color, validFlags);
131}
132
133///////////////////////////////////////////////////////////////////////////////
134
135GR_DEFINE_EFFECT_TEST(GrConfigConversionEffect);
136
137GrEffectRef* GrConfigConversionEffect::TestCreate(SkMWCRandom* random,
138                                                  GrContext*,
139                                                  const GrDrawTargetCaps&,
140                                                  GrTexture* textures[]) {
141    PMConversion pmConv = static_cast<PMConversion>(random->nextULessThan(kPMConversionCnt));
142    bool swapRB;
143    if (kNone_PMConversion == pmConv) {
144        swapRB = true;
145    } else {
146        swapRB = random->nextBool();
147    }
148    AutoEffectUnref effect(SkNEW_ARGS(GrConfigConversionEffect,
149                                      (textures[GrEffectUnitTest::kSkiaPMTextureIdx],
150                                       swapRB,
151                                       pmConv,
152                                       GrEffectUnitTest::TestMatrix(random))));
153    return CreateEffectRef(effect);
154}
155
156///////////////////////////////////////////////////////////////////////////////
157void GrConfigConversionEffect::TestForPreservingPMConversions(GrContext* context,
158                                                              PMConversion* pmToUPMRule,
159                                                              PMConversion* upmToPMRule) {
160    *pmToUPMRule = kNone_PMConversion;
161    *upmToPMRule = kNone_PMConversion;
162    SkAutoTMalloc<uint32_t> data(256 * 256 * 3);
163    uint32_t* srcData = data.get();
164    uint32_t* firstRead = data.get() + 256 * 256;
165    uint32_t* secondRead = data.get() + 2 * 256 * 256;
166
167    // Fill with every possible premultiplied A, color channel value. There will be 256-y duplicate
168    // values in row y. We set r,g, and b to the same value since they are handled identically.
169    for (int y = 0; y < 256; ++y) {
170        for (int x = 0; x < 256; ++x) {
171            uint8_t* color = reinterpret_cast<uint8_t*>(&srcData[256*y + x]);
172            color[3] = y;
173            color[2] = GrMin(x, y);
174            color[1] = GrMin(x, y);
175            color[0] = GrMin(x, y);
176        }
177    }
178
179    GrTextureDesc desc;
180    desc.fFlags = kRenderTarget_GrTextureFlagBit |
181                  kNoStencil_GrTextureFlagBit;
182    desc.fWidth = 256;
183    desc.fHeight = 256;
184    desc.fConfig = kRGBA_8888_GrPixelConfig;
185
186    SkAutoTUnref<GrTexture> readTex(context->createUncachedTexture(desc, NULL, 0));
187    if (!readTex.get()) {
188        return;
189    }
190    SkAutoTUnref<GrTexture> tempTex(context->createUncachedTexture(desc, NULL, 0));
191    if (!tempTex.get()) {
192        return;
193    }
194    desc.fFlags = kNone_GrTextureFlags;
195    SkAutoTUnref<GrTexture> dataTex(context->createUncachedTexture(desc, data, 0));
196    if (!dataTex.get()) {
197        return;
198    }
199
200    static const PMConversion kConversionRules[][2] = {
201        {kDivByAlpha_RoundDown_PMConversion, kMulByAlpha_RoundUp_PMConversion},
202        {kDivByAlpha_RoundUp_PMConversion, kMulByAlpha_RoundDown_PMConversion},
203    };
204
205    GrContext::AutoWideOpenIdentityDraw awoid(context, NULL);
206
207    bool failed = true;
208
209    for (size_t i = 0; i < GR_ARRAY_COUNT(kConversionRules) && failed; ++i) {
210        *pmToUPMRule = kConversionRules[i][0];
211        *upmToPMRule = kConversionRules[i][1];
212
213        static const SkRect kDstRect = SkRect::MakeWH(SkIntToScalar(256), SkIntToScalar(256));
214        static const SkRect kSrcRect = SkRect::MakeWH(SK_Scalar1, SK_Scalar1);
215        // We do a PM->UPM draw from dataTex to readTex and read the data. Then we do a UPM->PM draw
216        // from readTex to tempTex followed by a PM->UPM draw to readTex and finally read the data.
217        // We then verify that two reads produced the same values.
218
219        AutoEffectUnref pmToUPM1(SkNEW_ARGS(GrConfigConversionEffect, (dataTex,
220                                                                       false,
221                                                                       *pmToUPMRule,
222                                                                       SkMatrix::I())));
223        AutoEffectUnref upmToPM(SkNEW_ARGS(GrConfigConversionEffect, (readTex,
224                                                                      false,
225                                                                      *upmToPMRule,
226                                                                      SkMatrix::I())));
227        AutoEffectUnref pmToUPM2(SkNEW_ARGS(GrConfigConversionEffect, (tempTex,
228                                                                       false,
229                                                                       *pmToUPMRule,
230                                                                       SkMatrix::I())));
231
232        SkAutoTUnref<GrEffectRef> pmToUPMEffect1(CreateEffectRef(pmToUPM1));
233        SkAutoTUnref<GrEffectRef> upmToPMEffect(CreateEffectRef(upmToPM));
234        SkAutoTUnref<GrEffectRef> pmToUPMEffect2(CreateEffectRef(pmToUPM2));
235
236        context->setRenderTarget(readTex->asRenderTarget());
237        GrPaint paint1;
238        paint1.addColorEffect(pmToUPMEffect1);
239        context->drawRectToRect(paint1, kDstRect, kSrcRect);
240
241        readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, firstRead);
242
243        context->setRenderTarget(tempTex->asRenderTarget());
244        GrPaint paint2;
245        paint2.addColorEffect(upmToPMEffect);
246        context->drawRectToRect(paint2, kDstRect, kSrcRect);
247        context->setRenderTarget(readTex->asRenderTarget());
248
249        GrPaint paint3;
250        paint3.addColorEffect(pmToUPMEffect2);
251        context->drawRectToRect(paint3, kDstRect, kSrcRect);
252
253        readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, secondRead);
254
255        failed = false;
256        for (int y = 0; y < 256 && !failed; ++y) {
257            for (int x = 0; x <= y; ++x) {
258                if (firstRead[256 * y + x] != secondRead[256 * y + x]) {
259                    failed = true;
260                    break;
261                }
262            }
263        }
264    }
265    if (failed) {
266        *pmToUPMRule = kNone_PMConversion;
267        *upmToPMRule = kNone_PMConversion;
268    }
269}
270
271const GrEffectRef* GrConfigConversionEffect::Create(GrTexture* texture,
272                                                    bool swapRedAndBlue,
273                                                    PMConversion pmConversion,
274                                                    const SkMatrix& matrix) {
275    if (!swapRedAndBlue && kNone_PMConversion == pmConversion) {
276        // If we returned a GrConfigConversionEffect that was equivalent to a GrSimpleTextureEffect
277        // then we may pollute our texture cache with redundant shaders. So in the case that no
278        // conversions were requested we instead return a GrSimpleTextureEffect.
279        return GrSimpleTextureEffect::Create(texture, matrix);
280    } else {
281        if (kRGBA_8888_GrPixelConfig != texture->config() &&
282            kBGRA_8888_GrPixelConfig != texture->config() &&
283            kNone_PMConversion != pmConversion) {
284            // The PM conversions assume colors are 0..255
285            return NULL;
286        }
287        AutoEffectUnref effect(SkNEW_ARGS(GrConfigConversionEffect, (texture,
288                                                                     swapRedAndBlue,
289                                                                     pmConversion,
290                                                                     matrix)));
291        return CreateEffectRef(effect);
292    }
293}
294