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
};
// 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
// 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
// partitioning schemes defined by the BPTC format.
struct ShapeSelection {
// This is the shape index to use when evaluating two-partition shapes.
uint32 m_TwoShapeIndex;
// This is the number of indices from which to select the appropriate
// 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.
uint32 m_ThreeShapeIndex;
// These are the shape indices to use when evaluating two-partition shapes.
Shape m_Shapes[10];
// This is the additional mask to prevent modes once shape selection
// is done. This value is &-ed with m_BlockModes from CompressionSettings
@ -127,7 +138,8 @@ namespace BPTCC {
// Defaults
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];
int8 m_RotationMode;
int8 m_IndexMode;
const uint16 m_ShapeIdx;
uint16 m_ShapeIdx;
Params() { }
explicit Params(uint32 shape)
: m_RotationMode(-1), m_IndexMode(-1), m_ShapeIdx(shape) {
memset(m_Indices, 0xFF, sizeof(m_Indices));
@ -141,7 +142,7 @@ class CompressionMode {
void Pack(Params &params, FasTC::BitStream &stream) const;
// 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);
// 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);
// 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_RotationMode, m_Attributes->hasRotation? 2 : 0);
@ -1392,13 +1393,13 @@ void CompressionMode::Pack(Params &params, BitStream &stream) const {
}
double CompressionMode::Compress(
BitStream &stream, const int shapeIdx, RGBACluster &cluster
Params &params, const int shapeIdx, RGBACluster &cluster
) {
const int kModeNumber = GetModeNumber();
const int nSubsets = GetNumberOfSubsets();
Params params(shapeIdx);
params = Params(shapeIdx);
double totalErr = 0.0;
for(int cidx = 0; cidx < nSubsets; cidx++) {
@ -1461,8 +1462,6 @@ double CompressionMode::Compress(
}
}
Pack(params, stream);
assert(stream.GetBitsWritten() == 128);
return totalErr;
}
@ -1781,6 +1780,7 @@ static ShapeSelection BoxSelection(
RGBACluster cluster(pixels);
result.m_NumIndices = 1;
for(unsigned int i = 0; i < kNumShapes2; i++) {
cluster.SetShapeIndex(i, 2);
@ -1792,7 +1792,8 @@ static ShapeSelection BoxSelection(
if(err < bestError[0]) {
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!
@ -1815,6 +1816,7 @@ static ShapeSelection BoxSelection(
~(static_cast<uint32>(eBlockMode_Four) |
static_cast<uint32>(eBlockMode_Five));
result.m_NumIndices++;
for(unsigned int i = 0; i < kNumShapes3; i++) {
cluster.SetShapeIndex(i, 3);
@ -1826,7 +1828,8 @@ static ShapeSelection BoxSelection(
if(err < bestError[1]) {
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!
@ -1843,16 +1846,19 @@ static void CompressClusters(const ShapeSelection &selection, const uint32 pixel
const CompressionSettings &settings, uint8 *outBuf,
double *errors, int *modeChosen) {
RGBACluster cluster(pixels);
uint8 tmpBuf[16];
double bestError = std::numeric_limits<double>::max();
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;
if(selection.m_ThreeShapeIndex >= 16) {
selectedModes &= ~(static_cast<uint32>(eBlockMode_Zero));
uint32 numShapeIndices = std::min<uint32>(5, selection.m_NumIndices);
// 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++) {
@ -1862,28 +1868,45 @@ static void CompressClusters(const ShapeSelection &selection, const uint32 pixel
continue;
}
uint32 shape = 0;
if(modeIdx < 2) {
shape = selection.m_ThreeShapeIndex;
} else if(modeIdx < 5) {
shape = selection.m_TwoShapeIndex;
}
for(uint32 shapeIdx = 0; shapeIdx < numShapeIndices; shapeIdx++) {
const Shape &shape = selection.m_Shapes[shapeIdx];
cluster.SetShapeIndex(
shape, CompressionMode::GetAttributesForMode(mode)->numSubsets);
// If the shape doesn't support the number of subsets then skip it.
uint32 nParts = CompressionMode::GetAttributesForMode(mode)->numSubsets;
if(nParts != 1 && nParts != shape.m_NumPartitions) {
continue;
}
BitStream tmpStream(tmpBuf, 128, 0);
double error = CompressionMode(mode, settings).Compress(tmpStream, shape, cluster);
// 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...
if(shape.m_Index >= 16 && mode == 0) {
continue;
}
if(errors)
errors[mode] = error;
if(error < bestError) {
memcpy(outBuf, tmpBuf, sizeof(tmpBuf));
bestError = error;
if(modeChosen)
*modeChosen = mode;
uint32 idx = shape.m_Index;
cluster.SetShapeIndex(idx, nParts);
CompressionMode::Params params;
double error = CompressionMode(mode, settings).Compress(params, idx, cluster);
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,
@ -2137,6 +2160,7 @@ static void CompressBC7Block(
ShapeSelection selection;
uint32 path = 0;
selection.m_NumIndices = 1;
for(unsigned int i = 0; i < kNumShapes2; i++) {
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(err < 1e-9) {
path = 2;
selection.m_TwoShapeIndex = i;
selection.m_SelectedModes = kTwoPartitionModes;
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
// if the entire block is opaque.
if(opaque) {
selection.m_NumIndices++;
for(unsigned int i = 0; i < kNumShapes3; i++) {
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(err < 1e-9) {
path = 2;
selection.m_TwoShapeIndex = i;
selection.m_SelectedModes = kThreePartitionModes;
break;
}
if(err < bestError[1]) {
bestError[1] = err;
selection.m_ThreeShapeIndex = i;
}
}
}