| // | |
| // 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. | |
| // | |
| // loadimage_etc.cpp: Decodes ETC and EAC encoded textures. | |
| #include "image_util/loadimage.h" | |
| #include <type_traits> | |
| #include "common/mathutil.h" | |
| #include "image_util/imageformats.h" | |
| namespace angle | |
| { | |
| namespace | |
| { | |
| 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 unsigned single or dual channel EAC block to 16-bit color | |
| void decodeAsSingleEACChannel(uint16_t *dest, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| size_t destPixelStride, | |
| size_t destRowPitch, | |
| bool isSigned, | |
| bool isFloat) const | |
| { | |
| for (size_t j = 0; j < 4 && (y + j) < h; j++) | |
| { | |
| uint16_t *row = reinterpret_cast<uint16_t *>(reinterpret_cast<uint8_t *>(dest) + | |
| (j * destRowPitch)); | |
| for (size_t i = 0; i < 4 && (x + i) < w; i++) | |
| { | |
| uint16_t *pixel = row + (i * destPixelStride); | |
| if (isSigned) | |
| { | |
| int16_t tempPixel = | |
| renormalizeEAC<int16_t>(getSingleEACChannel(i, j, isSigned)); | |
| *pixel = | |
| isFloat ? gl::float32ToFloat16(float(gl::normalize(tempPixel))) : tempPixel; | |
| } | |
| else | |
| { | |
| uint16_t tempPixel = | |
| renormalizeEAC<uint16_t>(getSingleEACChannel(i, j, isSigned)); | |
| *pixel = | |
| isFloat ? gl::float32ToFloat16(float(gl::normalize(tempPixel))) : tempPixel; | |
| } | |
| } | |
| } | |
| } | |
| // 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); | |
| } | |
| } | |
| // Transcodes RGB block to BC1 | |
| void transcodeAsBC1(uint8_t *dest, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| 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) | |
| { | |
| transcodeTBlockToBC1(dest, x, y, w, h, alphaValues, nonOpaquePunchThroughAlpha); | |
| } | |
| else if (g < 0 || g > 31) | |
| { | |
| transcodeHBlockToBC1(dest, x, y, w, h, alphaValues, nonOpaquePunchThroughAlpha); | |
| } | |
| else if (b < 0 || b > 31) | |
| { | |
| transcodePlanarBlockToBC1(dest, x, y, w, h, alphaValues); | |
| } | |
| else | |
| { | |
| transcodeDifferentialBlockToBC1(dest, x, y, w, h, alphaValues, | |
| nonOpaquePunchThroughAlpha); | |
| } | |
| } | |
| else | |
| { | |
| transcodeIndividualBlockToBC1(dest, x, y, w, h, 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 TunusedB : 1; | |
| unsigned char TR1a : 2; | |
| unsigned char TunusedA : 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 HunusedA : 1; | |
| // Byte 2 | |
| unsigned char HB1b : 2; | |
| unsigned char HunusedC : 1; | |
| unsigned char HB1a : 1; | |
| unsigned char HG1b : 1; | |
| unsigned char HunusedB : 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 PunusedA : 1; | |
| // Byte 2 | |
| unsigned char BO1 : 1; | |
| unsigned char GO2 : 6; | |
| unsigned char PunusedB : 1; | |
| // Byte 3 | |
| unsigned char BO3a : 2; | |
| unsigned char PunusedD : 1; | |
| unsigned char BO2 : 2; | |
| unsigned char PunusedC : 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<unsigned char>(gl::clamp(value, 0, 255)); | |
| } | |
| static signed char clampSByte(int value) | |
| { | |
| return static_cast<signed char>(gl::clamp(value, -128, 127)); | |
| } | |
| template <typename T> | |
| static T renormalizeEAC(int value) | |
| { | |
| int upper = 0; | |
| int lower = 0; | |
| int shift = 0; | |
| if (std::is_same<T, int16_t>::value) | |
| { | |
| // The spec states that -1024 invalid and should be clamped to -1023 | |
| upper = 1023; | |
| lower = -1023; | |
| shift = 5; | |
| } | |
| else if (std::is_same<T, uint16_t>::value) | |
| { | |
| upper = 2047; | |
| lower = 0; | |
| shift = 5; | |
| } | |
| else | |
| { | |
| // We currently only support renormalizing int16_t or uint16_t | |
| UNREACHABLE(); | |
| } | |
| return static_cast<T>(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<R8G8B8A8 *>(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<R8G8B8A8 *>(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<R8G8B8A8 *>(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<R8G8B8A8 *>(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<R8G8B8A8 *>(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<R8G8B8A8 *>(curPixel); | |
| int ry = static_cast<int>(j) * (rv - ro) + 2; | |
| int gy = static_cast<int>(j) * (gv - go) + 2; | |
| int by = static_cast<int>(j) * (bv - bo) + 2; | |
| for (size_t i = 0; i < 4 && (x + i) < w; i++) | |
| { | |
| row[i] = createRGBA(((static_cast<int>(i) * (rh - ro) + ry) >> 2) + ro, | |
| ((static_cast<int>(i) * (gh - go) + gy) >> 2) + go, | |
| ((static_cast<int>(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<R8G8B8A8 *>(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<uint16_t>(rgba.R >> 3) << 11) | | |
| (static_cast<uint16_t>(rgba.G >> 2) << 5) | | |
| (static_cast<uint16_t>(rgba.B >> 3) << 0); | |
| } | |
| uint32_t matchBC1Bits(const int *pixelIndices, | |
| const int *pixelIndexCounts, | |
| const R8G8B8A8 *subblockColors, | |
| size_t numColors, | |
| const R8G8B8A8 &minColor, | |
| const R8G8B8A8 &maxColor, | |
| bool nonOpaquePunchThroughAlpha) const | |
| { | |
| // Project each pixel on the (maxColor, minColor) line to decide which | |
| // BC1 code to assign to it. | |
| uint8_t decodedColors[2][3] = {{maxColor.R, maxColor.G, maxColor.B}, | |
| {minColor.R, minColor.G, minColor.B}}; | |
| int direction[3]; | |
| for (int ch = 0; ch < 3; ch++) | |
| { | |
| direction[ch] = decodedColors[0][ch] - decodedColors[1][ch]; | |
| } | |
| int stops[2]; | |
| for (int i = 0; i < 2; i++) | |
| { | |
| stops[i] = decodedColors[i][0] * direction[0] + decodedColors[i][1] * direction[1] + | |
| decodedColors[i][2] * direction[2]; | |
| } | |
| ASSERT(numColors <= kNumPixelsInBlock); | |
| int encodedColors[kNumPixelsInBlock]; | |
| if (nonOpaquePunchThroughAlpha) | |
| { | |
| for (size_t i = 0; i < numColors; i++) | |
| { | |
| const int count = pixelIndexCounts[i]; | |
| if (count > 0) | |
| { | |
| // In non-opaque mode, 3 is for tranparent pixels. | |
| if (0 == subblockColors[i].A) | |
| { | |
| encodedColors[i] = 3; | |
| } | |
| else | |
| { | |
| const R8G8B8A8 &pixel = subblockColors[i]; | |
| const int dot = pixel.R * direction[0] + pixel.G * direction[1] + | |
| pixel.B * direction[2]; | |
| const int factor = gl::clamp( | |
| static_cast<int>( | |
| (static_cast<float>(dot - stops[1]) / (stops[0] - stops[1])) * 2 + | |
| 0.5f), | |
| 0, 2); | |
| switch (factor) | |
| { | |
| case 0: | |
| encodedColors[i] = 0; | |
| break; | |
| case 1: | |
| encodedColors[i] = 2; | |
| break; | |
| case 2: | |
| default: | |
| encodedColors[i] = 1; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| else | |
| { | |
| for (size_t i = 0; i < numColors; i++) | |
| { | |
| const int count = pixelIndexCounts[i]; | |
| if (count > 0) | |
| { | |
| // In opaque mode, the code is from 0 to 3. | |
| const R8G8B8A8 &pixel = subblockColors[i]; | |
| const int dot = | |
| pixel.R * direction[0] + pixel.G * direction[1] + pixel.B * direction[2]; | |
| const int factor = gl::clamp( | |
| static_cast<int>( | |
| (static_cast<float>(dot - stops[1]) / (stops[0] - stops[1])) * 3 + | |
| 0.5f), | |
| 0, 3); | |
| switch (factor) | |
| { | |
| case 0: | |
| encodedColors[i] = 1; | |
| break; | |
| case 1: | |
| encodedColors[i] = 3; | |
| break; | |
| case 2: | |
| encodedColors[i] = 2; | |
| break; | |
| case 3: | |
| default: | |
| encodedColors[i] = 0; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| uint32_t bits = 0; | |
| for (int i = kNumPixelsInBlock - 1; i >= 0; i--) | |
| { | |
| bits <<= 2; | |
| bits |= encodedColors[pixelIndices[i]]; | |
| } | |
| return bits; | |
| } | |
| void packBC1(void *bc1, | |
| const int *pixelIndices, | |
| const int *pixelIndexCounts, | |
| const R8G8B8A8 *subblockColors, | |
| size_t numColors, | |
| int minColorIndex, | |
| int maxColorIndex, | |
| bool nonOpaquePunchThroughAlpha) const | |
| { | |
| const R8G8B8A8 &minColor = subblockColors[minColorIndex]; | |
| const R8G8B8A8 &maxColor = subblockColors[maxColorIndex]; | |
| uint32_t bits; | |
| uint16_t max16 = RGB8ToRGB565(maxColor); | |
| uint16_t min16 = RGB8ToRGB565(minColor); | |
| if (max16 != min16) | |
| { | |
| // Find the best BC1 code for each pixel | |
| bits = matchBC1Bits(pixelIndices, pixelIndexCounts, subblockColors, numColors, minColor, | |
| maxColor, nonOpaquePunchThroughAlpha); | |
| } | |
| else | |
| { | |
| // Same colors, BC1 index 0 is the color in both opaque and transparent mode | |
| bits = 0; | |
| // BC1 index 3 is transparent | |
| if (nonOpaquePunchThroughAlpha) | |
| { | |
| for (int i = 0; i < kNumPixelsInBlock; i++) | |
| { | |
| if (0 == subblockColors[pixelIndices[i]].A) | |
| { | |
| bits |= (3 << (i * 2)); | |
| } | |
| } | |
| } | |
| } | |
| if (max16 < min16) | |
| { | |
| std::swap(max16, min16); | |
| uint32_t xorMask = 0; | |
| if (nonOpaquePunchThroughAlpha) | |
| { | |
| // In transparent mode switching the colors is doing the | |
| // following code swap: 0 <-> 1. 0xA selects the second bit of | |
| // each code, bits >> 1 selects the first bit of the code when | |
| // the seconds bit is set (case 2 and 3). We invert all the | |
| // non-selected bits, that is the first bit when the code is | |
| // 0 or 1. | |
| xorMask = ~((bits >> 1) | 0xAAAAAAAA); | |
| } | |
| else | |
| { | |
| // In opaque mode switching the two colors is doing the | |
| // following code swaps: 0 <-> 1 and 2 <-> 3. This is | |
| // equivalent to flipping the first bit of each code | |
| // (5 = 0b0101) | |
| xorMask = 0x55555555; | |
| } | |
| bits ^= xorMask; | |
| } | |
| struct BC1Block | |
| { | |
| uint16_t color0; | |
| uint16_t color1; | |
| uint32_t bits; | |
| }; | |
| // Encode the opaqueness in the order of the two BC1 colors | |
| BC1Block *dest = reinterpret_cast<BC1Block *>(bc1); | |
| if (nonOpaquePunchThroughAlpha) | |
| { | |
| dest->color0 = min16; | |
| dest->color1 = max16; | |
| } | |
| else | |
| { | |
| dest->color0 = max16; | |
| dest->color1 = min16; | |
| } | |
| dest->bits = bits; | |
| } | |
| void transcodeIndividualBlockToBC1(uint8_t *dest, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| 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); | |
| transcodeIndividualOrDifferentialBlockToBC1(dest, x, y, w, h, r1, g1, b1, r2, g2, b2, | |
| alphaValues, nonOpaquePunchThroughAlpha); | |
| } | |
| void transcodeDifferentialBlockToBC1(uint8_t *dest, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| 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); | |
| transcodeIndividualOrDifferentialBlockToBC1(dest, x, y, w, h, r1, g1, b1, r2, g2, b2, | |
| alphaValues, nonOpaquePunchThroughAlpha); | |
| } | |
| void extractPixelIndices(int *pixelIndices, | |
| int *pixelIndicesCounts, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| bool flipbit, | |
| size_t subblockIdx) const | |
| { | |
| size_t dxBegin = 0; | |
| size_t dxEnd = 4; | |
| size_t dyBegin = subblockIdx * 2; | |
| size_t dyEnd = dyBegin + 2; | |
| if (!flipbit) | |
| { | |
| std::swap(dxBegin, dyBegin); | |
| std::swap(dxEnd, dyEnd); | |
| } | |
| for (size_t j = dyBegin; j < dyEnd; j++) | |
| { | |
| int *row = &pixelIndices[j * 4]; | |
| for (size_t i = dxBegin; i < dxEnd; i++) | |
| { | |
| const size_t pixelIndex = subblockIdx * 4 + getIndex(i, j); | |
| row[i] = static_cast<int>(pixelIndex); | |
| pixelIndicesCounts[pixelIndex]++; | |
| } | |
| } | |
| } | |
| void selectEndPointPCA(const int *pixelIndexCounts, | |
| const R8G8B8A8 *subblockColors, | |
| size_t numColors, | |
| int *minColorIndex, | |
| int *maxColorIndex) const | |
| { | |
| // determine color distribution | |
| int mu[3], min[3], max[3]; | |
| for (int ch = 0; ch < 3; ch++) | |
| { | |
| int muv = 0; | |
| int minv = 255; | |
| int maxv = 0; | |
| for (size_t i = 0; i < numColors; i++) | |
| { | |
| const int count = pixelIndexCounts[i]; | |
| if (count > 0) | |
| { | |
| const auto &pixel = subblockColors[i]; | |
| if (pixel.A > 0) | |
| { | |
| // Non-transparent pixels | |
| muv += (&pixel.R)[ch] * count; | |
| minv = std::min<int>(minv, (&pixel.R)[ch]); | |
| maxv = std::max<int>(maxv, (&pixel.R)[ch]); | |
| } | |
| } | |
| } | |
| mu[ch] = (muv + kNumPixelsInBlock / 2) / kNumPixelsInBlock; | |
| min[ch] = minv; | |
| max[ch] = maxv; | |
| } | |
| // determine covariance matrix | |
| int cov[6] = {0, 0, 0, 0, 0, 0}; | |
| for (size_t i = 0; i < numColors; i++) | |
| { | |
| const int count = pixelIndexCounts[i]; | |
| if (count > 0) | |
| { | |
| const auto &pixel = subblockColors[i]; | |
| if (pixel.A > 0) | |
| { | |
| int r = pixel.R - mu[0]; | |
| int g = pixel.G - mu[1]; | |
| int b = pixel.B - mu[2]; | |
| cov[0] += r * r * count; | |
| cov[1] += r * g * count; | |
| cov[2] += r * b * count; | |
| cov[3] += g * g * count; | |
| cov[4] += g * b * count; | |
| cov[5] += b * b * count; | |
| } | |
| } | |
| } | |
| // Power iteration algorithm to get the eigenvalues and eigenvector | |
| // Starts with diagonal vector | |
| float vfr = static_cast<float>(max[0] - min[0]); | |
| float vfg = static_cast<float>(max[1] - min[1]); | |
| float vfb = static_cast<float>(max[2] - min[2]); | |
| float eigenvalue = 0.0f; | |
| constexpr size_t kPowerIterations = 4; | |
| for (size_t i = 0; i < kPowerIterations; i++) | |
| { | |
| float r = vfr * cov[0] + vfg * cov[1] + vfb * cov[2]; | |
| float g = vfr * cov[1] + vfg * cov[3] + vfb * cov[4]; | |
| float b = vfr * cov[2] + vfg * cov[4] + vfb * cov[5]; | |
| vfr = r; | |
| vfg = g; | |
| vfb = b; | |
| eigenvalue = sqrt(r * r + g * g + b * b); | |
| if (eigenvalue > 0) | |
| { | |
| float invNorm = 1.0f / eigenvalue; | |
| vfr *= invNorm; | |
| vfg *= invNorm; | |
| vfb *= invNorm; | |
| } | |
| } | |
| int vr, vg, vb; | |
| static const float kDefaultLuminanceThreshold = 4.0f * 255; | |
| static const float kQuantizeRange = 512.0f; | |
| if (eigenvalue < kDefaultLuminanceThreshold) // too small, default to luminance | |
| { | |
| // Luminance weights defined by ITU-R Recommendation BT.601, scaled by 1000 | |
| vr = 299; | |
| vg = 587; | |
| vb = 114; | |
| } | |
| else | |
| { | |
| // From the eigenvalue and eigenvector, choose the axis to project | |
| // colors on. When projecting colors we want to do integer computations | |
| // for speed, so we normalize the eigenvector to the [0, 512] range. | |
| float magn = std::max(std::max(std::abs(vfr), std::abs(vfg)), std::abs(vfb)); | |
| magn = kQuantizeRange / magn; | |
| vr = static_cast<int>(vfr * magn); | |
| vg = static_cast<int>(vfg * magn); | |
| vb = static_cast<int>(vfb * magn); | |
| } | |
| // Pick colors at extreme points | |
| int minD = INT_MAX; | |
| int maxD = 0; | |
| size_t minIndex = 0; | |
| size_t maxIndex = 0; | |
| for (size_t i = 0; i < numColors; i++) | |
| { | |
| const int count = pixelIndexCounts[i]; | |
| if (count > 0) | |
| { | |
| const auto &pixel = subblockColors[i]; | |
| if (pixel.A > 0) | |
| { | |
| int dot = pixel.R * vr + pixel.G * vg + pixel.B * vb; | |
| if (dot < minD) | |
| { | |
| minD = dot; | |
| minIndex = i; | |
| } | |
| if (dot > maxD) | |
| { | |
| maxD = dot; | |
| maxIndex = i; | |
| } | |
| } | |
| } | |
| } | |
| *minColorIndex = static_cast<int>(minIndex); | |
| *maxColorIndex = static_cast<int>(maxIndex); | |
| } | |
| void transcodeIndividualOrDifferentialBlockToBC1(uint8_t *dest, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| int r1, | |
| int g1, | |
| int b1, | |
| int r2, | |
| int g2, | |
| int b2, | |
| const uint8_t alphaValues[4][4], | |
| bool nonOpaquePunchThroughAlpha) const | |
| { | |
| // A BC1 block has 2 endpoints, pixels is encoded as linear | |
| // interpolations of them. A ETC1/ETC2 individual or differential block | |
| // has 2 subblocks. Each subblock has one color and a modifier. We | |
| // select axis by principal component analysis (PCA) to use as | |
| // our two BC1 endpoints and then map pixels to BC1 by projecting on the | |
| // line between the two endpoints and choosing the right fraction. | |
| // The goal of this algorithm is make it faster than decode ETC to RGBs | |
| // and then encode to BC. To achieve this, we only extract subblock | |
| // colors, pixel indices, and counts of each pixel indices from ETC. | |
| // With those information, we can only encode used subblock colors | |
| // to BC1, and copy the bits to the right pixels. | |
| // Fully decode and encode need to process 16 RGBA pixels. With this | |
| // algorithm, it's 8 pixels at maximum for a individual or | |
| // differential block. Saves us bandwidth and computations. | |
| static const size_t kNumColors = 8; | |
| const IntensityModifier *intensityModifier = | |
| nonOpaquePunchThroughAlpha ? intensityModifierNonOpaque : intensityModifierDefault; | |
| // Compute the colors that pixels can have in each subblock both for | |
| // the decoding of the RGBA data and BC1 encoding | |
| R8G8B8A8 subblockColors[kNumColors]; | |
| for (size_t modifierIdx = 0; modifierIdx < 4; modifierIdx++) | |
| { | |
| if (nonOpaquePunchThroughAlpha && (modifierIdx == 2)) | |
| { | |
| // In ETC opaque punch through formats, individual and | |
| // differential blocks take index 2 as transparent pixel. | |
| // Thus we don't need to compute its color, just assign it | |
| // as black. | |
| subblockColors[modifierIdx] = createRGBA(0, 0, 0, 0); | |
| subblockColors[4 + modifierIdx] = createRGBA(0, 0, 0, 0); | |
| } | |
| else | |
| { | |
| const int i1 = intensityModifier[u.idht.mode.idm.cw1][modifierIdx]; | |
| subblockColors[modifierIdx] = createRGBA(r1 + i1, g1 + i1, b1 + i1); | |
| const int i2 = intensityModifier[u.idht.mode.idm.cw2][modifierIdx]; | |
| subblockColors[4 + modifierIdx] = createRGBA(r2 + i2, g2 + i2, b2 + i2); | |
| } | |
| } | |
| int pixelIndices[kNumPixelsInBlock]; | |
| int pixelIndexCounts[kNumColors] = {0}; | |
| // Extract pixel indices from a ETC block. | |
| for (size_t blockIdx = 0; blockIdx < 2; blockIdx++) | |
| { | |
| extractPixelIndices(pixelIndices, pixelIndexCounts, x, y, w, h, u.idht.mode.idm.flipbit, | |
| blockIdx); | |
| } | |
| int minColorIndex, maxColorIndex; | |
| selectEndPointPCA(pixelIndexCounts, subblockColors, kNumColors, &minColorIndex, | |
| &maxColorIndex); | |
| packBC1(dest, pixelIndices, pixelIndexCounts, subblockColors, kNumColors, minColorIndex, | |
| maxColorIndex, nonOpaquePunchThroughAlpha); | |
| } | |
| void transcodeTBlockToBC1(uint8_t *dest, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| const uint8_t alphaValues[4][4], | |
| bool nonOpaquePunchThroughAlpha) const | |
| { | |
| static const size_t kNumColors = 4; | |
| // 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]; | |
| // In ETC opaque punch through formats, index == 2 means transparent pixel. | |
| // Thus we don't need to compute its color, just assign it as black. | |
| const R8G8B8A8 paintColors[kNumColors] = { | |
| createRGBA(r1, g1, b1), | |
| createRGBA(r2 + d, g2 + d, b2 + d), | |
| nonOpaquePunchThroughAlpha ? createRGBA(0, 0, 0, 0) : createRGBA(r2, g2, b2), | |
| createRGBA(r2 - d, g2 - d, b2 - d), | |
| }; | |
| int pixelIndices[kNumPixelsInBlock]; | |
| int pixelIndexCounts[kNumColors] = {0}; | |
| for (size_t j = 0; j < 4; j++) | |
| { | |
| int *row = &pixelIndices[j * 4]; | |
| for (size_t i = 0; i < 4; i++) | |
| { | |
| const size_t pixelIndex = getIndex(i, j); | |
| row[i] = static_cast<int>(pixelIndex); | |
| pixelIndexCounts[pixelIndex]++; | |
| } | |
| } | |
| int minColorIndex, maxColorIndex; | |
| selectEndPointPCA(pixelIndexCounts, paintColors, kNumColors, &minColorIndex, | |
| &maxColorIndex); | |
| packBC1(dest, pixelIndices, pixelIndexCounts, paintColors, kNumColors, minColorIndex, | |
| maxColorIndex, nonOpaquePunchThroughAlpha); | |
| } | |
| void transcodeHBlockToBC1(uint8_t *dest, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| const uint8_t alphaValues[4][4], | |
| bool nonOpaquePunchThroughAlpha) const | |
| { | |
| static const size_t kNumColors = 4; | |
| // 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]; | |
| // In ETC opaque punch through formats, index == 2 means transparent pixel. | |
| // Thus we don't need to compute its color, just assign it as black. | |
| const R8G8B8A8 paintColors[kNumColors] = { | |
| createRGBA(r1 + d, g1 + d, b1 + d), | |
| createRGBA(r1 - d, g1 - d, b1 - d), | |
| nonOpaquePunchThroughAlpha ? createRGBA(0, 0, 0, 0) | |
| : createRGBA(r2 + d, g2 + d, b2 + d), | |
| createRGBA(r2 - d, g2 - d, b2 - d), | |
| }; | |
| int pixelIndices[kNumPixelsInBlock]; | |
| int pixelIndexCounts[kNumColors] = {0}; | |
| for (size_t j = 0; j < 4; j++) | |
| { | |
| int *row = &pixelIndices[j * 4]; | |
| for (size_t i = 0; i < 4; i++) | |
| { | |
| const size_t pixelIndex = getIndex(i, j); | |
| row[i] = static_cast<int>(pixelIndex); | |
| pixelIndexCounts[pixelIndex]++; | |
| } | |
| } | |
| int minColorIndex, maxColorIndex; | |
| selectEndPointPCA(pixelIndexCounts, paintColors, kNumColors, &minColorIndex, | |
| &maxColorIndex); | |
| packBC1(dest, pixelIndices, pixelIndexCounts, paintColors, kNumColors, minColorIndex, | |
| maxColorIndex, nonOpaquePunchThroughAlpha); | |
| } | |
| void transcodePlanarBlockToBC1(uint8_t *dest, | |
| size_t x, | |
| size_t y, | |
| size_t w, | |
| size_t h, | |
| const uint8_t alphaValues[4][4]) const | |
| { | |
| static const size_t kNumColors = kNumPixelsInBlock; | |
| R8G8B8A8 rgbaBlock[kNumColors]; | |
| decodePlanarBlock(reinterpret_cast<uint8_t *>(rgbaBlock), x, y, w, h, sizeof(R8G8B8A8) * 4, | |
| alphaValues); | |
| // Planar block doesn't have a color table, fill indices as full | |
| int pixelIndices[kNumPixelsInBlock] = {0, 1, 2, 3, 4, 5, 6, 7, | |
| 8, 9, 10, 11, 12, 13, 14, 15}; | |
| int pixelIndexCounts[kNumColors] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | |
| int minColorIndex, maxColorIndex; | |
| selectEndPointPCA(pixelIndexCounts, rgbaBlock, kNumColors, &minColorIndex, &maxColorIndex); | |
| packBC1(dest, pixelIndices, pixelIndexCounts, rgbaBlock, kNumColors, minColorIndex, | |
| maxColorIndex, false); | |
| } | |
| // 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 }, | |
| }; | |
| // clang-format on | |
| void LoadR11EACToR8(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 isSigned) | |
| { | |
| for (size_t z = 0; z < depth; z++) | |
| { | |
| for (size_t y = 0; y < height; y += 4) | |
| { | |
| const ETC2Block *sourceRow = | |
| priv::OffsetDataPointer<ETC2Block>(input, y / 4, z, inputRowPitch, inputDepthPitch); | |
| uint8_t *destRow = | |
| priv::OffsetDataPointer<uint8_t>(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; | |
| sourceBlock->decodeAsSingleETC2Channel(destPixels, x, y, width, height, 1, | |
| outputRowPitch, isSigned); | |
| } | |
| } | |
| } | |
| } | |
| void LoadRG11EACToRG8(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 isSigned) | |
| { | |
| for (size_t z = 0; z < depth; z++) | |
| { | |
| for (size_t y = 0; y < height; y += 4) | |
| { | |
| const ETC2Block *sourceRow = | |
| priv::OffsetDataPointer<ETC2Block>(input, y / 4, z, inputRowPitch, inputDepthPitch); | |
| uint8_t *destRow = | |
| priv::OffsetDataPointer<uint8_t>(output, y, z, outputRowPitch, outputDepthPitch); | |
| for (size_t x = 0; x < width; x += 4) | |
| { | |
| uint8_t *destPixelsRed = destRow + (x * 2); | |
| const ETC2Block *sourceBlockRed = sourceRow + (x / 2); | |
| sourceBlockRed->decodeAsSingleETC2Channel(destPixelsRed, x, y, width, height, 2, | |
| outputRowPitch, isSigned); | |
| uint8_t *destPixelsGreen = destPixelsRed + 1; | |
| const ETC2Block *sourceBlockGreen = sourceBlockRed + 1; | |
| sourceBlockGreen->decodeAsSingleETC2Channel(destPixelsGreen, x, y, width, height, 2, | |
| outputRowPitch, isSigned); | |
| } | |
| } | |
| } | |
| } | |
| void LoadR11EACToR16(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 isSigned, | |
| bool isFloat) | |
| { | |
| for (size_t z = 0; z < depth; z++) | |
| { | |
| for (size_t y = 0; y < height; y += 4) | |
| { | |
| const ETC2Block *sourceRow = | |
| priv::OffsetDataPointer<ETC2Block>(input, y / 4, z, inputRowPitch, inputDepthPitch); | |
| uint16_t *destRow = | |
| priv::OffsetDataPointer<uint16_t>(output, y, z, outputRowPitch, outputDepthPitch); | |
| for (size_t x = 0; x < width; x += 4) | |
| { | |
| const ETC2Block *sourceBlock = sourceRow + (x / 4); | |
| uint16_t *destPixels = destRow + x; | |
| sourceBlock->decodeAsSingleEACChannel(destPixels, x, y, width, height, 1, | |
| outputRowPitch, isSigned, isFloat); | |
| } | |
| } | |
| } | |
| } | |
| void LoadRG11EACToRG16(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 isSigned, | |
| bool isFloat) | |
| { | |
| for (size_t z = 0; z < depth; z++) | |
| { | |
| for (size_t y = 0; y < height; y += 4) | |
| { | |
| const ETC2Block *sourceRow = | |
| priv::OffsetDataPointer<ETC2Block>(input, y / 4, z, inputRowPitch, inputDepthPitch); | |
| uint16_t *destRow = | |
| priv::OffsetDataPointer<uint16_t>(output, y, z, outputRowPitch, outputDepthPitch); | |
| for (size_t x = 0; x < width; x += 4) | |
| { | |
| uint16_t *destPixelsRed = destRow + (x * 2); | |
| const ETC2Block *sourceBlockRed = sourceRow + (x / 2); | |
| sourceBlockRed->decodeAsSingleEACChannel(destPixelsRed, x, y, width, height, 2, | |
| outputRowPitch, isSigned, isFloat); | |
| uint16_t *destPixelsGreen = destPixelsRed + 1; | |
| const ETC2Block *sourceBlockGreen = sourceBlockRed + 1; | |
| sourceBlockGreen->decodeAsSingleEACChannel(destPixelsGreen, x, y, width, height, 2, | |
| outputRowPitch, isSigned, isFloat); | |
| } | |
| } | |
| } | |
| } | |
| 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<ETC2Block>(input, y / 4, z, inputRowPitch, inputDepthPitch); | |
| uint8_t *destRow = | |
| priv::OffsetDataPointer<uint8_t>(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 LoadETC2RGB8ToBC1(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<ETC2Block>(input, y / 4, z, inputRowPitch, inputDepthPitch); | |
| uint8_t *destRow = priv::OffsetDataPointer<uint8_t>(output, y / 4, z, outputRowPitch, | |
| outputDepthPitch); | |
| for (size_t x = 0; x < width; x += 4) | |
| { | |
| const ETC2Block *sourceBlock = sourceRow + (x / 4); | |
| uint8_t *destPixels = destRow + (x * 2); | |
| sourceBlock->transcodeAsBC1(destPixels, x, y, width, height, 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<ETC2Block>(input, y / 4, z, inputRowPitch, inputDepthPitch); | |
| uint8_t *destRow = | |
| priv::OffsetDataPointer<uint8_t>(output, y, z, outputRowPitch, outputDepthPitch); | |
| for (size_t x = 0; x < width; x += 4) | |
| { | |
| const ETC2Block *sourceBlockAlpha = sourceRow + (x / 2); | |
| sourceBlockAlpha->decodeAsSingleETC2Channel( | |
| reinterpret_cast<uint8_t *>(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); | |
| } | |
| } | |
| } | |
| } | |
| } // anonymous namespace | |
| void LoadETC1RGB8ToRGBA8(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) | |
| { | |
| LoadETC2RGB8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadETC1RGB8ToBC1(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) | |
| { | |
| LoadETC2RGB8ToBC1(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadEACR11ToR8(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) | |
| { | |
| LoadR11EACToR8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadEACR11SToR8(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) | |
| { | |
| LoadR11EACToR8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true); | |
| } | |
| void LoadEACRG11ToRG8(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) | |
| { | |
| LoadRG11EACToRG8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadEACRG11SToRG8(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) | |
| { | |
| LoadRG11EACToRG8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true); | |
| } | |
| void LoadEACR11ToR16(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) | |
| { | |
| LoadR11EACToR16(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false, false); | |
| } | |
| void LoadEACR11SToR16(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) | |
| { | |
| LoadR11EACToR16(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true, false); | |
| } | |
| void LoadEACRG11ToRG16(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) | |
| { | |
| LoadRG11EACToRG16(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false, false); | |
| } | |
| void LoadEACRG11SToRG16(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) | |
| { | |
| LoadRG11EACToRG16(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true, false); | |
| } | |
| void LoadEACR11ToR16F(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) | |
| { | |
| LoadR11EACToR16(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false, true); | |
| } | |
| void LoadEACR11SToR16F(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) | |
| { | |
| LoadR11EACToR16(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true, true); | |
| } | |
| void LoadEACRG11ToRG16F(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) | |
| { | |
| LoadRG11EACToRG16(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false, true); | |
| } | |
| void LoadEACRG11SToRG16F(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) | |
| { | |
| LoadRG11EACToRG16(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true, true); | |
| } | |
| 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) | |
| { | |
| LoadETC2RGB8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadETC2RGB8ToBC1(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) | |
| { | |
| LoadETC2RGB8ToBC1(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadETC2SRGB8ToRGBA8(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) | |
| { | |
| LoadETC2RGB8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadETC2SRGB8ToBC1(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) | |
| { | |
| LoadETC2RGB8ToBC1(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadETC2RGB8A1ToRGBA8(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) | |
| { | |
| LoadETC2RGB8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true); | |
| } | |
| void LoadETC2RGB8A1ToBC1(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) | |
| { | |
| LoadETC2RGB8ToBC1(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true); | |
| } | |
| void LoadETC2SRGB8A1ToRGBA8(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) | |
| { | |
| LoadETC2RGB8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true); | |
| } | |
| void LoadETC2SRGB8A1ToBC1(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) | |
| { | |
| LoadETC2RGB8ToBC1(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true); | |
| } | |
| 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) | |
| { | |
| LoadETC2RGBA8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, false); | |
| } | |
| void LoadETC2SRGBA8ToSRGBA8(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) | |
| { | |
| LoadETC2RGBA8ToRGBA8(width, height, depth, input, inputRowPitch, inputDepthPitch, output, | |
| outputRowPitch, outputDepthPitch, true); | |
| } | |
| } // namespace angle |