flip Y in uploads to bottom-up textures (and add gm test)

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



git-svn-id: http://skia.googlecode.com/svn/trunk@1882 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index 1587a9a..373356e 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -482,6 +482,13 @@
 #endif
 };
 
+namespace skiagm {
+static GrContext* gGrContext;
+GrContext* GetGr() {
+    return gGrContext;
+}
+}
+
 int main(int argc, char * const argv[]) {
     SkAutoGraphics ag;
 
@@ -533,10 +540,9 @@
         maxH = SkMax32(size.height(), maxH);
     }
     // setup a GL context for drawing offscreen
-    GrContext* context = NULL;
     SkEGLContext eglContext;
     if (eglContext.init(maxW, maxH)) {
-        context = GrContext::CreateGLShaderContext();
+        gGrContext = GrContext::CreateGLShaderContext();
     }
 
 
@@ -558,7 +564,7 @@
 
         for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) {
             bool testSuccess = test_drawing(gm, gRec[i],
-                         writePath, readPath, diffPath, context,
+                         writePath, readPath, diffPath, gGrContext,
                          &forwardRenderedBitmap);
             overallSuccess &= testSuccess;
 
diff --git a/gm/texdata.cpp b/gm/texdata.cpp
new file mode 100644
index 0000000..e1ad4b3
--- /dev/null
+++ b/gm/texdata.cpp
@@ -0,0 +1,139 @@
+#include "gm.h"
+#include "GrContext.h"
+#include "SkColorPriv.h"
+#include "SkDevice.h"
+
+namespace skiagm {
+
+extern GrContext* GetGr();
+
+static const int S = 200;
+
+class TexDataGM : public GM {
+public:
+    TexDataGM() {}
+
+protected:
+    virtual SkString onShortName() {
+        return SkString("texdata");
+    }
+
+    virtual SkISize onISize() {
+        return make_isize(2*S, 2*S);
+    }
+
+    void drawBG(SkCanvas* canvas) {
+        canvas->drawColor(0xff000000);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        drawBG(canvas);
+        SkDevice* device = canvas->getDevice();
+        GrRenderTarget* target = (GrRenderTarget*) device->accessRenderTarget();
+        GrContext* ctx = GetGr();
+        if (ctx && target) {
+            SkPMColor gTextureData[(2 * S) * (2 * S)];
+            static const int stride = 2 * S;
+            static const SkPMColor gray  = SkPackARGB32(0x40, 0x40, 0x40, 0x40);
+            static const SkPMColor white = SkPackARGB32(0xff, 0xff, 0xff, 0xff);
+            static const SkPMColor red   = SkPackARGB32(0x80, 0x80, 0x00, 0x00);
+            static const SkPMColor blue  = SkPackARGB32(0x80, 0x00, 0x00, 0x80);
+            static const SkPMColor green = SkPackARGB32(0x80, 0x00, 0x80, 0x00);
+            static const SkPMColor black = SkPackARGB32(0x00, 0x00, 0x00, 0x00);
+            for (int i = 0; i < 2; ++i) {
+                int offset = 0;
+                // fill upper-left
+                for (int y = 0; y < S; ++y) {
+                    for (int x = 0; x < S; ++x) {
+                        gTextureData[offset + y * stride + x] = gray;
+                    }
+                }
+                // fill upper-right
+                offset = S;
+                for (int y = 0; y < S; ++y) {
+                    for (int x = 0; x < S; ++x) {
+                        gTextureData[offset + y * stride + x] = white;
+                    }
+                }
+                // fill lower left
+                offset = S * stride;
+                for (int y = 0; y < S; ++y) {
+                    for (int x = 0; x < S; ++x) {
+                        gTextureData[offset + y * stride + x] = black;
+                    }
+                }
+                // fill lower right
+                offset = S * stride + S;
+                for (int y = 0; y < S; ++y) {
+                    for (int x = 0; x < S; ++x) {
+                        gTextureData[offset + y * stride + x] = gray;
+                    }
+                }
+
+                GrTextureDesc desc;
+                desc.fAALevel   = kNone_GrAALevel;
+                // use RT flag bit because in GL it makes the texture be bottom-up
+                desc.fFlags     = i ? kRenderTarget_GrTextureFlagBit :
+                                      kNone_GrTextureFlags;
+                desc.fFormat    = kRGBA_8888_GrPixelConfig;
+                desc.fWidth     = 2 * S;
+                desc.fHeight    = 2 * S;
+                GrTexture* texture = 
+                    ctx->createUncachedTexture(desc, gTextureData, 0);
+
+                if (!texture) {
+                    return;
+                }
+                GrAutoUnref au(texture);
+
+                ctx->setClip(GrRect::MakeWH(2*S, 2*S));
+                ctx->setRenderTarget(target);
+
+                GrPaint paint;
+                paint.reset();
+                paint.fColor = 0xffffffff;
+                paint.fSrcBlendCoeff = kOne_BlendCoeff;
+                paint.fDstBlendCoeff = kISA_BlendCoeff;
+                GrMatrix vm;
+                if (i) {
+                    vm.setRotate(90, S , S);
+                } else {
+                    vm.reset();
+                }
+                ctx->setMatrix(vm);
+                GrMatrix tm;
+                tm = vm;
+                tm.postIDiv(2*S, 2*S);
+                paint.getTextureSampler(0)->setMatrix(tm);
+                paint.setTexture(0, texture);
+
+                ctx->drawRect(paint, GrRect::MakeWH(2*S, 2*S));
+
+                // now update the lower right of the texture in first pass
+                // or upper right in second pass
+                offset = 0;
+                for (int y = 0; y < S; ++y) {
+                    for (int x = 0; x < S; ++x) {
+                        gTextureData[offset + y * stride + x] = 
+                            ((x + y) % 2) ? (i ? green : red) : blue;
+                    }
+                }
+                // BUG: uploadTextureData doesn't force a flush
+                ctx->flush();
+                texture->uploadTextureData(S, i ? 0 : S, S, S, gTextureData, 4 * stride);
+                ctx->drawRect(paint, GrRect::MakeWH(2*S, 2*S));
+            }
+        }
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new TexDataGM; }
+static GMRegistry reg(MyFactory);
+
+}
+
diff --git a/gpu/src/GrGLTexture.cpp b/gpu/src/GrGLTexture.cpp
index 207246b..bbca5ed 100644
--- a/gpu/src/GrGLTexture.cpp
+++ b/gpu/src/GrGLTexture.cpp
@@ -162,51 +162,65 @@
 
     GPUGL->setSpareTextureUnit();
 
-    // glCompressedTexSubImage2D doesn't support any formats
+    // ES2 glCompressedTexSubImage2D doesn't support any formats
     // (at least without extensions)
     GrAssert(fUploadFormat != GR_GL_PALETTE8_RGBA8);
 
     // in case we need a temporary, trimmed copy of the src pixels
-    SkAutoSMalloc<128 * 128> trimStorage;
+    SkAutoSMalloc<128 * 128> tempStorage;
 
+    if (!rowBytes) {
+        rowBytes = fUploadByteCount * width;
+    }
     /*
-     *  check if our srcData has extra bytes past each row. If so, we need
-     *  to trim those off here, since GL doesn't let us pass the rowBytes as
-     *  a parameter to glTexImage2D
+     *  check whether to allocate a temporary buffer for flipping y or
+     *  because our srcData has extra bytes past each row. If so, we need
+     *  to trim those off here, since GL ES doesn't let us specify
+     *  GL_UNPACK_ROW_LENGTH.
      */
-
-    if (GR_GL_SUPPORT_DESKTOP) {
+    bool restoreGLRowLength = false;
+    bool flipY = kBottomUp_Orientation == fOrientation;
+    if (GR_GL_SUPPORT_DESKTOP && !flipY) {
+        // can't use this for flipping, only non-neg values allowed. :(
         if (srcData && rowBytes) {
             GR_GL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH,
                               rowBytes / fUploadByteCount));
+            restoreGLRowLength = true;
         }
     } else {
         size_t trimRowBytes = width * fUploadByteCount;
-        if (srcData && (trimRowBytes < rowBytes)) {
+        if (srcData && (trimRowBytes < rowBytes || flipY)) {
             // copy the data into our new storage, skipping the trailing bytes
             size_t trimSize = height * trimRowBytes;
             const char* src = (const char*)srcData;
-            char* dst = (char*)trimStorage.realloc(trimSize);
+            if (flipY) {
+                src += (height - 1) * rowBytes;
+            }
+            char* dst = (char*)tempStorage.realloc(trimSize);
             for (int y = 0; y < height; y++) {
                 memcpy(dst, src, trimRowBytes);
-                src += rowBytes;
+                if (flipY) {
+                    src -= rowBytes;
+                } else {
+                    src += rowBytes;
+                }
                 dst += trimRowBytes;
             }
-            // now point srcData to our trimmed version
-            srcData = trimStorage.get();
+            // now point srcData to our copied version
+            srcData = tempStorage.get();
         }
     }
 
-    // If we need to update textures that are created upside down
-    // then we have to modify this code to flip the srcData
-    GrAssert(kTopDown_Orientation == fOrientation);
+    if (flipY) {
+        y = this->height() - (y + height);
+    }
     GR_GL(BindTexture(GR_GL_TEXTURE_2D, fTexIDObj->id()));
     GR_GL(PixelStorei(GR_GL_UNPACK_ALIGNMENT, fUploadByteCount));
     GR_GL(TexSubImage2D(GR_GL_TEXTURE_2D, 0, x, y, width, height,
                         fUploadFormat, fUploadType, srcData));
 
     if (GR_GL_SUPPORT_DESKTOP) {
-        if (srcData && rowBytes) {
+        if (restoreGLRowLength) {
             GR_GL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0));
         }
     }
diff --git a/gpu/src/GrGpuGL.cpp b/gpu/src/GrGpuGL.cpp
index 1a9d7fe..09b337c 100644
--- a/gpu/src/GrGpuGL.cpp
+++ b/gpu/src/GrGpuGL.cpp
@@ -790,46 +790,60 @@
         return return_null_texture();
     }
 
+    // We keep GrRenderTargets in GL's normal orientation so that they
+    // can be drawn to by the outside world without the client having
+    // to render upside down.
+    glDesc.fOrientation = renderTarget ? GrGLTexture::kBottomUp_Orientation :
+                                         GrGLTexture::kTopDown_Orientation;
+
     GrAssert(as_size_t(desc.fAALevel) < GR_ARRAY_COUNT(fAASamples));
     GrGLint samples = fAASamples[desc.fAALevel];
     if (kNone_MSFBO == fMSFBOType && desc.fAALevel != kNone_GrAALevel) {
         GrPrintf("AA RT requested but not supported on this platform.");
     }
 
-    GR_GL(GenTextures(1, &glDesc.fTextureID));
-    if (!glDesc.fTextureID) {
-        return return_null_texture();
-    }
-
     glDesc.fUploadByteCount = GrBytesPerPixel(desc.fFormat);
 
     // in case we need a temporary, trimmed copy of the src pixels
-    SkAutoSMalloc<128 * 128> trimStorage;
+    SkAutoSMalloc<128 * 128> tempStorage;
 
+    if (!rowBytes) {
+        rowBytes = glDesc.fUploadByteCount * desc.fWidth;
+    }
     /*
-     *  check if our srcData has extra bytes past each row. If so, we need
-     *  to trim those off here, since GL doesn't let us pass the rowBytes as
-     *  a parameter to glTexImage2D
+     * check whether to allocate a temporary buffer for flipping y or
+     *  because our srcData has extra bytes past each row. If so, we need
+     *  to trim those off here, since GL ES doesn't let us specify
+     *  GL_UNPACK_ROW_LENGTH.
      */
-    if (GR_GL_SUPPORT_DESKTOP) {
-        if (srcData) {
+    bool flipY = GrGLTexture::kBottomUp_Orientation == glDesc.fOrientation;
+    if (GR_GL_SUPPORT_DESKTOP && !flipY) {
+        if (srcData && rowBytes != desc.fWidth * glDesc.fUploadByteCount) {
             GR_GL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH,
                               rowBytes / glDesc.fUploadByteCount));
         }
     } else {
         size_t trimRowBytes = desc.fWidth * glDesc.fUploadByteCount;
-        if (srcData && (trimRowBytes < rowBytes)) {
+        if (srcData && (trimRowBytes < rowBytes || flipY)) {
             // copy the data into our new storage, skipping the trailing bytes
             size_t trimSize = desc.fHeight * trimRowBytes;
             const char* src = (const char*)srcData;
-            char* dst = (char*)trimStorage.realloc(trimSize);
+            if (flipY) {
+                src += (desc.fHeight - 1) * rowBytes;
+            }
+            char* dst = (char*)tempStorage.realloc(trimSize);
             for (int y = 0; y < desc.fHeight; y++) {
                 memcpy(dst, src, trimRowBytes);
-                src += rowBytes;
+                if (flipY) {
+                    src -= rowBytes;
+                } else {
+                    src += rowBytes;
+                }
                 dst += trimRowBytes;
             }
             // now point srcData to our trimmed version
-            srcData = trimStorage.get();
+            srcData = tempStorage.get();
+            rowBytes = trimRowBytes;
         }
     }
 
@@ -856,6 +870,11 @@
         }
     }
 
+    GR_GL(GenTextures(1, &glDesc.fTextureID));
+    if (!glDesc.fTextureID) {
+        return return_null_texture();
+    }
+
     GR_GL(BindTexture(GR_GL_TEXTURE_2D, glDesc.fTextureID));
     GR_GL(TexParameteri(GR_GL_TEXTURE_2D,
                         GR_GL_TEXTURE_MAG_FILTER,
@@ -901,36 +920,39 @@
 
             SkAutoSMalloc<128*128> texels(glDesc.fUploadByteCount * maxTexels);
 
-            uint32_t rowSize = desc.fWidth * glDesc.fUploadByteCount;
+            // rowBytes is actual stride between rows in srcData
+            // rowDataBytes is the actual amount of non-pad data in a row
+            // and the stride used for uploading extraH rows.
+            uint32_t rowDataBytes = desc.fWidth * glDesc.fUploadByteCount;
             if (extraH) {
                 uint8_t* lastRowStart = (uint8_t*) srcData +
-                                        (desc.fHeight - 1) * rowSize;
+                                        (desc.fHeight - 1) * rowBytes;
                 uint8_t* extraRowStart = (uint8_t*)texels.get();
 
                 for (int i = 0; i < extraH; ++i) {
-                    memcpy(extraRowStart, lastRowStart, rowSize);
-                    extraRowStart += rowSize;
+                    memcpy(extraRowStart, lastRowStart, rowDataBytes);
+                    extraRowStart += rowDataBytes;
                 }
                 GR_GL(TexSubImage2D(GR_GL_TEXTURE_2D, 0, 0, desc.fHeight, desc.fWidth,
                                     extraH, glDesc.fUploadFormat, glDesc.fUploadType,
                                     texels.get()));
             }
             if (extraW) {
-                uint8_t* edgeTexel = (uint8_t*)srcData + rowSize - glDesc.fUploadByteCount;
+                uint8_t* edgeTexel = (uint8_t*)srcData + rowDataBytes - glDesc.fUploadByteCount;
                 uint8_t* extraTexel = (uint8_t*)texels.get();
                 for (int j = 0; j < desc.fHeight; ++j) {
                     for (int i = 0; i < extraW; ++i) {
                         memcpy(extraTexel, edgeTexel, glDesc.fUploadByteCount);
                         extraTexel += glDesc.fUploadByteCount;
                     }
-                    edgeTexel += rowSize;
+                    edgeTexel += rowBytes;
                 }
                 GR_GL(TexSubImage2D(GR_GL_TEXTURE_2D, 0, desc.fWidth, 0, extraW,
                                     desc.fHeight, glDesc.fUploadFormat,
                                     glDesc.fUploadType, texels.get()));
             }
             if (extraW && extraH) {
-                uint8_t* cornerTexel = (uint8_t*)srcData + desc.fHeight * rowSize
+                uint8_t* cornerTexel = (uint8_t*)srcData + desc.fHeight * rowBytes
                                        - glDesc.fUploadByteCount;
                 uint8_t* extraTexel = (uint8_t*)texels.get();
                 for (int i = 0; i < extraW*extraH; ++i) {
@@ -950,7 +972,6 @@
         }
     }
 
-    glDesc.fOrientation = GrGLTexture::kTopDown_Orientation;
 
     GrGLRenderTarget::GLRenderTargetIDs rtIDs;
     rtIDs.fStencilRenderbufferID = 0;
@@ -968,12 +989,6 @@
         GrGLenum status;
         GrGLint err;
 
-        // If need have both RT flag and srcData we have
-        // to invert the data before uploading because FBO
-        // will be rendered bottom up
-        GrAssert(NULL == srcData);
-        glDesc.fOrientation =  GrGLTexture::kBottomUp_Orientation;
-
         GR_GL(GenFramebuffers(1, &rtIDs.fTexFBOID));
         GrAssert(rtIDs.fTexFBOID);
 
diff --git a/gyp/gm.gyp b/gyp/gm.gyp
index 1219995..4ecbb47 100644
--- a/gyp/gm.gyp
+++ b/gyp/gm.gyp
@@ -22,6 +22,7 @@
         '../gm/shapes.cpp',
         '../gm/strokerects.cpp',
         '../gm/strokes.cpp',
+        '../gm/texdata.cpp',
         '../gm/tilemodes.cpp',
         '../gm/xfermodes.cpp',
         '../gm/shadertext.cpp',
diff --git a/include/core/SkDevice.h b/include/core/SkDevice.h
index 142748c..b9cafc7 100644
--- a/include/core/SkDevice.h
+++ b/include/core/SkDevice.h
@@ -30,6 +30,9 @@
 class SkMetaData;
 class SkRegion;
 
+// This is an opaque classes, not interpreted by skia
+class SkGpuRenderTarget;
+
 class SK_API SkDevice : public SkRefCnt {
 public:
 //    SkDevice();
@@ -135,10 +138,10 @@
     virtual void lockPixels();
     virtual void unlockPixels();
 
-    /** Return the device's associated texture, or NULL. If returned, it may be
-        drawn into another device
+    /**
+     * Return the device's associated gpu render target, or NULL.
      */
-    virtual SkGpuTexture* accessTexture() { return NULL; }
+    virtual SkGpuRenderTarget* accessRenderTarget() { return NULL; }
 
     /**
      *  Called with the correct matrix and clip before this device is drawn
diff --git a/include/gpu/SkGpuDevice.h b/include/gpu/SkGpuDevice.h
index 2bea0bf..bddf24e 100644
--- a/include/gpu/SkGpuDevice.h
+++ b/include/gpu/SkGpuDevice.h
@@ -74,7 +74,9 @@
     virtual void gainFocus(SkCanvas*, const SkMatrix&, const SkRegion&,
                            const SkClipStack& clipStack);
 
-    virtual SkGpuTexture* accessTexture() { return (SkGpuTexture*)fTexture; }
+    virtual SkGpuRenderTarget* accessRenderTarget() { 
+        return (SkGpuRenderTarget*)fRenderTarget; 
+    }
 
     // overrides from SkDevice