From 9c0e629c64c0fa93ac9bf5c2eaa1821370a6fbe5 Mon Sep 17 00:00:00 2001 From: krajcevski Date: Mon, 2 Jun 2014 07:38:14 -0700 Subject: [PATCH] Initial work to get ETC1 data up to the GPU Committed: http://code.google.com/p/skia/source/detail?r=15001 R=bsalomon@google.com, robertphillips@google.com Author: krajcevski@google.com Review URL: https://codereview.chromium.org/302783002 --- gm/etc1bitmap.cpp | 19 +++++--- gyp/common_conditions.gypi | 1 + gyp/gpu.gyp | 1 + include/gpu/GrContext.h | 6 +-- include/gpu/GrTypes.h | 20 +++++++++ src/gpu/GrContext.cpp | 5 ++- src/gpu/GrGpu.cpp | 35 ++++++++++----- src/gpu/GrGpu.h | 19 ++++++-- src/gpu/SkGr.cpp | 60 +++++++++++++++++++++++++ src/gpu/gl/GrGpuGL.cpp | 109 +++++++++++++++++++++++++++++++++++++++++++++ src/gpu/gl/GrGpuGL.h | 6 +++ 11 files changed, 259 insertions(+), 22 deletions(-) diff --git a/gm/etc1bitmap.cpp b/gm/etc1bitmap.cpp index d2cd726..bb8ca6f 100644 --- a/gm/etc1bitmap.cpp +++ b/gm/etc1bitmap.cpp @@ -7,6 +7,8 @@ #include "gm.h" #include "SkCanvas.h" +#include "SkData.h" +#include "SkDecodingImageGenerator.h" #include "SkImageDecoder.h" #include "SkOSFile.h" @@ -35,13 +37,20 @@ protected: SkBitmap bm; SkString filename = SkOSPath::SkPathJoin( INHERITED::gResourcePath.c_str(), "mandrill_512.pkm"); - if (!SkImageDecoder::DecodeFile(filename.c_str(), &bm, - SkBitmap::kARGB_8888_Config, - SkImageDecoder::kDecodePixels_Mode)) { - SkDebugf("Could not decode the file. Did you forget to set the " - "resourcePath?\n"); + + SkData *fileData = SkData::NewFromFileName(filename.c_str()); + if (NULL == fileData) { + SkDebugf("Could not open the file. Did you forget to set the resourcePath?\n"); return; } + + if (!SkInstallDiscardablePixelRef( + SkDecodingImageGenerator::Create( + fileData, SkDecodingImageGenerator::Options()), &bm)) { + SkDebugf("Could not install discardable pixel ref.\n"); + return; + } + canvas->drawBitmap(bm, 0, 0); } diff --git a/gyp/common_conditions.gypi b/gyp/common_conditions.gypi index 2e607e3..46afef5 100644 --- a/gyp/common_conditions.gypi +++ b/gyp/common_conditions.gypi @@ -6,6 +6,7 @@ 'SK_SUPPORT_GPU=<(skia_gpu)', 'SK_SUPPORT_OPENCL=<(skia_opencl)', 'SK_FORCE_DISTANCEFIELD_FONTS=<(skia_force_distancefield_fonts)', + 'SK_SUPPORT_ETC1' ], 'conditions' : [ [ 'skia_arch_type == "arm64"', { diff --git a/gyp/gpu.gyp b/gyp/gpu.gyp index c69a94a..fd81d60 100644 --- a/gyp/gpu.gyp +++ b/gyp/gpu.gyp @@ -84,6 +84,7 @@ 'dependencies': [ 'core.gyp:*', 'utils.gyp:*', + 'etc1.gyp:libetc1', ], 'includes': [ 'gpu.gypi', diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h index 5b7ef25..c54f2e6 100644 --- a/include/gpu/GrContext.h +++ b/include/gpu/GrContext.h @@ -204,10 +204,11 @@ public: * for different wrap modes on GPUs with limited NPOT * texture support). NULL implies clamp wrap modes. * @param desc Description of the texture properties. - * @param cacheID Cache-specific properties (e.g., texture gen ID) + * @param cacheID Cache-specific properties (e.g., texture gen ID) * @param srcData Pointer to the pixel values. * @param rowBytes The number of bytes between rows of the texture. Zero - * implies tightly packed rows. + * implies tightly packed rows. For compressed pixel configs, this + * field is ignored. * @param cacheKey (optional) If non-NULL, we'll write the cache key we used to cacheKey. */ GrTexture* createTexture(const GrTextureParams* params, @@ -216,7 +217,6 @@ public: const void* srcData, size_t rowBytes, GrResourceKey* cacheKey = NULL); - /** * Search for an entry based on key and dimensions. If found, ref it and return it. The return * value will be NULL if not found. The caller must balance with a call to unref. diff --git a/include/gpu/GrTypes.h b/include/gpu/GrTypes.h index 53e633d..34f4e28 100644 --- a/include/gpu/GrTypes.h +++ b/include/gpu/GrTypes.h @@ -641,6 +641,26 @@ enum GrGLBackendState { }; /** + * Returns the data size for the given compressed pixel config + */ +static inline size_t GrCompressedFormatDataSize(GrPixelConfig config, + int width, int height) { + SkASSERT(GrPixelConfigIsCompressed(config)); + + switch (config) { + case kLATC_GrPixelConfig: + case kETC1_GrPixelConfig: + SkASSERT((width & 3) == 0); + SkASSERT((height & 3) == 0); + return (width >> 2) * (height >> 2) * 8; + + default: + SkFAIL("Unknown compressed pixel config"); + return 4 * width * height; + } +} + +/** * This value translates to reseting all the context state for any backend. */ static const uint32_t kAll_GrBackendState = 0xffffffff; diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index 5a59bc1..66588c4 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -388,11 +388,14 @@ GrTexture* GrContext::createTexture(const GrTextureParams* params, GrTexture* texture; if (GrTextureImpl::NeedsResizing(resourceKey)) { + // We do not know how to resize compressed textures. + SkASSERT(!GrPixelConfigIsCompressed(desc.fConfig)); + texture = this->createResizedTexture(desc, cacheID, srcData, rowBytes, GrTextureImpl::NeedsBilerp(resourceKey)); } else { - texture= fGpu->createTexture(desc, srcData, rowBytes); + texture = fGpu->createTexture(desc, srcData, rowBytes); } if (NULL != texture) { diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp index 111f632..36a9cf1 100644 --- a/src/gpu/GrGpu.cpp +++ b/src/gpu/GrGpu.cpp @@ -110,25 +110,40 @@ void GrGpu::unimpl(const char msg[]) { GrTexture* GrGpu::createTexture(const GrTextureDesc& desc, const void* srcData, size_t rowBytes) { - if (kUnknown_GrPixelConfig == desc.fConfig) { + if (!this->caps()->isConfigTexturable(desc.fConfig)) { return NULL; } + if ((desc.fFlags & kRenderTarget_GrTextureFlagBit) && !this->caps()->isConfigRenderable(desc.fConfig, desc.fSampleCnt > 0)) { return NULL; } - this->handleDirtyContext(); - GrTexture* tex = this->onCreateTexture(desc, srcData, rowBytes); - if (NULL != tex && - (kRenderTarget_GrTextureFlagBit & desc.fFlags) && - !(kNoStencil_GrTextureFlagBit & desc.fFlags)) { - SkASSERT(NULL != tex->asRenderTarget()); - // TODO: defer this and attach dynamically - if (!this->attachStencilBufferToRenderTarget(tex->asRenderTarget())) { - tex->unref(); + GrTexture *tex = NULL; + if (GrPixelConfigIsCompressed(desc.fConfig)) { + // We shouldn't be rendering into this + SkASSERT((desc.fFlags & kRenderTarget_GrTextureFlagBit) == 0); + + if (!this->caps()->npotTextureTileSupport() && + (!GrIsPow2(desc.fWidth) || !GrIsPow2(desc.fHeight))) { return NULL; } + + this->handleDirtyContext(); + tex = this->onCreateCompressedTexture(desc, srcData); + } else { + this->handleDirtyContext(); + tex = this->onCreateTexture(desc, srcData, rowBytes); + if (NULL != tex && + (kRenderTarget_GrTextureFlagBit & desc.fFlags) && + !(kNoStencil_GrTextureFlagBit & desc.fFlags)) { + SkASSERT(NULL != tex->asRenderTarget()); + // TODO: defer this and attach dynamically + if (!this->attachStencilBufferToRenderTarget(tex->asRenderTarget())) { + tex->unref(); + return NULL; + } + } } return tex; } diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h index 11f87e0..cd7502e 100644 --- a/src/gpu/GrGpu.h +++ b/src/gpu/GrGpu.h @@ -71,15 +71,26 @@ public: * two but underlying API requires a power of two texture then srcData * will be embedded in a power of two texture. The extra width and height * is filled as though srcData were rendered clamped into the texture. + * The exception is when using compressed data formats. In this case, the + * desc width and height must be a multiple of the compressed format block + * size otherwise this function returns NULL. Similarly, if the underlying + * API requires a power of two texture and the source width and height are not + * a power of two, then this function returns NULL. * * If kRenderTarget_TextureFlag is specified the GrRenderTarget is * accessible via GrTexture::asRenderTarget(). The texture will hold a ref - * on the render target until the texture is destroyed. + * on the render target until the texture is destroyed. Compressed textures + * cannot have the kRenderTarget_TextureFlag set. * * @param desc describes the texture to be created. * @param srcData texel data to load texture. Begins with full-size - * palette data for paletted textures. Contains width* - * height texels. If NULL texture data is uninitialized. + * palette data for paletted textures. For compressed + * formats it contains the compressed pixel data. Otherwise, + * it contains width*height texels. If NULL texture data + * is uninitialized. + * @param rowBytes the number of bytes between consecutive rows. Zero + * means rows are tightly packed. This field is ignored + * for compressed formats. * * @return The texture object if successful, otherwise NULL. */ @@ -414,6 +425,8 @@ private: virtual GrTexture* onCreateTexture(const GrTextureDesc& desc, const void* srcData, size_t rowBytes) = 0; + virtual GrTexture* onCreateCompressedTexture(const GrTextureDesc& desc, + const void* srcData) = 0; virtual GrTexture* onWrapBackendTexture(const GrBackendTextureDesc&) = 0; virtual GrRenderTarget* onWrapBackendRenderTarget(const GrBackendRenderTargetDesc&) = 0; virtual GrVertexBuffer* onCreateVertexBuffer(size_t size, bool dynamic) = 0; diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp index 6bd04de..9579866 100644 --- a/src/gpu/SkGr.cpp +++ b/src/gpu/SkGr.cpp @@ -8,9 +8,16 @@ #include "SkGr.h" #include "SkColorFilter.h" #include "SkConfig8888.h" +#include "SkData.h" #include "SkMessageBus.h" #include "SkPixelRef.h" #include "GrResourceCache.h" +#include "GrGpu.h" +#include "GrDrawTargetCaps.h" + +#if SK_SUPPORT_ETC1 +# include "etc1.h" +#endif /* Fill out buffer with the compressed format Ganesh expects from a colortable based bitmap. [palette (colortable) + indices]. @@ -124,6 +131,50 @@ static void add_genID_listener(GrResourceKey key, SkPixelRef* pixelRef) { pixelRef->addGenIDChangeListener(SkNEW_ARGS(GrResourceInvalidator, (key))); } +#if SK_SUPPORT_ETC1 +static GrTexture *load_etc1_texture(GrContext* ctx, + const GrTextureParams* params, + const SkBitmap &bm, GrTextureDesc desc) { + SkData *data = bm.pixelRef()->refEncodedData(); + + // Is this even encoded data? + if (NULL == data) { + return NULL; + } + + // Is this a valid PKM encoded data? + const uint8_t *bytes = data->bytes(); + if (!etc1_pkm_is_valid(bytes)) { + return NULL; + } + + uint32_t encodedWidth = etc1_pkm_get_width(bytes); + uint32_t encodedHeight = etc1_pkm_get_height(bytes); + + // Does the data match the dimensions of the bitmap? If not, + // then we don't know how to scale the image to match it... + if (encodedWidth != static_cast(bm.width()) || + encodedHeight != static_cast(bm.height())) { + return NULL; + } + + // Everything seems good... skip ahead to the data. + bytes += ETC_PKM_HEADER_SIZE; + desc.fConfig = kETC1_GrPixelConfig; + + // This texture is likely to be used again so leave it in the cache + GrCacheID cacheID; + generate_bitmap_cache_id(bm, &cacheID); + + GrResourceKey key; + GrTexture* result = ctx->createTexture(params, desc, cacheID, bytes, 0, &key); + if (NULL != result) { + add_genID_listener(key, bm.pixelRef()); + } + return result; +} +#endif // SK_SUPPORT_ETC1 + static GrTexture* sk_gr_create_bitmap_texture(GrContext* ctx, bool cache, const GrTextureParams* params, @@ -172,7 +223,16 @@ static GrTexture* sk_gr_create_bitmap_texture(GrContext* ctx, bitmap = &tmpBitmap; desc.fConfig = SkImageInfo2GrPixelConfig(bitmap->info()); } + + // Is this an ETC1 encoded texture? +#if SK_SUPPORT_ETC1 + } else if (cache && ctx->getGpu()->caps()->isConfigTexturable(kETC1_GrPixelConfig)) { + GrTexture *texture = load_etc1_texture(ctx, params, *bitmap, desc); + if (NULL != texture) { + return texture; + } } +#endif // SK_SUPPORT_ETC1 SkAutoLockPixels alp(*bitmap); if (!bitmap->readyToDraw()) { diff --git a/src/gpu/gl/GrGpuGL.cpp b/src/gpu/gl/GrGpuGL.cpp index 7275f6f..7568ba3 100644 --- a/src/gpu/gl/GrGpuGL.cpp +++ b/src/gpu/gl/GrGpuGL.cpp @@ -702,6 +702,41 @@ bool GrGpuGL::uploadTexData(const GrGLTexture::Desc& desc, return succeeded; } +bool GrGpuGL::uploadCompressedTexData(const GrGLTexture::Desc& desc, + const void* data) { + SkASSERT(NULL != data); + + // No support for software flip y, yet... + SkASSERT(kBottomLeft_GrSurfaceOrigin != desc.fOrigin); + + // Make sure that the width and height that we pass to OpenGL + // is a multiple of the block size. + int dataSize = GrCompressedFormatDataSize(desc.fConfig, desc.fWidth, desc.fHeight); + + // We only need the internal format for compressed 2D textures. + GrGLenum internalFormat = 0; + if (!this->configToGLFormats(desc.fConfig, false, &internalFormat, NULL, NULL)) { + return false; + } + + bool succeeded = true; + CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); + GL_ALLOC_CALL(this->glInterface(), + CompressedTexImage2D(GR_GL_TEXTURE_2D, + 0, // level + internalFormat, + desc.fWidth, desc.fHeight, + 0, // border + dataSize, + data)); + + GrGLenum error = check_alloc_error(desc, this->glInterface()); + if (error != GR_GL_NO_ERROR) { + succeeded = false; + } + return succeeded; +} + static bool renderbuffer_storage_msaa(GrGLContext& ctx, int sampleCount, GrGLenum format, @@ -981,6 +1016,80 @@ GrTexture* GrGpuGL::onCreateTexture(const GrTextureDesc& desc, return tex; } +GrTexture* GrGpuGL::onCreateCompressedTexture(const GrTextureDesc& desc, + const void* srcData) { + + if(SkToBool(desc.fFlags & kRenderTarget_GrTextureFlagBit)) { + return return_null_texture(); + } + + // Make sure that we're not flipping Y. + GrSurfaceOrigin texOrigin = resolve_origin(desc.fOrigin, false); + if (kBottomLeft_GrSurfaceOrigin == texOrigin) { + return return_null_texture(); + } + + GrGLTexture::Desc glTexDesc; + + glTexDesc.fFlags = desc.fFlags; + glTexDesc.fWidth = desc.fWidth; + glTexDesc.fHeight = desc.fHeight; + glTexDesc.fConfig = desc.fConfig; + glTexDesc.fIsWrapped = false; + glTexDesc.fOrigin = texOrigin; + + int maxSize = this->caps()->maxTextureSize(); + if (glTexDesc.fWidth > maxSize || glTexDesc.fHeight > maxSize) { + return return_null_texture(); + } + + GL_CALL(GenTextures(1, &glTexDesc.fTextureID)); + + if (!glTexDesc.fTextureID) { + return return_null_texture(); + } + + this->setScratchTextureUnit(); + GL_CALL(BindTexture(GR_GL_TEXTURE_2D, glTexDesc.fTextureID)); + + // Some drivers like to know filter/wrap before seeing glTexImage2D. Some + // drivers have a bug where an FBO won't be complete if it includes a + // texture that is not mipmap complete (considering the filter in use). + GrGLTexture::TexParams initialTexParams; + // we only set a subset here so invalidate first + initialTexParams.invalidate(); + initialTexParams.fMinFilter = GR_GL_NEAREST; + initialTexParams.fMagFilter = GR_GL_NEAREST; + initialTexParams.fWrapS = GR_GL_CLAMP_TO_EDGE; + initialTexParams.fWrapT = GR_GL_CLAMP_TO_EDGE; + GL_CALL(TexParameteri(GR_GL_TEXTURE_2D, + GR_GL_TEXTURE_MAG_FILTER, + initialTexParams.fMagFilter)); + GL_CALL(TexParameteri(GR_GL_TEXTURE_2D, + GR_GL_TEXTURE_MIN_FILTER, + initialTexParams.fMinFilter)); + GL_CALL(TexParameteri(GR_GL_TEXTURE_2D, + GR_GL_TEXTURE_WRAP_S, + initialTexParams.fWrapS)); + GL_CALL(TexParameteri(GR_GL_TEXTURE_2D, + GR_GL_TEXTURE_WRAP_T, + initialTexParams.fWrapT)); + + if (!this->uploadCompressedTexData(glTexDesc, srcData)) { + GL_CALL(DeleteTextures(1, &glTexDesc.fTextureID)); + return return_null_texture(); + } + + GrGLTexture* tex; + tex = SkNEW_ARGS(GrGLTexture, (this, glTexDesc)); + tex->setCachedTexParams(initialTexParams, this->getResetTimestamp()); +#ifdef TRACE_TEXTURE_CREATION + GrPrintf("--- new compressed texture [%d] size=(%d %d) config=%d\n", + glTexDesc.fTextureID, desc.fWidth, desc.fHeight, desc.fConfig); +#endif + return tex; +} + namespace { const GrGLuint kUnknownBitCount = GrGLStencilBuffer::kUnknownBitCount; diff --git a/src/gpu/gl/GrGpuGL.h b/src/gpu/gl/GrGpuGL.h index f548af5..cfb8b52 100644 --- a/src/gpu/gl/GrGpuGL.h +++ b/src/gpu/gl/GrGpuGL.h @@ -124,6 +124,8 @@ private: virtual GrTexture* onCreateTexture(const GrTextureDesc& desc, const void* srcData, size_t rowBytes) SK_OVERRIDE; + virtual GrTexture* onCreateCompressedTexture(const GrTextureDesc& desc, + const void* srcData) SK_OVERRIDE; virtual GrVertexBuffer* onCreateVertexBuffer(size_t size, bool dynamic) SK_OVERRIDE; virtual GrIndexBuffer* onCreateIndexBuffer(size_t size, bool dynamic) SK_OVERRIDE; virtual GrPath* onCreatePath(const SkPath&, const SkStrokeRec&) SK_OVERRIDE; @@ -265,6 +267,10 @@ private: const void* data, size_t rowBytes); + // helper for onCreateCompressedTexture + bool uploadCompressedTexData(const GrGLTexture::Desc& desc, + const void* data); + bool createRenderTargetObjects(int width, int height, GrGLuint texID, GrGLRenderTarget::Desc* desc); -- 2.7.4