D3D11: Transcode ETC1 to BC1.

Adds a new extension to allow transcode ETC formats to suitable BC formats.
This commit implements ETC1 to BC1. More formats will be supported soon.

BUG=angleproject:1285

Change-Id: Iacbfbc2248dfe1aebf24b92696249a9404e331cd
Reviewed-on: https://chromium-review.googlesource.com/316511
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Minmin Gong
2015-12-01 15:36:51 -08:00
committed by Corentin Wallez
parent 7718c05b79
commit e3939b985c
16 changed files with 889 additions and 36 deletions

View File

@@ -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 <internalformat> parameter of CompressedTexImage2D
and the <format> 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 <internalformat> 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
<internalformat> is one of the lossy decode ETC-format values from
Table 3.x under the following conditions:
* <border> is non-zero.
* <width> is not one, two, nor a multiple of four.
* <height> 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:
* <width> is not a multiple of four nor equal to TEXTURE_WIDTH.
* <height> is not a multiple of four nor equal to TEXTURE_HEIGHT.
* <xoffset> or <yoffset> is not a multiple of four.
* <format> 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 <internalformat> is one of the
compressed internal formats from Table 3.x and any of the following apply:
- <border> is not equal to zero.
- <width> is not one, two, nor a multiple of four.
- <height> is not one, two, nor a multiple of four.
INVALID_OPERATION is generated by CompressedTexSubImage2D if
lossy decode ETC-format is used and <format> is one of the compressed
interal formats from Table 3.x and any of the following apply:
- <width> is not a multiple of four nor equal to TEXTURE_WIDTH;
- <height> is not a multiple of four nor equal to TEXTURE_HEIGHT;
- <xoffset> or <yoffset> is not a multiple of four;
- <format> 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

View File

@@ -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

View File

@@ -156,6 +156,7 @@ Extensions::Extensions()
maxDebugGroupStackDepth(0),
maxLabelLength(0),
noError(false),
lossyETCDecode(false),
colorBufferFloat(false)
{
}
@@ -225,6 +226,8 @@ std::vector<std::string> 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;

View File

@@ -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

View File

@@ -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;

View File

@@ -1103,5 +1103,14 @@
"requiresConversion": "true"
}
]
},
"GL_ETC1_RGB8_LOSSY_DECODE_ANGLE": {
"GL_UNSIGNED_BYTE": [
{
"loadFunction": "LoadETC1RGB8ToBC1",
"dxgiFormat": "DXGI_FORMAT_BC1_UNORM",
"requiresConversion": "true"
}
]
}
}

View File

@@ -771,6 +771,24 @@ const std::map<GLenum, LoadImageFunction> &GetLoadFunctionsMap(GLenum internalFo
}
}
}
case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE:
{
switch (dxgiFormat)
{
case DXGI_FORMAT_BC1_UNORM:
{
static const std::map<GLenum, LoadImageFunction> loadFunctionsMap = []() {
std::map<GLenum, LoadImageFunction> loadMap;
loadMap[GL_UNSIGNED_BYTE] = LoadETC1RGB8ToBC1;
return loadMap;
}();
return loadFunctionsMap;
}
default:
break;
}
}
case GL_ETC1_RGB8_OES:
{
switch (dxgiFormat)

View File

@@ -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.

View File

@@ -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",

View File

@@ -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))

View File

@@ -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<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 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<int>(
(static_cast<float>(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<int>(
(static_cast<float>(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<BC1Block *>(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<uint8_t *>(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<ETC2Block>(input, y / 4, z, inputRowPitch, inputDepthPitch);
uint8_t *destRow =
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,
@@ -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,

View File

@@ -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,

View File

@@ -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:

View File

@@ -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:

View File

@@ -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',

View File

@@ -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