Skia side RGB to YUV gpu conversion
authorsugoi <sugoi@chromium.org>
Mon, 21 Jul 2014 18:37:39 +0000 (11:37 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 21 Jul 2014 18:37:39 +0000 (11:37 -0700)
This code is the one that's currently working in my local chromium build. A few things still need to be addressed and I'll highlight these directly in the code.

BUG=skia:
R=reed@google.com, bsalomon@google.com, senorblanco@google.com, senorblanco@chromium.org, robertphillips@google.com, scroggo@google.com, halcanary@google.com

Author: sugoi@chromium.org

Review URL: https://codereview.chromium.org/374743003

gyp/tests.gypi
include/core/SkImageGenerator.h
include/core/SkPixelRef.h
src/core/SkImageGenerator.cpp
src/core/SkPixelRef.cpp
src/gpu/SkGr.cpp
src/lazy/SkDiscardablePixelRef.h
tests/ImageGeneratorTest.cpp [new file with mode: 0644]

index 6a64479..34e8f54 100644 (file)
     '../tests/ImageCacheTest.cpp',
     '../tests/ImageDecodingTest.cpp',
     '../tests/ImageFilterTest.cpp',
+    '../tests/ImageGeneratorTest.cpp',
     '../tests/ImageNewShaderTest.cpp',
     '../tests/InfRectTest.cpp',
     '../tests/InterpolatorTest.cpp',
index 157bfdb..17477e3 100644 (file)
@@ -116,12 +116,26 @@ public:
     bool getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes);
 #endif
 
+    /**
+     *  If planes or rowBytes is NULL or if any entry in planes is NULL or if any entry in rowBytes
+     *  is 0, this imagegenerator should output the sizes and return true if it can efficiently
+     *  return YUV planar data. If it cannot, it should return false. Note that either planes and
+     *  rowBytes are both fully defined and non NULL/non 0 or they are both NULL or have NULL or 0
+     *  entries only. Having only partial planes/rowBytes information is not supported.
+     *
+     *  If all planes and rowBytes entries are non NULL or non 0, then it should copy the
+     *  associated YUV data into those planes of memory supplied by the caller. It should validate
+     *  that the sizes match what it expected. If the sizes do not match, it should return false.
+     */
+    bool getYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3]);
+
 protected:
     virtual SkData* onRefEncodedData();
     virtual bool onGetInfo(SkImageInfo* info);
     virtual bool onGetPixels(const SkImageInfo& info,
                              void* pixels, size_t rowBytes,
                              SkPMColor ctable[], int* ctableCount);
+    virtual bool onGetYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3]);
 };
 
 #endif  // SkImageGenerator_DEFINED
index a24ef8e..afab7fa 100644 (file)
@@ -13,6 +13,7 @@
 #include "SkRefCnt.h"
 #include "SkString.h"
 #include "SkImageInfo.h"
+#include "SkSize.h"
 #include "SkTDArray.h"
 
 //#define xed
@@ -219,6 +220,18 @@ public:
      */
     virtual GrTexture* getTexture() { return NULL; }
 
+    /**
+     *  If any planes or rowBytes is NULL, this should output the sizes and return true
+     *  if it can efficiently return YUV planar data. If it cannot, it should return false.
+     *
+     *  If all planes and rowBytes are not NULL, then it should copy the associated Y,U,V data
+     *  into those planes of memory supplied by the caller. It should validate that the sizes
+     *  match what it expected. If the sizes do not match, it should return false.
+     */
+    bool getYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3]) {
+        return this->onGetYUV8Planes(sizes, planes, rowBytes);
+    }
+
     bool readPixels(SkBitmap* dst, const SkIRect* subset = NULL);
 
     /**
@@ -305,6 +318,9 @@ protected:
     // default impl returns NULL.
     virtual SkData* onRefEncodedData();
 
+    // default impl returns false.
+    virtual bool onGetYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3]);
+
     /**
      *  Returns the size (in bytes) of the internally allocated memory.
      *  This should be implemented in all serializable SkPixelRef derived classes.
index daa55a3..551d8f7 100644 (file)
@@ -57,6 +57,45 @@ bool SkImageGenerator::getPixels(const SkImageInfo& info, void* pixels, size_t r
 }
 #endif
 
+bool SkImageGenerator::getYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3]) {
+#ifdef SK_DEBUG
+    // In all cases, we need the sizes array
+    SkASSERT(NULL != sizes);
+
+    bool isValidWithPlanes = (NULL != planes) && (NULL != rowBytes) &&
+        ((NULL != planes[0]) && (NULL != planes[1]) && (NULL != planes[2]) &&
+         (0  != rowBytes[0]) && (0  != rowBytes[1]) && (0  != rowBytes[2]));
+    bool isValidWithoutPlanes =
+        ((NULL == planes) ||
+         ((NULL == planes[0]) && (NULL == planes[1]) && (NULL == planes[2]))) &&
+        ((NULL == rowBytes) ||
+         ((0 == rowBytes[0]) && (0 == rowBytes[1]) && (0 == rowBytes[2])));
+
+    // Either we have all planes and rowBytes information or we have none of it
+    // Having only partial information is not supported
+    SkASSERT(isValidWithPlanes || isValidWithoutPlanes);
+
+    // If we do have planes information, make sure all sizes are non 0
+    // and all rowBytes are valid
+    SkASSERT(!isValidWithPlanes ||
+             ((sizes[0].fWidth  >= 0) &&
+              (sizes[0].fHeight >= 0) &&
+              (sizes[1].fWidth  >= 0) &&
+              (sizes[1].fHeight >= 0) &&
+              (sizes[2].fWidth  >= 0) &&
+              (sizes[2].fHeight >= 0) &&
+              (rowBytes[0] >= (size_t)sizes[0].fWidth) &&
+              (rowBytes[1] >= (size_t)sizes[1].fWidth) &&
+              (rowBytes[2] >= (size_t)sizes[2].fWidth)));
+#endif
+
+    return this->onGetYUV8Planes(sizes, planes, rowBytes);
+}
+
+bool SkImageGenerator::onGetYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3]) {
+    return false;
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////////
 
 SkData* SkImageGenerator::onRefEncodedData() {
index 651b26e..1064070 100644 (file)
@@ -253,6 +253,10 @@ SkData* SkPixelRef::onRefEncodedData() {
     return NULL;
 }
 
+bool SkPixelRef::onGetYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3]) {
+    return false;
+}
+
 size_t SkPixelRef::getAllocatedSizeInBytes() const {
     return 0;
 }
index c4e2c1f..5c0464d 100644 (file)
@@ -15,6 +15,7 @@
 #include "GrGpu.h"
 #include "effects/GrDitherEffect.h"
 #include "GrDrawTargetCaps.h"
+#include "effects/GrYUVtoRGBEffect.h"
 
 #ifndef SK_IGNORE_ETC1_SUPPORT
 #  include "ktx.h"
@@ -193,6 +194,81 @@ static GrTexture *load_etc1_texture(GrContext* ctx,
 }
 #endif   // SK_IGNORE_ETC1_SUPPORT
 
+static GrTexture *load_yuv_texture(GrContext* ctx, const GrTextureParams* params,
+                                   const SkBitmap& bm, const GrTextureDesc& desc) {
+    GrTexture* result = NULL;
+    
+    SkPixelRef* pixelRef = bm.pixelRef();
+    SkISize yuvSizes[3];
+    if ((NULL == pixelRef) || !pixelRef->getYUV8Planes(yuvSizes, NULL, NULL)) {
+        return NULL;
+    }
+
+    // Allocate the memory for YUV
+    size_t totalSize(0);
+    size_t sizes[3], rowBytes[3];
+    for (int i = 0; i < 3; ++i) {
+        rowBytes[i] = yuvSizes[i].fWidth;
+        totalSize  += sizes[i] = rowBytes[i] * yuvSizes[i].fHeight;
+    }
+    SkAutoMalloc storage(totalSize);
+    void* planes[3];
+    planes[0] = storage.get();
+    planes[1] = (uint8_t*)planes[0] + sizes[0];
+    planes[2] = (uint8_t*)planes[1] + sizes[1];
+
+    // Get the YUV planes
+    if (!pixelRef->getYUV8Planes(yuvSizes, planes, rowBytes)) {
+        return NULL;
+    }
+
+    GrTextureDesc yuvDesc;
+    yuvDesc.fConfig = kAlpha_8_GrPixelConfig;
+    GrAutoScratchTexture yuvTextures[3];
+    for (int i = 0; i < 3; ++i) {
+        yuvDesc.fWidth  = yuvSizes[i].fWidth;
+        yuvDesc.fHeight = yuvSizes[i].fHeight;
+        yuvTextures[i].set(ctx, yuvDesc);
+        if ((NULL == yuvTextures[i].texture()) ||
+            !ctx->writeTexturePixels(yuvTextures[i].texture(),
+                0, 0, yuvDesc.fWidth, yuvDesc.fHeight,
+                yuvDesc.fConfig, planes[i], rowBytes[i])) {
+            return NULL;
+        }
+    }
+
+    GrTextureDesc rtDesc = desc;
+    rtDesc.fFlags = rtDesc.fFlags |
+                    kRenderTarget_GrTextureFlagBit |
+                    kNoStencil_GrTextureFlagBit;
+
+    // This texture is likely to be used again so leave it in the cache
+    GrCacheID cacheID;
+    generate_bitmap_cache_id(bm, &cacheID);
+
+    GrResourceKey key;
+    result = ctx->createTexture(params, rtDesc, cacheID, NULL, 0, &key);
+    GrRenderTarget* renderTarget = result ? result->asRenderTarget() : NULL;
+    if (NULL != renderTarget) {
+        add_genID_listener(key, bm.pixelRef());
+        SkAutoTUnref<GrEffect> yuvToRgbEffect(GrYUVtoRGBEffect::Create(
+            yuvTextures[0].texture(), yuvTextures[1].texture(), yuvTextures[2].texture()));
+        GrPaint paint;
+        paint.addColorEffect(yuvToRgbEffect);
+        SkRect r = SkRect::MakeWH(SkIntToScalar(yuvSizes[0].fWidth),
+                                  SkIntToScalar(yuvSizes[0].fHeight));
+        GrContext::AutoRenderTarget autoRT(ctx, renderTarget);
+        GrContext::AutoMatrix am;
+        am.setIdentity(ctx);
+        GrContext::AutoClip ac(ctx, GrContext::AutoClip::kWideOpen_InitialClip);
+        ctx->drawRect(paint, r);
+    } else {
+        SkSafeSetNull(result);
+    }
+
+    return result;
+}
+
 static GrTexture* sk_gr_create_bitmap_texture(GrContext* ctx,
                                               bool cache,
                                               const GrTextureParams* params,
@@ -264,6 +340,12 @@ static GrTexture* sk_gr_create_bitmap_texture(GrContext* ctx,
     }
 #endif   // SK_IGNORE_ETC1_SUPPORT
 
+    else {
+        GrTexture *texture = load_yuv_texture(ctx, params, *bitmap, desc);
+        if (NULL != texture) {
+            return texture;
+        }
+    }
     SkAutoLockPixels alp(*bitmap);
     if (!bitmap->readyToDraw()) {
         return NULL;
index 2edef80..d31a040 100644 (file)
@@ -48,6 +48,12 @@ private:
                           size_t rowBytes,
                           SkDiscardableMemory::Factory* factory);
 
+    virtual bool onGetYUV8Planes(SkISize sizes[3],
+                                 void* planes[3],
+                                 size_t rowBytes[3]) SK_OVERRIDE {
+        return fGenerator->getYUV8Planes(sizes, planes, rowBytes);
+    }
+
     friend bool SkInstallDiscardablePixelRef(SkImageGenerator*, SkBitmap*,
                                              SkDiscardableMemory::Factory*);
 
diff --git a/tests/ImageGeneratorTest.cpp b/tests/ImageGeneratorTest.cpp
new file mode 100644 (file)
index 0000000..aaf149b
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkImageGenerator.h"
+#include "Test.h"
+
+DEF_TEST(ImageGenerator, reporter) {
+    SkImageGenerator ig;
+    SkISize sizes[3];
+    sizes[0] = SkISize::Make(200, 200);
+    sizes[1] = SkISize::Make(100, 100);
+    sizes[2] = SkISize::Make( 50,  50);
+    void*   planes[3] = { NULL };
+    size_t  rowBytes[3] = { 0 };
+
+    // Check that the YUV decoding API does not cause any crashes
+    ig.getYUV8Planes(sizes, NULL, NULL);
+    ig.getYUV8Planes(sizes, planes, NULL);
+    ig.getYUV8Planes(sizes, NULL, rowBytes);
+    ig.getYUV8Planes(sizes, planes, rowBytes);
+
+    int dummy;
+    planes[0] = planes[1] = planes[2] = &dummy;
+    rowBytes[0] = rowBytes[1] = rowBytes[2] = 250;
+
+    ig.getYUV8Planes(sizes, planes, rowBytes);
+}