Commit 41ee2024 by Minmin Gong Committed by Commit Bot

D3D11: Improvements on ETC1 to BC1 transcode.

ETC1 data doesn't need to be fully decoded before encoding to BC1. Saves ~50% computation. BUG=angleproject:1285 Change-Id: I0cb479a08159ccd0472e5fdf40dd84323151883c Reviewed-on: https://chromium-review.googlesource.com/361553 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 8cf70d55
...@@ -46,6 +46,8 @@ static const int intensityModifierNonOpaque[][4] = ...@@ -46,6 +46,8 @@ static const int intensityModifierNonOpaque[][4] =
}; };
// clang-format on // clang-format on
static const int kNumPixelsInBlock = 16;
struct ETC2Block struct ETC2Block
{ {
// Decodes unsigned single or dual channel block to bytes // Decodes unsigned single or dual channel block to bytes
...@@ -629,10 +631,13 @@ struct ETC2Block ...@@ -629,10 +631,13 @@ struct ETC2Block
(static_cast<uint16_t>(rgba.B >> 3) << 0); (static_cast<uint16_t>(rgba.B >> 3) << 0);
} }
uint32_t matchBC1Bits(const R8G8B8A8 *rgba, uint32_t matchBC1Bits(const int *pixelIndices,
const int *pixelIndexCounts,
const R8G8B8A8 *subblockColors,
size_t numColors,
const R8G8B8A8 &minColor, const R8G8B8A8 &minColor,
const R8G8B8A8 &maxColor, const R8G8B8A8 &maxColor,
bool opaque) const bool nonOpaquePunchThroughAlpha) const
{ {
// Project each pixel on the (maxColor, minColor) line to decide which // Project each pixel on the (maxColor, minColor) line to decide which
// BC1 code to assign to it. // BC1 code to assign to it.
...@@ -653,102 +658,127 @@ struct ETC2Block ...@@ -653,102 +658,127 @@ struct ETC2Block
decodedColors[i][2] * direction[2]; decodedColors[i][2] * direction[2];
} }
uint32_t bits = 0; ASSERT(numColors <= kNumPixelsInBlock);
if (opaque)
int encodedColors[kNumPixelsInBlock];
if (nonOpaquePunchThroughAlpha)
{ {
for (int i = 15; i >= 0; i--) for (size_t i = 0; i < numColors; i++)
{ {
// In opaque mode, the code is from 0 to 3. const int count = pixelIndexCounts[i];
if (count > 0)
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: // In non-opaque mode, 3 is for tranparent pixels.
bits |= 1;
break; if (0 == subblockColors[i].A)
case 1: {
bits |= 3; encodedColors[i] = 3;
break; }
case 2: else
bits |= 2; {
break; const R8G8B8A8 &pixel = subblockColors[i];
case 3: const int dot = pixel.R * direction[0] + pixel.G * direction[1] +
default: pixel.B * direction[2];
bits |= 0; const int factor = gl::clamp(
break; 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 else
{ {
for (int i = 15; i >= 0; i--) for (size_t i = 0; i < numColors; i++)
{ {
// In non-opaque mode, 3 is for tranparent pixels. const int count = pixelIndexCounts[i];
if (count > 0)
bits <<= 2;
if (0 == rgba[i].A)
{ {
bits |= 3; // In opaque mode, the code is from 0 to 3.
}
else const R8G8B8A8 &pixel = subblockColors[i];
{ const int dot =
const int dot = rgba[i].R * direction[0] + rgba[i].G * direction[1] + pixel.R * direction[0] + pixel.G * direction[1] + pixel.B * direction[2];
rgba[i].B * direction[2];
const int factor = gl::clamp( const int factor = gl::clamp(
static_cast<int>( static_cast<int>(
(static_cast<float>(dot - stops[1]) / (stops[0] - stops[1])) * 2 + (static_cast<float>(dot - stops[1]) / (stops[0] - stops[1])) * 3 +
0.5f), 0.5f),
0, 2); 0, 3);
switch (factor) switch (factor)
{ {
case 0: case 0:
bits |= 0; encodedColors[i] = 1;
break; break;
case 1: case 1:
bits |= 2; encodedColors[i] = 3;
break; break;
case 2: case 2:
encodedColors[i] = 2;
break;
case 3:
default: default:
bits |= 1; encodedColors[i] = 0;
break; break;
} }
} }
} }
} }
uint32_t bits = 0;
for (int i = kNumPixelsInBlock - 1; i >= 0; i--)
{
bits <<= 2;
bits |= encodedColors[pixelIndices[i]];
}
return bits; return bits;
} }
void packBC1(void *bc1, void packBC1(void *bc1,
const R8G8B8A8 *rgba, const int *pixelIndices,
const R8G8B8A8 &minColor, const int *pixelIndexCounts,
const R8G8B8A8 &maxColor, const R8G8B8A8 *subblockColors,
bool opaque) const size_t numColors,
int minColorIndex,
int maxColorIndex,
bool nonOpaquePunchThroughAlpha) const
{ {
const R8G8B8A8 &minColor = subblockColors[minColorIndex];
const R8G8B8A8 &maxColor = subblockColors[maxColorIndex];
uint32_t bits; uint32_t bits;
uint16_t max16 = RGB8ToRGB565(maxColor); uint16_t max16 = RGB8ToRGB565(maxColor);
uint16_t min16 = RGB8ToRGB565(minColor); uint16_t min16 = RGB8ToRGB565(minColor);
if (max16 != min16) if (max16 != min16)
{ {
// Find the best BC1 code for each pixel // Find the best BC1 code for each pixel
bits = matchBC1Bits(rgba, minColor, maxColor, opaque); bits = matchBC1Bits(pixelIndices, pixelIndexCounts, subblockColors, numColors, minColor,
maxColor, nonOpaquePunchThroughAlpha);
} }
else else
{ {
// Same colors, BC1 index 0 is the color in both opaque and transparent mode // Same colors, BC1 index 0 is the color in both opaque and transparent mode
bits = 0; bits = 0;
// BC1 index 3 is transparent // BC1 index 3 is transparent
if (!opaque) if (nonOpaquePunchThroughAlpha)
{ {
for (int i = 0; i < 16; i++) for (int i = 0; i < kNumPixelsInBlock; i++)
{ {
if (0 == rgba[i].A) if (0 == subblockColors[pixelIndices[i]].A)
{ {
bits |= (3 << (i * 2)); bits |= (3 << (i * 2));
} }
...@@ -761,15 +791,7 @@ struct ETC2Block ...@@ -761,15 +791,7 @@ struct ETC2Block
std::swap(max16, min16); std::swap(max16, min16);
uint32_t xorMask = 0; uint32_t xorMask = 0;
if (opaque) if (nonOpaquePunchThroughAlpha)
{
// 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 // In transparent mode switching the colors is doing the
// following code swap: 0 <-> 1. 0xA selects the second bit of // following code swap: 0 <-> 1. 0xA selects the second bit of
...@@ -779,6 +801,14 @@ struct ETC2Block ...@@ -779,6 +801,14 @@ struct ETC2Block
// 0 or 1. // 0 or 1.
xorMask = ~((bits >> 1) | 0xAAAAAAAA); 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; bits ^= xorMask;
} }
...@@ -791,15 +821,15 @@ struct ETC2Block ...@@ -791,15 +821,15 @@ struct ETC2Block
// Encode the opaqueness in the order of the two BC1 colors // Encode the opaqueness in the order of the two BC1 colors
BC1Block *dest = reinterpret_cast<BC1Block *>(bc1); BC1Block *dest = reinterpret_cast<BC1Block *>(bc1);
if (opaque) if (nonOpaquePunchThroughAlpha)
{ {
dest->color0 = max16; dest->color0 = min16;
dest->color1 = min16; dest->color1 = max16;
} }
else else
{ {
dest->color0 = min16; dest->color0 = max16;
dest->color1 = max16; dest->color1 = min16;
} }
dest->bits = bits; dest->bits = bits;
} }
...@@ -842,15 +872,14 @@ struct ETC2Block ...@@ -842,15 +872,14 @@ struct ETC2Block
alphaValues, nonOpaquePunchThroughAlpha); alphaValues, nonOpaquePunchThroughAlpha);
} }
void decodeSubblock(R8G8B8A8 *rgbaBlock, void extractPixelIndices(int *pixelIndices,
size_t x, int *pixelIndicesCounts,
size_t y, size_t x,
size_t w, size_t y,
size_t h, size_t w,
const uint8_t alphaValues[4][4], size_t h,
bool flipbit, bool flipbit,
size_t subblockIdx, size_t subblockIdx) const
const R8G8B8A8 subblockColors[2][4]) const
{ {
size_t dxBegin = 0; size_t dxBegin = 0;
size_t dxEnd = 4; size_t dxEnd = 4;
...@@ -864,53 +893,72 @@ struct ETC2Block ...@@ -864,53 +893,72 @@ struct ETC2Block
for (size_t j = dyBegin; j < dyEnd && (y + j) < h; j++) for (size_t j = dyBegin; j < dyEnd && (y + j) < h; j++)
{ {
R8G8B8A8 *row = &rgbaBlock[j * 4]; int *row = &pixelIndices[j * 4];
for (size_t i = dxBegin; i < dxEnd && (x + i) < w; i++) for (size_t i = dxBegin; i < dxEnd && (x + i) < w; i++)
{ {
const size_t pixelIndex = getIndex(i, j); const size_t pixelIndex = subblockIdx * 4 + getIndex(i, j);
row[i] = subblockColors[subblockIdx][pixelIndex]; row[i] = static_cast<int>(pixelIndex);
row[i].A = alphaValues[j][i]; pixelIndicesCounts[pixelIndex]++;
} }
} }
} }
void selectEndPointPCA(const R8G8B8A8 *pixels, R8G8B8A8 *minColor, R8G8B8A8 *maxColor) const void selectEndPointPCA(const int *pixelIndexCounts,
const R8G8B8A8 *subblockColors,
size_t numColors,
int *minColorIndex,
int *maxColorIndex) const
{ {
static const int kNumPixels = 16;
// determine color distribution // determine color distribution
int mu[3], min[3], max[3]; int mu[3], min[3], max[3];
for (int ch = 0; ch < 3; ch++) for (int ch = 0; ch < 3; ch++)
{ {
int muv, minv, maxv; int muv = 0;
int minv = 255;
muv = minv = maxv = (&pixels[0].R)[ch]; int maxv = 0;
for (size_t i = 1; i < kNumPixels; i++) for (size_t i = 0; i < numColors; i++)
{ {
muv += (&pixels[i].R)[ch]; const int count = pixelIndexCounts[i];
minv = std::min<int>(minv, (&pixels[i].R)[ch]); if (count > 0)
maxv = std::max<int>(maxv, (&pixels[i].R)[ch]); {
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 + kNumPixels / 2) / kNumPixels; mu[ch] = (muv + kNumPixelsInBlock / 2) / kNumPixelsInBlock;
min[ch] = minv; min[ch] = minv;
max[ch] = maxv; max[ch] = maxv;
} }
// determine covariance matrix // determine covariance matrix
int cov[6] = {0, 0, 0, 0, 0, 0}; int cov[6] = {0, 0, 0, 0, 0, 0};
for (size_t i = 0; i < kNumPixels; i++) for (size_t i = 0; i < numColors; i++)
{ {
int r = pixels[i].R - mu[0]; const int count = pixelIndexCounts[i];
int g = pixels[i].G - mu[1]; if (count > 0)
int b = pixels[i].B - mu[2]; {
const auto &pixel = subblockColors[i];
cov[0] += r * r; if (pixel.A > 0)
cov[1] += r * g; {
cov[2] += r * b; int r = pixel.R - mu[0];
cov[3] += g * g; int g = pixel.G - mu[1];
cov[4] += g * b; int b = pixel.B - mu[2];
cov[5] += b * b;
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 // Power iteration algorithm to get the eigenvalues and eigenvector
...@@ -966,27 +1014,35 @@ struct ETC2Block ...@@ -966,27 +1014,35 @@ struct ETC2Block
} }
// Pick colors at extreme points // Pick colors at extreme points
int minD = pixels[0].R * vr + pixels[0].G * vg + pixels[0].B * vb; int minD = INT_MAX;
int maxD = minD; int maxD = 0;
size_t minIndex = 0; size_t minIndex = 0;
size_t maxIndex = 0; size_t maxIndex = 0;
for (size_t i = 1; i < kNumPixels; i++) for (size_t i = 0; i < numColors; i++)
{ {
int dot = pixels[i].R * vr + pixels[i].G * vg + pixels[i].B * vb; const int count = pixelIndexCounts[i];
if (dot < minD) if (count > 0)
{
minD = dot;
minIndex = i;
}
if (dot > maxD)
{ {
maxD = dot; const auto &pixel = subblockColors[i];
maxIndex = 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;
}
}
} }
} }
*minColor = pixels[minIndex]; *minColorIndex = static_cast<int>(minIndex);
*maxColor = pixels[maxIndex]; *maxColorIndex = static_cast<int>(maxIndex);
} }
void transcodeIndividualOrDifferentialBlockToBC1(uint8_t *dest, void transcodeIndividualOrDifferentialBlockToBC1(uint8_t *dest,
...@@ -1009,45 +1065,60 @@ struct ETC2Block ...@@ -1009,45 +1065,60 @@ struct ETC2Block
// select axis by principal component analysis (PCA) to use as // select axis by principal component analysis (PCA) to use as
// our two BC1 endpoints and then map pixels to BC1 by projecting on the // our two BC1 endpoints and then map pixels to BC1 by projecting on the
// line between the two endpoints and choosing the right fraction. // line between the two endpoints and choosing the right fraction.
//
// In the future, we have a potential improvements to this algorithm. // The goal of this algorithm is make it faster than decode ETC to RGBs
// 1. We don't actually need to decode ETC blocks to RGBs. Instead, // and then encode to BC. To achieve this, we only extract subblock
// the subblock colors and pixel indices alreay contains enough // colors, pixel indices, and counts of each pixel indices from ETC.
// information for transcode. A direct mapping would be more // With those information, we can only encode used subblock colors
// efficient here. // 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 auto intensityModifier = const auto intensityModifier =
nonOpaquePunchThroughAlpha ? intensityModifierNonOpaque : intensityModifierDefault; nonOpaquePunchThroughAlpha ? intensityModifierNonOpaque : intensityModifierDefault;
// Compute the colors that pixels can have in each subblock both for // Compute the colors that pixels can have in each subblock both for
// the decoding of the RGBA data and BC1 encoding // the decoding of the RGBA data and BC1 encoding
R8G8B8A8 subblockColors[2][4]; R8G8B8A8 subblockColors[kNumColors];
for (size_t modifierIdx = 0; modifierIdx < 4; modifierIdx++) for (size_t modifierIdx = 0; modifierIdx < 4; modifierIdx++)
{ {
const int i1 = intensityModifier[u.idht.mode.idm.cw1][modifierIdx]; if (nonOpaquePunchThroughAlpha && (modifierIdx == 2))
subblockColors[0][modifierIdx] = createRGBA(r1 + i1, g1 + i1, b1 + i1); {
// 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]; const int i2 = intensityModifier[u.idht.mode.idm.cw2][modifierIdx];
subblockColors[1][modifierIdx] = createRGBA(r2 + i2, g2 + i2, b2 + i2); subblockColors[4 + modifierIdx] = createRGBA(r2 + i2, g2 + i2, b2 + i2);
}
} }
R8G8B8A8 rgbaBlock[16]; int pixelIndices[kNumPixelsInBlock];
// Decode the block in rgbaBlock. int pixelIndexCounts[kNumColors] = {0};
// Extract pixel indices from a ETC block.
for (size_t blockIdx = 0; blockIdx < 2; blockIdx++) for (size_t blockIdx = 0; blockIdx < 2; blockIdx++)
{ {
decodeSubblock(rgbaBlock, x, y, w, h, alphaValues, u.idht.mode.idm.flipbit, blockIdx, extractPixelIndices(pixelIndices, pixelIndexCounts, x, y, w, h, u.idht.mode.idm.flipbit,
subblockColors); blockIdx);
}
if (nonOpaquePunchThroughAlpha)
{
decodePunchThroughAlphaBlock(reinterpret_cast<uint8_t *>(rgbaBlock), x, y, w, h,
sizeof(R8G8B8A8) * 4);
} }
R8G8B8A8 minColor, maxColor; int minColorIndex, maxColorIndex;
selectEndPointPCA(rgbaBlock, &minColor, &maxColor); selectEndPointPCA(pixelIndexCounts, subblockColors, kNumColors, &minColorIndex,
&maxColorIndex);
packBC1(dest, rgbaBlock, minColor, maxColor, !nonOpaquePunchThroughAlpha); packBC1(dest, pixelIndices, pixelIndexCounts, subblockColors, kNumColors, minColorIndex,
maxColorIndex, nonOpaquePunchThroughAlpha);
} }
void transcodeTBlockToBC1(uint8_t *dest, void transcodeTBlockToBC1(uint8_t *dest,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment