Do writepixels alpha-premul using gpu

Review URL: http://codereview.appspot.com/5373064/



git-svn-id: http://skia.googlecode.com/svn/trunk@2668 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/gpu/GrTypes.h b/include/gpu/GrTypes.h
index e5908de..eb92f09 100644
--- a/include/gpu/GrTypes.h
+++ b/include/gpu/GrTypes.h
@@ -268,10 +268,13 @@
 
 /**
  * Pixel configurations.
- * Unpremultiplied configs are intended for conversion out from skia. They are
- * not supported as input (e.g. drawBitmap or a bitmap shader). When used a
- * render target config only draws that use blend coeffs 1,0 (AKA src-mode)
- * will succeed.
+ *
+ * Unpremultiplied configs are intended for converting pixel data in and out
+ * from skia. Surfaces with these configs have limited support. As an input
+ * (GrPaint texture) the corresponding GrSamplerState must have its filter set
+ * to kNearest_Filter. Otherwise, the draw will fail. When the render target
+ * has an unpremultiplied config draws must use blend coeffs 1,0 (AKA src-mode).
+ * Other coeffs will cause the draw to fail.
  */
 enum GrPixelConfig {
     kUnknown_GrPixelConfig,
diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp
index 03a68dd..3d9195c 100644
--- a/src/gpu/GrDrawTarget.cpp
+++ b/src/gpu/GrDrawTarget.cpp
@@ -810,8 +810,15 @@
         }
     }
     for (int s = 0; s < GrDrawState::kNumStages; ++s) {
+        // We don't support using unpremultiplied textures with filters (other
+        // than nearest). Alpha-premulling is not distributive WRT to filtering.
+        // We'd have to filter each texel before filtering. We could do this for
+        // our custom filters but we would also have to disable bilerp and do
+        // a custom bilerp in the shader. Until Skia itself supports unpremul
+        // configs there is no pressure to implement this.
         if (this->isStageEnabled(s) &&
-            GrPixelConfigIsUnpremultiplied(fCurrDrawState.fTextures[s]->config())) {
+            GrPixelConfigIsUnpremultiplied(fCurrDrawState.fTextures[s]->config()) &&
+            GrSamplerState::kNearest_Filter != fCurrDrawState.fSamplerStates[s].getFilter()) {
             return false;
         }
     }
diff --git a/src/gpu/GrGLProgram.cpp b/src/gpu/GrGLProgram.cpp
index 9fe195c..770d910 100644
--- a/src/gpu/GrGLProgram.cpp
+++ b/src/gpu/GrGLProgram.cpp
@@ -1564,7 +1564,9 @@
                                ShaderCodeSegments* segments,
                                StageUniLocations* locations) const {
 
-    GrAssert(stageNum >= 0 && stageNum <= 9);
+    GrAssert(stageNum >= 0 && stageNum <= GrDrawState::kNumStages);
+    GrAssert((desc.fInConfigFlags & StageDesc::kInConfigBitMask) ==
+             desc.fInConfigFlags);
 
     // First decide how many coords are needed to access the texture
     // Right now it's always 2 but we could start using 1D textures for
@@ -1710,19 +1712,14 @@
     };
 
     const char* swizzle = "";
-    switch (desc.fSwizzle) {
-        case StageDesc::kAlphaSmear_Swizzle:
-            swizzle = ".aaaa";
-            break;
-        case StageDesc::kSwapRAndB_Swizzle:
-            swizzle = ".bgra";
-            break;
-        case StageDesc::kNone_Swizzle:
-            swizzle = "";
-            break;
-        default:
-            GrCrash("Swizzle descriptor didn't match any expected value");
-    }
+    if (desc.fInConfigFlags & StageDesc::kSwapRAndB_InConfigFlag) {
+        GrAssert(!(desc.fInConfigFlags & StageDesc::kSmearAlpha_InConfigFlag));
+        swizzle = ".bgra";
+    } else if (desc.fInConfigFlags & StageDesc::kSmearAlpha_InConfigFlag) {
+        GrAssert(!(desc.fInConfigFlags &
+                   StageDesc::kMulRGBByAlpha_InConfigFlag));
+        swizzle = ".aaaa";
+    } 
 
     GrStringBuilder modulate;
     if (NULL != fsInColor) {
@@ -1747,20 +1744,36 @@
 
     switch (desc.fFetchMode) {
     case StageDesc::k2x2_FetchMode:
+        GrAssert(!(desc.fInConfigFlags &
+                   StageDesc::kMulRGBByAlpha_InConfigFlag));
         gen2x2FS(stageNum, segments, locations, &sampleCoords,
             samplerName, texelSizeName, swizzle, fsOutColor,
             texFunc, modulate, complexCoord, coordDims);
         break;
     case StageDesc::kConvolution_FetchMode:
+        GrAssert(!(desc.fInConfigFlags &
+                   StageDesc::kMulRGBByAlpha_InConfigFlag));
         genConvolutionFS(stageNum, desc, segments,
             samplerName, kernelName, swizzle, imageIncrementName, fsOutColor,
             sampleCoords, texFunc, modulate);
         break;
     default:
-        segments->fFSCode.appendf("\t%s = %s(%s, %s)%s%s;\n",
-                                  fsOutColor, texFunc.c_str(), 
-                                  samplerName, sampleCoords.c_str(),
-                                  swizzle, modulate.c_str());
+        if (desc.fInConfigFlags & StageDesc::kMulRGBByAlpha_InConfigFlag) {
+            GrAssert(!(desc.fInConfigFlags & 
+                       StageDesc::kSmearAlpha_InConfigFlag));
+            segments->fFSCode.appendf("\t%s = %s(%s, %s)%s;\n",
+                                      fsOutColor, texFunc.c_str(), 
+                                      samplerName, sampleCoords.c_str(),
+                                      swizzle);
+            segments->fFSCode.appendf("\t%s = vec4(%s.rgb*%s.a,%s.a)%s;\n",
+                                      fsOutColor, fsOutColor, fsOutColor,
+                                      fsOutColor, modulate.c_str());
+        } else {
+            segments->fFSCode.appendf("\t%s = %s(%s, %s)%s%s;\n",
+                                      fsOutColor, texFunc.c_str(), 
+                                      samplerName, sampleCoords.c_str(),
+                                      swizzle, modulate.c_str());
+        }
     }
 }
 
diff --git a/src/gpu/GrGLProgram.h b/src/gpu/GrGLProgram.h
index e0f8fbc..12f175f 100644
--- a/src/gpu/GrGLProgram.h
+++ b/src/gpu/GrGLProgram.h
@@ -126,25 +126,37 @@
                 kFetchModeCnt,
             };
             /**
-              Describes how to swizzle the texture's components. If swizzling
-              can be applied outside of the shader (GL_ARB_texture_swizzle) that
-              is preferrable to using this enum. Changing the enum value used
-              causes another program to be generated.
+              Flags set based on a src texture's pixel config. The operations
+              described are performed after reading a texel.
              */
-            enum Swizzle {
+            enum InConfigFlags {
+                kNone_InConfigFlag              = 0x0,
+
                 /**
-                  No swizzling applied to the inputs
+                  Swap the R and B channels. This is incompatible with
+                  kSmearAlpha. It is prefereable to perform the swizzle outside
+                  the shader using GL_ARB_texture_swizzle if possible rather
+                  than setting this flag.
                  */
-                kNone_Swizzle,
+                kSwapRAndB_InConfigFlag         = 0x1,
+
                 /**
-                  Swap the R and B channels
-                 */
-                kSwapRAndB_Swizzle,
-                /**
-                 Smear alpha across all four channels.
+                 Smear alpha across all four channels. This is incompatible with
+                 kSwapRAndB and kPremul.  It is prefereable to perform the
+                 smear outside the shader using GL_ARB_texture_swizzle if
+                 possible rather than setting this flag.
                 */
-                kAlphaSmear_Swizzle,
-                kSwizzleCnt
+                kSmearAlpha_InConfigFlag        = 0x2,
+
+                /**
+                 Multiply r,g,b by a after texture reads. This flag incompatible
+                 with kSmearAlpha and may only be used with FetchMode kSingle.
+                 */
+                kMulRGBByAlpha_InConfigFlag     =  0x4,
+
+                kDummyInConfigFlag,
+                kInConfigBitMask = (kDummyInConfigFlag-1) |
+                                   (kDummyInConfigFlag-2)
             };
             enum CoordMapping {
                 kIdentity_CoordMapping,
@@ -158,11 +170,14 @@
             };
 
             uint8_t fOptFlags;
-            uint8_t fSwizzle;       // casts to enum Swizzle
+            uint8_t fInConfigFlags; // bitfield of InConfigFlags values
             uint8_t fFetchMode;     // casts to enum FetchMode
             uint8_t fCoordMapping;  // casts to enum CoordMapping
             uint8_t fKernelWidth;
 
+            GR_STATIC_ASSERT((InConfigFlags)(uint8_t)kInConfigBitMask ==
+                             kInConfigBitMask);
+
             inline bool isEnabled() const {
                 return SkToBool(fOptFlags & kIsEnabled_OptFlagBit);
             }
diff --git a/src/gpu/GrGpuGL.cpp b/src/gpu/GrGpuGL.cpp
index dc6ace7..774b8d9 100644
--- a/src/gpu/GrGpuGL.cpp
+++ b/src/gpu/GrGpuGL.cpp
@@ -1419,26 +1419,35 @@
     // parent class should never let us get here with no RT
     GrAssert(NULL != fCurrDrawState.fRenderTarget);
 
-    GrIRect r;
+    GrIRect clippedRect;
     if (NULL != rect) {
         // flushScissor expects rect to be clipped to the target.
-        r = *rect;
+        clippedRect = *rect;
         GrIRect rtRect = SkIRect::MakeWH(fCurrDrawState.fRenderTarget->width(),
                                          fCurrDrawState.fRenderTarget->height());
-        if (r.intersect(rtRect)) {
-            rect = &r;
+        if (clippedRect.intersect(rtRect)) {
+            rect = &clippedRect;
         } else {
             return;
         }
     }
     this->flushRenderTarget(rect);
     this->flushScissor(rect);
-    GL_CALL(ColorMask(GR_GL_TRUE,GR_GL_TRUE,GR_GL_TRUE,GR_GL_TRUE));
+
+    GrGLfloat r, g, b, a;
+    static const GrGLfloat scale255 = 1.f / 255.f;
+    a = GrColorUnpackA(color) * scale255;
+    GrGLfloat scaleRGB = scale255;
+    if (GrPixelConfigIsUnpremultiplied(fCurrDrawState.fRenderTarget->config())) {
+        scaleRGB *= a;
+    }
+    r = GrColorUnpackR(color) * scaleRGB;
+    g = GrColorUnpackG(color) * scaleRGB;
+    b = GrColorUnpackB(color) * scaleRGB;
+
+    GL_CALL(ColorMask(GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE));
     fHWDrawState.fFlagBits &= ~kNoColorWrites_StateBit;
-    GL_CALL(ClearColor(GrColorUnpackR(color)/255.f,
-                       GrColorUnpackG(color)/255.f,
-                       GrColorUnpackB(color)/255.f,
-                       GrColorUnpackA(color)/255.f));
+    GL_CALL(ClearColor(r, g, b, a));
     GL_CALL(Clear(GR_GL_COLOR_BUFFER_BIT));
 }
 
diff --git a/src/gpu/GrGpuGLShaders.cpp b/src/gpu/GrGpuGLShaders.cpp
index 67b1c37..76b02ac 100644
--- a/src/gpu/GrGpuGLShaders.cpp
+++ b/src/gpu/GrGpuGLShaders.cpp
@@ -168,9 +168,19 @@
     }
 }
 
-template <typename T>
-T random_val(GrRandom* r, T count) {
-    return (T)(int)(r->nextF() * count);
+// GrRandoms nextU() values have patterns in the low bits
+// So using nextU() % array_count might never take some values.
+int random_int(GrRandom* r, int count) {
+    return (int)(r->nextF() * count);
+}
+
+// min is inclusive, max is exclusive
+int random_int(GrRandom* r, int min, int max) {
+    return (int)(r->nextF() * (max-min)) + min;
+}
+
+bool random_bool(GrRandom* r) {
+    return r->nextF() > .5f;
 }
 
 }
@@ -184,13 +194,18 @@
         StageDesc::kNoPerspective_OptFlagBit,
         StageDesc::kIdentity_CoordMapping
     };
+    static const int IN_CONFIG_FLAGS[] = {
+        StageDesc::kNone_InConfigFlag,
+        StageDesc::kSwapRAndB_InConfigFlag,
+        StageDesc::kSwapRAndB_InConfigFlag | StageDesc::kMulRGBByAlpha_InConfigFlag,
+        StageDesc::kMulRGBByAlpha_InConfigFlag,
+        StageDesc::kSmearAlpha_InConfigFlag,
+    };
     GrGLProgram program;
     ProgramDesc& pdesc = program.fProgramDesc;
 
     static const int NUM_TESTS = 512;
 
-    // GrRandoms nextU() values have patterns in the low bits
-    // So using nextU() % array_count might never take some values.
     GrRandom random;
     for (int t = 0; t < NUM_TESTS; ++t) {
 
@@ -204,33 +219,29 @@
 
         pdesc.fVertexLayout = 0;
         pdesc.fEmitsPointSize = random.nextF() > .5f;
-        pdesc.fColorInput = static_cast<int>(random.nextF() *
-                                             ProgramDesc::kColorInputCnt);
+        pdesc.fColorInput = random_int(&random, ProgramDesc::kColorInputCnt);
 
-        int idx = (int)(random.nextF() * (SkXfermode::kCoeffModesCnt));
-        pdesc.fColorFilterXfermode = (SkXfermode::Mode)idx;
+        pdesc.fColorFilterXfermode = random_int(&random, SkXfermode::kCoeffModesCnt);
 
-        idx = (int)(random.nextF() * (GrDrawState::kNumStages + 1));
-        pdesc.fFirstCoverageStage = idx;
+        pdesc.fFirstCoverageStage = random_int(&random, GrDrawState::kNumStages);
 
-        pdesc.fVertexLayout |= (random.nextF() > .5f) ?
+        pdesc.fVertexLayout |= random_bool(&random) ?
                                     GrDrawTarget::kCoverage_VertexLayoutBit :
                                     0;
 
 #if GR_GL_EXPERIMENTAL_GS
         pdesc.fExperimentalGS = this->getCaps().fGeometryShaderSupport &&
-                                random.nextF() > .5f;
+                                random_bool(&random);
 #endif
-        pdesc.fOutputPM =  static_cast<int>(random.nextF() *
-                                            ProgramDesc::kOutputPMCnt);
+        pdesc.fOutputPM =  random_int(&random, ProgramDesc::kOutputPMCnt);
 
-        bool edgeAA = random.nextF() > .5f;
+        bool edgeAA = random_bool(&random);
         if (edgeAA) {
-            bool vertexEdgeAA = random.nextF() > .5f;
+            bool vertexEdgeAA = random_bool(&random);
             if (vertexEdgeAA) {
                 pdesc.fVertexLayout |= GrDrawTarget::kEdge_VertexLayoutBit;
                 if (this->getCaps().fShaderDerivativeSupport) {
-                    pdesc.fVertexEdgeType = random.nextF() > 0.5f ?
+                    pdesc.fVertexEdgeType = random_bool(&random) ?
                         GrDrawState::kHairQuad_EdgeType :
                         GrDrawState::kHairLine_EdgeType;
                 } else {
@@ -238,49 +249,57 @@
                 }
                 pdesc.fEdgeAANumEdges = 0;
             } else {
-                pdesc.fEdgeAANumEdges =  static_cast<int>(1 + random.nextF() *
-                                                          this->getMaxEdges());
-                pdesc.fEdgeAAConcave = random.nextF() > .5f;
+                pdesc.fEdgeAANumEdges = random_int(&random, 1, this->getMaxEdges());
+                pdesc.fEdgeAAConcave = random_bool(&random);
             }
         } else {
             pdesc.fEdgeAANumEdges = 0;
         }
 
         if (this->getCaps().fDualSourceBlendingSupport) {
-            pdesc.fDualSrcOutput =
-               (ProgramDesc::DualSrcOutput)
-               (int)(random.nextF() * ProgramDesc::kDualSrcOutputCnt);
+            pdesc.fDualSrcOutput = random_int(&random, ProgramDesc::kDualSrcOutputCnt);
         } else {
             pdesc.fDualSrcOutput = ProgramDesc::kNone_DualSrcOutput;
         }
 
         for (int s = 0; s < GrDrawState::kNumStages; ++s) {
             // enable the stage?
-            if (random.nextF() > .5f) {
+            if (random_bool(&random)) {
                 // use separate tex coords?
-                if (random.nextF() > .5f) {
-                    int t = (int)(random.nextF() * GrDrawState::kMaxTexCoords);
+                if (random_bool(&random)) {
+                    int t = random_int(&random, GrDrawState::kMaxTexCoords);
                     pdesc.fVertexLayout |= StageTexCoordVertexLayoutBit(s, t);
                 } else {
                     pdesc.fVertexLayout |= StagePosAsTexCoordVertexLayoutBit(s);
                 }
             }
             // use text-formatted verts?
-            if (random.nextF() > .5f) {
+            if (random_bool(&random)) {
                 pdesc.fVertexLayout |= kTextFormat_VertexLayoutBit;
             }
-            idx = (int)(random.nextF() * GR_ARRAY_COUNT(STAGE_OPTS));
             StageDesc& stage = pdesc.fStages[s];
-            stage.fOptFlags = STAGE_OPTS[idx];
-            stage.fSwizzle = random_val(&random, StageDesc::kSwizzleCnt);
-            stage.fCoordMapping =  random_val(&random, StageDesc::kCoordMappingCnt);
-            stage.fFetchMode = random_val(&random, StageDesc::kFetchModeCnt);
+            stage.fOptFlags = STAGE_OPTS[random_int(&random, GR_ARRAY_COUNT(STAGE_OPTS))];
+            stage.fInConfigFlags = IN_CONFIG_FLAGS[random_int(&random, GR_ARRAY_COUNT(IN_CONFIG_FLAGS))];
+            stage.fCoordMapping =  random_int(&random, StageDesc::kCoordMappingCnt);
+            stage.fFetchMode = random_int(&random, StageDesc::kFetchModeCnt);
             // convolution shaders don't work with persp tex matrix
             if (stage.fFetchMode == StageDesc::kConvolution_FetchMode) {
                 stage.fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
             }
             stage.setEnabled(VertexUsesStage(s, pdesc.fVertexLayout));
-            stage.fKernelWidth = static_cast<int8_t>(4 * random.nextF() + 2);
+            switch (stage.fFetchMode) {
+                case StageDesc::kSingle_FetchMode:
+                    stage.fKernelWidth = 0;
+                    break;
+                case StageDesc::kConvolution_FetchMode:
+                    stage.fKernelWidth = random_int(&random, 2, 8);
+                    stage.fInConfigFlags &= ~StageDesc::kMulRGBByAlpha_InConfigFlag;
+                    break;
+                case StageDesc::k2x2_FetchMode:
+                    stage.fKernelWidth = 0;
+                    stage.fInConfigFlags &= ~StageDesc::kMulRGBByAlpha_InConfigFlag;
+                    break;
+            }
         }
         CachedData cachedData;
         if (!program.genProgram(this->glInterface(),
@@ -1001,19 +1020,19 @@
                 stage.fOptFlags |= StageDesc::kCustomTextureDomain_OptFlagBit;
             }
 
+            stage.fInConfigFlags = 0;
             if (!this->glCaps().fTextureSwizzleSupport) {
                 if (GrPixelConfigIsAlphaOnly(texture->config())) {
                     // if we don't have texture swizzle support then
                     // the shader must do an alpha smear after reading
                     // the texture
-                    stage.fSwizzle = StageDesc::kAlphaSmear_Swizzle;
+                    stage.fInConfigFlags |= StageDesc::kSmearAlpha_InConfigFlag;
                 } else if (sampler.swapsRAndB()) {
-                    stage.fSwizzle = StageDesc::kSwapRAndB_Swizzle;
-                } else {
-                    stage.fSwizzle = StageDesc::kNone_Swizzle;
+                    stage.fInConfigFlags |= StageDesc::kSwapRAndB_InConfigFlag;
                 }
-            } else {
-                stage.fSwizzle = StageDesc::kNone_Swizzle;
+            }
+            if (GrPixelConfigIsUnpremultiplied(texture->config())) {
+                stage.fInConfigFlags = StageDesc::kMulRGBByAlpha_InConfigFlag;
             }
 
             if (sampler.getFilter() == GrSamplerState::kConvolution_Filter) {
@@ -1022,11 +1041,11 @@
                 stage.fKernelWidth = 0;
             }
         } else {
-            stage.fOptFlags     = 0;
-            stage.fCoordMapping = (StageDesc::CoordMapping) 0;
-            stage.fSwizzle      = (StageDesc::Swizzle) 0;
-            stage.fFetchMode    = (StageDesc::FetchMode) 0;
-            stage.fKernelWidth  = 0;
+            stage.fOptFlags         = 0;
+            stage.fCoordMapping     = (StageDesc::CoordMapping) 0;
+            stage.fInConfigFlags    = 0;
+            stage.fFetchMode        = (StageDesc::FetchMode) 0;
+            stage.fKernelWidth      = 0;
         }
     }
 
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 7230516..91184b7 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -299,9 +299,6 @@
                                             bitmap.rowBytes());
 }
 
-// This can be removed when temporary code in writePixels is removed
-#include "SkConfig8888.h"
-
 void SkGpuDevice::writePixels(const SkBitmap& bitmap, int x, int y,
                               SkCanvas::Config8888 config8888) {
     SkAutoLockPixels alp(bitmap);
@@ -317,30 +314,13 @@
                                                bitmap.isOpaque());
     }
 
-    // Temporary until we add support for drawing from an unpremul config in
-    // GrContext
-    const SkBitmap* src = &bitmap;
-    SkBitmap tmp;
-    if (GrPixelConfigIsUnpremultiplied(config)) {
-        config = kSkia8888_PM_GrPixelConfig;
-        tmp.setConfig(SkBitmap::kARGB_8888_Config,
-                      bitmap.width(), bitmap.height());
-        if (!tmp.allocPixels()) {
-            return;
-        }
-        SkAutoLockPixels alp(bitmap);
-        uint32_t* pixels = reinterpret_cast<uint32_t*>(bitmap.getPixels()); 
-        SkCopyConfig8888ToBitmap(tmp, pixels, bitmap.rowBytes(), config8888);
-        src = &tmp;
-    }
-
     fContext->setRenderTarget(fRenderTarget);
     // we aren't setting the clip or matrix, so mark as dirty
     // we don't need to set them for this call and don't have them anyway
     fNeedPrepareRenderTarget = true;
 
-    fContext->writePixels(x, y, src->width(), src->height(),
-                          config, src->getPixels(), src->rowBytes());
+    fContext->writePixels(x, y, bitmap.width(), bitmap.height(),
+                          config, bitmap.getPixels(), bitmap.rowBytes());
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/tests/WritePixelsTest.cpp b/tests/WritePixelsTest.cpp
index c82b0b0..00e08f0 100644
--- a/tests/WritePixelsTest.cpp
+++ b/tests/WritePixelsTest.cpp
@@ -245,7 +245,6 @@
                 bool mul;
                 SkPMColor bmpPMColor = convertConfig8888ToPMColor(config8888, bmpColor8888, &mul);
                 bool check;
-                // do we need fuzzy test?
                 REPORTER_ASSERT(reporter, check = checkPixel(bmpPMColor, canvasPixel, mul));
                 if (!check) {
                     success = false;