Generalize BPTC compression.

1. Split compression parameter generation and compression parameter
packing. This gives a good performance boost, since we don't pack every
single time we compress. The error is computed each time, and only the
best parameters are packed.

2. Allow the shape selection function to specify up to ten shapes to
try for compression. We were already doing this kind of hackily where
we allowed both a three and two partition shape. This makes it a little
cleaner and exposes it to the user.
This commit is contained in:
Pavel Krajcevski 2014-03-25 11:40:06 -04:00
parent d03732fc09
commit 663caada50
3 changed files with 87 additions and 49 deletions

View File

@ -109,16 +109,27 @@ namespace BPTCC {
kNumErrorMetrics kNumErrorMetrics
}; };
// A shape consists of an index into the table of shapes and the number
// of partitions that the index corresponds to. Different BPTC modes
// interpret the shape differently and some are even illegal (such as
// having an index >= 16 on mode 0). Hence, each shape corresponds to
// these two variables.
struct Shape {
uint32 m_NumPartitions;
uint32 m_Index;
};
// A shape selection can influence the results of the compressor by choosing // A shape selection can influence the results of the compressor by choosing
// different modes to compress or not compress. The shape index is a value // different modes to compress or not compress. The shape index is a value
// between zero and sixty-four that corresponds to one of the available // between zero and sixty-four that corresponds to one of the available
// partitioning schemes defined by the BPTC format. // partitioning schemes defined by the BPTC format.
struct ShapeSelection { struct ShapeSelection {
// This is the shape index to use when evaluating two-partition shapes. // This is the number of indices from which to select the appropriate
uint32 m_TwoShapeIndex; // shapes. I.e. the compressor will try the first m_NumIndices shapes
uint32 m_NumIndices;
// This is the shape index to use when evaluating three-partition shapes. // These are the shape indices to use when evaluating two-partition shapes.
uint32 m_ThreeShapeIndex; Shape m_Shapes[10];
// This is the additional mask to prevent modes once shape selection // This is the additional mask to prevent modes once shape selection
// is done. This value is &-ed with m_BlockModes from CompressionSettings // is done. This value is &-ed with m_BlockModes from CompressionSettings
@ -127,7 +138,8 @@ namespace BPTCC {
// Defaults // Defaults
ShapeSelection() ShapeSelection()
: m_SelectedModes(static_cast<EBlockMode>(0xFF)) : m_NumIndices(0)
, m_SelectedModes(static_cast<EBlockMode>(0xFF))
{ } { }
}; };

View File

@ -125,7 +125,8 @@ class CompressionMode {
uint8 m_PbitCombo[kMaxNumSubsets]; uint8 m_PbitCombo[kMaxNumSubsets];
int8 m_RotationMode; int8 m_RotationMode;
int8 m_IndexMode; int8 m_IndexMode;
const uint16 m_ShapeIdx; uint16 m_ShapeIdx;
Params() { }
explicit Params(uint32 shape) explicit Params(uint32 shape)
: m_RotationMode(-1), m_IndexMode(-1), m_ShapeIdx(shape) { : m_RotationMode(-1), m_IndexMode(-1), m_ShapeIdx(shape) {
memset(m_Indices, 0xFF, sizeof(m_Indices)); memset(m_Indices, 0xFF, sizeof(m_Indices));
@ -141,7 +142,7 @@ class CompressionMode {
void Pack(Params &params, FasTC::BitStream &stream) const; void Pack(Params &params, FasTC::BitStream &stream) const;
// This function compresses a group of clusters into the passed bitstream. // This function compresses a group of clusters into the passed bitstream.
double Compress(FasTC::BitStream &stream, const int shapeIdx, double Compress(Params &params, const int shapeIdx,
RGBACluster &cluster); RGBACluster &cluster);
// This switch controls the quality of the simulated annealing optimizer. We // This switch controls the quality of the simulated annealing optimizer. We

View File

@ -1198,7 +1198,8 @@ void CompressionMode::Pack(Params &params, BitStream &stream) const {
stream.WriteBits(1 << kModeNumber, kModeNumber + 1); stream.WriteBits(1 << kModeNumber, kModeNumber + 1);
// Partition # // Partition #
assert((((1 << nPartitionBits) - 1) & params.m_ShapeIdx) == params.m_ShapeIdx); assert(!nPartitionBits ||
(((1 << nPartitionBits) - 1) & params.m_ShapeIdx) == params.m_ShapeIdx);
stream.WriteBits(params.m_ShapeIdx, nPartitionBits); stream.WriteBits(params.m_ShapeIdx, nPartitionBits);
stream.WriteBits(params.m_RotationMode, m_Attributes->hasRotation? 2 : 0); stream.WriteBits(params.m_RotationMode, m_Attributes->hasRotation? 2 : 0);
@ -1392,13 +1393,13 @@ void CompressionMode::Pack(Params &params, BitStream &stream) const {
} }
double CompressionMode::Compress( double CompressionMode::Compress(
BitStream &stream, const int shapeIdx, RGBACluster &cluster Params &params, const int shapeIdx, RGBACluster &cluster
) { ) {
const int kModeNumber = GetModeNumber(); const int kModeNumber = GetModeNumber();
const int nSubsets = GetNumberOfSubsets(); const int nSubsets = GetNumberOfSubsets();
Params params(shapeIdx); params = Params(shapeIdx);
double totalErr = 0.0; double totalErr = 0.0;
for(int cidx = 0; cidx < nSubsets; cidx++) { for(int cidx = 0; cidx < nSubsets; cidx++) {
@ -1461,8 +1462,6 @@ double CompressionMode::Compress(
} }
} }
Pack(params, stream);
assert(stream.GetBitsWritten() == 128);
return totalErr; return totalErr;
} }
@ -1781,6 +1780,7 @@ static ShapeSelection BoxSelection(
RGBACluster cluster(pixels); RGBACluster cluster(pixels);
result.m_NumIndices = 1;
for(unsigned int i = 0; i < kNumShapes2; i++) { for(unsigned int i = 0; i < kNumShapes2; i++) {
cluster.SetShapeIndex(i, 2); cluster.SetShapeIndex(i, 2);
@ -1792,7 +1792,8 @@ static ShapeSelection BoxSelection(
if(err < bestError[0]) { if(err < bestError[0]) {
bestError[0] = err; bestError[0] = err;
result.m_TwoShapeIndex = i; result.m_Shapes[0].m_Index = i;
result.m_Shapes[0].m_NumPartitions = 2;
} }
// If it's small, we'll take it! // If it's small, we'll take it!
@ -1815,6 +1816,7 @@ static ShapeSelection BoxSelection(
~(static_cast<uint32>(eBlockMode_Four) | ~(static_cast<uint32>(eBlockMode_Four) |
static_cast<uint32>(eBlockMode_Five)); static_cast<uint32>(eBlockMode_Five));
result.m_NumIndices++;
for(unsigned int i = 0; i < kNumShapes3; i++) { for(unsigned int i = 0; i < kNumShapes3; i++) {
cluster.SetShapeIndex(i, 3); cluster.SetShapeIndex(i, 3);
@ -1826,7 +1828,8 @@ static ShapeSelection BoxSelection(
if(err < bestError[1]) { if(err < bestError[1]) {
bestError[1] = err; bestError[1] = err;
result.m_ThreeShapeIndex = i; result.m_Shapes[1].m_Index = i;
result.m_Shapes[1].m_NumPartitions = 3;
} }
// If it's small, we'll take it! // If it's small, we'll take it!
@ -1843,16 +1846,19 @@ static void CompressClusters(const ShapeSelection &selection, const uint32 pixel
const CompressionSettings &settings, uint8 *outBuf, const CompressionSettings &settings, uint8 *outBuf,
double *errors, int *modeChosen) { double *errors, int *modeChosen) {
RGBACluster cluster(pixels); RGBACluster cluster(pixels);
uint8 tmpBuf[16];
double bestError = std::numeric_limits<double>::max(); double bestError = std::numeric_limits<double>::max();
uint32 modes[8] = {0, 2, 1, 3, 7, 4, 5, 6}; uint32 modes[8] = {0, 2, 1, 3, 7, 4, 5, 6};
uint32 bestMode = 8;
CompressionMode::Params bestParams;
// Block mode zero only has four bits for the partition index,
// so if the chosen three-partition shape is not within this range,
// then we shouldn't consider using this block mode...
uint32 selectedModes = selection.m_SelectedModes; uint32 selectedModes = selection.m_SelectedModes;
if(selection.m_ThreeShapeIndex >= 16) { uint32 numShapeIndices = std::min<uint32>(5, selection.m_NumIndices);
selectedModes &= ~(static_cast<uint32>(eBlockMode_Zero));
// If we don't have any indices, turn off two and three partition modes,
// since the compressor will simply ignore the shapeIndex variable afterwards...
if(numShapeIndices == 0) {
numShapeIndices = 1;
selectedModes &= ~(kTwoPartitionModes | kThreePartitionModes);
} }
for(uint32 modeIdx = 0; modeIdx < 8; modeIdx++) { for(uint32 modeIdx = 0; modeIdx < 8; modeIdx++) {
@ -1862,28 +1868,45 @@ static void CompressClusters(const ShapeSelection &selection, const uint32 pixel
continue; continue;
} }
uint32 shape = 0; for(uint32 shapeIdx = 0; shapeIdx < numShapeIndices; shapeIdx++) {
if(modeIdx < 2) { const Shape &shape = selection.m_Shapes[shapeIdx];
shape = selection.m_ThreeShapeIndex;
} else if(modeIdx < 5) {
shape = selection.m_TwoShapeIndex;
}
cluster.SetShapeIndex( // If the shape doesn't support the number of subsets then skip it.
shape, CompressionMode::GetAttributesForMode(mode)->numSubsets); uint32 nParts = CompressionMode::GetAttributesForMode(mode)->numSubsets;
if(nParts != 1 && nParts != shape.m_NumPartitions) {
continue;
}
BitStream tmpStream(tmpBuf, 128, 0); // Block mode zero only has four bits for the partition index,
double error = CompressionMode(mode, settings).Compress(tmpStream, shape, cluster); // so if the chosen three-partition shape is not within this range,
// then we shouldn't consider using this block mode...
if(shape.m_Index >= 16 && mode == 0) {
continue;
}
if(errors) uint32 idx = shape.m_Index;
errors[mode] = error; cluster.SetShapeIndex(idx, nParts);
if(error < bestError) {
memcpy(outBuf, tmpBuf, sizeof(tmpBuf)); CompressionMode::Params params;
bestError = error; double error = CompressionMode(mode, settings).Compress(params, idx, cluster);
if(modeChosen)
*modeChosen = mode; if(errors)
errors[mode] = std::min(error, errors[mode]);
if(error < bestError) {
bestError = error;
bestMode = mode;
bestParams = params;
}
} }
} }
assert(bestMode < 8);
BitStream stream(outBuf, 128, 0);
CompressionMode(bestMode, settings).Pack(bestParams, stream);
if(modeChosen)
*modeChosen = bestMode;
} }
static void CompressBC7Block(const uint32 x, const uint32 y, static void CompressBC7Block(const uint32 x, const uint32 y,
@ -2137,6 +2160,7 @@ static void CompressBC7Block(
ShapeSelection selection; ShapeSelection selection;
uint32 path = 0; uint32 path = 0;
selection.m_NumIndices = 1;
for(unsigned int i = 0; i < kNumShapes2; i++) { for(unsigned int i = 0; i < kNumShapes2; i++) {
blockCluster.SetShapeIndex(i, 2); blockCluster.SetShapeIndex(i, 2);
@ -2173,23 +2197,24 @@ static void CompressBC7Block(
); );
} }
if(err < bestError[0]) {
bestError[0] = err;
selection.m_Shapes[0].m_Index = i;
selection.m_Shapes[0].m_NumPartitions = 2;
}
// If it's small, we'll take it! // If it's small, we'll take it!
if(err < 1e-9) { if(err < 1e-9) {
path = 2; path = 2;
selection.m_TwoShapeIndex = i;
selection.m_SelectedModes = kTwoPartitionModes; selection.m_SelectedModes = kTwoPartitionModes;
break; break;
} }
if(err < bestError[0]) {
bestError[0] = err;
selection.m_TwoShapeIndex = i;
}
} }
// There are not 3 subset blocks that support alpha, so only check these // There are not 3 subset blocks that support alpha, so only check these
// if the entire block is opaque. // if the entire block is opaque.
if(opaque) { if(opaque) {
selection.m_NumIndices++;
for(unsigned int i = 0; i < kNumShapes3; i++) { for(unsigned int i = 0; i < kNumShapes3; i++) {
blockCluster.SetShapeIndex(i, 3); blockCluster.SetShapeIndex(i, 3);
@ -2226,18 +2251,18 @@ static void CompressBC7Block(
); );
} }
if(err < bestError[1]) {
bestError[1] = err;
selection.m_Shapes[1].m_Index = i;
selection.m_Shapes[1].m_NumPartitions = 3;
}
// If it's small, we'll take it! // If it's small, we'll take it!
if(err < 1e-9) { if(err < 1e-9) {
path = 2; path = 2;
selection.m_TwoShapeIndex = i;
selection.m_SelectedModes = kThreePartitionModes; selection.m_SelectedModes = kThreePartitionModes;
break; break;
} }
if(err < bestError[1]) {
bestError[1] = err;
selection.m_ThreeShapeIndex = i;
}
} }
} }