diff --git a/BPTCEncoder/include/BPTCCompressor.h b/BPTCEncoder/include/BPTCCompressor.h index c1b23db..b37a4a3 100755 --- a/BPTCEncoder/include/BPTCCompressor.h +++ b/BPTCEncoder/include/BPTCCompressor.h @@ -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(0xFF)) + : m_NumIndices(0) + , m_SelectedModes(static_cast(0xFF)) { } }; diff --git a/BPTCEncoder/src/CompressionMode.h b/BPTCEncoder/src/CompressionMode.h index 5bff270..83d2411 100755 --- a/BPTCEncoder/src/CompressionMode.h +++ b/BPTCEncoder/src/CompressionMode.h @@ -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 ¶ms, 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 ¶ms, const int shapeIdx, RGBACluster &cluster); // This switch controls the quality of the simulated annealing optimizer. We diff --git a/BPTCEncoder/src/Compressor.cpp b/BPTCEncoder/src/Compressor.cpp index 529cb2a..cb6e133 100755 --- a/BPTCEncoder/src/Compressor.cpp +++ b/BPTCEncoder/src/Compressor.cpp @@ -1198,7 +1198,8 @@ void CompressionMode::Pack(Params ¶ms, 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 ¶ms, BitStream &stream) const { } double CompressionMode::Compress( - BitStream &stream, const int shapeIdx, RGBACluster &cluster + Params ¶ms, 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(eBlockMode_Four) | static_cast(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::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(eBlockMode_Zero)); + uint32 numShapeIndices = std::min(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; - } } }