diff --git a/cocos/base/CCConfiguration.cpp b/cocos/base/CCConfiguration.cpp index 0553309748..e551ec19d2 100644 --- a/cocos/base/CCConfiguration.cpp +++ b/cocos/base/CCConfiguration.cpp @@ -127,18 +127,12 @@ void Configuration::gatherGPUInfo() _supportsNPOT = true; _valueDict["supports_NPOT"] = Value(_supportsNPOT); + _supportsETC1 = _deviceInfo->checkForFeatureSupported(backend::FeatureType::ETC1); + _valueDict["supports_ETC1"] = Value(_supportsETC1); + _supportsETC2 = _deviceInfo->checkForFeatureSupported(backend::FeatureType::ETC2); _valueDict["supports_ETC2"] = Value(_supportsETC2); -#if defined(_WIN32) - // GL_ETC1_RGB8_OES is not available in any desktop GL extension but the compression - // format is forwards compatible so just use the ETC2 format. - _supportsETC1 = _deviceInfo->checkForFeatureSupported(backend::FeatureType::ETC1) || _supportsETC2; -#else - _supportsETC1 = _deviceInfo->checkForFeatureSupported(backend::FeatureType::ETC1); -#endif - _valueDict["supports_ETC1"] = Value(_supportsETC1); - _supportsS3TC = _deviceInfo->checkForFeatureSupported(backend::FeatureType::S3TC); _valueDict["supports_S3TC"] = Value(_supportsS3TC); diff --git a/cocos/base/etc2.cpp b/cocos/base/etc2.cpp index 5d50ad636d..39f8cb5f1f 100644 --- a/cocos/base/etc2.cpp +++ b/cocos/base/etc2.cpp @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2020 c4games.com http://www.cocos2d-x.org @@ -76,3 +77,933 @@ etc2_uint32 etc2_pkm_get_height(const etc2_byte* pHeader){ etc2_uint32 etc2_pkm_get_format(const uint8_t* pHeader) { return readBEUint16(pHeader + ETC2_PKM_FORMAT_OFFSET); } + +/// The etc2 software decode implementation is modified from angleproject +/// only support ETC2_RGB and ETC2_RGBA +// +// Copyright 2013 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +#define UNREACHABLE() + +namespace { + namespace priv { + template + inline T* OffsetDataPointer(uint8_t* data, size_t y, size_t z, size_t rowPitch, size_t depthPitch) + { + return reinterpret_cast(data + (y * rowPitch) + (z * depthPitch)); + } + + template + inline const T* OffsetDataPointer(const uint8_t* data, size_t y, size_t z, size_t rowPitch, size_t depthPitch) + { + return reinterpret_cast(data + (y * rowPitch) + (z * depthPitch)); + } + } + + namespace gl { + template const _Ty& clamp(const _Ty& v, const _Ty& lo, const _Ty& hi) + { + assert(!(hi < lo)); + return v < lo ? lo : hi < v ? hi : v; + } + // https://stackoverflow.com/a/37581284 + template + static constexpr double normalize(T value) + { + return value < 0 ? -static_cast(value) / std::numeric_limits::min() + : static_cast(value) / std::numeric_limits::max(); + } + + template + destType bitCast(const sourceType& source) + { + size_t copySize = std::min(sizeof(destType), sizeof(sourceType)); + destType output; + memcpy(&output, &source, copySize); + return output; + } + + inline unsigned short float32ToFloat16(float fp32) + { + unsigned int fp32i = bitCast(fp32); + unsigned int sign = (fp32i & 0x80000000) >> 16; + unsigned int abs = fp32i & 0x7FFFFFFF; + + if (abs > 0x7F800000) + { // NaN + return 0x7FFF; + } + else if (abs > 0x47FFEFFF) + { // Infinity + return static_cast(sign | 0x7C00); + } + else if (abs < 0x38800000) // Denormal + { + unsigned int mantissa = (abs & 0x007FFFFF) | 0x00800000; + int e = 113 - (abs >> 23); + + if (e < 24) + { + abs = mantissa >> e; + } + else + { + abs = 0; + } + + return static_cast(sign | (abs + 0x00000FFF + ((abs >> 13) & 1)) >> 13); + } + else + { + return static_cast( + sign | (abs + 0xC8000000 + 0x00000FFF + ((abs >> 13) & 1)) >> 13); + } + } + } + + /* The ETC format load from google/angle */ + struct R8G8B8A8 + { + uint8_t R; + uint8_t G; + uint8_t B; + uint8_t A; + }; + + /* The ETC format load from google/angle */ + + using IntensityModifier = const int[4]; + + // Table 3.17.2 sorted according to table 3.17.3 + // clang-format off + static IntensityModifier intensityModifierDefault[] = + { + { 2, 8, -2, -8 }, + { 5, 17, -5, -17 }, + { 9, 29, -9, -29 }, + { 13, 42, -13, -42 }, + { 18, 60, -18, -60 }, + { 24, 80, -24, -80 }, + { 33, 106, -33, -106 }, + { 47, 183, -47, -183 }, + }; + // clang-format on + + // Table C.12, intensity modifier for non opaque punchthrough alpha + // clang-format off + static IntensityModifier intensityModifierNonOpaque[] = + { + { 0, 8, 0, -8 }, + { 0, 17, 0, -17 }, + { 0, 29, 0, -29 }, + { 0, 42, 0, -42 }, + { 0, 60, 0, -60 }, + { 0, 80, 0, -80 }, + { 0, 106, 0, -106 }, + { 0, 183, 0, -183 }, + }; + // clang-format on + + static const int kNumPixelsInBlock = 16; + + struct ETC2Block + { + // Decodes unsigned single or dual channel ETC2 block to 8-bit color + void decodeAsSingleETC2Channel(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t destPixelStride, + size_t destRowPitch, + bool isSigned) const + { + for (size_t j = 0; j < 4 && (y + j) < h; j++) + { + uint8_t* row = dest + (j * destRowPitch); + for (size_t i = 0; i < 4 && (x + i) < w; i++) + { + uint8_t* pixel = row + (i * destPixelStride); + if (isSigned) + { + *pixel = clampSByte(getSingleETC2Channel(i, j, isSigned)); + } + else + { + *pixel = clampByte(getSingleETC2Channel(i, j, isSigned)); + } + } + } + } + + // Decodes RGB block to rgba8 + void decodeAsRGB(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t destRowPitch, + const uint8_t alphaValues[4][4], + bool punchThroughAlpha) const + { + bool opaqueBit = u.idht.mode.idm.diffbit; + bool nonOpaquePunchThroughAlpha = punchThroughAlpha && !opaqueBit; + // Select mode + if (u.idht.mode.idm.diffbit || punchThroughAlpha) + { + const auto& block = u.idht.mode.idm.colors.diff; + int r = (block.R + block.dR); + int g = (block.G + block.dG); + int b = (block.B + block.dB); + if (r < 0 || r > 31) + { + decodeTBlock(dest, x, y, w, h, destRowPitch, alphaValues, + nonOpaquePunchThroughAlpha); + } + else if (g < 0 || g > 31) + { + decodeHBlock(dest, x, y, w, h, destRowPitch, alphaValues, + nonOpaquePunchThroughAlpha); + } + else if (b < 0 || b > 31) + { + decodePlanarBlock(dest, x, y, w, h, destRowPitch, alphaValues); + } + else + { + decodeDifferentialBlock(dest, x, y, w, h, destRowPitch, alphaValues, + nonOpaquePunchThroughAlpha); + } + } + else + { + decodeIndividualBlock(dest, x, y, w, h, destRowPitch, alphaValues, + nonOpaquePunchThroughAlpha); + } + } + + private: + union + { + // Individual, differential, H and T modes + struct + { + union + { + // Individual and differential modes + struct + { + union + { + struct // Individual colors + { + unsigned char R2 : 4; + unsigned char R1 : 4; + unsigned char G2 : 4; + unsigned char G1 : 4; + unsigned char B2 : 4; + unsigned char B1 : 4; + } indiv; + struct // Differential colors + { + signed char dR : 3; + unsigned char R : 5; + signed char dG : 3; + unsigned char G : 5; + signed char dB : 3; + unsigned char B : 5; + } diff; + } colors; + bool flipbit : 1; + bool diffbit : 1; + unsigned char cw2 : 3; + unsigned char cw1 : 3; + } idm; + // T mode + struct + { + // Byte 1 + unsigned char TR1b : 2; + unsigned char TdummyB : 1; + unsigned char TR1a : 2; + unsigned char TdummyA : 3; + // Byte 2 + unsigned char TB1 : 4; + unsigned char TG1 : 4; + // Byte 3 + unsigned char TG2 : 4; + unsigned char TR2 : 4; + // Byte 4 + unsigned char Tdb : 1; + bool Tflipbit : 1; + unsigned char Tda : 2; + unsigned char TB2 : 4; + } tm; + // H mode + struct + { + // Byte 1 + unsigned char HG1a : 3; + unsigned char HR1 : 4; + unsigned char HdummyA : 1; + // Byte 2 + unsigned char HB1b : 2; + unsigned char HdummyC : 1; + unsigned char HB1a : 1; + unsigned char HG1b : 1; + unsigned char HdummyB : 3; + // Byte 3 + unsigned char HG2a : 3; + unsigned char HR2 : 4; + unsigned char HB1c : 1; + // Byte 4 + unsigned char Hdb : 1; + bool Hflipbit : 1; + unsigned char Hda : 1; + unsigned char HB2 : 4; + unsigned char HG2b : 1; + } hm; + } mode; + unsigned char pixelIndexMSB[2]; + unsigned char pixelIndexLSB[2]; + } idht; + // planar mode + struct + { + // Byte 1 + unsigned char GO1 : 1; + unsigned char RO : 6; + unsigned char PdummyA : 1; + // Byte 2 + unsigned char BO1 : 1; + unsigned char GO2 : 6; + unsigned char PdummyB : 1; + // Byte 3 + unsigned char BO3a : 2; + unsigned char PdummyD : 1; + unsigned char BO2 : 2; + unsigned char PdummyC : 3; + // Byte 4 + unsigned char RH2 : 1; + bool Pflipbit : 1; + unsigned char RH1 : 5; + unsigned char BO3b : 1; + // Byte 5 + unsigned char BHa : 1; + unsigned char GH : 7; + // Byte 6 + unsigned char RVa : 3; + unsigned char BHb : 5; + // Byte 7 + unsigned char GVa : 5; + unsigned char RVb : 3; + // Byte 8 + unsigned char BV : 6; + unsigned char GVb : 2; + } pblk; + // Single channel block + struct + { + union + { + unsigned char us; + signed char s; + } base_codeword; + unsigned char table_index : 4; + unsigned char multiplier : 4; + unsigned char mc1 : 2; + unsigned char mb : 3; + unsigned char ma : 3; + unsigned char mf1 : 1; + unsigned char me : 3; + unsigned char md : 3; + unsigned char mc2 : 1; + unsigned char mh : 3; + unsigned char mg : 3; + unsigned char mf2 : 2; + unsigned char mk1 : 2; + unsigned char mj : 3; + unsigned char mi : 3; + unsigned char mn1 : 1; + unsigned char mm : 3; + unsigned char ml : 3; + unsigned char mk2 : 1; + unsigned char mp : 3; + unsigned char mo : 3; + unsigned char mn2 : 2; + } scblk; + } u; + + static unsigned char clampByte(int value) + { + return static_cast(gl::clamp(value, 0, 255)); + } + + static signed char clampSByte(int value) + { + return static_cast(gl::clamp(value, -128, 127)); + } + + template + static T renormalizeEAC(int value) + { + int upper = 0; + int lower = 0; + int shift = 0; + + if (std::is_same::value) + { + // The spec states that -1024 invalid and should be clamped to -1023 + upper = 1023; + lower = -1023; + shift = 5; + } + else if (std::is_same::value) + { + upper = 2047; + lower = 0; + shift = 5; + } + else + { + // We currently only support renormalizing int16_t or uint16_t + UNREACHABLE(); + } + + return static_cast(gl::clamp(value, lower, upper)) << shift; + } + + static R8G8B8A8 createRGBA(int red, int green, int blue, int alpha) + { + R8G8B8A8 rgba; + rgba.R = clampByte(red); + rgba.G = clampByte(green); + rgba.B = clampByte(blue); + rgba.A = clampByte(alpha); + return rgba; + } + + static R8G8B8A8 createRGBA(int red, int green, int blue) + { + return createRGBA(red, green, blue, 255); + } + + static int extend_4to8bits(int x) { return (x << 4) | x; } + static int extend_5to8bits(int x) { return (x << 3) | (x >> 2); } + static int extend_6to8bits(int x) { return (x << 2) | (x >> 4); } + static int extend_7to8bits(int x) { return (x << 1) | (x >> 6); } + + void decodeIndividualBlock(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t destRowPitch, + const uint8_t alphaValues[4][4], + bool nonOpaquePunchThroughAlpha) const + { + const auto& block = u.idht.mode.idm.colors.indiv; + int r1 = extend_4to8bits(block.R1); + int g1 = extend_4to8bits(block.G1); + int b1 = extend_4to8bits(block.B1); + int r2 = extend_4to8bits(block.R2); + int g2 = extend_4to8bits(block.G2); + int b2 = extend_4to8bits(block.B2); + decodeIndividualOrDifferentialBlock(dest, x, y, w, h, destRowPitch, r1, g1, b1, r2, g2, b2, + alphaValues, nonOpaquePunchThroughAlpha); + } + + void decodeDifferentialBlock(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t destRowPitch, + const uint8_t alphaValues[4][4], + bool nonOpaquePunchThroughAlpha) const + { + const auto& block = u.idht.mode.idm.colors.diff; + int b1 = extend_5to8bits(block.B); + int g1 = extend_5to8bits(block.G); + int r1 = extend_5to8bits(block.R); + int r2 = extend_5to8bits(block.R + block.dR); + int g2 = extend_5to8bits(block.G + block.dG); + int b2 = extend_5to8bits(block.B + block.dB); + decodeIndividualOrDifferentialBlock(dest, x, y, w, h, destRowPitch, r1, g1, b1, r2, g2, b2, + alphaValues, nonOpaquePunchThroughAlpha); + } + + void decodeIndividualOrDifferentialBlock(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t destRowPitch, + int r1, + int g1, + int b1, + int r2, + int g2, + int b2, + const uint8_t alphaValues[4][4], + bool nonOpaquePunchThroughAlpha) const + { + const IntensityModifier* intensityModifier = + nonOpaquePunchThroughAlpha ? intensityModifierNonOpaque : intensityModifierDefault; + + R8G8B8A8 subblockColors0[4]; + R8G8B8A8 subblockColors1[4]; + for (size_t modifierIdx = 0; modifierIdx < 4; modifierIdx++) + { + const int i1 = intensityModifier[u.idht.mode.idm.cw1][modifierIdx]; + subblockColors0[modifierIdx] = createRGBA(r1 + i1, g1 + i1, b1 + i1); + + const int i2 = intensityModifier[u.idht.mode.idm.cw2][modifierIdx]; + subblockColors1[modifierIdx] = createRGBA(r2 + i2, g2 + i2, b2 + i2); + } + + if (u.idht.mode.idm.flipbit) + { + uint8_t* curPixel = dest; + for (size_t j = 0; j < 2 && (y + j) < h; j++) + { + R8G8B8A8* row = reinterpret_cast(curPixel); + for (size_t i = 0; i < 4 && (x + i) < w; i++) + { + row[i] = subblockColors0[getIndex(i, j)]; + row[i].A = alphaValues[j][i]; + } + curPixel += destRowPitch; + } + for (size_t j = 2; j < 4 && (y + j) < h; j++) + { + R8G8B8A8* row = reinterpret_cast(curPixel); + for (size_t i = 0; i < 4 && (x + i) < w; i++) + { + row[i] = subblockColors1[getIndex(i, j)]; + row[i].A = alphaValues[j][i]; + } + curPixel += destRowPitch; + } + } + else + { + uint8_t* curPixel = dest; + for (size_t j = 0; j < 4 && (y + j) < h; j++) + { + R8G8B8A8* row = reinterpret_cast(curPixel); + for (size_t i = 0; i < 2 && (x + i) < w; i++) + { + row[i] = subblockColors0[getIndex(i, j)]; + row[i].A = alphaValues[j][i]; + } + for (size_t i = 2; i < 4 && (x + i) < w; i++) + { + row[i] = subblockColors1[getIndex(i, j)]; + row[i].A = alphaValues[j][i]; + } + curPixel += destRowPitch; + } + } + if (nonOpaquePunchThroughAlpha) + { + decodePunchThroughAlphaBlock(dest, x, y, w, h, destRowPitch); + } + } + + void decodeTBlock(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t destRowPitch, + const uint8_t alphaValues[4][4], + bool nonOpaquePunchThroughAlpha) const + { + // Table C.8, distance index for T and H modes + const auto& block = u.idht.mode.tm; + + int r1 = extend_4to8bits(block.TR1a << 2 | block.TR1b); + int g1 = extend_4to8bits(block.TG1); + int b1 = extend_4to8bits(block.TB1); + int r2 = extend_4to8bits(block.TR2); + int g2 = extend_4to8bits(block.TG2); + int b2 = extend_4to8bits(block.TB2); + + static int distance[8] = { 3, 6, 11, 16, 23, 32, 41, 64 }; + const int d = distance[block.Tda << 1 | block.Tdb]; + + const R8G8B8A8 paintColors[4] = { + createRGBA(r1, g1, b1), + createRGBA(r2 + d, g2 + d, b2 + d), + createRGBA(r2, g2, b2), + createRGBA(r2 - d, g2 - d, b2 - d), + }; + + uint8_t* curPixel = dest; + for (size_t j = 0; j < 4 && (y + j) < h; j++) + { + R8G8B8A8* row = reinterpret_cast(curPixel); + for (size_t i = 0; i < 4 && (x + i) < w; i++) + { + row[i] = paintColors[getIndex(i, j)]; + row[i].A = alphaValues[j][i]; + } + curPixel += destRowPitch; + } + + if (nonOpaquePunchThroughAlpha) + { + decodePunchThroughAlphaBlock(dest, x, y, w, h, destRowPitch); + } + } + + void decodeHBlock(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t destRowPitch, + const uint8_t alphaValues[4][4], + bool nonOpaquePunchThroughAlpha) const + { + // Table C.8, distance index for T and H modes + const auto& block = u.idht.mode.hm; + + int r1 = extend_4to8bits(block.HR1); + int g1 = extend_4to8bits(block.HG1a << 1 | block.HG1b); + int b1 = extend_4to8bits(block.HB1a << 3 | block.HB1b << 1 | block.HB1c); + int r2 = extend_4to8bits(block.HR2); + int g2 = extend_4to8bits(block.HG2a << 1 | block.HG2b); + int b2 = extend_4to8bits(block.HB2); + + static const int distance[8] = { 3, 6, 11, 16, 23, 32, 41, 64 }; + const int orderingTrickBit = + ((r1 << 16 | g1 << 8 | b1) >= (r2 << 16 | g2 << 8 | b2) ? 1 : 0); + const int d = distance[(block.Hda << 2) | (block.Hdb << 1) | orderingTrickBit]; + + const R8G8B8A8 paintColors[4] = { + createRGBA(r1 + d, g1 + d, b1 + d), + createRGBA(r1 - d, g1 - d, b1 - d), + createRGBA(r2 + d, g2 + d, b2 + d), + createRGBA(r2 - d, g2 - d, b2 - d), + }; + + uint8_t* curPixel = dest; + for (size_t j = 0; j < 4 && (y + j) < h; j++) + { + R8G8B8A8* row = reinterpret_cast(curPixel); + for (size_t i = 0; i < 4 && (x + i) < w; i++) + { + row[i] = paintColors[getIndex(i, j)]; + row[i].A = alphaValues[j][i]; + } + curPixel += destRowPitch; + } + + if (nonOpaquePunchThroughAlpha) + { + decodePunchThroughAlphaBlock(dest, x, y, w, h, destRowPitch); + } + } + + void decodePlanarBlock(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t pitch, + const uint8_t alphaValues[4][4]) const + { + int ro = extend_6to8bits(u.pblk.RO); + int go = extend_7to8bits(u.pblk.GO1 << 6 | u.pblk.GO2); + int bo = + extend_6to8bits(u.pblk.BO1 << 5 | u.pblk.BO2 << 3 | u.pblk.BO3a << 1 | u.pblk.BO3b); + int rh = extend_6to8bits(u.pblk.RH1 << 1 | u.pblk.RH2); + int gh = extend_7to8bits(u.pblk.GH); + int bh = extend_6to8bits(u.pblk.BHa << 5 | u.pblk.BHb); + int rv = extend_6to8bits(u.pblk.RVa << 3 | u.pblk.RVb); + int gv = extend_7to8bits(u.pblk.GVa << 2 | u.pblk.GVb); + int bv = extend_6to8bits(u.pblk.BV); + + uint8_t* curPixel = dest; + for (size_t j = 0; j < 4 && (y + j) < h; j++) + { + R8G8B8A8* row = reinterpret_cast(curPixel); + + int ry = static_cast(j) * (rv - ro) + 2; + int gy = static_cast(j) * (gv - go) + 2; + int by = static_cast(j) * (bv - bo) + 2; + for (size_t i = 0; i < 4 && (x + i) < w; i++) + { + row[i] = createRGBA(((static_cast(i) * (rh - ro) + ry) >> 2) + ro, + ((static_cast(i) * (gh - go) + gy) >> 2) + go, + ((static_cast(i) * (bh - bo) + by) >> 2) + bo, + alphaValues[j][i]); + } + curPixel += pitch; + } + } + + // Index for individual, differential, H and T modes + size_t getIndex(size_t x, size_t y) const + { + size_t bitIndex = x * 4 + y; + size_t bitOffset = bitIndex & 7; + size_t lsb = (u.idht.pixelIndexLSB[1 - (bitIndex >> 3)] >> bitOffset) & 1; + size_t msb = (u.idht.pixelIndexMSB[1 - (bitIndex >> 3)] >> bitOffset) & 1; + return (msb << 1) | lsb; + } + + void decodePunchThroughAlphaBlock(uint8_t* dest, + size_t x, + size_t y, + size_t w, + size_t h, + size_t destRowPitch) const + { + uint8_t* curPixel = dest; + for (size_t j = 0; j < 4 && (y + j) < h; j++) + { + R8G8B8A8* row = reinterpret_cast(curPixel); + for (size_t i = 0; i < 4 && (x + i) < w; i++) + { + if (getIndex(i, j) == 2) // msb == 1 && lsb == 0 + { + row[i] = createRGBA(0, 0, 0, 0); + } + } + curPixel += destRowPitch; + } + } + + uint16_t RGB8ToRGB565(const R8G8B8A8& rgba) const + { + return (static_cast(rgba.R >> 3) << 11) | + (static_cast(rgba.G >> 2) << 5) | + (static_cast(rgba.B >> 3) << 0); + } + + // Single channel utility functions + int getSingleEACChannel(size_t x, size_t y, bool isSigned) const + { + int codeword = isSigned ? u.scblk.base_codeword.s : u.scblk.base_codeword.us; + int multiplier = (u.scblk.multiplier == 0) ? 1 : u.scblk.multiplier * 8; + return codeword * 8 + 4 + getSingleChannelModifier(x, y) * multiplier; + } + + int getSingleETC2Channel(size_t x, size_t y, bool isSigned) const + { + int codeword = isSigned ? u.scblk.base_codeword.s : u.scblk.base_codeword.us; + return codeword + getSingleChannelModifier(x, y) * u.scblk.multiplier; + } + + int getSingleChannelIndex(size_t x, size_t y) const + { + assert(x < 4 && y < 4); + + // clang-format off + switch (x * 4 + y) + { + case 0: return u.scblk.ma; + case 1: return u.scblk.mb; + case 2: return u.scblk.mc1 << 1 | u.scblk.mc2; + case 3: return u.scblk.md; + case 4: return u.scblk.me; + case 5: return u.scblk.mf1 << 2 | u.scblk.mf2; + case 6: return u.scblk.mg; + case 7: return u.scblk.mh; + case 8: return u.scblk.mi; + case 9: return u.scblk.mj; + case 10: return u.scblk.mk1 << 1 | u.scblk.mk2; + case 11: return u.scblk.ml; + case 12: return u.scblk.mm; + case 13: return u.scblk.mn1 << 2 | u.scblk.mn2; + case 14: return u.scblk.mo; + case 15: return u.scblk.mp; + default: UNREACHABLE(); return 0; + } + // clang-format on + } + + int getSingleChannelModifier(size_t x, size_t y) const + { + // clang-format off + static const int modifierTable[16][8] = + { + { -3, -6, -9, -15, 2, 5, 8, 14 }, + { -3, -7, -10, -13, 2, 6, 9, 12 }, + { -2, -5, -8, -13, 1, 4, 7, 12 }, + { -2, -4, -6, -13, 1, 3, 5, 12 }, + { -3, -6, -8, -12, 2, 5, 7, 11 }, + { -3, -7, -9, -11, 2, 6, 8, 10 }, + { -4, -7, -8, -11, 3, 6, 7, 10 }, + { -3, -5, -8, -11, 2, 4, 7, 10 }, + { -2, -6, -8, -10, 1, 5, 7, 9 }, + { -2, -5, -8, -10, 1, 4, 7, 9 }, + { -2, -4, -8, -10, 1, 3, 7, 9 }, + { -2, -5, -7, -10, 1, 4, 6, 9 }, + { -3, -4, -7, -10, 2, 3, 6, 9 }, + { -1, -2, -3, -10, 0, 1, 2, 9 }, + { -4, -6, -8, -9, 3, 5, 7, 8 }, + { -3, -5, -7, -9, 2, 4, 6, 8 } + }; + // clang-format on + + return modifierTable[u.scblk.table_index][getSingleChannelIndex(x, y)]; + } + }; + + // clang-format off + static const uint8_t DefaultETCAlphaValues[4][4] = + { + { 255, 255, 255, 255 }, + { 255, 255, 255, 255 }, + { 255, 255, 255, 255 }, + { 255, 255, 255, 255 }, + }; + + void LoadETC2RGB8ToRGBA8(size_t width, + size_t height, + size_t depth, + const uint8_t* input, + size_t inputRowPitch, + size_t inputDepthPitch, + uint8_t* output, + size_t outputRowPitch, + size_t outputDepthPitch, + bool punchthroughAlpha) + { + for (size_t z = 0; z < depth; z++) + { + for (size_t y = 0; y < height; y += 4) + { + const ETC2Block* sourceRow = + priv::OffsetDataPointer(input, y / 4, z, inputRowPitch, inputDepthPitch); + uint8_t* destRow = + priv::OffsetDataPointer(output, y, z, outputRowPitch, outputDepthPitch); + + for (size_t x = 0; x < width; x += 4) + { + const ETC2Block* sourceBlock = sourceRow + (x / 4); + uint8_t* destPixels = destRow + (x * 4); + + sourceBlock->decodeAsRGB(destPixels, x, y, width, height, outputRowPitch, + DefaultETCAlphaValues, punchthroughAlpha); + } + } + } + } + + void LoadETC2RGBA8ToRGBA8(size_t width, + size_t height, + size_t depth, + const uint8_t* input, + size_t inputRowPitch, + size_t inputDepthPitch, + uint8_t* output, + size_t outputRowPitch, + size_t outputDepthPitch, + bool srgb) + { + uint8_t decodedAlphaValues[4][4]; + + for (size_t z = 0; z < depth; z++) + { + for (size_t y = 0; y < height; y += 4) + { + const ETC2Block* sourceRow = + priv::OffsetDataPointer(input, y / 4, z, inputRowPitch, inputDepthPitch); + uint8_t* destRow = + priv::OffsetDataPointer(output, y, z, outputRowPitch, outputDepthPitch); + + for (size_t x = 0; x < width; x += 4) + { + const ETC2Block* sourceBlockAlpha = sourceRow + (x / 2); + sourceBlockAlpha->decodeAsSingleETC2Channel( + reinterpret_cast(decodedAlphaValues), x, y, width, height, 1, 4, + false); + + uint8_t* destPixels = destRow + (x * 4); + const ETC2Block* sourceBlockRGB = sourceBlockAlpha + 1; + sourceBlockRGB->decodeAsRGB(destPixels, x, y, width, height, outputRowPitch, + decodedAlphaValues, false); + } + } + } + } + + void LoadETC2RGB8ToRGBA8(size_t width, + size_t height, + size_t depth, + const uint8_t* input, + size_t inputRowPitch, + size_t inputDepthPitch, + uint8_t* output, + size_t outputRowPitch, + size_t outputDepthPitch) + { + return LoadETC2RGB8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, outputRowPitch, outputDepthPitch, false); + } + + void LoadETC2RGBA8ToRGBA8(size_t width, + size_t height, + size_t depth, + const uint8_t* input, + size_t inputRowPitch, + size_t inputDepthPitch, + uint8_t* output, + size_t outputRowPitch, + size_t outputDepthPitch) { + return LoadETC2RGBA8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, outputRowPitch, outputDepthPitch, false); + } + + size_t ComputeETC2RowPitch(size_t width, size_t blockWidth, size_t bytesPerPixel) { + return ((width + blockWidth - 1) / blockWidth) * bytesPerPixel; + } + + size_t ComputeETC2DepthPitch(size_t height, size_t blockHeight, size_t rowPitch) + { + return ((height + blockHeight - 1) / blockHeight) * rowPitch; + } + + typedef void (*LoadTextureFunction)(size_t width, + size_t height, + size_t depth, + const uint8_t* input, + size_t inputRowPitch, + size_t inputDepthPitch, + uint8_t* output, + size_t outputRowPitch, + size_t outputDepthPitch); +} + +int etc2_decode_image(int format, const etc2_byte* input, etc2_byte* output, size_t width, size_t height) +{ + size_t outputRowPitch = 4 * width; + size_t outputDepthPitch = outputRowPitch * height; + + size_t bytesPerPixel = 0; + LoadTextureFunction loadTexture = nullptr; + switch (format) { + case ETC2_RGBA_NO_MIPMAPS: + bytesPerPixel = 16; + loadTexture = &LoadETC2RGBA8ToRGBA8; + break; + case ETC2_RGB_NO_MIPMAPS: + bytesPerPixel = 8; + loadTexture = &LoadETC2RGB8ToRGBA8; + break; + } + + if (loadTexture) { + size_t inputRowPitch = ComputeETC2RowPitch(width, 4 /*blockWidth*/, bytesPerPixel); + size_t inputDepthPitch = ComputeETC2DepthPitch(height, 4 /*blockHeight*/, inputRowPitch); + loadTexture(width, height, 1, input, inputRowPitch, inputDepthPitch, output, outputRowPitch, outputDepthPitch); + return 0; + } + + return -1; +} diff --git a/cocos/base/etc2.h b/cocos/base/etc2.h index 7ac8991655..1f8aab5c52 100644 --- a/cocos/base/etc2.h +++ b/cocos/base/etc2.h @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2020 c4games.com http://www.cocos2d-x.org @@ -40,34 +41,41 @@ typedef unsigned int etc2_uint32; #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 #endif -#ifdef __cplusplus -extern "C" { -#endif // Size of a PKM header, in bytes. - #define ETC2_PKM_HEADER_SIZE 16 #define ETC2_RGB_NO_MIPMAPS 1 #define ETC2_RGBA_NO_MIPMAPS 3 // Check if a PKM header is correctly formatted. +#if defined(__cplusplus) +extern "C" { +#endif + etc2_bool etc2_pkm_is_valid(const etc2_byte* pHeader); -etc2_bool etc2_pkm_is_valid(const etc2_byte* pHeader); + // Read the image width from a PKM header + etc2_uint32 etc2_pkm_get_width(const etc2_byte* pHeader); -// Read the image width from a PKM header + // Read the image height from a PKM header + etc2_uint32 etc2_pkm_get_height(const etc2_byte* pHeader); -etc2_uint32 etc2_pkm_get_width(const etc2_byte* pHeader); + // Read the image format from a PKM header + etc2_uint32 etc2_pkm_get_format(const etc2_byte* pHeader); -// Read the image height from a PKM header + /// + /// Decode ETC2_RGB/ETC2_RGBA to RGBA8888 + /// Copy from: https://github.com/google/angle/tree/master/src/image_util + /// + /// Must be ETC2_RGBA_NO_MIPMAPS or ETC2_RGB_NO_MIPMAPS + /// + /// + /// pixelsHeight + /// pixelsWidth + /// 0: success, -1: failed + int etc2_decode_image(int format, const etc2_byte* input, etc2_byte* output, size_t width, size_t height); -etc2_uint32 etc2_pkm_get_height(const etc2_byte* pHeader); - -// Read the image format from a PKM header - -etc2_uint32 etc2_pkm_get_format(const etc2_byte* pHeader); - -#ifdef __cplusplus +#if defined(__cplusplus) } #endif diff --git a/cocos/platform/CCImage.cpp b/cocos/platform/CCImage.cpp index ef5ceb8569..360572afa3 100644 --- a/cocos/platform/CCImage.cpp +++ b/cocos/platform/CCImage.cpp @@ -1522,11 +1522,21 @@ bool Image::initWithETCData(const unsigned char* data, ssize_t dataLen, bool own return false; } + // GL_ETC1_RGB8_OES is not available in any desktop GL extension but the compression + // format is forwards compatible so just use the ETC2 format. + backend::PixelFormat compressedFormat; if (Configuration::getInstance()->supportsETC()) + compressedFormat = backend::PixelFormat::ETC; + else if (Configuration::getInstance()->supportsETC2()) + compressedFormat = backend::PixelFormat::ETC2_RGB; + else + compressedFormat = backend::PixelFormat::NONE; + + if (compressedFormat != backend::PixelFormat::NONE) { //old opengl version has no define for GL_ETC1_RGB8_OES, add macro to make compiler happy. #if defined(GL_ETC1_RGB8_OES) || defined(CC_USE_METAL) - _pixelFormat = backend::PixelFormat::ETC; + _pixelFormat = compressedFormat; if(ownData) _data = (unsigned char*)data; else { _data = (unsigned char*)malloc(dataLen); @@ -1589,6 +1599,9 @@ bool Image::initWithETC2Data(const unsigned char* data, ssize_t dataLen, bool ow etc2_uint32 format = etc2_pkm_get_format(header); + // We only support ETC2_RGBA_NO_MIPMAPS and ETC2_RGB_NO_MIPMAPS + assert(format == ETC2_RGBA_NO_MIPMAPS || format == ETC2_RGB_NO_MIPMAPS); + if (Configuration::getInstance()->supportsETC2()) { _pixelFormat = format == ETC2_RGBA_NO_MIPMAPS ? backend::PixelFormat::ETC2_RGBA : backend::PixelFormat::ETC2_RGB; @@ -1599,12 +1612,33 @@ bool Image::initWithETC2Data(const unsigned char* data, ssize_t dataLen, bool ow } _dataLen = dataLen; _offset = ETC2_PKM_HEADER_SIZE; + return true; } else { - ; // TODO: software decoder - } + CCLOG("cocos2d: Hardware ETC2 decoder not present. Using software decoder"); - return true; + bool ret = true; + // if it is not gles or device do not support ETC2, decode texture by software + // etc2_decode_image always decode to RGBA8888 + _pixelFormat = backend::PixelFormat::RGBA8888; + + _dataLen = _width * _height * 4; + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + if (etc2_decode_image(format, static_cast(data) + ETC2_PKM_HEADER_SIZE, static_cast(_data), _width, _height) != 0) + { + _dataLen = 0; + if (_data != nullptr) + { + free(_data); + _data = nullptr; + } + ret = false; + } + + if (ownData) free((void*)data); + + return ret; + } } bool Image::initWithASTCData(const unsigned char* data, ssize_t dataLen, bool ownData) diff --git a/cocos/renderer/CCTexture2D.cpp b/cocos/renderer/CCTexture2D.cpp index 59baf0d4bf..8253baa193 100644 --- a/cocos/renderer/CCTexture2D.cpp +++ b/cocos/renderer/CCTexture2D.cpp @@ -280,6 +280,8 @@ bool Texture2D::updateWithImage(Image* image, backend::PixelFormat format, int i case PixelFormat::PVRTC2: case PixelFormat::A8: case PixelFormat::ETC: + case PixelFormat::ETC2_RGB: + case PixelFormat::ETC2_RGBA: case PixelFormat::ASTC4: case PixelFormat::ASTC8: renderFormat = imagePixelFormat; @@ -331,6 +333,8 @@ bool Texture2D::updateWithImage(Image* image, backend::PixelFormat format, int i #ifndef CC_USE_METAL switch (imagePixelFormat) { case PixelFormat::ETC: + case PixelFormat::ETC2_RGB: + case PixelFormat::ETC2_RGBA: case PixelFormat::ASTC4: case PixelFormat::ASTC8: renderFormat = imagePixelFormat; @@ -723,7 +727,7 @@ const char* Texture2D::getStringForFormat() const return "ETC2_RGB"; case backend::PixelFormat::ETC2_RGBA: - return "ETC_RGBA"; + return "ETC2_RGBA"; case backend::PixelFormat::S3TC_DXT1: return "S3TC_DXT1";