From bc262e110a7292950ce912e42de75b7573d0367e Mon Sep 17 00:00:00 2001 From: Brian Osman Date: Mon, 22 May 2017 20:14:41 +0000 Subject: [PATCH] Revert "Remove compressed (ETC1) texture support from Ganesh" This reverts commit ee26363aaae62db2a851f2873e2405a9cf7f995a. Reason for revert: Failing Google 3 roll. Original change's description: > Remove compressed (ETC1) texture support from Ganesh > > Change-Id: If4cf286df87ea87338aba47001d90a5fcc4f2667 > Reviewed-on: https://skia-review.googlesource.com/17456 > Commit-Queue: Robert Phillips > Reviewed-by: Brian Salomon > TBR=bsalomon@google.com,robertphillips@google.com NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true Change-Id: Ie1a57187287e03600a69e374501478e93c41415c Reviewed-on: https://skia-review.googlesource.com/17527 Reviewed-by: Brian Osman Commit-Queue: Brian Osman --- BUILD.gn | 2 + gm/etc1.cpp | 118 +++++++ gn/gm.gni | 1 + include/gpu/GrCaps.h | 2 + include/gpu/GrTypes.h | 100 +++++- src/gpu/GrCaps.cpp | 3 + src/gpu/GrDrawOpAtlas.cpp | 3 + src/gpu/GrGpu.cpp | 37 ++- src/gpu/GrGpu.h | 8 +- src/gpu/GrResourceProvider.cpp | 36 ++- src/gpu/GrShaderCaps.cpp | 3 +- src/gpu/GrSurface.cpp | 15 +- src/gpu/GrSurfaceProxy.cpp | 12 + src/gpu/GrTexture.cpp | 16 +- src/gpu/GrTextureProducer.cpp | 4 +- src/gpu/SkGr.h | 2 +- src/gpu/gl/GrGLCaps.cpp | 48 +++ src/gpu/gl/GrGLCaps.h | 2 + src/gpu/gl/GrGLGpu.cpp | 265 +++++++++++++++- src/gpu/gl/GrGLGpu.h | 15 + src/gpu/ops/GrCopySurfaceOp.cpp | 3 + src/gpu/vk/GrVkCaps.cpp | 6 + src/gpu/vk/GrVkGpu.cpp | 65 ++-- src/gpu/vk/GrVkGpu.h | 3 + src/gpu/vk/GrVkUtil.cpp | 6 + tests/ProxyTest.cpp | 2 +- third_party/etc1/LICENSE | 161 ++++++++++ third_party/etc1/README.google | 7 + third_party/etc1/etc1.cpp | 678 ++++++++++++++++++++++++++++++++++++++++ third_party/etc1/etc1.h | 114 +++++++ tools/gpu/GrTest.cpp | 5 + 31 files changed, 1673 insertions(+), 69 deletions(-) create mode 100644 gm/etc1.cpp create mode 100644 third_party/etc1/LICENSE create mode 100644 third_party/etc1/README.google create mode 100644 third_party/etc1/etc1.cpp create mode 100644 third_party/etc1/etc1.h diff --git a/BUILD.gn b/BUILD.gn index a18b8b1..19691ed 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -136,6 +136,7 @@ config("skia_private") { "src/utils", "src/utils/win", "src/xml", + "third_party/etc1", "third_party/gif", ] @@ -631,6 +632,7 @@ component("skia") { "src/sfnt/SkOTTable_name.cpp", "src/sfnt/SkOTUtils.cpp", "src/utils/mac/SkStream_mac.cpp", + "third_party/etc1/etc1.cpp", "third_party/gif/SkGifImageReader.cpp", ] diff --git a/gm/etc1.cpp b/gm/etc1.cpp new file mode 100644 index 0000000..cc01a0e --- /dev/null +++ b/gm/etc1.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm.h" +#include "sk_tool_utils.h" +#include "SkRandom.h" + +#if SK_SUPPORT_GPU +#include "etc1.h" + +#include "GrContext.h" +#include "GrRenderTargetContext.h" +#include "GrRenderTargetContextPriv.h" +#include "GrTextureProxy.h" +#include "effects/GrSimpleTextureEffect.h" +#include "ops/GrNonAAFillRectOp.h" + +// Basic test of Ganesh's ETC1 support +class ETC1GM : public skiagm::GM { +public: + ETC1GM() { + this->setBGColor(sk_tool_utils::color_to_565(0xFFCCCCCC)); + } + +protected: + SkString onShortName() override { + return SkString("etc1"); + } + + SkISize onISize() override { + return SkISize::Make(kTexWidth + 2*kPad, kTexHeight + 2*kPad); + } + + void onOnceBeforeDraw() override { + SkBitmap bm; + SkImageInfo ii = SkImageInfo::Make(kTexWidth, kTexHeight, kRGB_565_SkColorType, + kOpaque_SkAlphaType); + bm.allocPixels(ii); + + bm.erase(SK_ColorBLUE, SkIRect::MakeWH(kTexWidth, kTexHeight)); + + for (int y = 0; y < kTexHeight; y += 4) { + for (int x = 0; x < kTexWidth; x += 4) { + bm.erase((x+y) % 8 ? SK_ColorRED : SK_ColorGREEN, SkIRect::MakeXYWH(x, y, 4, 4)); + } + } + + int size = etc1_get_encoded_data_size(bm.width(), bm.height()); + fETC1Data.reset(size); + + unsigned char* pixels = (unsigned char*) fETC1Data.get(); + + if (etc1_encode_image((unsigned char*) bm.getAddr16(0, 0), + bm.width(), bm.height(), 2, bm.rowBytes(), pixels)) { + fETC1Data.reset(); + } + } + + void onDraw(SkCanvas* canvas) override { + GrRenderTargetContext* renderTargetContext = + canvas->internal_private_accessTopLayerRenderTargetContext(); + if (!renderTargetContext) { + skiagm::GM::DrawGpuOnlyMessage(canvas); + return; + } + + GrContext* context = canvas->getGrContext(); + if (!context) { + return; + } + + GrSurfaceDesc desc; + desc.fConfig = kETC1_GrPixelConfig; + desc.fWidth = kTexWidth; + desc.fHeight = kTexHeight; + + sk_sp proxy = GrSurfaceProxy::MakeDeferred(context->resourceProvider(), + desc, SkBudgeted::kYes, + fETC1Data.get(), 0); + if (!proxy) { + return; + } + + const SkMatrix trans = SkMatrix::MakeTrans(-kPad, -kPad); + + sk_sp fp = GrSimpleTextureEffect::Make(context->resourceProvider(), + std::move(proxy), + nullptr, trans); + + GrPaint grPaint; + grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc)); + grPaint.addColorFragmentProcessor(std::move(fp)); + + SkRect rect = SkRect::MakeXYWH(kPad, kPad, kTexWidth, kTexHeight); + + renderTargetContext->priv().testingOnly_addDrawOp(GrNonAAFillRectOp::Make( + std::move(grPaint), SkMatrix::I(), rect, nullptr, nullptr, GrAAType::kNone)); + } + +private: + static const int kPad = 8; + static const int kTexWidth = 16; + static const int kTexHeight = 20; + + SkAutoTMalloc fETC1Data; + + typedef GM INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +DEF_GM(return new ETC1GM;) + +#endif diff --git a/gn/gm.gni b/gn/gm.gni index 65c2f7c..0a6681b 100644 --- a/gn/gm.gni +++ b/gn/gm.gni @@ -117,6 +117,7 @@ gm_sources = [ "$_gm/encode-alpha-jpeg.cpp", "$_gm/encode-platform.cpp", "$_gm/encode-srgb.cpp", + "$_gm/etc1.cpp", "$_gm/extractbitmap.cpp", "$_gm/fadefilter.cpp", "$_gm/fatpathfill.cpp", diff --git a/include/gpu/GrCaps.h b/include/gpu/GrCaps.h index 4dc7767..2fb367d 100644 --- a/include/gpu/GrCaps.h +++ b/include/gpu/GrCaps.h @@ -45,6 +45,7 @@ public: bool srgbWriteControl() const { return fSRGBWriteControl; } bool discardRenderTargetSupport() const { return fDiscardRenderTargetSupport; } bool gpuTracingSupport() const { return fGpuTracingSupport; } + bool compressedTexSubImageSupport() const { return fCompressedTexSubImageSupport; } bool oversizedStencilSupport() const { return fOversizedStencilSupport; } bool textureBarrierSupport() const { return fTextureBarrierSupport; } bool sampleLocationsSupport() const { return fSampleLocationsSupport; } @@ -211,6 +212,7 @@ protected: bool fReuseScratchTextures : 1; bool fReuseScratchBuffers : 1; bool fGpuTracingSupport : 1; + bool fCompressedTexSubImageSupport : 1; bool fOversizedStencilSupport : 1; bool fTextureBarrierSupport : 1; bool fSampleLocationsSupport : 1; diff --git a/include/gpu/GrTypes.h b/include/gpu/GrTypes.h index e963027..1619b9a 100644 --- a/include/gpu/GrTypes.h +++ b/include/gpu/GrTypes.h @@ -298,7 +298,10 @@ enum GrPixelConfig { * 8 bit signed integers per-channel. Byte order is b,g,r,a. */ kRGBA_8888_sint_GrPixelConfig, - + /** + * ETC1 Compressed Data + */ + kETC1_GrPixelConfig, /** * Byte order is r, g, b, a. This color format is 32 bits per channel */ @@ -334,6 +337,58 @@ static const int kGrPixelConfigCnt = kLast_GrPixelConfig + 1; #error "SK_*32_SHIFT values must correspond to GL_BGRA or GL_RGBA format." #endif +// Returns true if the pixel config is a GPU-specific compressed format +// representation. +static inline bool GrPixelConfigIsCompressed(GrPixelConfig config) { + switch (config) { + case kETC1_GrPixelConfig: + return true; + case kUnknown_GrPixelConfig: + case kAlpha_8_GrPixelConfig: + case kGray_8_GrPixelConfig: + case kRGB_565_GrPixelConfig: + case kRGBA_4444_GrPixelConfig: + case kRGBA_8888_GrPixelConfig: + case kBGRA_8888_GrPixelConfig: + case kSRGBA_8888_GrPixelConfig: + case kSBGRA_8888_GrPixelConfig: + case kRGBA_8888_sint_GrPixelConfig: + case kRGBA_float_GrPixelConfig: + case kRG_float_GrPixelConfig: + case kAlpha_half_GrPixelConfig: + case kRGBA_half_GrPixelConfig: + return false; + } + SkFAIL("Invalid pixel config"); + return false; +} + +/** If the pixel config is compressed, return an equivalent uncompressed format. */ +static inline GrPixelConfig GrMakePixelConfigUncompressed(GrPixelConfig config) { + switch (config) { + case kETC1_GrPixelConfig: + return kRGBA_8888_GrPixelConfig; + case kUnknown_GrPixelConfig: + case kAlpha_8_GrPixelConfig: + case kGray_8_GrPixelConfig: + case kRGB_565_GrPixelConfig: + case kRGBA_4444_GrPixelConfig: + case kRGBA_8888_GrPixelConfig: + case kBGRA_8888_GrPixelConfig: + case kSRGBA_8888_GrPixelConfig: + case kSBGRA_8888_GrPixelConfig: + case kRGBA_8888_sint_GrPixelConfig: + case kRGBA_float_GrPixelConfig: + case kRG_float_GrPixelConfig: + case kAlpha_half_GrPixelConfig: + case kRGBA_half_GrPixelConfig: + SkASSERT(!GrPixelConfigIsCompressed(config)); + return config; + } + SkFAIL("Invalid pixel config"); + return config; +} + // Returns true if the pixel config is 32 bits per pixel static inline bool GrPixelConfigIs8888Unorm(GrPixelConfig config) { switch (config) { @@ -348,6 +403,7 @@ static inline bool GrPixelConfigIs8888Unorm(GrPixelConfig config) { case kRGB_565_GrPixelConfig: case kRGBA_4444_GrPixelConfig: case kRGBA_8888_sint_GrPixelConfig: + case kETC1_GrPixelConfig: case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: case kAlpha_half_GrPixelConfig: @@ -373,6 +429,7 @@ static inline bool GrPixelConfigIsSRGB(GrPixelConfig config) { case kRGBA_8888_GrPixelConfig: case kBGRA_8888_GrPixelConfig: case kRGBA_8888_sint_GrPixelConfig: + case kETC1_GrPixelConfig: case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: case kAlpha_half_GrPixelConfig: @@ -401,6 +458,7 @@ static inline GrPixelConfig GrPixelConfigSwapRAndB(GrPixelConfig config) { case kRGB_565_GrPixelConfig: case kRGBA_4444_GrPixelConfig: case kRGBA_8888_sint_GrPixelConfig: + case kETC1_GrPixelConfig: case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: case kAlpha_half_GrPixelConfig: @@ -412,6 +470,7 @@ static inline GrPixelConfig GrPixelConfigSwapRAndB(GrPixelConfig config) { } static inline size_t GrBytesPerPixel(GrPixelConfig config) { + SkASSERT(!GrPixelConfigIsCompressed(config)); switch (config) { case kAlpha_8_GrPixelConfig: case kGray_8_GrPixelConfig: @@ -433,6 +492,7 @@ static inline size_t GrBytesPerPixel(GrPixelConfig config) { case kRG_float_GrPixelConfig: return 8; case kUnknown_GrPixelConfig: + case kETC1_GrPixelConfig: return 0; } SkFAIL("Invalid pixel config"); @@ -441,6 +501,7 @@ static inline size_t GrBytesPerPixel(GrPixelConfig config) { static inline bool GrPixelConfigIsOpaque(GrPixelConfig config) { switch (config) { + case kETC1_GrPixelConfig: case kRGB_565_GrPixelConfig: case kGray_8_GrPixelConfig: return true; @@ -476,6 +537,7 @@ static inline bool GrPixelConfigIsAlphaOnly(GrPixelConfig config) { case kSRGBA_8888_GrPixelConfig: case kSBGRA_8888_GrPixelConfig: case kRGBA_8888_sint_GrPixelConfig: + case kETC1_GrPixelConfig: case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: case kRGBA_half_GrPixelConfig: @@ -502,6 +564,7 @@ static inline bool GrPixelConfigIsFloatingPoint(GrPixelConfig config) { case kSRGBA_8888_GrPixelConfig: case kSBGRA_8888_GrPixelConfig: case kRGBA_8888_sint_GrPixelConfig: + case kETC1_GrPixelConfig: return false; } SkFAIL("Invalid pixel config"); @@ -717,6 +780,41 @@ 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 kETC1_GrPixelConfig: + SkASSERT((width & 3) == 0); + SkASSERT((height & 3) == 0); + return (width >> 2) * (height >> 2) * 8; + + case kUnknown_GrPixelConfig: + case kAlpha_8_GrPixelConfig: + case kGray_8_GrPixelConfig: + case kRGB_565_GrPixelConfig: + case kRGBA_4444_GrPixelConfig: + case kRGBA_8888_GrPixelConfig: + case kBGRA_8888_GrPixelConfig: + case kSRGBA_8888_GrPixelConfig: + case kSBGRA_8888_GrPixelConfig: + case kRGBA_8888_sint_GrPixelConfig: + case kRGBA_float_GrPixelConfig: + case kRG_float_GrPixelConfig: + case kAlpha_half_GrPixelConfig: + case kRGBA_half_GrPixelConfig: + SkFAIL("Unknown compressed pixel config"); + return 4 * width * height; + } + + SkFAIL("Invalid 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/GrCaps.cpp b/src/gpu/GrCaps.cpp index 5c04d19..4acf90e 100644 --- a/src/gpu/GrCaps.cpp +++ b/src/gpu/GrCaps.cpp @@ -21,6 +21,7 @@ static const char* pixel_config_name(GrPixelConfig config) { case kSRGBA_8888_GrPixelConfig: return "SRGBA8888"; case kSBGRA_8888_GrPixelConfig: return "SBGRA8888"; case kRGBA_8888_sint_GrPixelConfig: return "RGBA8888_sint"; + case kETC1_GrPixelConfig: return "ETC1"; case kRGBA_float_GrPixelConfig: return "RGBAFloat"; case kRG_float_GrPixelConfig: return "RGFloat"; case kAlpha_half_GrPixelConfig: return "AlphaHalf"; @@ -39,6 +40,7 @@ GrCaps::GrCaps(const GrContextOptions& options) { fReuseScratchTextures = true; fReuseScratchBuffers = true; fGpuTracingSupport = false; + fCompressedTexSubImageSupport = false; fOversizedStencilSupport = false; fTextureBarrierSupport = false; fSampleLocationsSupport = false; @@ -128,6 +130,7 @@ SkString GrCaps::dump() const { r.appendf("Reuse Scratch Textures : %s\n", gNY[fReuseScratchTextures]); r.appendf("Reuse Scratch Buffers : %s\n", gNY[fReuseScratchBuffers]); r.appendf("Gpu Tracing Support : %s\n", gNY[fGpuTracingSupport]); + r.appendf("Compressed Update Support : %s\n", gNY[fCompressedTexSubImageSupport]); r.appendf("Oversized Stencil Support : %s\n", gNY[fOversizedStencilSupport]); r.appendf("Texture Barrier Support : %s\n", gNY[fTextureBarrierSupport]); r.appendf("Sample Locations Support : %s\n", gNY[fSampleLocationsSupport]); diff --git a/src/gpu/GrDrawOpAtlas.cpp b/src/gpu/GrDrawOpAtlas.cpp index be8258a..0e8de3c 100644 --- a/src/gpu/GrDrawOpAtlas.cpp +++ b/src/gpu/GrDrawOpAtlas.cpp @@ -172,6 +172,9 @@ GrDrawOpAtlas::GrDrawOpAtlas(GrContext* context, sk_sp proxy, SkDEBUGCODE(fNumPlots = numPlotsX * numPlotsY;) + // We currently do not support compressed atlases... + SkASSERT(!GrPixelConfigIsCompressed(fProxy->config())); + // set up allocated plots fPlotArray.reset(new sk_sp[ numPlotsX * numPlotsY ]); diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp index 14ce050..c1970af 100644 --- a/src/gpu/GrGpu.cpp +++ b/src/gpu/GrGpu.cpp @@ -143,8 +143,24 @@ GrTexture* GrGpu::createTexture(const GrSurfaceDesc& origDesc, SkBudgeted budget desc.fOrigin = resolve_origin(desc.fOrigin, isRT); - this->handleDirtyContext(); - GrTexture* tex = this->onCreateTexture(desc, budgeted, texels); + GrTexture* tex = nullptr; + + if (GrPixelConfigIsCompressed(desc.fConfig)) { + // We shouldn't be rendering into this + SkASSERT(!isRT); + SkASSERT(0 == desc.fSampleCnt); + + if (!caps->npotTextureTileSupport() && + (!SkIsPow2(desc.fWidth) || !SkIsPow2(desc.fHeight))) { + return nullptr; + } + + this->handleDirtyContext(); + tex = this->onCreateCompressedTexture(desc, budgeted, texels); + } else { + this->handleDirtyContext(); + tex = this->onCreateTexture(desc, budgeted, texels); + } if (tex) { if (!caps->reuseScratchTextures() && !isRT) { tex->resourcePriv().removeScratchKey(); @@ -245,6 +261,9 @@ bool GrGpu::copySurface(GrSurface* dst, if (GrPixelConfigIsSint(dst->config()) != GrPixelConfigIsSint(src->config())) { return false; } + if (GrPixelConfigIsCompressed(dst->config())) { + return false; + } return this->onCopySurface(dst, src, srcRect, dstPoint); } @@ -256,6 +275,11 @@ bool GrGpu::getReadPixelsInfo(GrSurface* srcSurface, int width, int height, size SkASSERT(srcSurface); SkASSERT(kGpuPrefersDraw_DrawPreference != *drawPreference); + // We currently do not support reading into a compressed buffer + if (GrPixelConfigIsCompressed(readConfig)) { + return false; + } + // We currently do not support reading into the packed formats 565 or 4444 as they are not // required to have read back support on all devices and backends. if (kRGB_565_GrPixelConfig == readConfig || kRGBA_4444_GrPixelConfig == readConfig) { @@ -287,6 +311,10 @@ bool GrGpu::getWritePixelsInfo(GrSurface* dstSurface, int width, int height, SkASSERT(dstSurface); SkASSERT(kGpuPrefersDraw_DrawPreference != *drawPreference); + if (GrPixelConfigIsCompressed(dstSurface->config()) && dstSurface->config() != srcConfig) { + return false; + } + if (SkToBool(dstSurface->asRenderTarget())) { if (this->caps()->useDrawInsteadOfAllRenderTargetWrites()) { ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); @@ -325,6 +353,11 @@ bool GrGpu::readPixels(GrSurface* surface, return false; } + // We cannot read pixels into a compressed buffer + if (GrPixelConfigIsCompressed(config)) { + return false; + } + size_t bpp = GrBytesPerPixel(config); if (!GrSurfacePriv::AdjustReadPixelParams(surface->width(), surface->height(), bpp, &left, &top, &width, &height, diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h index 897f2b8..259c573 100644 --- a/src/gpu/GrGpu.h +++ b/src/gpu/GrGpu.h @@ -100,7 +100,8 @@ public: * @param budgeted does this texture count against the resource cache budget? * @param texels array of mipmap levels containing texel data to load. * Each level begins with full-size palette data for paletted textures. - * It contains width*height texels. If there is only one + * For compressed formats the level contains the compressed pixel data. + * Otherwise, it contains width*height texels. If there is only one * element and it contains nullptr fPixels, texture data is * uninitialized. * @return The texture object if successful, otherwise nullptr. @@ -545,10 +546,13 @@ private: // overridden by backend-specific derived class to create objects. // Texture size and sample size will have already been validated in base class before - // onCreateTexture is called. + // onCreateTexture/CompressedTexture are called. virtual GrTexture* onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted, const SkTArray& texels) = 0; + virtual GrTexture* onCreateCompressedTexture(const GrSurfaceDesc& desc, + SkBudgeted budgeted, + const SkTArray& texels) = 0; virtual sk_sp onWrapBackendTexture(const GrBackendTexture&, GrSurfaceOrigin, diff --git a/src/gpu/GrResourceProvider.cpp b/src/gpu/GrResourceProvider.cpp index 84e274e..2eb8271 100644 --- a/src/gpu/GrResourceProvider.cpp +++ b/src/gpu/GrResourceProvider.cpp @@ -157,15 +157,17 @@ sk_sp GrResourceProvider::createTextureProxy(const GrSurfaceDesc GrContext* context = fGpu->getContext(); - SkImageInfo srcInfo; - - if (make_info(desc.fWidth, desc.fHeight, desc.fConfig, &srcInfo)) { - sk_sp tex = this->getExactScratch(desc, budgeted, 0); - sk_sp sContext = - context->contextPriv().makeWrappedSurfaceContext(std::move(tex)); - if (sContext) { - if (sContext->writePixels(srcInfo, mipLevel.fPixels, mipLevel.fRowBytes, 0, 0)) { - return sContext->asTextureProxyRef(); + if (!GrPixelConfigIsCompressed(desc.fConfig)) { + SkImageInfo srcInfo; + + if (make_info(desc.fWidth, desc.fHeight, desc.fConfig, &srcInfo)) { + sk_sp tex = this->getExactScratch(desc, budgeted, 0); + sk_sp sContext = + context->contextPriv().makeWrappedSurfaceContext(std::move(tex)); + if (sContext) { + if (sContext->writePixels(srcInfo, mipLevel.fPixels, mipLevel.fRowBytes, 0, 0)) { + return sContext->asTextureProxyRef(); + } } } } @@ -190,12 +192,14 @@ sk_sp GrResourceProvider::createTexture(const GrSurfaceDesc& desc, Sk return nullptr; } - sk_sp tex = this->getExactScratch(desc, budgeted, flags); - if (tex) { - return tex; + if (!GrPixelConfigIsCompressed(desc.fConfig)) { + sk_sp tex = this->getExactScratch(desc, budgeted, flags); + if (tex) { + return tex; + } } - tex.reset(fGpu->createTexture(desc, budgeted)); + sk_sp tex(fGpu->createTexture(desc, budgeted)); return tex; } @@ -207,6 +211,11 @@ GrTexture* GrResourceProvider::createApproxTexture(const GrSurfaceDesc& desc, ui return nullptr; } + // Currently we don't recycle compressed textures as scratch. + if (GrPixelConfigIsCompressed(desc.fConfig)) { + return nullptr; + } + if (!validate_desc(desc, *fCaps)) { return nullptr; } @@ -217,6 +226,7 @@ GrTexture* GrResourceProvider::createApproxTexture(const GrSurfaceDesc& desc, ui GrTexture* GrResourceProvider::refScratchTexture(const GrSurfaceDesc& inDesc, uint32_t flags) { ASSERT_SINGLE_OWNER SkASSERT(!this->isAbandoned()); + SkASSERT(!GrPixelConfigIsCompressed(inDesc.fConfig)); SkASSERT(validate_desc(inDesc, *fCaps)); SkTCopyOnFirstWrite desc(inDesc); diff --git a/src/gpu/GrShaderCaps.cpp b/src/gpu/GrShaderCaps.cpp index 9a429bb..636b955 100644 --- a/src/gpu/GrShaderCaps.cpp +++ b/src/gpu/GrShaderCaps.cpp @@ -213,12 +213,13 @@ void GrShaderCaps::initSamplerPrecisionTable() { table[kSRGBA_8888_GrPixelConfig] = lowp; table[kSBGRA_8888_GrPixelConfig] = lowp; table[kRGBA_8888_sint_GrPixelConfig] = lowp; + table[kETC1_GrPixelConfig] = lowp; table[kRGBA_float_GrPixelConfig] = kHigh_GrSLPrecision; table[kRG_float_GrPixelConfig] = kHigh_GrSLPrecision; table[kAlpha_half_GrPixelConfig] = mediump; table[kRGBA_half_GrPixelConfig] = mediump; - GR_STATIC_ASSERT(14 == kGrPixelConfigCnt); + GR_STATIC_ASSERT(15 == kGrPixelConfigCnt); } } diff --git a/src/gpu/GrSurface.cpp b/src/gpu/GrSurface.cpp index 635e74e..2658428 100644 --- a/src/gpu/GrSurface.cpp +++ b/src/gpu/GrSurface.cpp @@ -29,6 +29,7 @@ size_t GrSurface::WorstCaseSize(const GrSurfaceDesc& desc, bool useNextPow2) { colorValuesPerPixel += 1; } SkASSERT(kUnknown_GrPixelConfig != desc.fConfig); + SkASSERT(!GrPixelConfigIsCompressed(desc.fConfig)); size_t colorBytes = (size_t) width * height * GrBytesPerPixel(desc.fConfig); // This would be a nice assert to have (i.e., we aren't creating 0 width/height surfaces). @@ -38,7 +39,11 @@ size_t GrSurface::WorstCaseSize(const GrSurfaceDesc& desc, bool useNextPow2) { size = colorValuesPerPixel * colorBytes; size += colorBytes/3; // in case we have to mipmap } else { - size = (size_t) width * height * GrBytesPerPixel(desc.fConfig); + if (GrPixelConfigIsCompressed(desc.fConfig)) { + size = GrCompressedFormatDataSize(desc.fConfig, width, height); + } else { + size = (size_t) width * height * GrBytesPerPixel(desc.fConfig); + } size += size/3; // in case we have to mipmap } @@ -52,11 +57,17 @@ size_t GrSurface::ComputeSize(GrPixelConfig config, int colorSamplesPerPixel, bool hasMIPMaps, bool useNextPow2) { + size_t colorSize; + width = useNextPow2 ? GrNextPow2(width) : width; height = useNextPow2 ? GrNextPow2(height) : height; SkASSERT(kUnknown_GrPixelConfig != config); - size_t colorSize = (size_t)width * height * GrBytesPerPixel(config); + if (GrPixelConfigIsCompressed(config)) { + colorSize = GrCompressedFormatDataSize(config, width, height); + } else { + colorSize = (size_t)width * height * GrBytesPerPixel(config); + } SkASSERT(colorSize > 0); size_t finalSize = colorSamplesPerPixel * colorSize; diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp index 3ced81b..b22d0aa 100644 --- a/src/gpu/GrSurfaceProxy.cpp +++ b/src/gpu/GrSurfaceProxy.cpp @@ -136,6 +136,18 @@ sk_sp GrSurfaceProxy::MakeDeferred(GrResourceProvider* resourceP // TODO: move this logic into GrResourceProvider! // TODO: share this testing code with check_texture_creation_params + if (GrPixelConfigIsCompressed(desc.fConfig)) { + if (SkBackingFit::kApprox == fit || kBottomLeft_GrSurfaceOrigin == desc.fOrigin) { + // We don't allow scratch compressed textures and, apparently can't Y-flip compressed + // textures + return nullptr; + } + + if (!caps->npotTextureTileSupport() && (!SkIsPow2(desc.fWidth) || !SkIsPow2(desc.fHeight))) { + return nullptr; + } + } + if (!caps->isConfigTexturable(desc.fConfig)) { return nullptr; } diff --git a/src/gpu/GrTexture.cpp b/src/gpu/GrTexture.cpp index 064cc0a..cbab5f0 100644 --- a/src/gpu/GrTexture.cpp +++ b/src/gpu/GrTexture.cpp @@ -76,14 +76,16 @@ GrTexture::GrTexture(GrGpu* gpu, const GrSurfaceDesc& desc, GrSLType samplerType } void GrTexture::computeScratchKey(GrScratchKey* key) const { - const GrRenderTarget* rt = this->asRenderTarget(); - int sampleCount = 0; - if (rt) { - sampleCount = rt->numStencilSamples(); + if (!GrPixelConfigIsCompressed(this->config())) { + const GrRenderTarget* rt = this->asRenderTarget(); + int sampleCount = 0; + if (rt) { + sampleCount = rt->numStencilSamples(); + } + GrTexturePriv::ComputeScratchKey(this->config(), this->width(), this->height(), + this->origin(), SkToBool(rt), sampleCount, + this->texturePriv().hasMipMaps(), key); } - GrTexturePriv::ComputeScratchKey(this->config(), this->width(), this->height(), - this->origin(), SkToBool(rt), sampleCount, - this->texturePriv().hasMipMaps(), key); } void GrTexturePriv::ComputeScratchKey(GrPixelConfig config, int width, int height, diff --git a/src/gpu/GrTextureProducer.cpp b/src/gpu/GrTextureProducer.cpp index b9e7dfc..75796b1 100644 --- a/src/gpu/GrTextureProducer.cpp +++ b/src/gpu/GrTextureProducer.cpp @@ -23,10 +23,12 @@ sk_sp GrTextureProducer::CopyOnGpu(GrContext* context, SkASSERT(!subset || !subset->isEmpty()); SkASSERT(context); + GrPixelConfig config = GrMakePixelConfigUncompressed(inputProxy->config()); + const SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight); sk_sp copyRTC = context->makeDeferredRenderTargetContextWithFallback( - SkBackingFit::kExact, dstRect.width(), dstRect.height(), inputProxy->config(), nullptr); + SkBackingFit::kExact, dstRect.width(), dstRect.height(), config, nullptr); if (!copyRTC) { return nullptr; } diff --git a/src/gpu/SkGr.h b/src/gpu/SkGr.h index 0145513..7784c53 100644 --- a/src/gpu/SkGr.h +++ b/src/gpu/SkGr.h @@ -209,7 +209,7 @@ sk_sp GrRefCachedBitmapTextureProxy(GrContext*, /** * Creates a new texture for the bitmap. Does not concern itself with cache keys or texture params. * The bitmap must have CPU-accessible pixels. Attempts to take advantage of faster paths for - * yuv planes. + * compressed textures and yuv planes. */ sk_sp GrUploadBitmapToTextureProxy(GrResourceProvider*, const SkBitmap&, SkColorSpace* dstColorSpace); diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp index b9eee08..f4e5062 100644 --- a/src/gpu/gl/GrGLCaps.cpp +++ b/src/gpu/gl/GrGLCaps.cpp @@ -1344,6 +1344,15 @@ bool GrGLCaps::getTexImageFormats(GrPixelConfig surfaceConfig, GrPixelConfig ext return true; } +bool GrGLCaps::getCompressedTexImageFormats(GrPixelConfig surfaceConfig, + GrGLenum* internalFormat) const { + if (!GrPixelConfigIsCompressed(surfaceConfig)) { + return false; + } + *internalFormat = fConfigTable[surfaceConfig].fFormats.fInternalFormatTexImage; + return true; +} + bool GrGLCaps::getReadPixelsFormat(GrPixelConfig surfaceConfig, GrPixelConfig externalConfig, GrGLenum* externalFormat, GrGLenum* externalType) const { if (!this->getExternalFormat(surfaceConfig, externalConfig, kOther_ExternalFormatUsage, @@ -1354,6 +1363,9 @@ bool GrGLCaps::getReadPixelsFormat(GrPixelConfig surfaceConfig, GrPixelConfig ex } bool GrGLCaps::getRenderbufferFormat(GrPixelConfig config, GrGLenum* internalFormat) const { + if (GrPixelConfigIsCompressed(config)) { + return false; + } *internalFormat = fConfigTable[config].fFormats.fInternalFormatRenderbuffer; return true; } @@ -1362,6 +1374,9 @@ bool GrGLCaps::getExternalFormat(GrPixelConfig surfaceConfig, GrPixelConfig memo ExternalFormatUsage usage, GrGLenum* externalFormat, GrGLenum* externalType) const { SkASSERT(externalFormat && externalType); + if (GrPixelConfigIsCompressed(memoryConfig)) { + return false; + } bool surfaceIsAlphaOnly = GrPixelConfigIsAlphaOnly(surfaceConfig); bool memoryIsAlphaOnly = GrPixelConfigIsAlphaOnly(memoryConfig); @@ -1911,6 +1926,39 @@ void GrGLCaps::initConfigTable(const GrContextOptions& contextOptions, } fConfigTable[kRGBA_half_GrPixelConfig].fSwizzle = GrSwizzle::RGBA(); + // Compressed texture support + + // glCompressedTexImage2D is available on all OpenGL ES devices. It is available on standard + // OpenGL after version 1.3. We'll assume at least that level of OpenGL support. + + // TODO: Fix command buffer bindings and remove this. + fCompressedTexSubImageSupport = SkToBool(gli->fFunctions.fCompressedTexSubImage2D); + + // No sized/unsized internal format distinction for compressed formats, no external format. + // Below we set the external formats and types to 0. + { + fConfigTable[kETC1_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_COMPRESSED_ETC1_RGB8; + fConfigTable[kETC1_GrPixelConfig].fFormats.fSizedInternalFormat = + GR_GL_COMPRESSED_ETC1_RGB8; + fConfigTable[kETC1_GrPixelConfig].fFormats.fExternalFormat[kOther_ExternalFormatUsage] = 0; + fConfigTable[kETC1_GrPixelConfig].fFormats.fExternalType = 0; + fConfigTable[kETC1_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType; + if (kGL_GrGLStandard == standard) { + if (version >= GR_GL_VER(4, 3) || ctxInfo.hasExtension("GL_ARB_ES3_compatibility")) { + fConfigTable[kETC1_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag; + } + } else { + if (version >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_OES_compressed_ETC1_RGB8_texture") || + // ETC2 is a superset of ETC1, so we can just check for that, too. + (ctxInfo.hasExtension("GL_OES_compressed_ETC2_RGB8_texture") && + ctxInfo.hasExtension("GL_OES_compressed_ETC2_RGBA8_texture"))) { + fConfigTable[kETC1_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag; + } + } + fConfigTable[kETC1_GrPixelConfig].fSwizzle = GrSwizzle::RGBA(); + } + // Bulk populate the texture internal/external formats here and then deal with exceptions below. // ES 2.0 requires that the internal/external formats match. diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h index 3059ea7..de3d847 100644 --- a/src/gpu/gl/GrGLCaps.h +++ b/src/gpu/gl/GrGLCaps.h @@ -153,6 +153,8 @@ public: GrGLenum* internalFormat, GrGLenum* externalFormat, GrGLenum* externalType) const; + bool getCompressedTexImageFormats(GrPixelConfig surfaceConfig, GrGLenum* internalFormat) const; + bool getReadPixelsFormat(GrPixelConfig surfaceConfig, GrPixelConfig externalConfig, GrGLenum* externalFormat, GrGLenum* externalType) const; diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp index 565e60a..b324280 100644 --- a/src/gpu/gl/GrGLGpu.cpp +++ b/src/gpu/gl/GrGLGpu.cpp @@ -662,6 +662,10 @@ bool GrGLGpu::onGetWritePixelsInfo(GrSurface* dstSurface, int width, int height, GrPixelConfig srcConfig, DrawPreference* drawPreference, WritePixelTempDrawInfo* tempDrawInfo) { + if (GrPixelConfigIsCompressed(dstSurface->config())) { + return false; + } + // This subclass only allows writes to textures. If the dst is not a texture we have to draw // into it. We could use glDrawPixels on GLs that have it, but we don't today. if (!dstSurface->asTexture()) { @@ -761,9 +765,20 @@ bool GrGLGpu::onWritePixels(GrSurface* surface, this->setScratchTextureUnit(); GL_CALL(BindTexture(glTex->target(), glTex->textureID())); - return this->uploadTexData(glTex->config(), glTex->width(), glTex->height(), - glTex->origin(), glTex->target(), kWrite_UploadType, - left, top, width, height, config, texels); + bool success = false; + if (GrPixelConfigIsCompressed(glTex->config())) { + // We check that config == desc.fConfig in GrGLGpu::canWriteTexturePixels() + SkASSERT(config == glTex->config()); + success = this->uploadCompressedTexData(glTex->config(), glTex->width(), glTex->height(), + glTex->origin(), glTex->target(), texels, + kWrite_UploadType, left, top, width, height); + } else { + success = this->uploadTexData(glTex->config(), glTex->width(), glTex->height(), + glTex->origin(), glTex->target(), kWrite_UploadType, + left, top, width, height, config, texels); + } + + return success; } bool GrGLGpu::onTransferPixels(GrSurface* surface, @@ -776,6 +791,11 @@ bool GrGLGpu::onTransferPixels(GrSurface* surface, return false; } + // For the moment, can't transfer compressed data + if (GrPixelConfigIsCompressed(glTex->config())) { + return false; + } + this->setScratchTextureUnit(); GL_CALL(BindTexture(glTex->target(), glTex->textureID())); @@ -798,6 +818,7 @@ bool GrGLGpu::onTransferPixels(GrSurface* surface, // For GL_[UN]PACK_ALIGNMENT. static inline GrGLint config_alignment(GrPixelConfig config) { + SkASSERT(!GrPixelConfigIsCompressed(config)); switch (config) { case kAlpha_8_GrPixelConfig: case kGray_8_GrPixelConfig: @@ -816,6 +837,7 @@ static inline GrGLint config_alignment(GrPixelConfig config) { case kRG_float_GrPixelConfig: return 4; case kUnknown_GrPixelConfig: + case kETC1_GrPixelConfig: return 0; } SkFAIL("Invalid pixel config"); @@ -836,16 +858,16 @@ static inline GrGLint config_alignment(GrPixelConfig config) { * @param baseWidth The width of the texture's base mipmap level * @param baseHeight The height of the texture's base mipmap level */ -static bool allocate_and_populate_texture(GrPixelConfig config, - const GrGLInterface& interface, - const GrGLCaps& caps, - GrGLenum target, - GrGLenum internalFormat, - GrGLenum internalFormatForTexStorage, - GrGLenum externalFormat, - GrGLenum externalType, - const SkTArray& texels, - int baseWidth, int baseHeight) { +static bool allocate_and_populate_uncompressed_texture(GrPixelConfig config, + const GrGLInterface& interface, + const GrGLCaps& caps, + GrGLenum target, + GrGLenum internalFormat, + GrGLenum internalFormatForTexStorage, + GrGLenum externalFormat, + GrGLenum externalType, + const SkTArray& texels, + int baseWidth, int baseHeight) { CLEAR_ERROR_BEFORE_ALLOC(&interface); bool useTexStorage = caps.isConfigTexSupportEnabled(config); @@ -929,6 +951,97 @@ static bool allocate_and_populate_texture(GrPixelConfig config, } /** + * Creates storage space for the texture and fills it with texels. + * + * @param config Compressed pixel config of the texture. + * @param desc The surface descriptor for the texture being created. + * @param interface The GL interface in use. + * @param caps The capabilities of the GL device. + * @param internalFormat The data format used for the internal storage of the texture. + * @param texels The texel data of the texture being created. + */ +static bool allocate_and_populate_compressed_texture(GrPixelConfig config, + const GrGLInterface& interface, + const GrGLCaps& caps, + GrGLenum target, GrGLenum internalFormat, + const SkTArray& texels, + int baseWidth, int baseHeight) { + CLEAR_ERROR_BEFORE_ALLOC(&interface); + + bool useTexStorage = caps.isConfigTexSupportEnabled(config); + // We can only use TexStorage if we know we will not later change the storage requirements. + // This means if we may later want to add mipmaps, we cannot use TexStorage. + // Right now, we cannot know if we will later add mipmaps or not. + // The only time we can use TexStorage is when we already have the + // mipmaps. + useTexStorage &= texels.count() > 1; + + if (useTexStorage) { + // We never resize or change formats of textures. + GL_ALLOC_CALL(&interface, + TexStorage2D(target, + texels.count(), + internalFormat, + baseWidth, baseHeight)); + GrGLenum error = CHECK_ALLOC_ERROR(&interface); + if (error != GR_GL_NO_ERROR) { + return false; + } else { + for (int currentMipLevel = 0; currentMipLevel < texels.count(); currentMipLevel++) { + const void* currentMipData = texels[currentMipLevel].fPixels; + if (currentMipData == nullptr) { + continue; + } + + int twoToTheMipLevel = 1 << currentMipLevel; + int currentWidth = SkTMax(1, baseWidth / twoToTheMipLevel); + int currentHeight = SkTMax(1, baseHeight / twoToTheMipLevel); + + // Make sure that the width and height that we pass to OpenGL + // is a multiple of the block size. + size_t dataSize = GrCompressedFormatDataSize(config, currentWidth, currentHeight); + GR_GL_CALL(&interface, CompressedTexSubImage2D(target, + currentMipLevel, + 0, // left + 0, // top + currentWidth, + currentHeight, + internalFormat, + SkToInt(dataSize), + currentMipData)); + } + } + } else { + for (int currentMipLevel = 0; currentMipLevel < texels.count(); currentMipLevel++) { + int twoToTheMipLevel = 1 << currentMipLevel; + int currentWidth = SkTMax(1, baseWidth / twoToTheMipLevel); + int currentHeight = SkTMax(1, baseHeight / twoToTheMipLevel); + + // Make sure that the width and height that we pass to OpenGL + // is a multiple of the block size. + size_t dataSize = GrCompressedFormatDataSize(config, baseWidth, baseHeight); + + GL_ALLOC_CALL(&interface, + CompressedTexImage2D(target, + currentMipLevel, + internalFormat, + currentWidth, + currentHeight, + 0, // border + SkToInt(dataSize), + texels[currentMipLevel].fPixels)); + + GrGLenum error = CHECK_ALLOC_ERROR(&interface); + if (error != GR_GL_NO_ERROR) { + return false; + } + } + } + + return true; +} + +/** * After a texture is created, any state which was altered during its creation * needs to be restored. * @@ -952,6 +1065,9 @@ bool GrGLGpu::uploadTexData(GrPixelConfig texConfig, int texWidth, int texHeight GrSurfaceOrigin texOrigin, GrGLenum target, UploadType uploadType, int left, int top, int width, int height, GrPixelConfig dataConfig, const SkTArray& texels) { + // If we're uploading compressed data then we should be using uploadCompressedTexData + SkASSERT(!GrPixelConfigIsCompressed(dataConfig)); + SkASSERT(this->caps()->isConfigTexturable(texConfig)); // texels is const. @@ -1106,7 +1222,7 @@ bool GrGLGpu::uploadTexData(GrPixelConfig texConfig, int texWidth, int texHeight bool succeeded = true; if (kNewTexture_UploadType == uploadType) { if (0 == left && 0 == top && texWidth == width && texHeight == height) { - succeeded = allocate_and_populate_texture( + succeeded = allocate_and_populate_uncompressed_texture( texConfig, *interface, caps, target, internalFormat, internalFormatForTexStorage, externalFormat, externalType, texelsShallowCopy, width, height); @@ -1138,6 +1254,75 @@ bool GrGLGpu::uploadTexData(GrPixelConfig texConfig, int texWidth, int texHeight return succeeded; } +// TODO: This function is using a lot of wonky semantics like, if width == -1 +// then set width = desc.fWdith ... blah. A better way to do it might be to +// create a CompressedTexData struct that takes a desc/ptr and figures out +// the proper upload semantics. Then users can construct this function how they +// see fit if they want to go against the "standard" way to do it. +bool GrGLGpu::uploadCompressedTexData(GrPixelConfig config, int texWidth, int texHeight, + GrSurfaceOrigin texOrigin, GrGLenum target, + const SkTArray& texels, UploadType uploadType, + int left, int top, int width, int height) { + SkASSERT(this->caps()->isConfigTexturable(config)); + + // No support for software flip y, yet... + SkASSERT(kBottomLeft_GrSurfaceOrigin != texOrigin); + + const GrGLInterface* interface = this->glInterface(); + const GrGLCaps& caps = this->glCaps(); + + if (-1 == width) { + width = texWidth; + } +#ifdef SK_DEBUG + else { + SkASSERT(width <= texWidth); + } +#endif + + if (-1 == height) { + height = texHeight; + } +#ifdef SK_DEBUG + else { + SkASSERT(height <= texHeight); + } +#endif + + // We only need the internal format for compressed 2D textures. + GrGLenum internalFormat; + if (!caps.getCompressedTexImageFormats(config, &internalFormat)) { + return false; + } + + if (kNewTexture_UploadType == uploadType) { + return allocate_and_populate_compressed_texture(config, *interface, caps, target, + internalFormat, texels, width, height); + } else { + for (int currentMipLevel = 0; currentMipLevel < texels.count(); currentMipLevel++) { + SkASSERT(texels[currentMipLevel].fPixels || kTransfer_UploadType == uploadType); + + int twoToTheMipLevel = 1 << currentMipLevel; + int currentWidth = SkTMax(1, width / twoToTheMipLevel); + int currentHeight = SkTMax(1, height / twoToTheMipLevel); + + // Make sure that the width and height that we pass to OpenGL + // is a multiple of the block size. + size_t dataSize = GrCompressedFormatDataSize(config, currentWidth, currentHeight); + GL_CALL(CompressedTexSubImage2D(target, + currentMipLevel, + left, top, + currentWidth, + currentHeight, + internalFormat, + SkToInt(dataSize), + texels[currentMipLevel].fPixels)); + } + } + + return true; +} + static bool renderbuffer_storage_msaa(const GrGLContext& ctx, int sampleCount, GrGLenum format, @@ -1292,6 +1477,18 @@ static size_t as_size_t(int x) { } #endif +static GrGLTexture::IDDesc generate_gl_texture(const GrGLInterface* interface) { + GrGLTexture::IDDesc idDesc; + idDesc.fInfo.fID = 0; + GR_GL_CALL(interface, GenTextures(1, &idDesc.fInfo.fID)); + idDesc.fOwnership = GrBackendObjectOwnership::kOwned; + // When we create the texture, we only + // create GL_TEXTURE_2D at the moment. + // External clients can do something different. + idDesc.fInfo.fTarget = GR_GL_TEXTURE_2D; + return idDesc; +} + static void set_initial_texture_params(const GrGLInterface* interface, const GrGLTextureInfo& info, GrGLTexture::TexParams* initialTexParams) { @@ -1364,6 +1561,41 @@ GrTexture* GrGLGpu::onCreateTexture(const GrSurfaceDesc& desc, return tex; } +GrTexture* GrGLGpu::onCreateCompressedTexture(const GrSurfaceDesc& desc, + SkBudgeted budgeted, + const SkTArray& texels) { + // Make sure that we're not flipping Y. + if (kBottomLeft_GrSurfaceOrigin == desc.fOrigin) { + return return_null_texture(); + } + + GrGLTexture::IDDesc idDesc = generate_gl_texture(this->glInterface()); + if (!idDesc.fInfo.fID) { + return return_null_texture(); + } + + this->setScratchTextureUnit(); + GL_CALL(BindTexture(idDesc.fInfo.fTarget, idDesc.fInfo.fID)); + + GrGLTexture::TexParams initialTexParams; + set_initial_texture_params(this->glInterface(), idDesc.fInfo, &initialTexParams); + + if (!this->uploadCompressedTexData(desc.fConfig, desc.fWidth, desc.fHeight, desc.fOrigin, + idDesc.fInfo.fTarget, texels)) { + GL_CALL(DeleteTextures(1, &idDesc.fInfo.fID)); + return return_null_texture(); + } + + GrGLTexture* tex; + tex = new GrGLTexture(this, budgeted, desc, idDesc); + tex->setCachedTexParams(initialTexParams, this->getResetTimestamp()); +#ifdef TRACE_TEXTURE_CREATION + SkDebugf("--- new compressed texture [%d] size=(%d %d) config=%d\n", + idDesc.fInfo.fID, desc.fWidth, desc.fHeight, desc.fConfig); +#endif + return tex; +} + namespace { const GrGLuint kUnknownBitCount = GrGLStencilAttachment::kUnknownBitCount; @@ -2795,7 +3027,7 @@ void GrGLGpu::bindTexture(int unitIdx, const GrSamplerParams& params, bool allow GrSamplerParams::FilterMode filterMode = params.filterMode(); if (GrSamplerParams::kMipMap_FilterMode == filterMode) { - if (!this->caps()->mipMapSupport()) { + if (!this->caps()->mipMapSupport() || GrPixelConfigIsCompressed(texture->config())) { filterMode = GrSamplerParams::kBilerp_FilterMode; } } @@ -2941,7 +3173,7 @@ void GrGLGpu::generateMipmaps(const GrSamplerParams& params, bool allowSRGBInput GrSamplerParams::FilterMode filterMode = params.filterMode(); if (GrSamplerParams::kMipMap_FilterMode == filterMode) { - if (!this->caps()->mipMapSupport()) { + if (!this->caps()->mipMapSupport() || GrPixelConfigIsCompressed(texture->config())) { filterMode = GrSamplerParams::kBilerp_FilterMode; } } @@ -3163,6 +3395,7 @@ static inline bool can_copy_texsubimage(const GrSurface* dst, // Check that we could wrap the source in an FBO, that the dst is TEXTURE_2D, that no mirroring // is required. if (gpu->glCaps().canConfigBeFBOColorAttachment(src->config()) && + !GrPixelConfigIsCompressed(src->config()) && (!srcTex || srcTex->target() == GR_GL_TEXTURE_2D) && dstTex->target() == GR_GL_TEXTURE_2D && dst->origin() == src->origin()) { return true; diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h index a4706c7..cf3d38f 100644 --- a/src/gpu/gl/GrGLGpu.h +++ b/src/gpu/gl/GrGLGpu.h @@ -163,6 +163,9 @@ private: GrTexture* onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted, const SkTArray& texels) override; + GrTexture* onCreateCompressedTexture(const GrSurfaceDesc& desc, + SkBudgeted budgeted, + const SkTArray& texels) override; GrBuffer* onCreateBuffer(size_t size, GrBufferType intendedType, GrAccessPattern, const void* data) override; @@ -363,6 +366,18 @@ private: int top, int width, int height, GrPixelConfig dataConfig, const SkTArray& texels); + // helper for onCreateCompressedTexture. If width and height are + // set to -1, then this function will use desc.fWidth and desc.fHeight + // for the size of the data. The isNewTexture flag should be set to true + // whenever a new texture needs to be created. Otherwise, we assume that + // the texture is already in GPU memory and that it's going to be updated + // with new data. + bool uploadCompressedTexData(GrPixelConfig texAndDataConfig, int texWidth, int texHeight, + GrSurfaceOrigin texOrigin, GrGLenum target, + const SkTArray& texels, + UploadType uploadType = kNewTexture_UploadType, int left = 0, + int top = 0, int width = -1, int height = -1); + bool createRenderTargetObjects(const GrSurfaceDesc&, const GrGLTextureInfo& texInfo, GrGLRenderTarget::IDDesc*); diff --git a/src/gpu/ops/GrCopySurfaceOp.cpp b/src/gpu/ops/GrCopySurfaceOp.cpp index 9cbde0e..5feed18 100644 --- a/src/gpu/ops/GrCopySurfaceOp.cpp +++ b/src/gpu/ops/GrCopySurfaceOp.cpp @@ -67,6 +67,9 @@ std::unique_ptr GrCopySurfaceOp::Make(GrResourceProvider* resourceProvider if (GrPixelConfigIsSint(dstProxy->config()) != GrPixelConfigIsSint(srcProxy->config())) { return nullptr; } + if (GrPixelConfigIsCompressed(dstProxy->config())) { + return nullptr; + } SkIRect clippedSrcRect; SkIPoint clippedDstPoint; // If the rect is outside the srcProxy or dstProxy then we've already succeeded. diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp index ef0004b..0e5ccf0 100644 --- a/src/gpu/vk/GrVkCaps.cpp +++ b/src/gpu/vk/GrVkCaps.cpp @@ -31,6 +31,7 @@ GrVkCaps::GrVkCaps(const GrContextOptions& contextOptions, const GrVkInterface* fDiscardRenderTargetSupport = true; fReuseScratchTextures = true; //TODO: figure this out fGpuTracingSupport = false; //TODO: figure this out + fCompressedTexSubImageSupport = false; //TODO: figure this out fOversizedStencilSupport = false; //TODO: figure this out fUseDrawInsteadOfClear = false; @@ -306,6 +307,11 @@ void GrVkCaps::initConfigTable(const GrVkInterface* interface, VkPhysicalDevice fConfigTable[i].init(interface, physDev, format); } } + + // We currently do not support compressed textures in Vulkan + const uint16_t kFlagsToRemove = ConfigInfo::kTextureable_Flag|ConfigInfo::kRenderable_Flag; + fConfigTable[kETC1_GrPixelConfig].fOptimalFlags &= ~kFlagsToRemove; + fConfigTable[kETC1_GrPixelConfig].fLinearFlags &= ~kFlagsToRemove; } void GrVkCaps::ConfigInfo::InitConfigFlags(VkFormatFeatureFlags vkFlags, uint16_t* flags) { diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp index 7fbca9f..6969e06 100644 --- a/src/gpu/vk/GrVkGpu.cpp +++ b/src/gpu/vk/GrVkGpu.cpp @@ -315,6 +315,10 @@ GrBuffer* GrVkGpu::onCreateBuffer(size_t size, GrBufferType type, GrAccessPatter bool GrVkGpu::onGetWritePixelsInfo(GrSurface* dstSurface, int width, int height, GrPixelConfig srcConfig, DrawPreference* drawPreference, WritePixelTempDrawInfo* tempDrawInfo) { + if (GrPixelConfigIsCompressed(dstSurface->config())) { + return false; + } + GrRenderTarget* renderTarget = dstSurface->asRenderTarget(); // Start off assuming no swizzling @@ -375,32 +379,43 @@ bool GrVkGpu::onWritePixels(GrSurface* surface, } bool success = false; - bool linearTiling = vkTex->isLinearTiled(); - if (linearTiling) { - if (texels.count() > 1) { - SkDebugf("Can't upload mipmap data to linear tiled texture"); - return false; - } - if (VK_IMAGE_LAYOUT_PREINITIALIZED != vkTex->currentLayout()) { - // Need to change the layout to general in order to perform a host write - vkTex->setImageLayout(this, - VK_IMAGE_LAYOUT_GENERAL, - VK_ACCESS_HOST_WRITE_BIT, - VK_PIPELINE_STAGE_HOST_BIT, - false); - this->submitCommandBuffer(kForce_SyncQueue); - } - success = this->uploadTexDataLinear(vkTex, left, top, width, height, config, - texels.begin()->fPixels, texels.begin()->fRowBytes); + if (GrPixelConfigIsCompressed(vkTex->config())) { + // We check that config == desc.fConfig in GrGpu::getWritePixelsInfo() + SkASSERT(config == vkTex->config()); + // TODO: add compressed texture support + // delete the following two lines and uncomment the two after that when ready + vkTex->unref(); + return false; + //success = this->uploadCompressedTexData(vkTex->desc(), buffer, false, left, top, width, + // height); } else { - int newMipLevels = texels.count(); - int currentMipLevels = vkTex->texturePriv().maxMipMapLevel() + 1; - if (newMipLevels > currentMipLevels) { - if (!vkTex->reallocForMipmap(this, newMipLevels)) { + bool linearTiling = vkTex->isLinearTiled(); + if (linearTiling) { + if (texels.count() > 1) { + SkDebugf("Can't upload mipmap data to linear tiled texture"); return false; } + if (VK_IMAGE_LAYOUT_PREINITIALIZED != vkTex->currentLayout()) { + // Need to change the layout to general in order to perform a host write + vkTex->setImageLayout(this, + VK_IMAGE_LAYOUT_GENERAL, + VK_ACCESS_HOST_WRITE_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + false); + this->submitCommandBuffer(kForce_SyncQueue); + } + success = this->uploadTexDataLinear(vkTex, left, top, width, height, config, + texels.begin()->fPixels, texels.begin()->fRowBytes); + } else { + int newMipLevels = texels.count(); + int currentMipLevels = vkTex->texturePriv().maxMipMapLevel() + 1; + if (newMipLevels > currentMipLevels) { + if (!vkTex->reallocForMipmap(this, newMipLevels)) { + return false; + } + } + success = this->uploadTexDataOptimal(vkTex, left, top, width, height, config, texels); } - success = this->uploadTexDataOptimal(vkTex, left, top, width, height, config, texels); } return success; @@ -483,6 +498,9 @@ bool GrVkGpu::uploadTexDataLinear(GrVkTexture* tex, SkASSERT(data); SkASSERT(tex->isLinearTiled()); + // If we're uploading compressed data then we should be using uploadCompressedTexData + SkASSERT(!GrPixelConfigIsCompressed(dataConfig)); + size_t bpp = GrBytesPerPixel(dataConfig); if (!GrSurfacePriv::AdjustWritePixelParams(tex->width(), tex->height(), bpp, &left, &top, @@ -551,6 +569,9 @@ bool GrVkGpu::uploadTexDataOptimal(GrVkTexture* tex, // first. SkASSERT(1 == texels.count() || texels.count() == (tex->texturePriv().maxMipMapLevel() + 1)); + // If we're uploading compressed data then we should be using uploadCompressedTexData + SkASSERT(!GrPixelConfigIsCompressed(dataConfig)); + if (width == 0 || height == 0) { return false; } diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h index 913662d..9442d3f 100644 --- a/src/gpu/vk/GrVkGpu.h +++ b/src/gpu/vk/GrVkGpu.h @@ -176,6 +176,9 @@ private: GrTexture* onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted, const SkTArray&) override; + GrTexture* onCreateCompressedTexture(const GrSurfaceDesc& desc, SkBudgeted, + const SkTArray&) override { return NULL; } + sk_sp onWrapBackendTexture(const GrBackendTexture&, GrSurfaceOrigin, GrBackendTextureFlags, diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp index c56a4fb..ec71fb0 100644 --- a/src/gpu/vk/GrVkUtil.cpp +++ b/src/gpu/vk/GrVkUtil.cpp @@ -48,6 +48,10 @@ bool GrPixelConfigToVkFormat(GrPixelConfig config, VkFormat* format) { case kGray_8_GrPixelConfig: *format = VK_FORMAT_R8_UNORM; return true; + case kETC1_GrPixelConfig: + // converting to ETC2 which is a superset of ETC1 + *format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; + return true; case kRGBA_float_GrPixelConfig: *format = VK_FORMAT_R32G32B32A32_SFLOAT; return true; @@ -86,6 +90,8 @@ GrPixelConfig GrVkFormatToPixelConfig(VkFormat format) { return kRGBA_4444_GrPixelConfig; case VK_FORMAT_R8_UNORM: return kAlpha_8_GrPixelConfig; + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + return kETC1_GrPixelConfig; // this conversion seems a bit sketchy case VK_FORMAT_R32G32B32A32_SFLOAT: return kRGBA_float_GrPixelConfig; case VK_FORMAT_R32G32_SFLOAT: diff --git a/tests/ProxyTest.cpp b/tests/ProxyTest.cpp index 1799a75..0bd8537 100644 --- a/tests/ProxyTest.cpp +++ b/tests/ProxyTest.cpp @@ -118,7 +118,7 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredProxyTest, reporter, ctxInfo) { for (auto origin : { kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin }) { for (auto widthHeight : { 100, 128, 1048576 }) { for (auto config : { kAlpha_8_GrPixelConfig, kRGB_565_GrPixelConfig, - kRGBA_8888_GrPixelConfig }) { + kETC1_GrPixelConfig, kRGBA_8888_GrPixelConfig }) { for (auto fit : { SkBackingFit::kExact, SkBackingFit::kApprox }) { for (auto budgeted : { SkBudgeted::kYes, SkBudgeted::kNo }) { for (auto numSamples : { 0, 4, 16, 128 }) { diff --git a/third_party/etc1/LICENSE b/third_party/etc1/LICENSE new file mode 100644 index 0000000..64635a4 --- /dev/null +++ b/third_party/etc1/LICENSE @@ -0,0 +1,161 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the +copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other +entities that control, are controlled by, or are under common control with +that entity. For the purposes of this definition, "control" means (i) the +power, direct or indirect, to cause the direction or management of such +entity, whether by contract or otherwise, or (ii) ownership of fifty +percent (50%) or more of the outstanding shares, or (iii) beneficial +ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation +or translation of a Source form, including but not limited to compiled +object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object +form, made available under the License, as indicated by a copyright +notice that is included in or attached to the work (an example is +provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original +version of the Work and any modifications or additions to that Work or +Derivative Works thereof, that is intentionally submitted to Licensor +for inclusion in the Work by the copyright owner or by an individual or +Legal Entity authorized to submit on behalf of the copyright owner. For +the purposes of this definition, "submitted" means any form of electronic, +verbal, or written communication sent to the Licensor or its +representatives, including but not limited to communication on electronic +mailing lists, source code control systems, and issue tracking systems that +are managed by, or on behalf of, the Licensor for the purpose of discussing +and improving the Work, but excluding communication that is conspicuously +marked or otherwise designated in writing by the copyright owner as "Not +a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on +behalf of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, +non-exclusive, no-charge, royalty-free, irrevocable copyright license to +reproduce, prepare Derivative Works of, publicly display, publicly perform, +sublicense, and distribute the Work and such Derivative Works in Source or +Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, +non-exclusive, no-charge, royalty-free, irrevocable (except as stated in +this section) patent license to make, have made, use, offer to sell, sell, +import, and otherwise transfer the Work, where such license applies only to +those patent claims licensable by such Contributor that are necessarily +infringed by their Contribution(s) alone or by combination of their +Contribution(s) with the Work to which such Contribution(s) was submitted. +If You institute patent litigation against any entity (including a cross-claim +or counterclaim in a lawsuit) alleging that the Work or a Contribution +incorporated within the Work constitutes direct or contributory patent +infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or +Derivative Works thereof in any medium, with or without modifications, and +in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that +You changed the files; and +You must retain, in the Source form of any Derivative Works that You +distribute, all copyright, patent, trademark, and attribution notices +from the Source form of the Work, excluding those notices that do not +pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, +then any Derivative Works that You distribute must include a readable +copy of the attribution notices contained within such NOTICE file, excluding +those notices that do not pertain to any part of the Derivative Works, in +at least one of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or documentation, if +provided along with the Derivative Works; or, within a display generated by +the Derivative Works, if and wherever such third-party notices normally +appear. The contents of the NOTICE file are for informational purposes +only and do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside or as +an addendum to the NOTICE text from the Work, provided that such additional +attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a +whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any +Contribution intentionally submitted for inclusion in the Work by You to the +Licensor shall be under the terms and conditions of this License, without any +additional terms or conditions. Notwithstanding the above, nothing herein +shall supersede or modify the terms of any separate license agreement you +may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as +required for reasonable and customary use in describing the origin of the +Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to +in writing, Licensor provides the Work (and each Contributor provides its +Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +ANY KIND, either express or implied, including, without limitation, any +warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or +FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining +the appropriateness of using or redistributing the Work and assume any risks +associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in +tort (including negligence), contract, or otherwise, unless required by +applicable law (such as deliberate and grossly negligent acts) or agreed to +in writing, shall any Contributor be liable to You for damages, including +any direct, indirect, special, incidental, or consequential damages of any +character arising as a result of this License or out of the use or inability +to use the Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other +commercial damages or losses), even if such Contributor has been advised +of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the +Work or Derivative Works thereof, You may choose to offer, and charge a +fee for, acceptance of support, warranty, indemnity, or other liability +obligations and/or rights consistent with this License. However, in accepting +such obligations, You may act only on Your own behalf and on Your sole +responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any +liability incurred by, or claims asserted against, such Contributor by +reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/third_party/etc1/README.google b/third_party/etc1/README.google new file mode 100644 index 0000000..029c801 --- /dev/null +++ b/third_party/etc1/README.google @@ -0,0 +1,7 @@ +URL: https://android.googlesource.com/platform/frameworks/native/+/master/opengl/ +Version: 01cc538b +License: Apache 2.0 +License File: LICENSE +Description: PKM file format (ETC1 data) support +Local Modifications: Created LICENSE file for compliance purposes. Not included in original + distribution. diff --git a/third_party/etc1/etc1.cpp b/third_party/etc1/etc1.cpp new file mode 100644 index 0000000..421ebfe --- /dev/null +++ b/third_party/etc1/etc1.cpp @@ -0,0 +1,678 @@ +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +////////////////////////////////////////////////////////////////////////////////////////// + +// This is a fork of the AOSP project ETC1 codec. The original code can be found +// at the following web site: +// https://android.googlesource.com/platform/frameworks/native/+/master/opengl/include/ETC1/ + +////////////////////////////////////////////////////////////////////////////////////////// + +#include "etc1.h" + +#include + +/* From http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt + + The number of bits that represent a 4x4 texel block is 64 bits if + is given by ETC1_RGB8_OES. + + The data for a block is a number of bytes, + + {q0, q1, q2, q3, q4, q5, q6, q7} + + where byte q0 is located at the lowest memory address and q7 at + the highest. The 64 bits specifying the block is then represented + by the following 64 bit integer: + + int64bit = 256*(256*(256*(256*(256*(256*(256*q0+q1)+q2)+q3)+q4)+q5)+q6)+q7; + + ETC1_RGB8_OES: + + a) bit layout in bits 63 through 32 if diffbit = 0 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | base col2 | base col1 | base col2 | + | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col1 | base col2 | table | table |diff|flip| + | B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + b) bit layout in bits 63 through 32 if diffbit = 1 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | dcol 2 | base col1 | dcol 2 | + | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col 1 | dcol 2 | table | table |diff|flip| + | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + c) bit layout in bits 31 through 0 (in both cases) + + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 + ----------------------------------------------- + | most significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| + ----------------------------------------------- + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + -------------------------------------------------- + | least significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + -------------------------------------------------- + + + Add table 3.17.2: Intensity modifier sets for ETC1 compressed textures: + + table codeword modifier table + ------------------ ---------------------- + 0 -8 -2 2 8 + 1 -17 -5 5 17 + 2 -29 -9 9 29 + 3 -42 -13 13 42 + 4 -60 -18 18 60 + 5 -80 -24 24 80 + 6 -106 -33 33 106 + 7 -183 -47 47 183 + + + Add table 3.17.3 Mapping from pixel index values to modifier values for + ETC1 compressed textures: + + pixel index value + --------------- + msb lsb resulting modifier value + ----- ----- ------------------------- + 1 1 -b (large negative value) + 1 0 -a (small negative value) + 0 0 a (small positive value) + 0 1 b (large positive value) + + + */ + +static const int kModifierTable[] = { +/* 0 */2, 8, -2, -8, +/* 1 */5, 17, -5, -17, +/* 2 */9, 29, -9, -29, +/* 3 */13, 42, -13, -42, +/* 4 */18, 60, -18, -60, +/* 5 */24, 80, -24, -80, +/* 6 */33, 106, -33, -106, +/* 7 */47, 183, -47, -183 }; + +static const int kLookup[8] = { 0, 1, 2, 3, -4, -3, -2, -1 }; + +static inline etc1_byte clamp(int x) { + return (etc1_byte) (x >= 0 ? (x < 255 ? x : 255) : 0); +} + +static +inline int convert4To8(int b) { + int c = b & 0xf; + return (c << 4) | c; +} + +static +inline int convert5To8(int b) { + int c = b & 0x1f; + return (c << 3) | (c >> 2); +} + +static +inline int convert6To8(int b) { + int c = b & 0x3f; + return (c << 2) | (c >> 4); +} + +static +inline int divideBy255(int d) { + return (d + 128 + (d >> 8)) >> 8; +} + +static +inline int convert8To4(int b) { + int c = b & 0xff; + return divideBy255(c * 15); +} + +static +inline int convert8To5(int b) { + int c = b & 0xff; + return divideBy255(c * 31); +} + +static +inline int convertDiff(int base, int diff) { + return convert5To8((0x1f & base) + kLookup[0x7 & diff]); +} + +static +void decode_subblock(etc1_byte* pOut, int r, int g, int b, const int* table, + etc1_uint32 low, bool second, bool flipped) { + int baseX = 0; + int baseY = 0; + if (second) { + if (flipped) { + baseY = 2; + } else { + baseX = 2; + } + } + for (int i = 0; i < 8; i++) { + int x, y; + if (flipped) { + x = baseX + (i >> 1); + y = baseY + (i & 1); + } else { + x = baseX + (i >> 2); + y = baseY + (i & 3); + } + int k = y + (x * 4); + int offset = ((low >> k) & 1) | ((low >> (k + 15)) & 2); + int delta = table[offset]; + etc1_byte* q = pOut + 3 * (x + 4 * y); + *q++ = clamp(r + delta); + *q++ = clamp(g + delta); + *q++ = clamp(b + delta); + } +} + +// Input is an ETC1 compressed version of the data. +// Output is a 4 x 4 square of 3-byte pixels in form R, G, B + +void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut) { + etc1_uint32 high = (pIn[0] << 24) | (pIn[1] << 16) | (pIn[2] << 8) | pIn[3]; + etc1_uint32 low = (pIn[4] << 24) | (pIn[5] << 16) | (pIn[6] << 8) | pIn[7]; + int r1, r2, g1, g2, b1, b2; + if (high & 2) { + // differential + int rBase = high >> 27; + int gBase = high >> 19; + int bBase = high >> 11; + r1 = convert5To8(rBase); + r2 = convertDiff(rBase, high >> 24); + g1 = convert5To8(gBase); + g2 = convertDiff(gBase, high >> 16); + b1 = convert5To8(bBase); + b2 = convertDiff(bBase, high >> 8); + } else { + // not differential + r1 = convert4To8(high >> 28); + r2 = convert4To8(high >> 24); + g1 = convert4To8(high >> 20); + g2 = convert4To8(high >> 16); + b1 = convert4To8(high >> 12); + b2 = convert4To8(high >> 8); + } + int tableIndexA = 7 & (high >> 5); + int tableIndexB = 7 & (high >> 2); + const int* tableA = kModifierTable + tableIndexA * 4; + const int* tableB = kModifierTable + tableIndexB * 4; + bool flipped = (high & 1) != 0; + decode_subblock(pOut, r1, g1, b1, tableA, low, false, flipped); + decode_subblock(pOut, r2, g2, b2, tableB, low, true, flipped); +} + +typedef struct { + etc1_uint32 high; + etc1_uint32 low; + etc1_uint32 score; // Lower is more accurate +} etc_compressed; + +static +inline void take_best(etc_compressed* a, const etc_compressed* b) { + if (a->score > b->score) { + *a = *b; + } +} + +static +void etc_average_colors_subblock(const etc1_byte* pIn, etc1_uint32 inMask, + etc1_byte* pColors, bool flipped, bool second) { + int r = 0; + int g = 0; + int b = 0; + + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for (int y = 0; y < 2; y++) { + int yy = by + y; + for (int x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } + pColors[0] = (etc1_byte)((r + 4) >> 3); + pColors[1] = (etc1_byte)((g + 4) >> 3); + pColors[2] = (etc1_byte)((b + 4) >> 3); +} + +static +inline int square(int x) { + return x * x; +} + +static etc1_uint32 chooseModifier(const etc1_byte* pBaseColors, + const etc1_byte* pIn, etc1_uint32 *pLow, int bitIndex, + const int* pModifierTable) { + etc1_uint32 bestScore = ~0; + int bestIndex = 0; + int pixelR = pIn[0]; + int pixelG = pIn[1]; + int pixelB = pIn[2]; + int r = pBaseColors[0]; + int g = pBaseColors[1]; + int b = pBaseColors[2]; + for (int i = 0; i < 4; i++) { + int modifier = pModifierTable[i]; + int decodedG = clamp(g + modifier); + etc1_uint32 score = (etc1_uint32) (6 * square(decodedG - pixelG)); + if (score >= bestScore) { + continue; + } + int decodedR = clamp(r + modifier); + score += (etc1_uint32) (3 * square(decodedR - pixelR)); + if (score >= bestScore) { + continue; + } + int decodedB = clamp(b + modifier); + score += (etc1_uint32) square(decodedB - pixelB); + if (score < bestScore) { + bestScore = score; + bestIndex = i; + } + } + etc1_uint32 lowMask = (((bestIndex >> 1) << 16) | (bestIndex & 1)) + << bitIndex; + *pLow |= lowMask; + return bestScore; +} + +static +void etc_encode_subblock_helper(const etc1_byte* pIn, etc1_uint32 inMask, + etc_compressed* pCompressed, bool flipped, bool second, + const etc1_byte* pBaseColors, const int* pModifierTable) { + int score = pCompressed->score; + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for (int y = 0; y < 2; y++) { + int yy = by + y; + for (int x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, yy + x * 4, pModifierTable); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, y + xx * 4, pModifierTable); + } + } + } + } + pCompressed->score = score; +} + +static bool inRange4bitSigned(int color) { + return color >= -4 && color <= 3; +} + +static void etc_encodeBaseColors(etc1_byte* pBaseColors, + const etc1_byte* pColors, etc_compressed* pCompressed) { + int r1, g1, b1, r2, g2, b2; // 8 bit base colors for sub-blocks + bool differential; + { + int r51 = convert8To5(pColors[0]); + int g51 = convert8To5(pColors[1]); + int b51 = convert8To5(pColors[2]); + int r52 = convert8To5(pColors[3]); + int g52 = convert8To5(pColors[4]); + int b52 = convert8To5(pColors[5]); + + r1 = convert5To8(r51); + g1 = convert5To8(g51); + b1 = convert5To8(b51); + + int dr = r52 - r51; + int dg = g52 - g51; + int db = b52 - b51; + + differential = inRange4bitSigned(dr) && inRange4bitSigned(dg) + && inRange4bitSigned(db); + if (differential) { + r2 = convert5To8(r51 + dr); + g2 = convert5To8(g51 + dg); + b2 = convert5To8(b51 + db); + pCompressed->high |= (r51 << 27) | ((7 & dr) << 24) | (g51 << 19) + | ((7 & dg) << 16) | (b51 << 11) | ((7 & db) << 8) | 2; + } + } + + if (!differential) { + int r41 = convert8To4(pColors[0]); + int g41 = convert8To4(pColors[1]); + int b41 = convert8To4(pColors[2]); + int r42 = convert8To4(pColors[3]); + int g42 = convert8To4(pColors[4]); + int b42 = convert8To4(pColors[5]); + r1 = convert4To8(r41); + g1 = convert4To8(g41); + b1 = convert4To8(b41); + r2 = convert4To8(r42); + g2 = convert4To8(g42); + b2 = convert4To8(b42); + pCompressed->high |= (r41 << 28) | (r42 << 24) | (g41 << 20) | (g42 + << 16) | (b41 << 12) | (b42 << 8); + } + pBaseColors[0] = r1; + pBaseColors[1] = g1; + pBaseColors[2] = b1; + pBaseColors[3] = r2; + pBaseColors[4] = g2; + pBaseColors[5] = b2; +} + +static +void etc_encode_block_helper(const etc1_byte* pIn, etc1_uint32 inMask, + const etc1_byte* pColors, etc_compressed* pCompressed, bool flipped) { + pCompressed->score = ~0; + pCompressed->high = (flipped ? 1 : 0); + pCompressed->low = 0; + + etc1_byte pBaseColors[6]; + + etc_encodeBaseColors(pBaseColors, pColors, pCompressed); + + int originalHigh = pCompressed->high; + + const int* pModifierTable = kModifierTable; + for (int i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = 0; + temp.high = originalHigh | (i << 5); + temp.low = 0; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, false, + pBaseColors, pModifierTable); + take_best(pCompressed, &temp); + } + pModifierTable = kModifierTable; + etc_compressed firstHalf = *pCompressed; + for (int i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = firstHalf.score; + temp.high = firstHalf.high | (i << 2); + temp.low = firstHalf.low; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, true, + pBaseColors + 3, pModifierTable); + if (i == 0) { + *pCompressed = temp; + } else { + take_best(pCompressed, &temp); + } + } +} + +static void writeBigEndian(etc1_byte* pOut, etc1_uint32 d) { + pOut[0] = (etc1_byte)(d >> 24); + pOut[1] = (etc1_byte)(d >> 16); + pOut[2] = (etc1_byte)(d >> 8); + pOut[3] = (etc1_byte) d; +} + +// Input is a 4 x 4 square of 3-byte pixels in form R, G, B +// inmask is a 16-bit mask where bit (1 << (x + y * 4)) tells whether the corresponding (x,y) +// pixel is valid or not. Invalid pixel color values are ignored when compressing. +// Output is an ETC1 compressed version of the data. + +void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask, + etc1_byte* pOut) { + etc1_byte colors[6]; + etc1_byte flippedColors[6]; + etc_average_colors_subblock(pIn, inMask, colors, false, false); + etc_average_colors_subblock(pIn, inMask, colors + 3, false, true); + etc_average_colors_subblock(pIn, inMask, flippedColors, true, false); + etc_average_colors_subblock(pIn, inMask, flippedColors + 3, true, true); + + etc_compressed a, b; + etc_encode_block_helper(pIn, inMask, colors, &a, false); + etc_encode_block_helper(pIn, inMask, flippedColors, &b, true); + take_best(&a, &b); + writeBigEndian(pOut, a.high); + writeBigEndian(pOut + 4, a.low); +} + +// Return the size of the encoded image data (does not include size of PKM header). + +etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height) { + return (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1; +} + +// Encode an entire image. +// pIn - pointer to the image data. Formatted such that the Red component of +// pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset; +// pOut - pointer to encoded data. Must be large enough to store entire encoded image. + +int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut) { + if (pixelSize < 2 || pixelSize > 3) { + return -1; + } + static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff }; + static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777, + 0xffff }; + etc1_byte block[ETC1_DECODED_BLOCK_SIZE]; + etc1_byte encoded[ETC1_ENCODED_BLOCK_SIZE]; + + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + + for (etc1_uint32 y = 0; y < encodedHeight; y += 4) { + etc1_uint32 yEnd = height - y; + if (yEnd > 4) { + yEnd = 4; + } + int ymask = kYMask[yEnd]; + for (etc1_uint32 x = 0; x < encodedWidth; x += 4) { + etc1_uint32 xEnd = width - x; + if (xEnd > 4) { + xEnd = 4; + } + int mask = ymask & kXMask[xEnd]; + for (etc1_uint32 cy = 0; cy < yEnd; cy++) { + etc1_byte* q = block + (cy * 4) * 3; + const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy); + if (pixelSize == 3) { + memcpy(q, p, xEnd * 3); + } else { + for (etc1_uint32 cx = 0; cx < xEnd; cx++) { + int pixel = (p[1] << 8) | p[0]; + *q++ = convert5To8(pixel >> 11); + *q++ = convert6To8(pixel >> 5); + *q++ = convert5To8(pixel); + p += pixelSize; + } + } + } + etc1_encode_block(block, mask, encoded); + memcpy(pOut, encoded, sizeof(encoded)); + pOut += sizeof(encoded); + } + } + return 0; +} + +// Decode an entire image. +// pIn - pointer to encoded data. +// pOut - pointer to the image data. Will be written such that the Red component of +// pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset. Must be +// large enough to store entire image. + + +int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut, + etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride) { + if (pixelSize < 2 || pixelSize > 3) { + return -1; + } + etc1_byte block[ETC1_DECODED_BLOCK_SIZE]; + + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + + for (etc1_uint32 y = 0; y < encodedHeight; y += 4) { + etc1_uint32 yEnd = height - y; + if (yEnd > 4) { + yEnd = 4; + } + for (etc1_uint32 x = 0; x < encodedWidth; x += 4) { + etc1_uint32 xEnd = width - x; + if (xEnd > 4) { + xEnd = 4; + } + etc1_decode_block(pIn, block); + pIn += ETC1_ENCODED_BLOCK_SIZE; + for (etc1_uint32 cy = 0; cy < yEnd; cy++) { + const etc1_byte* q = block + (cy * 4) * 3; + etc1_byte* p = pOut + pixelSize * x + stride * (y + cy); + if (pixelSize == 3) { + memcpy(p, q, xEnd * 3); + } else { + for (etc1_uint32 cx = 0; cx < xEnd; cx++) { + etc1_byte r = *q++; + etc1_byte g = *q++; + etc1_byte b = *q++; + etc1_uint32 pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + *p++ = (etc1_byte) pixel; + *p++ = (etc1_byte) (pixel >> 8); + } + } + } + } + } + return 0; +} + +static const char kMagic[] = { 'P', 'K', 'M', ' ', '1', '0' }; + +static const etc1_uint32 ETC1_PKM_FORMAT_OFFSET = 6; +static const etc1_uint32 ETC1_PKM_ENCODED_WIDTH_OFFSET = 8; +static const etc1_uint32 ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10; +static const etc1_uint32 ETC1_PKM_WIDTH_OFFSET = 12; +static const etc1_uint32 ETC1_PKM_HEIGHT_OFFSET = 14; + +static const etc1_uint32 ETC1_RGB_NO_MIPMAPS = 0; + +static void writeBEUint16(etc1_byte* pOut, etc1_uint32 data) { + pOut[0] = (etc1_byte) (data >> 8); + pOut[1] = (etc1_byte) data; +} + +static etc1_uint32 readBEUint16(const etc1_byte* pIn) { + return (pIn[0] << 8) | pIn[1]; +} + +// Format a PKM header + +void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height) { + memcpy(pHeader, kMagic, sizeof(kMagic)); + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + writeBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET, ETC1_RGB_NO_MIPMAPS); + writeBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET, encodedWidth); + writeBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET, encodedHeight); + writeBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET, width); + writeBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET, height); +} + +// Check if a PKM header is correctly formatted. + +etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader) { + if (memcmp(pHeader, kMagic, sizeof(kMagic))) { + return false; + } + etc1_uint32 format = readBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET); + etc1_uint32 encodedWidth = readBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET); + etc1_uint32 encodedHeight = readBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET); + etc1_uint32 width = readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); + etc1_uint32 height = readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); + return format == ETC1_RGB_NO_MIPMAPS && + encodedWidth >= width && encodedWidth - width < 4 && + encodedHeight >= height && encodedHeight - height < 4; +} + +// Read the image width from a PKM header + +etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader) { + return readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); +} + +// Read the image height from a PKM header + +etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader){ + return readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); +} diff --git a/third_party/etc1/etc1.h b/third_party/etc1/etc1.h new file mode 100644 index 0000000..d66ca9d --- /dev/null +++ b/third_party/etc1/etc1.h @@ -0,0 +1,114 @@ +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +////////////////////////////////////////////////////////////////////////////////////////// + +// This is a fork of the AOSP project ETC1 codec. The original code can be found +// at the following web site: +// https://android.googlesource.com/platform/frameworks/native/+/master/opengl/libs/ETC1/ + +////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef __etc1_h__ +#define __etc1_h__ + +#define ETC1_ENCODED_BLOCK_SIZE 8 +#define ETC1_DECODED_BLOCK_SIZE 48 + +#ifndef ETC1_RGB8_OES +#define ETC1_RGB8_OES 0x8D64 +#endif + +typedef unsigned char etc1_byte; +typedef int etc1_bool; +typedef unsigned int etc1_uint32; + +#ifdef __cplusplus +extern "C" { +#endif + +// Encode a block of pixels. +// +// pIn is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a +// 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R +// value of pixel (x, y). +// +// validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether +// the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing. +// +// pOut is an ETC1 compressed version of the data. + +void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 validPixelMask, etc1_byte* pOut); + +// Decode a block of pixels. +// +// pIn is an ETC1 compressed version of the data. +// +// pOut is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a +// 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R +// value of pixel (x, y). + +void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut); + +// Return the size of the encoded image data (does not include size of PKM header). + +etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height); + +// Encode an entire image. +// pIn - pointer to the image data. Formatted such that +// pixel (x,y) is at pIn + pixelSize * x + stride * y; +// pOut - pointer to encoded data. Must be large enough to store entire encoded image. +// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image. +// returns non-zero if there is an error. + +int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut); + +// Decode an entire image. +// pIn - pointer to encoded data. +// pOut - pointer to the image data. Will be written such that +// pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be +// large enough to store entire image. +// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image. +// returns non-zero if there is an error. + +int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut, + etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride); + +// Size of a PKM header, in bytes. + +#define ETC_PKM_HEADER_SIZE 16 + +// Format a PKM header + +void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height); + +// Check if a PKM header is correctly formatted. + +etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader); + +// Read the image width from a PKM header + +etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader); + +// Read the image height from a PKM header + +etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp index 2504b3c..8a187f6 100644 --- a/tools/gpu/GrTest.cpp +++ b/tools/gpu/GrTest.cpp @@ -360,6 +360,11 @@ private: return nullptr; } + GrTexture* onCreateCompressedTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted, + const SkTArray& texels) override { + return nullptr; + } + sk_sp onWrapBackendTexture(const GrBackendTexture&, GrSurfaceOrigin, GrBackendTextureFlags, -- 2.7.4