diff --git a/extensions/ANGLE_lossy_etc_decode.txt b/extensions/ANGLE_lossy_etc_decode.txt new file mode 100644 index 000000000..14881922e --- /dev/null +++ b/extensions/ANGLE_lossy_etc_decode.txt @@ -0,0 +1,159 @@ +Name + + ANGLE_lossy_etc_decode + +Name Strings + + GL_ANGLE_lossy_etc_decode + +Contributors + + Minmin Gong (mgong 'at' microsoft.com) + +Contacts + + Minmin Gong (mgong 'at' microsoft.com) + +Status + + Draft + +Version + + Last Modified Date: Nov 25, 2015 + Author Revision: 1 + +Number + + TBD + +Dependencies + + Requires OpenGL ES 3.0 for ETC2 and EAC formats, or OpenGL ES 2.0 and + OES_compressed_ETC1_RGB8_texture for ETC1 format. + The extension is written against the OpenGL ES 2.0 specification. + +Overview + + Both the OpenGL ES 3.0 specification and OES_compressed_ETC1_RGB8_texture + specify that Ericsson Texture Compression (ETC) decoding must not be lossy. + The goal of this extension is to allow a lossy decode of + compressed textures in the ETC formats in OpenGL ES, for lower memory + and bandwidth consumption. + + This extension uses the same ETC compression format as OpenGL ES 3.0 + and OES_compressed_ETC1_RGB8_texture, with the restriction that the texture + dimensions must be a multiple of four (except for mip levels where the + dimensions are either 2 or 1). And the requirement that ETC decoding must + not be lossy is relaxed. + + See OES_compressed_ETC1_RGB8_texture for a description of the ETC1 format. + Also see OpenGL ES 3.0 specification appendix C.2 (ETC Compressed Texture + ImageFormats) for a description of ETC2 and EAC formats. + +IP Status + + See Ericsson's "IP Statement" + +New Procedures and Functions + + None. + +New Types + + None. + +New Tokens + + Accepted by the parameter of CompressedTexImage2D + and the parameter of CompressedTexSubImage2D: + + ETC1_RGB8_LOSSY_DECODE_ANGLE 0x9690 + COMPRESSED_R11_LOSSY_DECODE_EAC_ANGLE 0x9691 + COMPRESSED_SIGNED_R11_LOSSY_DECODE_EAC_ANGLE 0x9692 + COMPRESSED_RG11_LOSSY_DECODE_EAC_ANGLE 0x9693 + COMPRESSED_SIGNED_RG11_LOSSY_DECODE_EAC_ANGLE 0x9694 + COMPRESSED_RGB8_LOSSY_DECODE_ETC2_ANGLE 0x9695 + COMPRESSED_SRGB8_LOSSY_DECODE_ETC2_ANGLE 0x9696 + COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE 0x9697 + COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE 0x9698 + COMPRESSED_RGBA8_LOSSY_DECODE_ETC2_EAC_ANGLE 0x9699 + COMPRESSED_SRGB8_ALPHA8_LOSSY_DECODE_ETC2_EAC_ANGLE 0x969A + +Additions to Chapter 3 of the OpenGL ES 2.0 Specification (Rasterization) + + Add the following to Section 3.7.3 (Compressed Texture Images) + (at the end of the description of the CompressedTexImage2D command): + + Compressed Internal Format Base Internal Format + ========================== ==================== + ETC1_RGB8_LOSSY_DECODE_ANGLE RGB + COMPRESSED_R11_LOSSY_DECODE_EAC_ANGLE R + COMPRESSED_SIGNED_R11_LOSSY_DECODE_EAC_ANGLE R + COMPRESSED_RG11_LOSSY_DECODE_EAC_ANGLE RG + COMPRESSED_SIGNED_RG11_LOSSY_DECODE_EAC_ANGLE RG + COMPRESSED_RGB8_LOSSY_DECODE_ETC2_ANGLE RGB + COMPRESSED_SRGB8_LOSSY_DECODE_ETC2_ANGLE RGB + COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE RGBA + COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE RGBA + COMPRESSED_RGBA8_LOSSY_DECODE_ETC2_EAC_ANGLE RGBA + COMPRESSED_SRGB8_ALPHA8_LOSSY_DECODE_ETC2_EAC_ANGLE RGBA + + Table 3.x: Specific Compressed Internal Formats + + If is one of the ETC lossy decode formats listed in + Table 3.x, the compressed texture is stored in an unspecified compressed + texture format, that may introduce losses of precision in the texture data. + The GL and the ETC texture compression algorithm support only 2D images + without borders. + + CompressedTexImage2D will produce the INVALID_OPERATION error when + is one of the lossy decode ETC-format values from + Table 3.x under the following conditions: + + * is non-zero. + * is not one, two, nor a multiple of four. + * is not one, two, nor a multiple of four. + + Add the following to Section 3.7.3 (Compressed Texture Images) + (at the end of the description of the CompressedTexSubImage2D command): + + If the internal format of the texture image being modified is an ETC-format + listed in Table 3.x, the compressed texture is stored in an unspecified + compressed texture format. The xoffset and yoffset must also be aligned to + 4x4 texel block boundaries, since ETC encoding makes it difficult to modify + non-aligned regions. CompressedTexSubImage2D will result in an + INVALID_OPERATION error only if one of the following conditions occurs: + + * is not a multiple of four nor equal to TEXTURE_WIDTH. + * is not a multiple of four nor equal to TEXTURE_HEIGHT. + * or is not a multiple of four. + * does not match the internal format of the texture image + being modified. + +Errors + + INVALID_OPERATION is generated by CompressedTexImage2D if + lossy decode ETC-format is used and is one of the + compressed internal formats from Table 3.x and any of the following apply: + - is not equal to zero. + - is not one, two, nor a multiple of four. + - is not one, two, nor a multiple of four. + + INVALID_OPERATION is generated by CompressedTexSubImage2D if + lossy decode ETC-format is used and is one of the compressed + interal formats from Table 3.x and any of the following apply: + - is not a multiple of four nor equal to TEXTURE_WIDTH; + - is not a multiple of four nor equal to TEXTURE_HEIGHT; + - or is not a multiple of four; + - does not match the internal format of the texture image + being modified. + +New State + + None. + +Revision History + + Revision 1, 2015/11/25 - mgong + - Initial revision diff --git a/include/GLES2/gl2ext.h b/include/GLES2/gl2ext.h index a878bcff6..51886a2dc 100644 --- a/include/GLES2/gl2ext.h +++ b/include/GLES2/gl2ext.h @@ -2920,6 +2920,21 @@ GL_APICALL void GL_APIENTRY glEndTilingQCOM (GLbitfield preserveMask); #define GL_SHADER_BINARY_VIV 0x8FC4 #endif /* GL_VIV_shader_binary */ +#ifndef GL_ANGLE_lossy_etc_decode +#define GL_ANGLE_lossy_etc_decode 1 +#define GL_ETC1_RGB8_LOSSY_DECODE_ANGLE 0x9690 +#define GL_COMPRESSED_R11_LOSSY_DECODE_EAC_ANGLE 0x9691 +#define GL_COMPRESSED_SIGNED_R11_LOSSY_DECODE_EAC_ANGLE 0x9692 +#define GL_COMPRESSED_RG11_LOSSY_DECODE_EAC_ANGLE 0x9693 +#define GL_COMPRESSED_SIGNED_RG11_LOSSY_DECODE_EAC_ANGLE 0x9694 +#define GL_COMPRESSED_RGB8_LOSSY_DECODE_ETC2_ANGLE 0x9695 +#define GL_COMPRESSED_SRGB8_LOSSY_DECODE_ETC2_ANGLE 0x9696 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE 0x9697 +#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE 0x9698 +#define GL_COMPRESSED_RGBA8_LOSSY_DECODE_ETC2_EAC_ANGLE 0x9699 +#define GL_COMPRESSED_SRGB8_ALPHA8_LOSSY_DECODE_ETC2_EAC_ANGLE 0x969A +#endif /* GL_ANGLE_lossy_etc_decode */ + #ifdef __cplusplus } #endif diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp index 4118e8c1d..1c5edccaf 100644 --- a/src/libANGLE/Caps.cpp +++ b/src/libANGLE/Caps.cpp @@ -156,6 +156,7 @@ Extensions::Extensions() maxDebugGroupStackDepth(0), maxLabelLength(0), noError(false), + lossyETCDecode(false), colorBufferFloat(false) { } @@ -225,6 +226,8 @@ std::vector Extensions::getStrings() const InsertExtensionString("GL_KHR_debug", debug, &extensionStrings); // TODO(jmadill): Enable this when complete. //InsertExtensionString("GL_KHR_no_error", noError, &extensionStrings); + + InsertExtensionString("GL_ANGLE_lossy_etc_decode", lossyETCDecode, &extensionStrings); // clang-format on return extensionStrings; diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h index 558668417..5d948518f 100644 --- a/src/libANGLE/Caps.h +++ b/src/libANGLE/Caps.h @@ -275,6 +275,9 @@ struct Extensions // KHR_no_error bool noError; + // GL_ANGLE_lossy_etc_decode + bool lossyETCDecode; + // ES3 Extension support // GL_EXT_color_buffer_float diff --git a/src/libANGLE/formatutils.cpp b/src/libANGLE/formatutils.cpp index 0d64ae533..3a4df126c 100644 --- a/src/libANGLE/formatutils.cpp +++ b/src/libANGLE/formatutils.cpp @@ -565,6 +565,10 @@ static InternalFormatInfoMap BuildInternalFormatInfoMap() // - It affects only validation of internalformat in RenderbufferStorageMultisample. // | Internal format | |D |S |X | Format | Type | Component type | Supported | Renderable | Filterable | map.insert(InternalFormatInfoPair(GL_STENCIL_INDEX8, DepthStencilFormat(0, 8, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_BYTE, GL_UNSIGNED_NORMALIZED, RequireES<2>, RequireES<2>, NeverSupported))); + + // From GL_ANGLE_lossy_etc_decode + map.insert(InternalFormatInfoPair(GL_ETC1_RGB8_LOSSY_DECODE_ANGLE, CompressedFormat(4, 4, 64, 3, GL_ETC1_RGB8_OES, GL_UNSIGNED_BYTE, false, RequireExt<&Extensions::lossyETCDecode>, NeverSupported, AlwaysSupported))); + // clang-format on return map; diff --git a/src/libANGLE/renderer/d3d/d3d11/load_functions_data.json b/src/libANGLE/renderer/d3d/d3d11/load_functions_data.json index cb1d8595a..c85393e06 100644 --- a/src/libANGLE/renderer/d3d/d3d11/load_functions_data.json +++ b/src/libANGLE/renderer/d3d/d3d11/load_functions_data.json @@ -1103,5 +1103,14 @@ "requiresConversion": "true" } ] + }, + "GL_ETC1_RGB8_LOSSY_DECODE_ANGLE": { + "GL_UNSIGNED_BYTE": [ + { + "loadFunction": "LoadETC1RGB8ToBC1", + "dxgiFormat": "DXGI_FORMAT_BC1_UNORM", + "requiresConversion": "true" + } + ] } } \ No newline at end of file diff --git a/src/libANGLE/renderer/d3d/d3d11/load_functions_table_autogen.cpp b/src/libANGLE/renderer/d3d/d3d11/load_functions_table_autogen.cpp index 788f792e7..1f7cec69a 100644 --- a/src/libANGLE/renderer/d3d/d3d11/load_functions_table_autogen.cpp +++ b/src/libANGLE/renderer/d3d/d3d11/load_functions_table_autogen.cpp @@ -771,6 +771,24 @@ const std::map &GetLoadFunctionsMap(GLenum internalFo } } } + case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE: + { + switch (dxgiFormat) + { + case DXGI_FORMAT_BC1_UNORM: + { + static const std::map loadFunctionsMap = []() { + std::map loadMap; + loadMap[GL_UNSIGNED_BYTE] = LoadETC1RGB8ToBC1; + return loadMap; + }(); + + return loadFunctionsMap; + } + default: + break; + } + } case GL_ETC1_RGB8_OES: { switch (dxgiFormat) diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp index a6d78fd2d..8c0cc1009 100644 --- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp +++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp @@ -1218,6 +1218,7 @@ void GenerateCaps(ID3D11Device *device, ID3D11DeviceContext *deviceContext, cons extensions->packSubimage = true; extensions->vertexArrayObject = true; extensions->noError = true; + extensions->lossyETCDecode = true; // D3D11 Feature Level 10_0+ uses SV_IsFrontFace in HLSL to emulate gl_FrontFacing. // D3D11 Feature Level 9_3 doesn't support SV_IsFrontFace, and has no equivalent, so can't support gl_FrontFacing. diff --git a/src/libANGLE/renderer/d3d/d3d11/texture_format_data.json b/src/libANGLE/renderer/d3d/d3d11/texture_format_data.json index 79f6f864f..87d303437 100644 --- a/src/libANGLE/renderer/d3d/d3d11/texture_format_data.json +++ b/src/libANGLE/renderer/d3d/d3d11/texture_format_data.json @@ -238,6 +238,12 @@ "srvFormat": "DXGI_FORMAT_R8G8B8A8_UNORM" } ], + "GL_ETC1_RGB8_LOSSY_DECODE_ANGLE": [ + { + "texFormat": "DXGI_FORMAT_BC1_UNORM", + "srvFormat": "DXGI_FORMAT_BC1_UNORM" + } + ], "GL_LUMINANCE": [ { "texFormat": "DXGI_FORMAT_R8G8B8A8_UNORM", diff --git a/src/libANGLE/renderer/d3d/d3d11/texture_format_table_autogen.cpp b/src/libANGLE/renderer/d3d/d3d11/texture_format_table_autogen.cpp index ecaa3fd28..0b214c975 100644 --- a/src/libANGLE/renderer/d3d/d3d11/texture_format_table_autogen.cpp +++ b/src/libANGLE/renderer/d3d/d3d11/texture_format_table_autogen.cpp @@ -732,6 +732,22 @@ const TextureFormat &GetTextureFormatInfo(GLenum internalFormat, break; } } + case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE: + { + if (AnyDevice(renderer11DeviceCaps)) + { + static const TextureFormat textureFormat = GetD3D11FormatInfo(internalFormat, + DXGI_FORMAT_BC1_UNORM, + DXGI_FORMAT_BC1_UNORM, + DXGI_FORMAT_UNKNOWN, + DXGI_FORMAT_UNKNOWN); + return textureFormat; + } + else + { + break; + } + } case GL_ETC1_RGB8_OES: { if (AnyDevice(renderer11DeviceCaps)) diff --git a/src/libANGLE/renderer/d3d/loadimage_etc.cpp b/src/libANGLE/renderer/d3d/loadimage_etc.cpp index 97eb99299..26a3b32ce 100644 --- a/src/libANGLE/renderer/d3d/loadimage_etc.cpp +++ b/src/libANGLE/renderer/d3d/loadimage_etc.cpp @@ -15,6 +15,43 @@ namespace rx { namespace { +// Table 3.17.2 sorted according to table 3.17.3 +// clang-format off +static const int intensityModifierDefault[][4] = +{ + { 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 const int intensityModifierNonOpaque[][4] = +{ + { 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 + +// Table C.7, mapping from pixel index values to modifier value orders +// clang-format off +static const int valueMappingTable[] = +{ + 2, 3, 1, 0 +}; +// clang-format on struct ETC2Block { @@ -92,6 +129,49 @@ struct ETC2Block } } + // 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 { @@ -329,48 +409,18 @@ struct ETC2Block const uint8_t alphaValues[4][4], bool nonOpaquePunchThroughAlpha) const { - // Table 3.17.2 sorted according to table 3.17.3 - // clang-format off - static const int intensityModifierDefault[8][4] = - { - { 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 const int intensityModifierNonOpaque[8][4] = - { - { 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 - - const int(&intensityModifier)[8][4] = + const auto intensityModifier = nonOpaquePunchThroughAlpha ? intensityModifierNonOpaque : intensityModifierDefault; R8G8B8A8 subblockColors0[4]; R8G8B8A8 subblockColors1[4]; - for (size_t blockIdx = 0; blockIdx < 4; blockIdx++) + for (size_t modifierIdx = 0; modifierIdx < 4; modifierIdx++) { - const int i1 = intensityModifier[u.idht.mode.idm.cw1][blockIdx]; - subblockColors0[blockIdx] = createRGBA(r1 + i1, g1 + i1, b1 + i1); + 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][blockIdx]; - subblockColors1[blockIdx] = createRGBA(r2 + i2, g2 + i2, b2 + i2); + 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) @@ -583,6 +633,392 @@ struct ETC2Block } } + 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); + } + + uint32_t matchBC1Bits(const R8G8B8A8 *rgba, + const R8G8B8A8 &minColor, + const R8G8B8A8 &maxColor, + bool opaque) 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]; + } + + uint32_t bits = 0; + if (opaque) + { + for (int i = 15; i >= 0; i--) + { + // In opaque mode, the code is from 0 to 3. + + bits <<= 2; + const int dot = + rgba[i].R * direction[0] + rgba[i].G * direction[1] + rgba[i].B * direction[2]; + const int factor = gl::clamp( + static_cast( + (static_cast(dot - stops[1]) / (stops[0] - stops[1])) * 3 + 0.5f), + 0, 3); + switch (factor) + { + case 0: + bits |= 1; + break; + case 1: + bits |= 3; + break; + case 2: + bits |= 2; + break; + case 3: + default: + bits |= 0; + break; + } + } + } + else + { + for (int i = 15; i >= 0; i--) + { + // In non-opaque mode, 3 is for tranparent pixels. + + bits <<= 2; + if (0 == rgba[i].A) + { + bits |= 3; + } + else + { + const int dot = rgba[i].R * direction[0] + rgba[i].G * direction[1] + + rgba[i].B * direction[2]; + const int factor = gl::clamp( + static_cast( + (static_cast(dot - stops[1]) / (stops[0] - stops[1])) * 2 + + 0.5f), + 0, 2); + switch (factor) + { + case 0: + bits |= 0; + break; + case 1: + bits |= 2; + break; + case 2: + default: + bits |= 1; + break; + } + } + } + } + + return bits; + } + + void packBC1(void *bc1, + const R8G8B8A8 *rgba, + R8G8B8A8 &minColor, + R8G8B8A8 &maxColor, + bool opaque) const + { + 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(rgba, minColor, maxColor, opaque); + } + else + { + // Same colors, BC1 index 0 is the color in both opaque and transparent mode + bits = 0; + // BC1 index 3 is transparent + if (!opaque) + { + for (int i = 0; i < 16; i++) + { + if (0 == rgba[i].A) + { + bits |= (3 << (i * 2)); + } + } + } + } + + if (max16 < min16) + { + std::swap(max16, min16); + + uint32_t xorMask = 0; + if (opaque) + { + // 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; + } + else + { + // 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); + } + 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(bc1); + if (opaque) + { + dest->color0 = max16; + dest->color1 = min16; + } + else + { + dest->color0 = min16; + dest->color1 = max16; + } + 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 decodeSubblock(R8G8B8A8 *rgbaBlock, + size_t pixelRange[2][2], + size_t x, + size_t y, + size_t w, + size_t h, + const uint8_t alphaValues[4][4], + bool flipbit, + size_t subblockIdx, + const R8G8B8A8 subblockColors[2][4]) 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 && (y + j) < h; j++) + { + R8G8B8A8 *row = &rgbaBlock[j * 4]; + for (size_t i = dxBegin; i < dxEnd && (x + i) < w; i++) + { + const size_t pixelIndex = getIndex(i, j); + if (valueMappingTable[pixelIndex] < valueMappingTable[pixelRange[subblockIdx][0]]) + { + pixelRange[subblockIdx][0] = pixelIndex; + } + if (valueMappingTable[pixelIndex] > valueMappingTable[pixelRange[subblockIdx][1]]) + { + pixelRange[subblockIdx][1] = pixelIndex; + } + + row[i] = subblockColors[subblockIdx][pixelIndex]; + row[i].A = alphaValues[j][i]; + } + } + } + + 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 + // compute the max intensity and min intensity pixel values 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. + // + // In the future, we have 2 potential improvements to this algorithm. + // 1. We don't actually need to decode ETC blocks to RGBs. Instead, + // the subblock colors and pixel indices alreay contains enough + // information for transcode. A direct mapping would be more + // efficient here. + // 2. Currently the BC1 endpoints come from the max and min intensity + // of ETC colors. A principal component analysis (PCA) on them might + // give us better quality results, with limited costs + + const auto 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[2][4]; + for (size_t modifierIdx = 0; modifierIdx < 4; modifierIdx++) + { + const int i1 = intensityModifier[u.idht.mode.idm.cw1][modifierIdx]; + subblockColors[0][modifierIdx] = createRGBA(r1 + i1, g1 + i1, b1 + i1); + + const int i2 = intensityModifier[u.idht.mode.idm.cw2][modifierIdx]; + subblockColors[1][modifierIdx] = createRGBA(r2 + i2, g2 + i2, b2 + i2); + } + + // 1 and 3 are the argmax and argmin of valueMappingTable + size_t pixelRange[2][2] = {{1, 3}, {1, 3}}; + R8G8B8A8 rgbaBlock[16]; + // Decode the block in rgbaBlock and store the inverse valueTableMapping + // of {min(modifier index), max(modifier index)} + for (size_t blockIdx = 0; blockIdx < 2; blockIdx++) + { + decodeSubblock(rgbaBlock, pixelRange, x, y, w, h, alphaValues, u.idht.mode.idm.flipbit, + blockIdx, subblockColors); + } + if (nonOpaquePunchThroughAlpha) + { + decodePunchThroughAlphaBlock(reinterpret_cast(rgbaBlock), x, y, w, h, + sizeof(R8G8B8A8) * 4); + } + + // Get the "min" and "max" pixel colors that have been used. + R8G8B8A8 minColor; + const R8G8B8A8 &minColor0 = subblockColors[0][pixelRange[0][0]]; + const R8G8B8A8 &minColor1 = subblockColors[1][pixelRange[1][0]]; + if (minColor0.R + minColor0.G + minColor0.B < minColor1.R + minColor1.G + minColor1.B) + { + minColor = minColor0; + } + else + { + minColor = minColor1; + } + + R8G8B8A8 maxColor; + const R8G8B8A8 &maxColor0 = subblockColors[0][pixelRange[0][1]]; + const R8G8B8A8 &maxColor1 = subblockColors[1][pixelRange[1][1]]; + if (maxColor0.R + maxColor0.G + maxColor0.B < maxColor1.R + maxColor1.G + maxColor1.B) + { + maxColor = maxColor1; + } + else + { + maxColor = maxColor0; + } + + packBC1(dest, rgbaBlock, minColor, maxColor, !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 + { + // TODO (mgong): Will be implemented soon + UNIMPLEMENTED(); + } + + 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 + { + // TODO (mgong): Will be implemented soon + UNIMPLEMENTED(); + } + + void transcodePlanarBlockToBC1(uint8_t *dest, + size_t x, + size_t y, + size_t w, + size_t h, + const uint8_t alphaValues[4][4]) const + { + // TODO (mgong): Will be implemented soon + UNIMPLEMENTED(); + } + // Single channel utility functions int getSingleChannel(size_t x, size_t y, bool isSigned) const { @@ -756,6 +1192,38 @@ void LoadETC2RGB8ToRGBA8(size_t width, } } +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 = + OffsetDataPointer(input, y / 4, z, inputRowPitch, inputDepthPitch); + uint8_t *destRow = + OffsetDataPointer(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, @@ -810,6 +1278,20 @@ void LoadETC1RGB8ToRGBA8(size_t width, 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, diff --git a/src/libANGLE/renderer/d3d/loadimage_etc.h b/src/libANGLE/renderer/d3d/loadimage_etc.h index 2ada743a2..dc64e0461 100644 --- a/src/libANGLE/renderer/d3d/loadimage_etc.h +++ b/src/libANGLE/renderer/d3d/loadimage_etc.h @@ -26,6 +26,16 @@ void LoadETC1RGB8ToRGBA8(size_t width, size_t outputRowPitch, size_t outputDepthPitch); +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); + void LoadEACR11ToR8(size_t width, size_t height, size_t depth, diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp index 0a14d12ee..12c76120b 100644 --- a/src/libANGLE/validationES.cpp +++ b/src/libANGLE/validationES.cpp @@ -334,6 +334,7 @@ bool CompressedTextureFormatRequiresExactSize(GLenum internalFormat) case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE: case GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE: + case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE: return true; default: diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp index f4964498b..2e5b955e9 100644 --- a/src/libANGLE/validationES2.cpp +++ b/src/libANGLE/validationES2.cpp @@ -195,6 +195,14 @@ bool ValidateES2TexImageParameters(Context *context, GLenum target, GLint level, return false; } break; + case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE: + if (!context->getExtensions().lossyETCDecode) + { + context->recordError( + Error(GL_INVALID_ENUM, "ANGLE_lossy_etc_decode extension is not supported")); + return false; + } + break; default: context->recordError(Error( GL_INVALID_ENUM, "internalformat is not a supported compressed internal format")); @@ -398,6 +406,21 @@ bool ValidateES2TexImageParameters(Context *context, GLenum target, GLint level, return false; } break; + case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE: + if (context->getExtensions().lossyETCDecode) + { + context->recordError( + Error(GL_INVALID_OPERATION, + "ETC1_RGB8_LOSSY_DECODE_ANGLE can't work with this type.")); + return false; + } + else + { + context->recordError( + Error(GL_INVALID_ENUM, "ANGLE_lossy_etc_decode extension is not supported.")); + return false; + } + break; case GL_DEPTH_COMPONENT: case GL_DEPTH_STENCIL_OES: if (!context->getExtensions().depthTextures) @@ -564,6 +587,7 @@ bool ValidateES2CopyTexImageParameters(ValidationContext *context, case GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE: case GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE: case GL_ETC1_RGB8_OES: + case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE: context->recordError(Error(GL_INVALID_OPERATION)); return false; case GL_DEPTH_COMPONENT: @@ -716,6 +740,20 @@ bool ValidateES2CopyTexImageParameters(ValidationContext *context, return false; } break; + case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE: + if (context->getExtensions().lossyETCDecode) + { + context->recordError(Error(GL_INVALID_OPERATION, + "ETC1_RGB8_LOSSY_DECODE_ANGLE can't be copied to.")); + return false; + } + else + { + context->recordError( + Error(GL_INVALID_ENUM, "ANGLE_lossy_etc_decode extension is not supported.")); + return false; + } + break; case GL_DEPTH_COMPONENT: case GL_DEPTH_COMPONENT16: case GL_DEPTH_COMPONENT32_OES: @@ -840,6 +878,14 @@ bool ValidateES2TexStorageParameters(Context *context, GLenum target, GLsizei le return false; } break; + case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE: + if (!context->getExtensions().lossyETCDecode) + { + context->recordError( + Error(GL_INVALID_ENUM, "ANGLE_lossy_etc_decode extension is not supported.")); + return false; + } + break; case GL_RGBA32F_EXT: case GL_RGB32F_EXT: case GL_ALPHA32F_EXT: diff --git a/src/tests/angle_end2end_tests.gypi b/src/tests/angle_end2end_tests.gypi index 6f50b0fbd..42e2704ed 100644 --- a/src/tests/angle_end2end_tests.gypi +++ b/src/tests/angle_end2end_tests.gypi @@ -28,6 +28,7 @@ '<(angle_path)/src/tests/gl_tests/DiscardFramebufferEXTTest.cpp', '<(angle_path)/src/tests/gl_tests/DrawBuffersTest.cpp', '<(angle_path)/src/tests/gl_tests/DrawElementsTest.cpp', + '<(angle_path)/src/tests/gl_tests/ETCTextureTest.cpp', '<(angle_path)/src/tests/gl_tests/FenceSyncTests.cpp', '<(angle_path)/src/tests/gl_tests/FramebufferFormatsTest.cpp', '<(angle_path)/src/tests/gl_tests/FramebufferRenderMipmapTest.cpp', diff --git a/src/tests/gl_tests/ETCTextureTest.cpp b/src/tests/gl_tests/ETCTextureTest.cpp new file mode 100644 index 000000000..f34ac24b3 --- /dev/null +++ b/src/tests/gl_tests/ETCTextureTest.cpp @@ -0,0 +1,79 @@ +// +// Copyright 2015 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. +// +// ETCTextureTest: +// Tests for ETC lossy decode formats. +// + +#include "test_utils/ANGLETest.h" + +using namespace angle; + +namespace +{ + +class ETCTextureTest : public ANGLETest +{ + protected: + ETCTextureTest() : mTexture(0u) + { + setWindowWidth(128); + setWindowHeight(128); + setConfigRedBits(8); + setConfigGreenBits(8); + setConfigBlueBits(8); + setConfigAlphaBits(8); + } + + void SetUp() override + { + ANGLETest::SetUp(); + + glGenTextures(1, &mTexture); + ASSERT_GL_NO_ERROR(); + } + + void TearDown() override + { + glDeleteTextures(1, &mTexture); + + ANGLETest::TearDown(); + } + + GLuint mTexture; +}; + +// Tests a texture with ETC1 lossy decode format +TEST_P(ETCTextureTest, ETC1Validation) +{ + bool supported = extensionEnabled("GL_ANGLE_lossy_etc_decode"); + + glBindTexture(GL_TEXTURE_2D, mTexture); + + GLubyte pixel[8] = {0}; + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC1_RGB8_LOSSY_DECODE_ANGLE, 4, 4, 0, + sizeof(pixel), pixel); + if (supported) + { + EXPECT_GL_NO_ERROR(); + + glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_ETC1_RGB8_LOSSY_DECODE_ANGLE, + sizeof(pixel), pixel); + EXPECT_GL_NO_ERROR(); + } + else + { + EXPECT_GL_ERROR(GL_INVALID_ENUM); + } +} + +ANGLE_INSTANTIATE_TEST(ETCTextureTest, + ES2_D3D9(), + ES2_D3D11(), + ES2_D3D11_FL9_3(), + ES3_D3D11(), + ES2_OPENGL(), + ES3_OPENGL()); +} // anonymous namespace