diff options
35 files changed, 1771 insertions, 74 deletions
@@ -167,6 +167,7 @@ config("skia_private") { "src/utils", "src/utils/win", "src/xml", + "third_party/etc1", "third_party/gif", ] @@ -950,6 +951,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", ] @@ -2264,6 +2266,7 @@ if (skia_enable_tools) { "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", ] deps = [ diff --git a/gm/etc1.cpp b/gm/etc1.cpp new file mode 100644 index 0000000000..8f11ee8793 --- /dev/null +++ b/gm/etc1.cpp @@ -0,0 +1,127 @@ +/* + * 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 "GrGpu.h" +#include "GrRenderTargetContext.h" +#include "GrRenderTargetContextPriv.h" +#include "GrTextureProxy.h" +#include "effects/GrSimpleTextureEffect.h" +#include "ops/GrFillRectOp.h" + +// Basic test of Ganesh's ETC1 support +class ETC1GM : public skiagm::GM { +public: + ETC1GM() { + this->setBGColor(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; + } + + GrBackendTexture tex = context->contextPriv().getGpu()->createTestingOnlyBackendTexture( + fETC1Data.get(), + kTexWidth, + kTexHeight, + GrColorType::kRGB_ETC1, + false, + GrMipMapped::kNo, + kTexWidth/2); // rowbytes are meaningless for compressed textures, but this is + // basically right + + if (!tex.isValid()) { + return; + } + + auto proxy = context->contextPriv().proxyProvider()->wrapBackendTexture( + tex, kTopLeft_GrSurfaceOrigin, + kAdopt_GrWrapOwnership, + kRead_GrIOType); + if (!proxy) { + return; + } + + const SkMatrix trans = SkMatrix::MakeTrans(-kPad, -kPad); + + auto fp = GrSimpleTextureEffect::Make(proxy, 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( + GrFillRectOp::Make(context, std::move(grPaint), GrAAType::kNone, SkMatrix::I(), rect)); + } + +private: + static const int kPad = 8; + static const int kTexWidth = 16; + static const int kTexHeight = 20; + + SkAutoTMalloc<char> fETC1Data; + + typedef GM INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +DEF_GM(return new ETC1GM;) + +#endif @@ -140,6 +140,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/private/GrTypesPriv.h b/include/private/GrTypesPriv.h index ae54c5a6c1..55d0c40789 100644 --- a/include/private/GrTypesPriv.h +++ b/include/private/GrTypesPriv.h @@ -31,7 +31,7 @@ using GrStdSteadyClock = std::chrono::steady_clock; /** * Pixel configurations. This type conflates texture formats, CPU pixel formats, and * premultipliedness. We are moving away from it towards SkColorType and backend API (GL, Vulkan) - * texture formats in the pulbic API. Right now this mostly refers to texture formats as we're + * texture formats in the public API. Right now this mostly refers to texture formats as we're * migrating. */ enum GrPixelConfig { @@ -51,6 +51,7 @@ enum GrPixelConfig { kRG_float_GrPixelConfig, kAlpha_half_GrPixelConfig, kRGBA_half_GrPixelConfig, + kRGB_ETC1_GrPixelConfig, /** For internal usage. */ kPrivateConfig1_GrPixelConfig, @@ -968,6 +969,7 @@ static inline GrSRGBEncoded GrPixelConfigIsSRGBEncoded(GrPixelConfig config) { case kAlpha_half_GrPixelConfig: case kAlpha_half_as_Red_GrPixelConfig: case kRGBA_half_GrPixelConfig: + case kRGB_ETC1_GrPixelConfig: return GrSRGBEncoded::kNo; } SK_ABORT("Invalid pixel config"); @@ -1007,6 +1009,7 @@ static inline size_t GrBytesPerPixel(GrPixelConfig config) { case kRG_float_GrPixelConfig: return 8; case kUnknown_GrPixelConfig: + case kRGB_ETC1_GrPixelConfig: return 0; } SK_ABORT("Invalid pixel config"); @@ -1022,6 +1025,7 @@ static inline bool GrPixelConfigIsOpaque(GrPixelConfig config) { case kGray_8_as_Lum_GrPixelConfig: case kGray_8_as_Red_GrPixelConfig: case kRG_float_GrPixelConfig: + case kRGB_ETC1_GrPixelConfig: return true; case kAlpha_8_GrPixelConfig: case kAlpha_8_as_Alpha_GrPixelConfig: @@ -1067,6 +1071,7 @@ static inline bool GrPixelConfigIsAlphaOnly(GrPixelConfig config) { case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: case kRGBA_half_GrPixelConfig: + case kRGB_ETC1_GrPixelConfig: return false; } SK_ABORT("Invalid pixel config."); @@ -1091,6 +1096,7 @@ static inline bool GrPixelConfigIsFloatingPoint(GrPixelConfig config) { case kSRGBA_8888_GrPixelConfig: case kSBGRA_8888_GrPixelConfig: case kRGBA_1010102_GrPixelConfig: + case kRGB_ETC1_GrPixelConfig: return false; case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: @@ -1104,6 +1110,117 @@ static inline bool GrPixelConfigIsFloatingPoint(GrPixelConfig config) { } /** + * Returns true if the pixel config is a GPU-specific compressed format + * representation. + */ +static inline bool GrPixelConfigIsCompressed(GrPixelConfig config) { + switch (config) { + case kRGB_ETC1_GrPixelConfig: + return true; + case kUnknown_GrPixelConfig: + case kAlpha_8_GrPixelConfig: + case kAlpha_8_as_Alpha_GrPixelConfig: + case kAlpha_8_as_Red_GrPixelConfig: + case kGray_8_GrPixelConfig: + case kGray_8_as_Lum_GrPixelConfig: + case kGray_8_as_Red_GrPixelConfig: + case kRGB_565_GrPixelConfig: + case kRGBA_4444_GrPixelConfig: + case kRGB_888_GrPixelConfig: + case kRG_88_GrPixelConfig: + case kRGBA_8888_GrPixelConfig: + case kBGRA_8888_GrPixelConfig: + case kSRGBA_8888_GrPixelConfig: + case kSBGRA_8888_GrPixelConfig: + case kRGBA_1010102_GrPixelConfig: + case kRGBA_float_GrPixelConfig: + case kRG_float_GrPixelConfig: + case kAlpha_half_GrPixelConfig: + case kAlpha_half_as_Red_GrPixelConfig: + case kRGBA_half_GrPixelConfig: + return false; + } + SK_ABORT("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 kRGB_ETC1_GrPixelConfig: + return kRGBA_8888_GrPixelConfig; + case kUnknown_GrPixelConfig: + case kAlpha_8_GrPixelConfig: + case kAlpha_8_as_Alpha_GrPixelConfig: + case kAlpha_8_as_Red_GrPixelConfig: + case kGray_8_GrPixelConfig: + case kGray_8_as_Lum_GrPixelConfig: + case kGray_8_as_Red_GrPixelConfig: + case kRGB_565_GrPixelConfig: + case kRGBA_4444_GrPixelConfig: + case kRGB_888_GrPixelConfig: + case kRG_88_GrPixelConfig: + case kRGBA_8888_GrPixelConfig: + case kBGRA_8888_GrPixelConfig: + case kSRGBA_8888_GrPixelConfig: + case kSBGRA_8888_GrPixelConfig: + case kRGBA_1010102_GrPixelConfig: + case kRGBA_float_GrPixelConfig: + case kRG_float_GrPixelConfig: + case kAlpha_half_GrPixelConfig: + case kAlpha_half_as_Red_GrPixelConfig: + case kRGBA_half_GrPixelConfig: + return config; + } + SK_ABORT("Invalid pixel config"); + return config; +} + +/** + * 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 kRGB_ETC1_GrPixelConfig: + SkASSERT((width & 3) == 0); + SkASSERT((height & 3) == 0); + return (width >> 2) * (height >> 2) * 8; + + case kUnknown_GrPixelConfig: + case kAlpha_8_GrPixelConfig: + case kAlpha_8_as_Alpha_GrPixelConfig: + case kAlpha_8_as_Red_GrPixelConfig: + case kGray_8_GrPixelConfig: + case kGray_8_as_Lum_GrPixelConfig: + case kGray_8_as_Red_GrPixelConfig: + case kRGB_565_GrPixelConfig: + case kRGBA_4444_GrPixelConfig: + case kRGB_888_GrPixelConfig: + case kRG_88_GrPixelConfig: + case kRGBA_8888_GrPixelConfig: + case kBGRA_8888_GrPixelConfig: + case kSRGBA_8888_GrPixelConfig: + case kSBGRA_8888_GrPixelConfig: + case kRGBA_1010102_GrPixelConfig: + case kRGBA_float_GrPixelConfig: + case kRG_float_GrPixelConfig: + case kAlpha_half_GrPixelConfig: + case kAlpha_half_as_Red_GrPixelConfig: + case kRGBA_half_GrPixelConfig: + SK_ABORT("Unknown compressed pixel config"); + return 4 * width * height; + } + + SK_ABORT("Invalid pixel config"); + return 4 * width * height; +} + +/** * Precision qualifier that should be used with a sampler. */ static inline GrSLPrecision GrSLSamplerPrecision(GrPixelConfig config) { @@ -1123,6 +1240,7 @@ static inline GrSLPrecision GrSLSamplerPrecision(GrPixelConfig config) { case kBGRA_8888_GrPixelConfig: case kSRGBA_8888_GrPixelConfig: case kSBGRA_8888_GrPixelConfig: + case kRGB_ETC1_GrPixelConfig: return kLow_GrSLPrecision; case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: @@ -1142,7 +1260,7 @@ static inline GrSLPrecision GrSLSamplerPrecision(GrPixelConfig config) { * their type, and width. This exists so that the GPU backend can have private types that have no * analog in the public facing SkColorType enum and omit types not implemented in the GPU backend. * It does not refer to a texture format and the mapping to texture formats may be many-to-many. - * It does not specify the sRGB encding of the stored values. + * It does not specify the sRGB encoding of the stored values. */ enum class GrColorType { kUnknown, @@ -1159,6 +1277,7 @@ enum class GrColorType { kRGBA_F16, kRG_F32, kRGBA_F32, + kRGB_ETC1, // This type doesn't appear in SkColorType at all. }; static inline SkColorType GrColorTypeToSkColorType(GrColorType ct) { @@ -1177,6 +1296,7 @@ static inline SkColorType GrColorTypeToSkColorType(GrColorType ct) { case GrColorType::kRGBA_F16: return kRGBA_F16_SkColorType; case GrColorType::kRG_F32: return kUnknown_SkColorType; case GrColorType::kRGBA_F32: return kRGBA_F32_SkColorType; + case GrColorType::kRGB_ETC1: return kUnknown_SkColorType; } SK_ABORT("Invalid GrColorType"); return kUnknown_SkColorType; @@ -1219,6 +1339,7 @@ static inline uint32_t GrColorTypeComponentFlags(GrColorType ct) { case GrColorType::kRG_F32: return kRed_SkColorTypeComponentFlag | kGreen_SkColorTypeComponentFlag; case GrColorType::kRGBA_F32: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kRGB_ETC1: return kRGB_SkColorTypeComponentFlags; } SK_ABORT("Invalid GrColorType"); return kUnknown_SkColorType; @@ -1235,6 +1356,7 @@ static inline bool GrColorTypeHasAlpha(GrColorType ct) { static inline int GrColorTypeBytesPerPixel(GrColorType ct) { switch (ct) { case GrColorType::kUnknown: return 0; + case GrColorType::kRGB_ETC1: return 0; case GrColorType::kAlpha_8: return 1; case GrColorType::kRGB_565: return 2; case GrColorType::kABGR_4444: return 2; @@ -1304,6 +1426,9 @@ static inline GrColorType GrPixelConfigToColorTypeAndEncoding(GrPixelConfig conf case kRGBA_half_GrPixelConfig: *srgbEncoded = GrSRGBEncoded::kNo; return GrColorType::kRGBA_F16; + case kRGB_ETC1_GrPixelConfig: + *srgbEncoded = GrSRGBEncoded::kNo; + return GrColorType::kRGB_ETC1; case kAlpha_8_as_Alpha_GrPixelConfig: *srgbEncoded = GrSRGBEncoded::kNo; return GrColorType::kAlpha_8; @@ -1384,6 +1509,10 @@ static inline GrPixelConfig GrColorTypeToPixelConfig(GrColorType config, case GrColorType::kRGBA_F16: return (GrSRGBEncoded::kYes == srgbEncoded) ? kUnknown_GrPixelConfig : kRGBA_half_GrPixelConfig; + + case GrColorType::kRGB_ETC1: + return (GrSRGBEncoded::kYes == srgbEncoded) ? kUnknown_GrPixelConfig + : kRGB_ETC1_GrPixelConfig; } SK_ABORT("Invalid GrColorType"); return kUnknown_GrPixelConfig; diff --git a/public.bzl b/public.bzl index 60ce79db3b..6a3ceba22a 100644 --- a/public.bzl +++ b/public.bzl @@ -276,7 +276,7 @@ def codec_srcs(limited): "src/codec/*Webp*.cpp", "src/codec/*Png*", ] - return native.glob(["src/codec/*.cpp", "third_party/gif/*.cpp"], exclude = exclude) + return native.glob(["src/codec/*.cpp", "third_party/etc1/*.cpp", "third_party/gif/*.cpp"], exclude = exclude) # Platform-dependent SRCS for google3-default platform. BASE_SRCS_UNIX = struct( @@ -414,6 +414,7 @@ INCLUDES = [ "src/shaders/gradients", "src/sksl", "src/utils", + "third_party/etc1", "third_party/gif", ] diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp index f77f83ebc2..638a70f2a3 100644 --- a/src/gpu/GrCaps.cpp +++ b/src/gpu/GrCaps.cpp @@ -148,6 +148,7 @@ static const char* pixel_config_name(GrPixelConfig config) { case kAlpha_half_GrPixelConfig: return "AlphaHalf"; case kAlpha_half_as_Red_GrPixelConfig: return "AlphaHalf_asRed"; case kRGBA_half_GrPixelConfig: return "RGBAHalf"; + case kRGB_ETC1_GrPixelConfig: return "RGBETC1"; } SK_ABORT("Invalid pixel config"); return "<invalid>"; diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h index 796f9818c3..633e0667b2 100644 --- a/src/gpu/GrCaps.h +++ b/src/gpu/GrCaps.h @@ -52,6 +52,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; } @@ -333,6 +334,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/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index 88d1ec2813..d4f99b2e5e 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -427,6 +427,7 @@ static bool valid_premul_config(GrPixelConfig config) { case kRG_float_GrPixelConfig: return false; case kAlpha_half_GrPixelConfig: return false; case kRGBA_half_GrPixelConfig: return true; + case kRGB_ETC1_GrPixelConfig: return false; case kAlpha_8_as_Alpha_GrPixelConfig: return false; case kAlpha_8_as_Red_GrPixelConfig: return false; case kAlpha_half_as_Red_GrPixelConfig: return false; @@ -453,6 +454,7 @@ static bool valid_premul_color_type(GrColorType ct) { case GrColorType::kRGBA_F16: return true; case GrColorType::kRG_F32: return false; case GrColorType::kRGBA_F32: return true; + case GrColorType::kRGB_ETC1: return false; } SK_ABORT("Invalid GrColorType"); return false; diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp index cd93f8d6d4..0e520d820e 100644 --- a/src/gpu/GrGpu.cpp +++ b/src/gpu/GrGpu.cpp @@ -123,6 +123,10 @@ sk_sp<GrTexture> GrGpu::createTexture(const GrSurfaceDesc& origDesc, SkBudgeted return nullptr; } + // We shouldn't be rendering into compressed textures + SkASSERT(!GrPixelConfigIsCompressed(desc.fConfig) || !isRT); + SkASSERT(!GrPixelConfigIsCompressed(desc.fConfig) || 1 == desc.fSampleCnt); + this->handleDirtyContext(); sk_sp<GrTexture> tex = this->onCreateTexture(desc, budgeted, texels, mipLevelCount); if (tex) { @@ -149,6 +153,7 @@ sk_sp<GrTexture> GrGpu::wrapBackendTexture(const GrBackendTexture& backendTex, bool purgeImmediately) { SkASSERT(ioType != kWrite_GrIOType); this->handleDirtyContext(); + SkASSERT(this->caps()); if (!this->caps()->isConfigTexturable(backendTex.config())) { return nullptr; } @@ -250,6 +255,10 @@ bool GrGpu::readPixels(GrSurface* surface, int left, int top, int width, int hei return false; } + if (GrPixelConfigIsCompressed(surface->config())) { + return false; + } + this->handleDirtyContext(); return this->onReadPixels(surface, left, top, width, height, dstColorType, buffer, rowBytes); diff --git a/src/gpu/GrResourceProvider.cpp b/src/gpu/GrResourceProvider.cpp index 6e821db3a2..6b5ff4f1e5 100644 --- a/src/gpu/GrResourceProvider.cpp +++ b/src/gpu/GrResourceProvider.cpp @@ -150,9 +150,12 @@ sk_sp<GrTexture> GrResourceProvider::createTexture(const GrSurfaceDesc& desc, Sk return nullptr; } - sk_sp<GrTexture> tex = this->getExactScratch(desc, budgeted, flags); - if (tex) { - return tex; + // Compressed textures are read-only so they don't support re-use for scratch. + if (!GrPixelConfigIsCompressed(desc.fConfig)) { + sk_sp<GrTexture> tex = this->getExactScratch(desc, budgeted, flags); + if (tex) { + return tex; + } } return fGpu->createTexture(desc, budgeted); @@ -167,6 +170,11 @@ sk_sp<GrTexture> GrResourceProvider::createApproxTexture(const GrSurfaceDesc& de return nullptr; } + // Currently we don't recycle compressed textures as scratch. + if (GrPixelConfigIsCompressed(desc.fConfig)) { + return nullptr; + } + if (!fCaps->validateSurfaceDesc(desc, GrMipMapped::kNo)) { return nullptr; } @@ -195,6 +203,7 @@ sk_sp<GrTexture> GrResourceProvider::createApproxTexture(const GrSurfaceDesc& de sk_sp<GrTexture> GrResourceProvider::refScratchTexture(const GrSurfaceDesc& desc, Flags flags) { ASSERT_SINGLE_OWNER SkASSERT(!this->isAbandoned()); + SkASSERT(!GrPixelConfigIsCompressed(desc.fConfig)); SkASSERT(fCaps->validateSurfaceDesc(desc, GrMipMapped::kNo)); // We could make initial clears work with scratch textures but it is a rare case so we just opt diff --git a/src/gpu/GrSurface.cpp b/src/gpu/GrSurface.cpp index b35683c548..9af60bb4e6 100644 --- a/src/gpu/GrSurface.cpp +++ b/src/gpu/GrSurface.cpp @@ -36,6 +36,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). @@ -45,7 +46,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 } @@ -59,6 +64,8 @@ size_t GrSurface::ComputeSize(GrPixelConfig config, int colorSamplesPerPixel, GrMipMapped mipMapped, bool useNextPow2) { + size_t colorSize; + width = useNextPow2 ? SkTMax(GrResourceProvider::kMinScratchTextureSize, GrNextPow2(width)) : width; @@ -67,7 +74,11 @@ size_t GrSurface::ComputeSize(GrPixelConfig config, : 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 61fb25d9e5..3da6aaa99c 100644 --- a/src/gpu/GrSurfaceProxy.cpp +++ b/src/gpu/GrSurfaceProxy.cpp @@ -72,6 +72,11 @@ GrSurfaceProxy::GrSurfaceProxy(LazyInstantiateCallback&& callback, LazyInstantia } else { SkASSERT(is_valid_non_lazy(desc)); } + + if (GrPixelConfigIsCompressed(desc.fConfig)) { + SkASSERT(!SkToBool(desc.fFlags & kRenderTarget_GrSurfaceFlag)); + fSurfaceFlags |= GrInternalSurfaceFlags::kReadOnly; + } } // Wrapped version diff --git a/src/gpu/GrTexture.cpp b/src/gpu/GrTexture.cpp index bd8ad3b6d8..f998c6ec14 100644 --- a/src/gpu/GrTexture.cpp +++ b/src/gpu/GrTexture.cpp @@ -65,14 +65,16 @@ bool GrTexture::StealBackendTexture(sk_sp<GrTexture>&& texture, } void GrTexture::computeScratchKey(GrScratchKey* key) const { - const GrRenderTarget* rt = this->asRenderTarget(); - int sampleCount = 1; - if (rt) { - sampleCount = rt->numStencilSamples(); + if (!GrPixelConfigIsCompressed(this->config())) { + const GrRenderTarget* rt = this->asRenderTarget(); + int sampleCount = 1; + if (rt) { + sampleCount = rt->numStencilSamples(); + } + GrTexturePriv::ComputeScratchKey(this->config(), this->width(), this->height(), + SkToBool(rt), sampleCount, + this->texturePriv().mipMapped(), key); } - GrTexturePriv::ComputeScratchKey(this->config(), this->width(), this->height(), - SkToBool(rt), sampleCount, - this->texturePriv().mipMapped(), key); } void GrTexturePriv::ComputeScratchKey(GrPixelConfig config, int width, int height, diff --git a/src/gpu/GrTextureProducer.cpp b/src/gpu/GrTextureProducer.cpp index 97417f96b8..9ae2eb431d 100644 --- a/src/gpu/GrTextureProducer.cpp +++ b/src/gpu/GrTextureProducer.cpp @@ -23,6 +23,8 @@ sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrContext* context, bool dstWillRequireMipMaps) { SkASSERT(context); + GrPixelConfig config = GrMakePixelConfigUncompressed(inputProxy->config()); + const SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight); GrMipMapped mipMapped = dstWillRequireMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo; @@ -52,7 +54,7 @@ sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrContext* context, sk_sp<GrRenderTargetContext> copyRTC = context->contextPriv().makeDeferredRenderTargetContextWithFallback( format, SkBackingFit::kExact, dstRect.width(), dstRect.height(), - inputProxy->config(), nullptr, 1, mipMapped, inputProxy->origin()); + config, nullptr, 1, mipMapped, inputProxy->origin()); if (!copyRTC) { return nullptr; } diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp index 261c1f9bca..27b4248c0d 100644 --- a/src/gpu/SkGr.cpp +++ b/src/gpu/SkGr.cpp @@ -358,6 +358,7 @@ static inline int32_t dither_range_type_for_config(GrPixelConfig dstConfig) { case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: case kRGBA_half_GrPixelConfig: + case kRGB_ETC1_GrPixelConfig: case kAlpha_8_GrPixelConfig: case kAlpha_8_as_Alpha_GrPixelConfig: case kAlpha_8_as_Red_GrPixelConfig: diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp index 42ee309b93..04ef735f8d 100644 --- a/src/gpu/gl/GrGLCaps.cpp +++ b/src/gpu/gl/GrGLCaps.cpp @@ -1202,6 +1202,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, kReadPixels_ExternalFormatUsage, @@ -1212,6 +1221,7 @@ bool GrGLCaps::getReadPixelsFormat(GrPixelConfig surfaceConfig, GrPixelConfig ex } void GrGLCaps::getRenderbufferFormat(GrPixelConfig config, GrGLenum* internalFormat) const { + SkASSERT(!GrPixelConfigIsCompressed(config)); *internalFormat = fConfigTable[config].fFormats.fInternalFormatRenderbuffer; } @@ -1223,6 +1233,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); @@ -1891,6 +1904,41 @@ 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 = (bool)(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[kRGB_ETC1_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_COMPRESSED_RGB8_ETC2; + fConfigTable[kRGB_ETC1_GrPixelConfig].fFormats.fSizedInternalFormat = + GR_GL_COMPRESSED_RGB8_ETC2; + fConfigTable[kRGB_ETC1_GrPixelConfig].fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] + = 0; + fConfigTable[kRGB_ETC1_GrPixelConfig].fFormats.fExternalType = 0; + fConfigTable[kRGB_ETC1_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType; + if (kGL_GrGLStandard == standard) { + if (version >= GR_GL_VER(4, 3) || ctxInfo.hasExtension("GL_ARB_ES3_compatibility")) { + fConfigTable[kRGB_ETC1_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag; + } + } else { + if (version >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_OES_compressed_ETC2_RGB8_texture")) { + fConfigTable[kRGB_ETC1_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag; + } else if (ctxInfo.hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) { + fConfigTable[kRGB_ETC1_GrPixelConfig].fFormats.fBaseInternalFormat = + GR_GL_COMPRESSED_ETC1_RGB8; + fConfigTable[kRGB_ETC1_GrPixelConfig].fFormats.fSizedInternalFormat = + GR_GL_COMPRESSED_ETC1_RGB8; + fConfigTable[kRGB_ETC1_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag; + } + } + fConfigTable[kRGB_ETC1_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 05f2abab08..1d42f0dd47 100644 --- a/src/gpu/gl/GrGLCaps.h +++ b/src/gpu/gl/GrGLCaps.h @@ -146,6 +146,9 @@ 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 6d5c4014b5..d0aadd3693 100644 --- a/src/gpu/gl/GrGLGpu.cpp +++ b/src/gpu/gl/GrGLGpu.cpp @@ -818,6 +818,8 @@ bool GrGLGpu::onWritePixels(GrSurface* surface, int left, int top, int width, in // caps knows to make the external format be GL_RGBA. auto srgbEncoded = GrPixelConfigIsSRGBEncoded(surface->config()); auto srcAsConfig = GrColorTypeToPixelConfig(srcColorType, srgbEncoded); + + SkASSERT(!GrPixelConfigIsCompressed(glTex->config())); return this->uploadTexData(glTex->config(), glTex->width(), glTex->height(), glTex->target(), kWrite_UploadType, left, top, width, height, srcAsConfig, texels, mipLevelCount); @@ -825,6 +827,7 @@ bool GrGLGpu::onWritePixels(GrSurface* surface, int left, int top, int width, in // For GL_[UN]PACK_ALIGNMENT. TODO: This really wants to be GrColorType. static inline GrGLint config_alignment(GrPixelConfig config) { + SkASSERT(!GrPixelConfigIsCompressed(config)); switch (config) { case kAlpha_8_GrPixelConfig: case kAlpha_8_as_Alpha_GrPixelConfig: @@ -849,6 +852,7 @@ static inline GrGLint config_alignment(GrPixelConfig config) { case kRGBA_float_GrPixelConfig: case kRG_float_GrPixelConfig: return 4; + case kRGB_ETC1_GrPixelConfig: case kUnknown_GrPixelConfig: return 0; } @@ -863,6 +867,9 @@ bool GrGLGpu::onTransferPixels(GrTexture* texture, int left, int top, int width, GrPixelConfig texConfig = glTex->config(); SkASSERT(this->caps()->isConfigTexturable(texConfig)); + // Can't transfer compressed data + SkASSERT(!GrPixelConfigIsCompressed(glTex->config())); + if (!check_write_and_transfer_input(glTex)) { return false; } @@ -937,11 +944,13 @@ bool GrGLGpu::onTransferPixels(GrTexture* texture, int left, int top, int width, * @param config Pixel config of the texture. * @param interface The GL interface in use. * @param caps The capabilities of the GL device. + * @param target Which bound texture to target (GR_GL_TEXTURE_2D, e.g.) * @param internalFormat The data format used for the internal storage of the texture. May be sized. * @param internalFormatForTexStorage The data format used for the TexStorage API. Must be sized. * @param externalFormat The data format used for the external storage of the texture. * @param externalType The type of the data used for the external storage of the texture. * @param texels The texel data of the texture being created. + * @param mipLevelCount Number of mipmap levels * @param baseWidth The width of the texture's base mipmap level * @param baseHeight The height of the texture's base mipmap level */ @@ -1038,6 +1047,107 @@ 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 interface The GL interface in use. + * @param caps The capabilities of the GL device. + * @param target Which bound texture to target (GR_GL_TEXTURE_2D, e.g.) + * @param internalFormat The data format used for the internal storage of the texture. + * @param texels The texel data of the texture being created. + * @param mipLevelCount Number of mipmap levels + * @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_compressed_texture(GrPixelConfig config, + const GrGLInterface& interface, + const GrGLCaps& caps, + GrGLenum target, GrGLenum internalFormat, + const GrMipLevel texels[], int mipLevelCount, + int baseWidth, int baseHeight) { + CLEAR_ERROR_BEFORE_ALLOC(&interface); + SkASSERT(GrPixelConfigIsCompressed(config)); + + 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 &= mipLevelCount > 1; + + if (useTexStorage) { + // We never resize or change formats of textures. + GL_ALLOC_CALL(&interface, + TexStorage2D(target, + mipLevelCount, + internalFormat, + baseWidth, baseHeight)); + GrGLenum error = CHECK_ALLOC_ERROR(&interface); + if (error != GR_GL_NO_ERROR) { + return false; + } else { + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + const void* currentMipData = texels[currentMipLevel].fPixels; + if (currentMipData == nullptr) { + // Compressed textures require data for every level + return false; + } + + 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 < mipLevelCount; currentMipLevel++) { + const void* currentMipData = texels[currentMipLevel].fPixels; + if (currentMipData == nullptr) { + // Compressed textures require data for every level + return false; + } + + 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), + currentMipData)); + + 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. * @@ -1069,6 +1179,9 @@ bool GrGLGpu::uploadTexData(GrPixelConfig texConfig, int texWidth, int texHeight UploadType uploadType, int left, int top, int width, int height, GrPixelConfig dataConfig, const GrMipLevel texels[], int mipLevelCount, GrMipMapsStatus* mipMapsStatus) { + // If we're uploading compressed data then we should be using uploadCompressedTexData + SkASSERT(!GrPixelConfigIsCompressed(dataConfig)); + SkASSERT(this->caps()->isConfigTexturable(texConfig)); SkDEBUGCODE( SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); @@ -1249,6 +1362,34 @@ bool GrGLGpu::uploadTexData(GrPixelConfig texConfig, int texWidth, int texHeight return succeeded; } +bool GrGLGpu::uploadCompressedTexData(GrPixelConfig texConfig, int texWidth, int texHeight, + GrGLenum target, GrPixelConfig dataConfig, + const GrMipLevel texels[], int mipLevelCount, + GrMipMapsStatus* mipMapsStatus) { + SkASSERT(this->caps()->isConfigTexturable(texConfig)); + + const GrGLInterface* interface = this->glInterface(); + const GrGLCaps& caps = this->glCaps(); + + // We only need the internal format for compressed 2D textures. + GrGLenum internalFormat; + if (!caps.getCompressedTexImageFormats(texConfig, &internalFormat)) { + return false; + } + + if (mipMapsStatus && mipLevelCount <= 1) { + *mipMapsStatus = GrMipMapsStatus::kNotAllocated; + } else { + *mipMapsStatus = GrMipMapsStatus::kValid; + } + + return allocate_and_populate_compressed_texture(texConfig, *interface, caps, target, + internalFormat, texels, mipLevelCount, + texWidth, texHeight); + + return true; +} + static bool renderbuffer_storage_msaa(const GrGLContext& ctx, int sampleCount, GrGLenum format, @@ -1419,7 +1560,8 @@ sk_sp<GrTexture> GrGLGpu::onCreateTexture(const GrSurfaceDesc& desc, return return_null_texture(); } - bool performClear = (desc.fFlags & kPerformInitialClear_GrSurfaceFlag); + bool performClear = (desc.fFlags & kPerformInitialClear_GrSurfaceFlag) && + !GrPixelConfigIsCompressed(desc.fConfig); GrMipLevel zeroLevel; std::unique_ptr<uint8_t[]> zeros; @@ -1656,9 +1798,18 @@ bool GrGLGpu::createTextureImpl(const GrSurfaceDesc& desc, GrGLTextureInfo* info *initialTexParams = set_initial_texture_params(this->glInterface(), *info); } - if (!this->uploadTexData(desc.fConfig, desc.fWidth, desc.fHeight, info->fTarget, - kNewTexture_UploadType, 0, 0, desc.fWidth, desc.fHeight, desc.fConfig, - texels, mipLevelCount, mipMapsStatus)) { + bool success = false; + if (GrPixelConfigIsCompressed(desc.fConfig)) { + SkASSERT(!renderTarget); + success = this->uploadCompressedTexData(desc.fConfig, desc.fWidth, desc.fHeight, + info->fTarget, desc.fConfig, + texels, mipLevelCount, mipMapsStatus); + } else { + success = this->uploadTexData(desc.fConfig, desc.fWidth, desc.fHeight, info->fTarget, + kNewTexture_UploadType, 0, 0, desc.fWidth, desc.fHeight, + desc.fConfig, texels, mipLevelCount, mipMapsStatus); + } + if (!success) { GL_CALL(DeleteTextures(1, &(info->fID))); return false; } @@ -4042,62 +4193,78 @@ GrBackendTexture GrGLGpu::createTestingOnlyBackendTexture(const void* pixels, in GL_CALL(TexParameteri(info.fTarget, GR_GL_TEXTURE_WRAP_S, GR_GL_CLAMP_TO_EDGE)); GL_CALL(TexParameteri(info.fTarget, GR_GL_TEXTURE_WRAP_T, GR_GL_CLAMP_TO_EDGE)); - bool restoreGLRowLength = false; - if (trimRowBytes != rowBytes && this->glCaps().unpackRowLengthSupport()) { - GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, rowBytes / bpp)); - restoreGLRowLength = true; - } + // we have to do something special for compressed textures + if (GrPixelConfigIsCompressed(config)) { + GrGLenum internalFormat; + const GrGLInterface* interface = this->glInterface(); + const GrGLCaps& caps = this->glCaps(); + if (!caps.getCompressedTexImageFormats(config, &internalFormat)) { + return GrBackendTexture(); + } - GrGLenum internalFormat; - GrGLenum externalFormat; - GrGLenum externalType; + GrMipLevel mipLevel = { pixels, rowBytes }; + if (!allocate_and_populate_compressed_texture(config, *interface, caps, info.fTarget, + internalFormat, &mipLevel, 1, + w, h)) { + return GrBackendTexture(); + } + } else { + bool restoreGLRowLength = false; + if (trimRowBytes != rowBytes && this->glCaps().unpackRowLengthSupport()) { + GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, rowBytes / bpp)); + restoreGLRowLength = true; + } - if (!this->glCaps().getTexImageFormats(config, config, &internalFormat, &externalFormat, - &externalType)) { - return GrBackendTexture(); // invalid - } + GrGLenum internalFormat; + GrGLenum externalFormat; + GrGLenum externalType; - info.fFormat = this->glCaps().configSizedInternalFormat(config); + if (!this->glCaps().getTexImageFormats(config, config, &internalFormat, &externalFormat, + &externalType)) { + return GrBackendTexture(); // invalid + } - this->unbindCpuToGpuXferBuffer(); + info.fFormat = this->glCaps().configSizedInternalFormat(config); - // Figure out the number of mip levels. - int mipLevels = 1; - if (GrMipMapped::kYes == mipMapped) { - mipLevels = SkMipMap::ComputeLevelCount(w, h) + 1; - } - - size_t baseLayerSize = bpp * w * h; - SkAutoMalloc defaultStorage(baseLayerSize); - if (!pixels) { - // Fill in the texture with all zeros so we don't have random garbage - pixels = defaultStorage.get(); - memset(defaultStorage.get(), 0, baseLayerSize); - } else if (trimRowBytes != rowBytes && !restoreGLRowLength) { - // We weren't able to use GR_GL_UNPACK_ROW_LENGTH so make a copy - char* copy = (char*) defaultStorage.get(); - for (int y = 0; y < h; ++y) { - memcpy(©[y*trimRowBytes], &((const char*)pixels)[y*rowBytes], trimRowBytes); + this->unbindCpuToGpuXferBuffer(); + + // Figure out the number of mip levels. + int mipLevels = 1; + if (GrMipMapped::kYes == mipMapped) { + mipLevels = SkMipMap::ComputeLevelCount(w, h) + 1; } - pixels = copy; - } - int width = w; - int height = h; - for (int i = 0; i < mipLevels; ++i) { - GL_CALL(TexImage2D(info.fTarget, i, internalFormat, width, height, 0, externalFormat, - externalType, pixels)); - width = SkTMax(1, width / 2); - height = SkTMax(1, height / 2); + size_t baseLayerSize = bpp * w * h; + SkAutoMalloc defaultStorage(baseLayerSize); + if (!pixels) { + // Fill in the texture with all zeros so we don't have random garbage + pixels = defaultStorage.get(); + memset(defaultStorage.get(), 0, baseLayerSize); + } else if (trimRowBytes != rowBytes && !restoreGLRowLength) { + // We weren't able to use GR_GL_UNPACK_ROW_LENGTH so make a copy + char* copy = (char*)defaultStorage.get(); + for (int y = 0; y < h; ++y) { + memcpy(©[y*trimRowBytes], &((const char*)pixels)[y*rowBytes], trimRowBytes); + } + pixels = copy; + } + + int width = w; + int height = h; + for (int i = 0; i < mipLevels; ++i) { + GL_CALL(TexImage2D(info.fTarget, i, internalFormat, width, height, 0, externalFormat, + externalType, pixels)); + width = SkTMax(1, width / 2); + height = SkTMax(1, height / 2); + } + if (restoreGLRowLength) { + GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0)); + } } // unbind the texture from the texture unit to avoid asserts GL_CALL(BindTexture(info.fTarget, 0)); - if (restoreGLRowLength) { - GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0)); - } - GrBackendTexture beTex = GrBackendTexture(w, h, mipMapped, info); // Lots of tests don't go through Skia's public interface which will set the config so for // testing we make sure we set a config here. diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h index bac91206f5..f6524b03a1 100644 --- a/src/gpu/gl/GrGLGpu.h +++ b/src/gpu/gl/GrGLGpu.h @@ -387,6 +387,13 @@ private: GrPixelConfig dataConfig, const GrMipLevel texels[], int mipLevelCount, GrMipMapsStatus* mipMapsStatus = nullptr); + // helper for onCreateCompressedTexture. Compressed textures are read-only so we + // only use this to populate a new texture. + bool uploadCompressedTexData(GrPixelConfig texConfig, int texWidth, int texHeight, + GrGLenum target, GrPixelConfig dataConfig, + const GrMipLevel texels[], int mipLevelCount, + GrMipMapsStatus* mipMapsStatus = nullptr); + bool createRenderTargetObjects(const GrSurfaceDesc&, const GrGLTextureInfo& texInfo, GrGLRenderTarget::IDDesc*); diff --git a/src/gpu/gl/GrGLTexture.cpp b/src/gpu/gl/GrGLTexture.cpp index 06bf6621ad..cbb6b5a75e 100644 --- a/src/gpu/gl/GrGLTexture.cpp +++ b/src/gpu/gl/GrGLTexture.cpp @@ -48,6 +48,9 @@ GrGLTexture::GrGLTexture(GrGLGpu* gpu, SkBudgeted budgeted, const GrSurfaceDesc& , INHERITED(gpu, desc, TextureTypeFromTarget(idDesc.fInfo.fTarget), mipMapsStatus) { this->init(desc, idDesc); this->registerWithCache(budgeted); + if (GrPixelConfigIsCompressed(desc.fConfig)) { + this->setReadOnly(); + } } GrGLTexture::GrGLTexture(GrGLGpu* gpu, Wrapped, const GrSurfaceDesc& desc, diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm index 132f455a98..68693b10d1 100644 --- a/src/gpu/mtl/GrMtlGpu.mm +++ b/src/gpu/mtl/GrMtlGpu.mm @@ -247,6 +247,10 @@ sk_sp<GrTexture> GrMtlGpu::onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted return nullptr; } + if (GrPixelConfigIsCompressed(desc.fConfig)) { + return nullptr; // TODO: add compressed texture support + } + bool renderTarget = SkToBool(desc.fFlags & kRenderTarget_GrSurfaceFlag); // This TexDesc refers to the texture that will be read by the client. Thus even if msaa is diff --git a/src/gpu/mtl/GrMtlUtil.mm b/src/gpu/mtl/GrMtlUtil.mm index 6d80fbeb1c..824bbf5da3 100644 --- a/src/gpu/mtl/GrMtlUtil.mm +++ b/src/gpu/mtl/GrMtlUtil.mm @@ -87,6 +87,13 @@ bool GrPixelConfigToMTLFormat(GrPixelConfig config, MTLPixelFormat* format) { case kAlpha_half_as_Red_GrPixelConfig: *format = MTLPixelFormatR16Float; return true; + case kRGB_ETC1_GrPixelConfig: +#ifdef SK_BUILD_FOR_IOS + *format = MTLPixelFormatETC2_RGB8; + return true; +#else + return false; +#endif } SK_ABORT("Unexpected config"); return false; @@ -123,6 +130,10 @@ GrPixelConfig GrMTLFormatToPixelConfig(MTLPixelFormat format) { return kRGBA_half_GrPixelConfig; case MTLPixelFormatR16Float: return kAlpha_half_GrPixelConfig; +#ifdef SK_BUILD_FOR_IOS + case MTLPixelFormatETC2_RGB8: + return kRGB_ETC1_GrPixelConfig; +#endif default: return kUnknown_GrPixelConfig; } diff --git a/src/gpu/ops/GrCopySurfaceOp.cpp b/src/gpu/ops/GrCopySurfaceOp.cpp index 4da757ed42..9e3a5215fe 100644 --- a/src/gpu/ops/GrCopySurfaceOp.cpp +++ b/src/gpu/ops/GrCopySurfaceOp.cpp @@ -77,6 +77,9 @@ std::unique_ptr<GrOp> GrCopySurfaceOp::Make(GrContext* context, &clippedSrcRect, &clippedDstPoint)) { return nullptr; } + if (GrPixelConfigIsCompressed(dstProxy->config())) { + return nullptr; + } GrOpMemoryPool* pool = context->contextPriv().opMemoryPool(); diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp index ba4d1a63bf..e115ea06e5 100644 --- a/src/gpu/vk/GrVkCaps.cpp +++ b/src/gpu/vk/GrVkCaps.cpp @@ -30,6 +30,7 @@ GrVkCaps::GrVkCaps(const GrContextOptions& contextOptions, const GrVkInterface* fDiscardRenderTargetSupport = true; fReuseScratchTextures = true; //TODO: figure this out fGpuTracingSupport = false; //TODO: figure this out + fCompressedTexSubImageSupport = true; fOversizedStencilSupport = false; //TODO: figure this out fInstanceAttribSupport = true; diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp index 1bb106275e..619394ded4 100644 --- a/src/gpu/vk/GrVkGpu.cpp +++ b/src/gpu/vk/GrVkGpu.cpp @@ -371,6 +371,7 @@ bool GrVkGpu::onWritePixels(GrSurface* surface, int left, int top, int width, in return false; } + SkASSERT(!GrPixelConfigIsCompressed(vkTex->config())); bool success = false; bool linearTiling = vkTex->isLinearTiled(); if (linearTiling) { @@ -401,6 +402,9 @@ bool GrVkGpu::onWritePixels(GrSurface* surface, int left, int top, int width, in bool GrVkGpu::onTransferPixels(GrTexture* texture, int left, int top, int width, int height, GrColorType bufferColorType, GrBuffer* transferBuffer, size_t bufferOffset, size_t rowBytes) { + // Can't transfer compressed data + SkASSERT(!GrPixelConfigIsCompressed(texture->config())); + // Vulkan only supports 4-byte aligned offsets if (SkToBool(bufferOffset & 0x2)) { return false; @@ -512,6 +516,10 @@ bool GrVkGpu::uploadTexDataLinear(GrVkTexture* tex, int left, int top, int width SkASSERT(data); SkASSERT(tex->isLinearTiled()); + // If we're uploading compressed data then we should be using uploadCompressedTexData + SkASSERT(!GrPixelConfigIsCompressed(GrColorTypeToPixelConfig(dataColorType, + GrSRGBEncoded::kNo))); + SkDEBUGCODE( SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); SkIRect bounds = SkIRect::MakeWH(tex->width(), tex->height()); @@ -570,6 +578,10 @@ bool GrVkGpu::uploadTexDataOptimal(GrVkTexture* tex, int left, int top, int widt // first. SkASSERT(1 == mipLevelCount || mipLevelCount == (tex->texturePriv().maxMipMapLevel() + 1)); + // If we're uploading compressed data then we should be using uploadCompressedTexData + SkASSERT(!GrPixelConfigIsCompressed(GrColorTypeToPixelConfig(dataColorType, + GrSRGBEncoded::kNo))); + if (width == 0 || height == 0) { return false; } @@ -751,6 +763,133 @@ bool GrVkGpu::uploadTexDataOptimal(GrVkTexture* tex, int left, int top, int widt return true; } +// It's probably possible to roll this into uploadTexDataOptimal, +// but for now it's easier to maintain as a separate entity. +bool GrVkGpu::uploadTexDataCompressed(GrVkTexture* tex, int left, int top, int width, int height, + GrColorType dataColorType, const GrMipLevel texels[], + int mipLevelCount) { + SkASSERT(!tex->isLinearTiled()); + // For now the assumption is that our rect is the entire texture. + // Compressed textures are read-only so this should be a reasonable assumption. + SkASSERT(0 == left && 0 == top && width == tex->width() && height == tex->height()); + + // We assume that if the texture has mip levels, we either upload to all the levels or just the + // first. + SkASSERT(1 == mipLevelCount || mipLevelCount == (tex->texturePriv().maxMipMapLevel() + 1)); + + SkASSERT(GrPixelConfigIsCompressed(GrColorTypeToPixelConfig(dataColorType, + GrSRGBEncoded::kNo))); + + if (width == 0 || height == 0) { + return false; + } + + if (GrPixelConfigToColorType(tex->config()) != dataColorType) { + return false; + } + + SkASSERT(this->caps()->isConfigTexturable(tex->config())); + + SkTArray<size_t> individualMipOffsets(mipLevelCount); + individualMipOffsets.push_back(0); + size_t combinedBufferSize = GrCompressedFormatDataSize(tex->config(), width, height); + int currentWidth = width; + int currentHeight = height; + if (!texels[0].fPixels) { + return false; + } + + // We assume that the alignment for any compressed format is at least 4 bytes and so we don't + // need to worry about alignment issues. For example, each block in ETC1 is 8 bytes. + for (int currentMipLevel = 1; currentMipLevel < mipLevelCount; currentMipLevel++) { + currentWidth = SkTMax(1, currentWidth / 2); + currentHeight = SkTMax(1, currentHeight / 2); + + if (texels[currentMipLevel].fPixels) { + const size_t dataSize = GrCompressedFormatDataSize(tex->config(), currentWidth, + currentHeight); + individualMipOffsets.push_back(combinedBufferSize); + combinedBufferSize += dataSize; + } else { + return false; + } + } + if (0 == combinedBufferSize) { + // We don't have any data to upload so fail (compressed textures are read-only). + return false; + } + + // allocate buffer to hold our mip data + GrVkTransferBuffer* transferBuffer = + GrVkTransferBuffer::Create(this, combinedBufferSize, GrVkBuffer::kCopyRead_Type); + if (!transferBuffer) { + return false; + } + + int uploadLeft = left; + int uploadTop = top; + GrVkTexture* uploadTexture = tex; + + char* buffer = (char*)transferBuffer->map(); + SkTArray<VkBufferImageCopy> regions(mipLevelCount); + + currentWidth = width; + currentHeight = height; + int layerHeight = uploadTexture->height(); + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + if (texels[currentMipLevel].fPixels) { + // Again, we're assuming that our rect is the entire texture + SkASSERT(currentHeight == layerHeight); + SkASSERT(0 == uploadLeft && 0 == uploadTop); + + const size_t dataSize = GrCompressedFormatDataSize(tex->config(), currentWidth, + currentHeight); + + // copy data into the buffer, skipping the trailing bytes + char* dst = buffer + individualMipOffsets[currentMipLevel]; + const char* src = (const char*)texels[currentMipLevel].fPixels; + memcpy(dst, src, dataSize); + + VkBufferImageCopy& region = regions.push_back(); + memset(®ion, 0, sizeof(VkBufferImageCopy)); + region.bufferOffset = transferBuffer->offset() + individualMipOffsets[currentMipLevel]; + region.bufferRowLength = currentWidth; + region.bufferImageHeight = currentHeight; + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, SkToU32(currentMipLevel), 0, 1 }; + region.imageOffset = { uploadLeft, uploadTop, 0 }; + region.imageExtent = { (uint32_t)currentWidth, (uint32_t)currentHeight, 1 }; + } + currentWidth = SkTMax(1, currentWidth / 2); + currentHeight = SkTMax(1, currentHeight / 2); + layerHeight = currentHeight; + } + + // no need to flush non-coherent memory, unmap will do that for us + transferBuffer->unmap(); + + // Change layout of our target so it can be copied to + uploadTexture->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + // Copy the buffer to the image + fCurrentCmdBuffer->copyBufferToImage(this, + transferBuffer, + uploadTexture, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + regions.count(), + regions.begin()); + transferBuffer->unref(); + + if (1 == mipLevelCount) { + tex->texturePriv().markMipMapsDirty(); + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////// sk_sp<GrTexture> GrVkGpu::onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted, const GrMipLevel texels[], int mipLevelCount) { @@ -811,16 +950,24 @@ sk_sp<GrTexture> GrVkGpu::onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted return nullptr; } + bool isCompressed = GrPixelConfigIsCompressed(desc.fConfig); auto colorType = GrPixelConfigToColorType(desc.fConfig); if (mipLevelCount) { - if (!this->uploadTexDataOptimal(tex.get(), 0, 0, desc.fWidth, desc.fHeight, colorType, - texels, mipLevelCount)) { + bool success; + if (isCompressed) { + success = this->uploadTexDataCompressed(tex.get(), 0, 0, desc.fWidth, desc.fHeight, + colorType, texels, mipLevelCount); + } else { + success = this->uploadTexDataOptimal(tex.get(), 0, 0, desc.fWidth, desc.fHeight, + colorType, texels, mipLevelCount); + } + if (!success) { tex->unref(); return nullptr; } } - if (desc.fFlags & kPerformInitialClear_GrSurfaceFlag) { + if (SkToBool(desc.fFlags & kPerformInitialClear_GrSurfaceFlag) && !isCompressed) { VkClearColorValue zeroClearColor; memset(&zeroClearColor, 0, sizeof(zeroClearColor)); VkImageSubresourceRange range; @@ -1276,6 +1423,10 @@ bool GrVkGpu::createTestingOnlyVkImage(GrPixelConfig config, int w, int h, bool SkTArray<size_t> individualMipOffsets(mipLevels); individualMipOffsets.push_back(0); size_t combinedBufferSize = w * bpp * h; + if (GrPixelConfigIsCompressed(config)) { + combinedBufferSize = GrCompressedFormatDataSize(config, w, h); + bpp = 4; // we have at least this alignment, which will pass the code below + } int currentWidth = w; int currentHeight = h; // The alignment must be at least 4 bytes and a multiple of the bytes per pixel of the image @@ -1287,7 +1438,12 @@ bool GrVkGpu::createTestingOnlyVkImage(GrPixelConfig config, int w, int h, bool currentWidth = SkTMax(1, currentWidth / 2); currentHeight = SkTMax(1, currentHeight / 2); - const size_t trimmedSize = currentWidth * bpp * currentHeight; + size_t trimmedSize; + if (GrPixelConfigIsCompressed(config)) { + trimmedSize = GrCompressedFormatDataSize(config, currentWidth, currentHeight); + } else { + trimmedSize = currentWidth * bpp * currentHeight; + } const size_t alignmentDiff = combinedBufferSize & alignmentMask; if (alignmentDiff != 0) { combinedBufferSize += alignmentMask - alignmentDiff + 1; @@ -1329,10 +1485,19 @@ bool GrVkGpu::createTestingOnlyVkImage(GrPixelConfig config, int w, int h, bool currentHeight = h; for (uint32_t currentMipLevel = 0; currentMipLevel < mipLevels; currentMipLevel++) { SkASSERT(0 == currentMipLevel || !srcData); - size_t currentRowBytes = bpp * currentWidth; size_t bufferOffset = individualMipOffsets[currentMipLevel]; - if (!copy_testing_data(this, srcData, bufferAlloc, bufferOffset, srcRowBytes, - currentRowBytes, trimRowBytes, currentHeight)) { + bool result; + if (GrPixelConfigIsCompressed(config)) { + size_t levelSize = GrCompressedFormatDataSize(config, currentWidth, currentHeight); + size_t currentRowBytes = levelSize / currentHeight; + result = copy_testing_data(this, srcData, bufferAlloc, bufferOffset, currentRowBytes, + currentRowBytes, currentRowBytes, currentHeight); + } else { + size_t currentRowBytes = bpp * currentWidth; + result = copy_testing_data(this, srcData, bufferAlloc, bufferOffset, srcRowBytes, + currentRowBytes, trimRowBytes, currentHeight); + } + if (!result) { GrVkMemory::FreeImageMemory(this, false, alloc); VK_CALL(DestroyImage(fDevice, image, nullptr)); GrVkMemory::FreeBufferMemory(this, GrVkBuffer::kCopyRead_Type, bufferAlloc); @@ -2209,4 +2374,3 @@ void GrVkGpu::storeVkPipelineCacheData() { this->resourceProvider().storePipelineCacheData(); } } - diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h index acd814cab5..6358560141 100644 --- a/src/gpu/vk/GrVkGpu.h +++ b/src/gpu/vk/GrVkGpu.h @@ -247,7 +247,9 @@ private: GrColorType colorType, const void* data, size_t rowBytes); bool uploadTexDataOptimal(GrVkTexture* tex, int left, int top, int width, int height, GrColorType colorType, const GrMipLevel texels[], int mipLevelCount); - + bool uploadTexDataCompressed(GrVkTexture* tex, int left, int top, int width, int height, + GrColorType dataColorType, const GrMipLevel texels[], + int mipLevelCount); void resolveImage(GrSurface* dst, GrVkRenderTarget* src, const SkIRect& srcRect, const SkIPoint& dstPoint); diff --git a/src/gpu/vk/GrVkTexture.cpp b/src/gpu/vk/GrVkTexture.cpp index fe9723d227..802bbcd6fc 100644 --- a/src/gpu/vk/GrVkTexture.cpp +++ b/src/gpu/vk/GrVkTexture.cpp @@ -31,6 +31,9 @@ GrVkTexture::GrVkTexture(GrVkGpu* gpu, , fTextureView(view) { SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == info.fLevelCount)); this->registerWithCache(budgeted); + if (GrPixelConfigIsCompressed(desc.fConfig)) { + this->setReadOnly(); + } } GrVkTexture::GrVkTexture(GrVkGpu* gpu, diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp index 90798c93fb..6524bb6ee5 100644 --- a/src/gpu/vk/GrVkUtil.cpp +++ b/src/gpu/vk/GrVkUtil.cpp @@ -69,6 +69,10 @@ bool GrPixelConfigToVkFormat(GrPixelConfig config, VkFormat* format) { case kRGBA_half_GrPixelConfig: *format = VK_FORMAT_R16G16B16A16_SFLOAT; return true; + case kRGB_ETC1_GrPixelConfig: + // converting to ETC2 which is a superset of ETC1 + *format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; + return true; case kAlpha_half_GrPixelConfig: // fall through case kAlpha_half_as_Red_GrPixelConfig: *format = VK_FORMAT_R16_SFLOAT; @@ -106,6 +110,8 @@ bool GrVkFormatPixelConfigPairIsValid(VkFormat format, GrPixelConfig config) { kAlpha_8_as_Red_GrPixelConfig == config || kGray_8_GrPixelConfig == config || kGray_8_as_Red_GrPixelConfig == config; + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + return kRGB_ETC1_GrPixelConfig == config; case VK_FORMAT_R32G32B32A32_SFLOAT: return kRGBA_float_GrPixelConfig == config; case VK_FORMAT_R32G32_SFLOAT: diff --git a/tests/GrSurfaceTest.cpp b/tests/GrSurfaceTest.cpp index 15d64410db..901c05dd7b 100644 --- a/tests/GrSurfaceTest.cpp +++ b/tests/GrSurfaceTest.cpp @@ -98,6 +98,7 @@ DEF_GPUTEST_FOR_ALL_CONTEXTS(GrSurfaceRenderability, reporter, ctxInfo) { kAlpha_half_GrPixelConfig, kAlpha_half_as_Red_GrPixelConfig, kRGBA_half_GrPixelConfig, + kRGB_ETC1_GrPixelConfig, }; GR_STATIC_ASSERT(kGrPixelConfigCnt == SK_ARRAY_COUNT(configs)); diff --git a/tests/ProxyTest.cpp b/tests/ProxyTest.cpp index a52ed8e0c0..02b483e8ee 100644 --- a/tests/ProxyTest.cpp +++ b/tests/ProxyTest.cpp @@ -113,10 +113,16 @@ 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, kRGBA_1010102_GrPixelConfig }) { + kRGBA_8888_GrPixelConfig, kRGBA_1010102_GrPixelConfig, + kRGB_ETC1_GrPixelConfig }) { for (auto fit : { SkBackingFit::kExact, SkBackingFit::kApprox }) { for (auto budgeted : { SkBudgeted::kYes, SkBudgeted::kNo }) { for (auto numSamples : {1, 4, 16, 128}) { + // We don't have recycling support for compressed textures + if (GrPixelConfigIsCompressed(config) && SkBackingFit::kApprox == fit) { + continue; + } + GrSurfaceDesc desc; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fWidth = widthHeight; diff --git a/third_party/etc1/LICENSE b/third_party/etc1/LICENSE new file mode 100644 index 0000000000..64635a4089 --- /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 0000000000..029c8014b9 --- /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 0000000000..65d5140a74 --- /dev/null +++ b/third_party/etc1/etc1.cpp @@ -0,0 +1,680 @@ +// 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 <cstring> + +/* 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 + <internalformat> 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; + } else { + r2 = g2 = b2 = 0; // to shut the compiler up + } + } + + 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 0000000000..d66ca9d3e9 --- /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/check-headers-self-sufficient b/tools/check-headers-self-sufficient index fe8a8acc18..6212839e51 100755 --- a/tools/check-headers-self-sufficient +++ b/tools/check-headers-self-sufficient @@ -78,6 +78,7 @@ all_header_args = [ '-Itools/flags', '-Itools/gpu', '-Itools/timer', + '-Ithird_party/etc1', '-Ithird_party/externals/jsoncpp/include', '-Ithird_party/externals/libjpeg-turbo', '-Ithird_party/externals/sfntly/cpp/src', |