From 518d83dbc1c899e316e8c896af5defb58b83120f Mon Sep 17 00:00:00 2001 From: sugoi Date: Mon, 21 Jul 2014 11:37:39 -0700 Subject: [PATCH] Skia side RGB to YUV gpu conversion 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 | 1 + include/core/SkImageGenerator.h | 14 +++++++ include/core/SkPixelRef.h | 16 ++++++++ src/core/SkImageGenerator.cpp | 39 +++++++++++++++++++ src/core/SkPixelRef.cpp | 4 ++ src/gpu/SkGr.cpp | 82 ++++++++++++++++++++++++++++++++++++++++ src/lazy/SkDiscardablePixelRef.h | 6 +++ tests/ImageGeneratorTest.cpp | 31 +++++++++++++++ 8 files changed, 193 insertions(+) create mode 100644 tests/ImageGeneratorTest.cpp diff --git a/gyp/tests.gypi b/gyp/tests.gypi index 6a64479..34e8f54 100644 --- a/gyp/tests.gypi +++ b/gyp/tests.gypi @@ -103,6 +103,7 @@ '../tests/ImageCacheTest.cpp', '../tests/ImageDecodingTest.cpp', '../tests/ImageFilterTest.cpp', + '../tests/ImageGeneratorTest.cpp', '../tests/ImageNewShaderTest.cpp', '../tests/InfRectTest.cpp', '../tests/InterpolatorTest.cpp', diff --git a/include/core/SkImageGenerator.h b/include/core/SkImageGenerator.h index 157bfdb..17477e3 100644 --- a/include/core/SkImageGenerator.h +++ b/include/core/SkImageGenerator.h @@ -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 diff --git a/include/core/SkPixelRef.h b/include/core/SkPixelRef.h index a24ef8e..afab7fa 100644 --- a/include/core/SkPixelRef.h +++ b/include/core/SkPixelRef.h @@ -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. diff --git a/src/core/SkImageGenerator.cpp b/src/core/SkImageGenerator.cpp index daa55a3..551d8f7 100644 --- a/src/core/SkImageGenerator.cpp +++ b/src/core/SkImageGenerator.cpp @@ -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() { diff --git a/src/core/SkPixelRef.cpp b/src/core/SkPixelRef.cpp index 651b26e..1064070 100644 --- a/src/core/SkPixelRef.cpp +++ b/src/core/SkPixelRef.cpp @@ -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; } diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp index c4e2c1f..5c0464d 100644 --- a/src/gpu/SkGr.cpp +++ b/src/gpu/SkGr.cpp @@ -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 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; diff --git a/src/lazy/SkDiscardablePixelRef.h b/src/lazy/SkDiscardablePixelRef.h index 2edef80..d31a040 100644 --- a/src/lazy/SkDiscardablePixelRef.h +++ b/src/lazy/SkDiscardablePixelRef.h @@ -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 index 0000000..aaf149b --- /dev/null +++ b/tests/ImageGeneratorTest.cpp @@ -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); +} -- 2.7.4