Chromium Code Reviews| Index: src/utils/SkTextureCompressor_ASTC.cpp |
| diff --git a/src/utils/SkTextureCompressor_ASTC.cpp b/src/utils/SkTextureCompressor_ASTC.cpp |
| index 8efffdfc9e8f04e035009bfc17a207ab8297758c..17269b84b0941267b3088064ab6d558ba15a38c2 100644 |
| --- a/src/utils/SkTextureCompressor_ASTC.cpp |
| +++ b/src/utils/SkTextureCompressor_ASTC.cpp |
| @@ -261,6 +261,1653 @@ inline void CompressA8ASTCBlockVertical(uint8_t* dst, const uint8_t* src) { |
| } |
| //////////////////////////////////////////////////////////////////////////////// |
| +// |
| +// ASTC Decoder |
| +// |
| +// Full details available in the spec: |
| +// http://www.khronos.org/registry/gles/extensions/OES/OES_texture_compression_astc.txt |
| +// |
| +//////////////////////////////////////////////////////////////////////////////// |
| + |
| +#define ASSERT_ASTC_DECODE_ERROR 0 |
| + |
| +// An ASTC block is 128 bits. We represent it as two 64-bit integers in order |
| +// to efficiently operate on the block using bitwise operations. |
|
robertphillips
2014/08/05 15:29:03
Skia-fy this? Maybe ASTCBlock ?
krajcevski
2014/08/05 20:55:06
Done.
|
| +struct astcBlock_t { |
| + uint64_t low; |
| + uint64_t high; |
| +}; |
| + |
| +// Writes the given color to every pixel in the block. This is used by void-extent |
| +// blocks (a special constant-color encoding of a block) and by the error function. |
| +static inline void write_constant_color(uint8_t* dst, int dstRowBytes, SkColor color) { |
|
robertphillips
2014/08/05 15:29:03
12? i,j -> x,y ?
krajcevski
2014/08/05 20:55:05
Done.
|
| + for (int j = 0; j < 12; ++j) { |
| + SkColor *dstColors = reinterpret_cast<SkColor*>(dst + j*dstRowBytes); |
| + for (int i = 0; i < 12; ++i) { |
| + dstColors[i] = color; |
| + } |
| + } |
| +} |
| + |
| +// Sets the entire block to the ASTC "error" color, a disgusting magenta |
| +// that's not supposed to appear in natural images. |
| +static inline void write_error_color(uint8_t* dst, int dstRowBytes) { |
| + static const SkColor kASTCErrorColor = SkColorSetRGB(0xFF, 0, 0xFF); |
| + |
| +#if ASSERT_ASTC_DECODE_ERROR |
| + SkDEBUGFAIL("ASTC decoding error!\n"); |
| +#endif |
| + |
| + write_constant_color(dst, dstRowBytes, kASTCErrorColor); |
| +} |
| + |
| +// Reads an ASTC block from the given pointer. |
| +static inline void read_astc_block(astcBlock_t *dst, const uint8_t* src) { |
| + const uint64_t* qword = reinterpret_cast<const uint64_t*>(src); |
| + dst->low = SkEndian_SwapLE64(qword[0]); |
| + dst->high = SkEndian_SwapLE64(qword[1]); |
| +} |
| + |
| +// Reads up to 64 bits of the ASTC block starting from bit |
| +// 'from' and going up to but not including bit 'to'. 'from' starts |
| +// counting from the LSB, counting up to the MSB. Returns -1 on |
| +// error. |
| +static uint64_t read_astc_bits(const astcBlock_t &block, int from, int to) { |
|
robertphillips
2014/08/05 15:29:02
Assert from & to are >= 0 && < 128 ?
krajcevski
2014/08/05 20:55:05
Done.
|
| + const int nBits = to - from; |
| + if (0 == nBits) { |
| + return 0; |
| + } |
| + |
| + if (nBits < 0 || 64 <= nBits) { |
| + SkDEBUGFAIL("ASTC -- shouldn't read more than 64 bits"); |
| + return -1; |
| + } |
| + |
|
robertphillips
2014/08/05 15:29:03
I don't think we need these two asserts.
krajcevski
2014/08/05 20:55:07
Done.
|
| + SkASSERT(to > from); |
| + SkASSERT(nBits > 0); |
| + |
| + uint64_t result = 0; |
|
robertphillips
2014/08/05 15:29:04
// remember: the 'to' bit isn't read
krajcevski
2014/08/05 20:55:07
Done.
|
| + if (to <= 64) { |
|
robertphillips
2014/08/05 15:29:02
// All desired bits are in low ?
krajcevski
2014/08/05 20:55:08
Done.
|
| + result = (block.low >> from) & ((1ULL << nBits) - 1); |
| + } else if (from >= 64) { |
|
robertphillips
2014/08/05 15:29:02
// All desired bits are in high ?
krajcevski
2014/08/05 20:55:06
Done.
|
| + result = (block.high >> (from - 64)) & ((1ULL << nBits) - 1); |
| + } else { |
| + // from < 64 && to > 64 |
| + SkASSERT(nBits > (64 - from)); |
|
robertphillips
2014/08/05 15:29:02
It seems like you should compute nLow first - clar
krajcevski
2014/08/05 20:55:08
Done.
|
| + const int nHigh = nBits - (64 - from); |
| + const int nLow = nBits - nHigh; |
| + SkASSERT(nLow + nHigh == nBits); |
| + result = |
| + ((block.low >> from) & ((1ULL << nLow) - 1)) | |
| + ((block.high & ((1ULL << nHigh) - 1)) << nLow); |
| + } |
| + |
| + return result; |
| +} |
| + |
| +// Reverse 64-bit integer taken from TAOCP 4a, although it's better |
| +// documented at this site: |
| +// http://matthewarcus.wordpress.com/2012/11/18/reversing-a-64-bit-word/ |
| + |
| +template <typename T, T m, int k> |
| +static inline T swap_bits(T p) { |
| + T q = ((p>>k)^p) & m; |
| + return p^q^(q<<k); |
| +} |
| + |
| +static inline uint64_t reverse64(uint64_t n) { |
| + static const uint64_t m0 = 0x5555555555555555LLU; |
| + static const uint64_t m1 = 0x0300c0303030c303LLU; |
| + static const uint64_t m2 = 0x00c0300c03f0003fLLU; |
| + static const uint64_t m3 = 0x00000ffc00003fffLLU; |
| + n = ((n>>1)&m0) | (n&m0)<<1; |
| + n = swap_bits<uint64_t, m1, 4>(n); |
| + n = swap_bits<uint64_t, m2, 8>(n); |
| + n = swap_bits<uint64_t, m3, 20>(n); |
| + n = (n >> 34) | (n << 30); |
| + return n; |
| +} |
| + |
|
robertphillips
2014/08/05 15:29:02
Would this make more sense as a method on astcBloc
krajcevski
2014/08/05 20:55:06
Done.
|
| +// Reverses the bits of an ASTC block, making the LSB of the |
| +// 128 bit block the MSB. |
|
robertphillips
2014/08/05 15:29:03
x -> block ?
krajcevski
2014/08/05 20:55:05
Done.
|
| +static inline void reverse_block(astcBlock_t *x) { |
|
robertphillips
2014/08/05 15:29:03
uint64_t newLow = reverse64(block->high);
block->h
krajcevski
2014/08/05 20:55:08
Done.
|
| + const astcBlock_t b = { reverse64(x->high), reverse64(x->low) }; |
| + x->high = b.high; |
| + x->low = b.low; |
| +} |
| + |
|
robertphillips
2014/08/05 15:29:02
SkIsPow2 in SkMath.h ?
krajcevski
2014/08/05 20:55:06
Done.
|
| +// Helper function that returns true if x is a power of two. |
| +static inline bool is_power_of_two(int x) { |
| + return (x & (x - 1)) == 0; |
| +} |
| + |
|
robertphillips
2014/08/05 15:29:04
Could SkNextLog2 serve here ?
krajcevski
2014/08/05 20:55:08
Done.
|
| +// Returns the number of bits needed to represent a number |
| +// in the given power-of-two range. |
| +static inline int bits_for_range(int x) { |
| + SkASSERT(is_power_of_two(x)); |
| + SkASSERT(0 != x); |
| + |
| + int cnt = 0; |
| + while (x >>= 1) { |
| + cnt++; |
| + } |
| + |
| + return cnt; |
| +} |
| + |
|
robertphillips
2014/08/05 15:29:02
Use SkClampMax ?
krajcevski
2014/08/05 20:55:09
Done.
|
| +// Clamps an integer to the range [0, 255] |
| +static inline int clamp_byte(int x) { |
| + return (x > 255)? 255 : ((x < 0)? 0 : x); |
| +} |
| + |
| +// Helper function defined in the ASTC spec, section C.2.14 |
|
robertphillips
2014/08/05 15:29:03
// It ... ?
krajcevski
2014/08/05 20:55:06
Done.
|
| +static inline void bit_transfer_signed(int *a, int *b) { |
| + *b >>= 1; |
| + *b |= *a & 0x80; |
| + *a >>= 1; |
| + *a &= 0x3F; |
| + if ( (*a & 0x20) != 0 ) { |
| + *a -= 0x40; |
| + } |
| +} |
| + |
| +// Helper function defined in the ASTC spec, section C.2.14 |
|
robertphillips
2014/08/05 15:29:03
// It ... ?
krajcevski
2014/08/05 20:55:07
Done.
|
| +static inline SkColor blue_contract(int a, int r, int g, int b) { |
| + return SkColorSetARGB(a, (r + b) >> 1, (g + b) >> 1, b); |
| +} |
| + |
| +// A helper class used to decode bit values from standard integer values. |
| +// We can't use this class with astcBlock_t because then it would need to |
| +// handle multi-value ranges, and it's non-trivial to lookup a range of bits |
| +// that splits across two different ints. |
| +template <typename T> |
| +class SkTBits { |
| +public: |
| + SkTBits(const T val) : fVal(val) { } |
| + |
| + // Returns the bit at the given position |
| + T operator [](const int idx) const { |
| + return (fVal >> idx) & 1; |
| + } |
| + |
| + // Returns the bits in the given range, inclusive |
|
robertphillips
2014/08/05 15:29:03
Why not start then end ?
krajcevski
2014/08/05 20:55:05
This way the code below matches the spec better.
|
| + T operator ()(const int end, const int start) const { |
| + SkASSERT(end >= start); |
| + return (fVal >> start) & ((1ULL << ((end - start) + 1)) - 1); |
| + } |
| + |
| +private: |
| + const T fVal; |
| +}; |
| + |
| +// This algorithm matches the trit block decoding in the spec (Table C.2.14) |
| +static void decode_trit_block(int* dst, int nBits, const uint64_t &block) { |
| + |
| + SkTBits<uint64_t> blockBits(block); |
| + |
|
robertphillips
2014/08/05 15:29:03
Is there a better name then 'm'?
krajcevski
2014/08/05 20:55:07
m is chosen to match the naming in the spec.
|
| + int m[5]; |
| + if (0 == nBits) { |
| + memset(m, 0, sizeof(m)); |
| + } else { |
|
robertphillips
2014/08/05 15:29:02
// The trits are arranged ...
krajcevski
2014/08/05 20:55:06
Done.
|
| + m[0] = blockBits(nBits - 1, 0); |
| + m[1] = blockBits(2*nBits - 1 + 2, nBits + 2); |
| + m[2] = blockBits(3*nBits - 1 + 4, 2*nBits + 4); |
| + m[3] = blockBits(4*nBits - 1 + 5, 3*nBits + 5); |
| + m[4] = blockBits(5*nBits - 1 + 7, 4*nBits + 7); |
| + } |
| + |
|
robertphillips
2014/08/05 15:29:03
Blarg for the rest of this function
krajcevski
2014/08/05 20:55:09
I tried to match this function to the spec as much
|
| + int T = |
| + blockBits(nBits + 1, nBits) | |
| + (blockBits(2*nBits + 2 + 1, 2*nBits + 2) << 2) | |
| + (blockBits[3*nBits + 4] << 4) | |
| + (blockBits(4*nBits + 5 + 1, 4*nBits + 5) << 5) | |
| + (blockBits[5*nBits + 7] << 7); |
| + |
| + int t[5]; |
| + |
| + int C; |
| + SkTBits<int> Tbits(T); |
| + if (0x7 == Tbits(4, 2)) { |
| + C = (Tbits(7, 5) << 2) | Tbits(1, 0); |
| + t[3] = t[4] = 2; |
| + } else { |
| + C = Tbits(4, 0); |
| + if (Tbits(6, 5) == 0x3) { |
| + t[4] = 2; t[3] = Tbits[7]; |
| + } else { |
| + t[4] = Tbits[7]; t[3] = Tbits(6, 5); |
| + } |
| + } |
| + |
| + SkTBits<int> Cbits(C); |
| + if (Cbits(1, 0) == 0x3) { |
| + t[2] = 2; |
| + t[1] = Cbits[4]; |
| + t[0] = (Cbits[3] << 1) | (Cbits[2] & (0x1 & ~(Cbits[3]))); |
| + } else if (Cbits(3, 2) == 0x3) { |
| + t[2] = 2; |
| + t[1] = 2; |
| + t[0] = Cbits(1, 0); |
| + } else { |
| + t[2] = Cbits[4]; |
| + t[1] = Cbits(3, 2); |
| + t[0] = (Cbits[1] << 1) | (Cbits[0] & (0x1 & ~(Cbits[1]))); |
| + } |
| + |
| +#if SK_DEBUG |
| + for (int i = 0; i < 5; ++i) { |
| + SkASSERT(t[i] < 3); |
| + SkASSERT(m[i] < (1 << nBits)); |
| + } |
| +#endif |
| + |
| + for (int i = 0; i < 5; ++i) { |
| + *dst = (t[i] << nBits) + m[i]; |
| + ++dst; |
| + } |
| +} |
| + |
|
robertphillips
2014/08/05 15:29:02
trit -> quint ?
krajcevski
2014/08/05 20:55:05
Done.
|
| +// This algorithm matches the trit block decoding in the spec (Table C.2.15) |
| +static void decode_quint_block(int* dst, int nBits, const uint64_t &block) { |
| + SkTBits<uint64_t> blockBits(block); |
| + |
| + int m[3]; |
| + if (0 == nBits) { |
| + memset(m, 0, sizeof(m)); |
| + } else { |
|
robertphillips
2014/08/05 15:29:02
// The quints are arranged ...
krajcevski
2014/08/05 20:55:08
Done.
|
| + m[0] = blockBits(nBits - 1, 0); |
| + m[1] = blockBits(2*nBits - 1 + 3, nBits + 3); |
| + m[2] = blockBits(3*nBits - 1 + 5, 2*nBits + 5); |
| + } |
| + |
|
robertphillips
2014/08/05 15:29:03
Blarg
krajcevski
2014/08/05 20:55:07
Blarg. :(
|
| + int Q = |
| + blockBits(nBits + 2, nBits) | |
| + (blockBits(2*nBits + 3 + 1, 2*nBits + 3) << 3) | |
| + (blockBits(3*nBits + 5 + 1, 3*nBits + 5) << 5); |
| + |
| + int q[3]; |
| + SkTBits<int> Qbits(Q); // quantum? |
| + |
| + if (Qbits(2, 1) == 0x3 && Qbits(6, 5) == 0) { |
| + const int notBitZero = (0x1 & ~(Qbits[0])); |
| + q[2] = (Qbits[0] << 2) | ((Qbits[4] & notBitZero) << 1) | (Qbits[3] & notBitZero); |
| + q[1] = 4; |
| + q[0] = 4; |
| + } else { |
| + int C; |
| + if (Qbits(2, 1) == 0x3) { |
| + q[2] = 4; |
| + C = (Qbits(4, 3) << 3) | ((0x3 & ~(Qbits(6, 5))) << 1) | Qbits[0]; |
| + } else { |
| + q[2] = Qbits(6, 5); |
| + C = Qbits(4, 0); |
| + } |
| + |
| + SkTBits<int> Cbits(C); |
| + if (Cbits(2, 0) == 0x5) { |
| + q[1] = 4; |
| + q[0] = Cbits(4, 3); |
| + } else { |
| + q[1] = Cbits(4, 3); |
| + q[0] = Cbits(2, 0); |
| + } |
| + } |
| + |
| +#if SK_DEBUG |
| + for (int i = 0; i < 3; ++i) { |
| + SkASSERT(q[i] < 5); |
| + SkASSERT(m[i] < (1 << nBits)); |
| + } |
| +#endif |
| + |
| + for (int i = 0; i < 3; ++i) { |
| + *dst = (q[i] << nBits) + m[i]; |
| + ++dst; |
| + } |
| +} |
| + |
| +// Function that decodes a sequence of integers stored as an ISE (Integer |
| +// Sequence Encoding) bit stream. The full details of this function are outlined |
| +// in section C.2.12 of the ASTC spec. A brief overview is as follows: |
| +// |
| +// - Each integer in the sequence is bounded by a specific range r. |
| +// - The range of each value determines the way the bit stream is interpreted, |
| +// - If the range is a power of two, then the sequence is a sequence of bits |
| +// - If the range is of the form 3*2^n, then the sequence is stored as a |
| +// sequence of blocks, each block contains 5 trits and 5 bit sequences, which |
| +// decodes into 5 values. |
| +// - Similarly, if the range is of the form 5*2^n, then the sequence is stored as a |
|
robertphillips
2014/08/05 15:29:02
trits -> quints ?
krajcevski
2014/08/05 20:55:06
Done.
|
| +// sequence of blocks, each block contains 3 trits and 3 bit sequences, which |
|
robertphillips
2014/08/05 15:29:02
5 -> 3 ?
krajcevski
2014/08/05 20:55:06
Done.
|
| +// decodes into 5 values. |
| +static bool decode_integer_sequence( |
| + int* dst, // The array holding the destination bits |
| + int dstSize, // The maximum size of the array |
| + int nVals, // The number of values that we'd like to decode |
| + const astcBlock_t &block, // The block that we're decoding from |
| + int startBit, // The bit from which we're going to do the reading |
|
robertphillips
2014/08/05 15:29:03
inclusive or not ?
krajcevski
2014/08/05 20:55:08
Done.
|
| + int endBit, // The bit at which we stop reading |
| + bool bReadForward, // If true, then read LSB -> MSB, else read MSB -> LSB |
| + int nBits, // The number of bits representing this encoding |
| + int nTrits, // The number of trits representing this encoding |
| + int nQuints // The number of quints representing this encoding |
| +) { |
| + // If we want more values than we have, then fail. |
| + if (nVals > dstSize) { |
| + return false; |
| + } |
| + |
| + astcBlock_t src = block; |
| + |
| + if (!bReadForward) { |
| + reverse_block(&src); |
| + startBit = 128 - startBit; |
| + endBit = 128 - endBit; |
| + } |
| + |
| + while (nVals > 0) { |
| + |
| + if (nTrits > 0) { |
| + SkASSERT(0 == nQuints); |
| + |
| + int endBlockBit = startBit + 8 + 5*nBits; |
| + if (endBlockBit > endBit) { |
| + endBlockBit = endBit; |
| + } |
| + |
| + decode_trit_block(dst, nBits, read_astc_bits(src, startBit, endBlockBit)); |
| + dst += 5; |
| + nVals -= 5; |
| + startBit = endBlockBit; |
| + |
| + } else if (nQuints > 0) { |
| + SkASSERT(0 == nTrits); |
| + |
| + int endBlockBit = startBit + 7 + 3*nBits; |
| + if (endBlockBit > endBit) { |
| + endBlockBit = endBit; |
| + } |
| + |
| + decode_quint_block(dst, nBits, read_astc_bits(src, startBit, endBlockBit)); |
| + dst += 3; |
| + nVals -= 3; |
| + startBit = endBlockBit; |
| + |
| + } else { |
| + // Just read the bits, but don't read more than we have... |
| + int endValBit = startBit + nBits; |
| + if (endValBit > endBit) { |
| + endValBit = endBit; |
| + } |
| + |
| + *dst = read_astc_bits(src, startBit, endValBit); |
| + ++dst; |
| + --nVals; |
| + startBit = endValBit; |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +// Helper function that unquantizes some (seemingly random) generated |
| +// numbers... meant to match the ASTC hardware. This function is used |
|
robertphillips
2014/08/05 15:29:02
to unquantize ?
krajcevski
2014/08/05 20:55:08
Done.
|
| +// by unquantizing both colors (Table C.2.16) and weights (Table C.2.26) |
|
robertphillips
2014/08/05 17:30:01
Is templating on a mask really useful? Since its i
krajcevski
2014/08/05 20:55:08
Done.
|
| +template<unsigned mask> |
| +static inline int unquantize_value(int A, int B, int C, int D) { |
| + int T = D * C + B; |
| + T = T ^ A; |
| + T = (A & mask) | (T >> 2); |
| + SkASSERT(T < 256); |
| + return T; |
| +} |
| + |
|
robertphillips
2014/08/05 15:29:04
replicated -> replicate ?
krajcevski
2014/08/05 20:55:05
Done.
|
| +// Helper function to replicated the bits in x that represents an oldPrec |
| +// precision integer into a prec precision integer. For example: |
| +// 255 == replicate_bits(7, 3, 8); |
| +static inline int replicate_bits(int x, int oldPrec, int prec) { |
| + while (oldPrec < prec) { |
| + const int toShift = SkMin32(prec-oldPrec, oldPrec); |
| + x = (x << toShift) | (x >> (oldPrec - toShift)); |
| + oldPrec += toShift; |
| + } |
|
robertphillips
2014/08/05 15:29:03
Add assert that no bits are set outside of desired
krajcevski
2014/08/05 20:55:05
Done.
|
| + return x; |
| +} |
| + |
| +// Returns the unquantized value of a color that's represented only as |
| +// a set of bits. |
| +static inline int unquantize_bits_color(int val, int nBits) { |
| + return replicate_bits(val, nBits, 8); |
| +} |
| + |
| +// Returns the unquantized value of a color that's represented as a |
| +// trit followed by nBits bits. This algorithm follows the sequence |
| +// defined in section C.2.13 of the ASTC spec. |
| +static inline int unquantize_trit_color(int val, int nBits) { |
| + const int D = (val >> nBits) & 0x3; |
| + SkASSERT(D < 3); |
| + |
| + const int A = -(val & 0x1) & 0x1FF; |
| + |
| + static const int Cvals[6] = { 204, 93, 44, 22, 11, 5 }; |
|
robertphillips
2014/08/05 17:30:00
Move these asserts to the start of the function?
krajcevski
2014/08/05 20:55:09
Done.
|
| + SkASSERT(nBits > 0); |
| + SkASSERT(nBits < 7); |
| + |
| + const int C = Cvals[nBits - 1]; |
| + |
| + int B = 0; |
| + const SkTBits<int> valBits(val); |
| + switch (nBits) { |
| + case 1: |
| + B = 0; |
| + break; |
| + |
|
robertphillips
2014/08/05 17:30:00
In Skia this is formatted as:
case kGaussian_
krajcevski
2014/08/05 20:55:06
Done.
|
| + case 2: |
| + { |
| + const int b = valBits[1]; |
| + B = (b << 1) | (b << 2) | (b << 4) | (b << 8); |
| + } |
| + break; |
| + |
| + case 3: |
| + { |
| + const int cb = valBits(2, 1); |
| + B = cb | (cb << 2) | (cb << 7); |
| + } |
| + break; |
| + |
| + case 4: |
| + { |
| + const int dcb = valBits(3, 1); |
| + B = dcb | (dcb << 6); |
| + } |
| + break; |
| + |
| + case 5: |
| + { |
| + const int edcb = valBits(4, 1); |
| + B = (edcb << 5) | (edcb >> 2); |
| + } |
| + break; |
| + |
| + case 6: |
| + { |
| + const int fedcb = valBits(5, 1); |
| + B = (fedcb << 4) | (fedcb >> 4); |
| + } |
| + break; |
| + } |
| + |
| + return unquantize_value<0x80>(A, B, C, D); |
| +} |
| + |
| +// Returns the unquantized value of a color that's represented as a |
| +// quint followed by nBits bits. This algorithm follows the sequence |
| +// defined in section C.2.13 of the ASTC spec. |
| +static inline int unquantize_quint_color(int val, int nBits) { |
| + const int D = (val >> nBits) & 0x7; |
| + SkASSERT(D < 5); |
| + |
| + const int A = -(val & 0x1) & 0x1FF; |
| + |
| + static const int Cvals[5] = { 113, 54, 26, 13, 6 }; |
| + SkASSERT(nBits > 0); |
| + SkASSERT(nBits < 6); |
| + |
| + const int C = Cvals[nBits - 1]; |
| + |
| + int B = 0; |
| + const SkTBits<int> valBits(val); |
| + switch (nBits) { |
| + case 1: |
| + B = 0; |
| + break; |
| + |
| + case 2: |
| + { |
| + const int b = valBits[1]; |
| + B = (b << 2) | (b << 3) | (b << 8); |
| + } |
| + break; |
| + |
| + case 3: |
| + { |
| + const int cb = valBits(2, 1); |
| + B = (cb >> 1) | (cb << 1) | (cb << 7); |
| + } |
| + break; |
| + |
| + case 4: |
| + { |
| + const int dcb = valBits(3, 1); |
| + B = (dcb >> 1) | (dcb << 6); |
| + } |
| + break; |
| + |
| + case 5: |
| + { |
| + const int edcb = valBits(4, 1); |
| + B = (edcb << 5) | (edcb >> 3); |
| + } |
| + break; |
| + } |
| + |
| + return unquantize_value<0x80>(A, B, C, D); |
| +} |
| + |
| +// This algorithm takes a list of integers, stored in vals, and unquantizes them |
| +// in place. This follows the algorithm laid out in section C.2.13 of the ASTC spec. |
|
robertphillips
2014/08/05 17:30:00
This seems a bit odd. It seems like you actually w
krajcevski
2014/08/05 20:55:06
nBits is a value that needs to be passed to either
|
| +static void unquantize_colors(int *vals, int nVals, int nBits, int nTrits, int nQuints) { |
| + for (int i = 0; i < nVals; ++i) { |
| + if (nTrits > 0) { |
| + SkASSERT(nQuints == 0); |
|
robertphillips
2014/08/05 17:30:00
extra space after ',' ?
krajcevski
2014/08/05 20:55:07
Done.
|
| + vals[i] = unquantize_trit_color(vals[i], nBits); |
| + } else if (nQuints > 0) { |
| + SkASSERT(nTrits == 0); |
| + vals[i] = unquantize_quint_color(vals[i], nBits); |
| + } else { |
| + SkASSERT(nQuints == 0 && nTrits == 0); |
| + vals[i] = unquantize_bits_color(vals[i], nBits); |
| + } |
| + } |
| +} |
| + |
| +// Returns an interpolated value between c0 and c1 based on the weight. This |
| +// follows the algorithm laid out in section C.2.19 of the ASTC spec. |
| +static int interpolate_channel(int c0, int c1, int weight) { |
| + SkASSERT(0 <= c0 && c0 < 256); |
| + SkASSERT(0 <= c1 && c1 < 256); |
| + |
| + c0 = (c0 << 8) | c0; |
| + c1 = (c1 << 8) | c1; |
| + |
| + const int result = ((c0*(64 - weight) + c1*weight + 32) / 64) >> 8; |
| + |
| + if (result > 255) { |
| + return 255; |
| + } |
| + |
| + SkASSERT(result >= 0); |
| + return result; |
| +} |
| + |
| +// Returns an interpolated color between the two endpoints based on the weight. |
| +static SkColor interpolate_endpoints(const SkColor endpoints[2], int weight) { |
| + return SkColorSetARGB( |
| + interpolate_channel(SkColorGetA(endpoints[0]), SkColorGetA(endpoints[1]), weight), |
| + interpolate_channel(SkColorGetR(endpoints[0]), SkColorGetR(endpoints[1]), weight), |
| + interpolate_channel(SkColorGetG(endpoints[0]), SkColorGetG(endpoints[1]), weight), |
| + interpolate_channel(SkColorGetB(endpoints[0]), SkColorGetB(endpoints[1]), weight)); |
| +} |
| + |
| +// Returns an interpolated color between the two endpoints based on the weight. |
| +// It uses separate weights for the channel depending on the value of the 'plane' |
| +// variable. By default, all channels will use weight 0, and the value of plane |
| +// means that weight1 will be used for: |
| +// 0: red |
| +// 1: green |
| +// 2: blue |
| +// 3: alpha |
| +static SkColor interpolate_dual_endpoints( |
| + const SkColor endpoints[2], int weight0, int weight1, int plane) { |
| + int a = interpolate_channel(SkColorGetA(endpoints[0]), SkColorGetA(endpoints[1]), weight0); |
| + int r = interpolate_channel(SkColorGetR(endpoints[0]), SkColorGetR(endpoints[1]), weight0); |
| + int g = interpolate_channel(SkColorGetG(endpoints[0]), SkColorGetG(endpoints[1]), weight0); |
| + int b = interpolate_channel(SkColorGetB(endpoints[0]), SkColorGetB(endpoints[1]), weight0); |
| + |
| + switch (plane) { |
| + |
| + case 0: |
| + r = interpolate_channel( |
| + SkColorGetR(endpoints[0]), SkColorGetR(endpoints[1]), weight1); |
| + break; |
| + |
| + case 1: |
| + g = interpolate_channel( |
| + SkColorGetG(endpoints[0]), SkColorGetG(endpoints[1]), weight1); |
| + break; |
| + |
| + case 2: |
| + b = interpolate_channel( |
| + SkColorGetB(endpoints[0]), SkColorGetB(endpoints[1]), weight1); |
| + break; |
| + |
| + case 3: |
| + a = interpolate_channel( |
| + SkColorGetA(endpoints[0]), SkColorGetA(endpoints[1]), weight1); |
| + break; |
| + |
| + default: |
| + SkDEBUGFAIL("Plane should be 0-3"); |
| + break; |
| + } |
| + |
| + return SkColorSetARGB(a, r, g, b); |
| +} |
| + |
| +// A struct of decoded values that we use to carry around information |
| +// about the block. dimX and dimY are the dimension in texels of the block, |
| +// for which there is only a limited subset of valid values. |
|
robertphillips
2014/08/05 15:29:02
such as ?
krajcevski
2014/08/05 20:55:05
Done.
|
| +struct ASTCDecompressionData { |
| + ASTCDecompressionData(int dimX, int dimY) : fDimX(dimX), fDimY(dimY) { } |
| + const int fDimX; // the X dimension of the decompressed block |
| + const int fDimY; // the Y dimension of the decompressed block |
| + astcBlock_t fBlock; // the block data |
| + int fBlockMode; // the block header that contains the block mode. |
| + |
| + bool fDualPlaneEnabled; // is this block compressing dual weight planes? |
| + int fDualPlane; // the independent plane in dual plane mode. |
| + |
| + bool fVoidExtent; // is this block a single color? |
| + bool fError; // does this block have an error encoding? |
| + |
| + int fWeightDimX; // the x dimension of the weight grid |
| + int fWeightDimY; // the y dimension of the weight grid |
| + |
| + int fWeightBits; // the number of bits used for each weight value |
| + int fWeightTrits; // the number of trits used for each weight value |
| + int fWeightQuints; // the number of quints used for each weight value |
| + |
| + int fPartCount; // the number of partitions in this block |
| + int fPartIndex; // the partition index: only relevant if fPartCount > 0 |
| + |
| + static const int kMaxPartitions = 4; |
|
robertphillips
2014/08/05 17:30:00
// The possible CEM values are ... ?
krajcevski
2014/08/05 20:55:08
Done.
|
| + int fCEM[kMaxPartitions]; // the color endpoint modes for this block. |
| + |
| + int fColorStartBit; // The bit position of the first bit of the color data |
| + int fColorEndBit; // The bit position of the last *possible* bit of the color data |
| + |
| + // Returns the number of partitions for this block. |
| + int numPartitions() const { |
| + return fPartCount; |
| + } |
| + |
| + // Returns the total number of weight values that are stored in this block |
| + int numWeights() const { |
|
robertphillips
2014/08/05 17:30:01
Add a space before the '?' ?
krajcevski
2014/08/05 20:55:07
Done.
|
| + return fWeightDimX * fWeightDimY * (fDualPlaneEnabled? 2 : 1); |
| + } |
| + |
|
robertphillips
2014/08/05 17:30:00
#ifdef SK_DEBUG ?
krajcevski
2014/08/05 20:55:07
Done.
|
| + // Returns the maximum value that any weight can take. We really only use |
| + // this function for debugging. |
| + int maxWeightValue() const { |
| + int maxVal = (1 << fWeightBits); |
| + if (fWeightTrits > 0) { |
| + SkASSERT(0 == fWeightQuints); |
| + maxVal *= 3; |
| + } else if (fWeightQuints > 0) { |
| + SkASSERT(0 == fWeightTrits); |
| + maxVal *= 5; |
| + } |
| + return maxVal - 1; |
| + } |
|
robertphillips
2014/08/05 17:30:00
#endif ?
krajcevski
2014/08/05 20:55:05
Done.
|
| + |
| + // The number of bits needed to represent the texel weight data. This |
| + // comes from the 'data size determination' section of the ASTC spec (C.2.22) |
| + int numWeightBits() const { |
| + const int nWeights = this->numWeights(); |
| + return |
| + ((nWeights*8*fWeightTrits + 4) / 5) + |
| + ((nWeights*7*fWeightQuints + 2) / 3) + |
| + (nWeights*fWeightBits); |
| + } |
| + |
| + // Returns the number of color values stored in this block. The number of |
| + // values stored is directly a function of the color endpoint modes. |
| + int numColorValues() const { |
| + int numValues = 0; |
| + for (int i = 0; i < this->numPartitions(); ++i) { |
| + numValues += ((fCEM[i] >> 2) + 1) * 2; |
| + } |
| + |
| + return numValues; |
| + } |
| + |
| + // Figures out the number of bits available for color values, and fills |
| + // in the maximum encoding that will fit the number of color values that |
| + // we need. Returns false on error. (See section C.2.22 of the spec) |
| + bool getColorValueEncoding(int *nBits, int *nTrits, int *nQuints) const { |
| + if (NULL == nBits || NULL == nTrits || NULL == nQuints) { |
| + return false; |
| + } |
| + |
| + const int nColorVals = this->numColorValues(); |
| + if (nColorVals <= 0) { |
| + return false; |
| + } |
| + |
| + const int colorBits = fColorEndBit - fColorStartBit; |
| + SkASSERT(colorBits > 0); |
| + |
| + // This is the minimum amount of accuracy required by the spec. |
| + if (colorBits < ((13 * nColorVals + 4) / 5)) { |
| + return false; |
| + } |
| + |
| + // Values can be represented as at most 8-bit values. |
| + // !SPEED! place this in a lookup table based on colorBits and nColorVals |
| + for (int i = 255; i > 0; --i) { |
| + int range = i + 1; |
| + int bits = 0, trits = 0, quints = 0; |
| + bool valid = false; |
| + if (is_power_of_two(range)) { |
| + bits = bits_for_range(range); |
| + valid = true; |
| + } else if ((range % 3) == 0 && is_power_of_two(range/3)) { |
| + trits = 1; |
| + bits = bits_for_range(range/3); |
| + valid = true; |
| + } else if ((range % 5) == 0 && is_power_of_two(range/5)) { |
| + quints = 1; |
| + bits = bits_for_range(range/5); |
| + valid = true; |
| + } |
| + |
| + if (valid) { |
| + const int actualColorBits = |
| + ((nColorVals*8*trits + 4) / 5) + |
| + ((nColorVals*7*quints + 2) / 3) + |
| + (nColorVals*bits); |
| + if (actualColorBits <= colorBits) { |
| + *nTrits = trits; |
| + *nQuints = quints; |
| + *nBits = bits; |
| + return true; |
| + } |
| + } |
| + } |
| + |
| + return false; |
| + } |
| + |
| + // Converts the sequence of color values into endpoints. The algorithm here |
| + // corresponds to the values determined by section C.2.14 of the ASTC spec |
| + void colorEndpoints(SkColor endpoints[4][2], const int* colorValues) const { |
| + for (int i = 0; i < this->numPartitions(); ++i) { |
| + switch (fCEM[i]) { |
| + // LDR Luminance, direct |
|
robertphillips
2014/08/05 17:30:00
Can these ints be happy, happy enum values ?
krajcevski
2014/08/05 20:55:06
Done.
|
| + case 0: |
|
robertphillips
2014/08/05 17:30:00
formatting ?
krajcevski
2014/08/05 20:55:06
Done.
|
| + { |
| + const int* v = colorValues; |
| + endpoints[i][0] = SkColorSetARGB(0xFF, v[0], v[0], v[0]); |
| + endpoints[i][1] = SkColorSetARGB(0xFF, v[1], v[1], v[1]); |
| + |
| + colorValues += 2; |
| + } |
| + break; |
| + |
| + // LDR Luminance, base+offset |
| + case 1: |
| + { |
| + const int* v = colorValues; |
| + const int L0 = (v[0] >> 2) | (v[1] & 0xC0); |
| + const int L1 = clamp_byte(L0 + (v[1] & 0x3F)); |
| + |
| + endpoints[i][0] = SkColorSetARGB(0xFF, L0, L0, L0); |
| + endpoints[i][1] = SkColorSetARGB(0xFF, L1, L1, L1); |
| + |
| + colorValues += 2; |
| + } |
| + break; |
| + |
| + // LDR Luminance + Alpha, direct |
| + case 4: |
| + { |
| + const int* v = colorValues; |
| + |
| + endpoints[i][0] = SkColorSetARGB(v[2], v[0], v[0], v[0]); |
| + endpoints[i][1] = SkColorSetARGB(v[3], v[1], v[1], v[1]); |
| + |
| + colorValues += 4; |
| + } |
| + break; |
| + |
| + // LDR Luminance + Alpha, base+offset |
| + case 5: |
| + { |
| + int v0 = colorValues[0]; |
| + int v1 = colorValues[1]; |
| + int v2 = colorValues[2]; |
| + int v3 = colorValues[3]; |
| + |
| + bit_transfer_signed(&v1, &v0); |
| + bit_transfer_signed(&v3, &v2); |
| + |
| + endpoints[i][0] = SkColorSetARGB(v2, v0, v0, v0); |
| + endpoints[i][1] = SkColorSetARGB( |
| + clamp_byte(v3+v2), |
| + clamp_byte(v1+v0), |
| + clamp_byte(v1+v0), |
| + clamp_byte(v1+v0)); |
| + |
| + colorValues += 4; |
| + } |
| + break; |
| + |
| + // LDR RGB, base+scale |
| + case 6: |
| + { |
| + const int* v = colorValues; |
| + |
| + endpoints[i][0] = SkColorSetARGB( |
| + 0xFF, v[0]*v[3] >> 8, v[1]*v[3] >> 8, v[2]*v[3] >> 8); |
| + endpoints[i][1] = SkColorSetARGB(0xFF, v[0], v[1], v[2]); |
| + |
| + colorValues += 4; |
| + } |
| + break; |
| + |
| + // LDR RGB, direct |
| + case 8: |
|
robertphillips
2014/08/05 17:30:00
Could 8 & 12 share a sub-routine ?
krajcevski
2014/08/05 20:55:07
Done.
|
| + { |
| + const int* v = colorValues; |
| + |
| + const int s0 = v[0] + v[2] + v[4]; |
| + const int s1 = v[1] + v[3] + v[5]; |
| + |
| + if (s1 >= s0) { |
| + endpoints[i][0] = SkColorSetARGB(0xFF, v[0], v[2], v[4]); |
| + endpoints[i][1] = SkColorSetARGB(0xFF, v[1], v[3], v[5]); |
| + } else { |
| + endpoints[i][0] = blue_contract(0xFF, v[1], v[3], v[5]); |
| + endpoints[i][1] = blue_contract(0xFF, v[0], v[2], v[4]); |
| + } |
| + |
| + colorValues += 6; |
| + } |
| + break; |
| + |
| + // LDR RGB, base+offset |
| + case 9: |
|
robertphillips
2014/08/05 17:30:00
Could 9 & 13 share a subroutine ?
krajcevski
2014/08/05 20:55:07
Done.
|
| + { |
| + int v0 = colorValues[0]; |
| + int v1 = colorValues[1]; |
| + int v2 = colorValues[2]; |
| + int v3 = colorValues[3]; |
| + int v4 = colorValues[4]; |
| + int v5 = colorValues[5]; |
| + |
| + bit_transfer_signed(&v1, &v0); |
| + bit_transfer_signed(&v3, &v2); |
| + bit_transfer_signed(&v5, &v4); |
| + |
| + int c[2][4]; |
| + if (v1 + v3 + v5 >= 0) { |
| + c[0][0] = 0xFF; |
| + c[0][1] = v0; |
| + c[0][2] = v2; |
| + c[0][3] = v4; |
| + |
| + c[1][0] = 0xFF; |
| + c[1][1] = v0 + v1; |
| + c[1][2] = v2 + v3; |
| + c[1][3] = v4 + v5; |
| + } else { |
| + c[0][0] = 0xFF; |
| + c[0][1] = (v0 + v1 + v4 + v5) >> 1; |
| + c[0][2] = (v2 + v3 + v4 + v5) >> 1; |
| + c[0][3] = v4 + v5; |
| + |
| + c[1][0] = 0xFF; |
| + c[1][1] = (v0 + v4) >> 1; |
| + c[1][2] = (v2 + v4) >> 1; |
| + c[1][3] = v4; |
| + } |
| + |
| + endpoints[i][0] = SkColorSetARGB( |
|
robertphillips
2014/08/05 17:30:00
Can we replace clamp_byte(c[0][0]) and clamp_byte(
krajcevski
2014/08/05 20:55:07
Not if they're in their own subroutine. :)
|
| + clamp_byte(c[0][0]), |
| + clamp_byte(c[0][1]), |
| + clamp_byte(c[0][2]), |
| + clamp_byte(c[0][3])); |
| + |
| + endpoints[i][1] = SkColorSetARGB( |
| + clamp_byte(c[1][0]), |
| + clamp_byte(c[1][1]), |
| + clamp_byte(c[1][2]), |
| + clamp_byte(c[1][3])); |
| + |
| + colorValues += 6; |
| + } |
| + break; |
| + |
| + // LDR RGBA, base+scale and two alpha |
| + case 10: |
| + { |
| + const int* v = colorValues; |
| + |
| + endpoints[i][0] = SkColorSetARGB(v[4], |
| + (v[0]*v[3]) >> 8, |
| + (v[1]*v[3]) >> 8, |
| + (v[2]*v[3]) >> 8); |
| + endpoints[i][1] = SkColorSetARGB(v[5], v[0], v[1], v[2]); |
| + |
| + colorValues += 6; |
| + } |
| + break; |
| + |
| + // LDR RGBA, direct |
| + case 12: |
| + { |
| + const int* v = colorValues; |
| + |
| + if ((v[0] + v[2] + v[4]) >= (v[1] + v[3] + v[5])) { |
| + endpoints[i][0] = SkColorSetARGB(v[6], v[0], v[2], v[4]); |
| + endpoints[i][1] = SkColorSetARGB(v[7], v[1], v[3], v[5]); |
| + } else { |
| + endpoints[i][0] = blue_contract(v[7], v[1], v[3], v[5]); |
| + endpoints[i][1] = blue_contract(v[6], v[0], v[2], v[4]); |
| + } |
| + |
| + colorValues += 8; |
| + } |
| + break; |
| + |
| + // LDR RGBA, base+offset |
| + case 13: |
| + { |
| + int v0 = colorValues[0]; |
| + int v1 = colorValues[1]; |
| + int v2 = colorValues[2]; |
| + int v3 = colorValues[3]; |
| + int v4 = colorValues[4]; |
| + int v5 = colorValues[5]; |
| + int v6 = colorValues[6]; |
| + int v7 = colorValues[7]; |
| + |
| + bit_transfer_signed(&v1, &v0); |
| + bit_transfer_signed(&v3, &v2); |
| + bit_transfer_signed(&v5, &v4); |
| + bit_transfer_signed(&v7, &v6); |
| + |
| + int c[2][4]; |
| + if ((v1 + v3 + v5) >= 0) { |
| + c[0][0] = v6; |
| + c[0][1] = v0; |
| + c[0][2] = v2; |
| + c[0][3] = v4; |
| + |
| + c[1][0] = v6 + v7; |
| + c[1][1] = v0 + v1; |
| + c[1][2] = v2 + v3; |
| + c[1][3] = v4 + v5; |
| + } else { |
| + c[0][0] = v6 + v7; |
| + c[0][1] = (v0 + v1 + v4 + v5) >> 1; |
| + c[0][2] = (v2 + v3 + v4 + v5) >> 1; |
| + c[0][3] = v4 + v5; |
| + |
| + c[1][0] = v6; |
| + c[1][1] = (v0 + v4) >> 1; |
| + c[1][2] = (v2 + v4) >> 1; |
| + c[1][3] = v4; |
| + } |
| + |
| + endpoints[i][0] = SkColorSetARGB( |
| + clamp_byte(c[0][0]), |
| + clamp_byte(c[0][1]), |
| + clamp_byte(c[0][2]), |
| + clamp_byte(c[0][3])); |
| + |
| + endpoints[i][1] = SkColorSetARGB( |
| + clamp_byte(c[1][0]), |
| + clamp_byte(c[1][1]), |
| + clamp_byte(c[1][2]), |
| + clamp_byte(c[1][3])); |
| + |
| + colorValues += 8; |
| + } |
| + break; |
| + |
| + default: |
| + SkDEBUGFAIL("HDR mode unsupported! This should be caught sooner."); |
| + break; |
| + } |
| + } |
| + } |
| + |
| + // Follows the procedure from section C.2.17 of the ASTC specification |
| + int unquantizeWeight(int x) const { |
| + SkASSERT(x <= this->maxWeightValue()); |
| + |
| + const int D = (x >> fWeightBits) & 0x7; |
| + const int A = -(x & 0x1) & 0x7F; |
| + |
| + SkTBits<int> xbits(x); |
| + |
| + int T = 0; |
| + if (fWeightTrits > 0) { |
| + SkASSERT(0 == fWeightQuints); |
| + switch (fWeightBits) { |
| + case 0: |
| + { |
| + // x is a single trit |
| + SkASSERT(x < 3); |
| + |
| + static const int kUnquantizationTable[3] = { 0, 32, 63 }; |
| + T = kUnquantizationTable[x]; |
| + } |
| + break; |
| + |
| + case 1: |
| + { |
| + const int B = 0; |
| + const int C = 50; |
| + T = unquantize_value<0x20>(A, B, C, D); |
| + } |
| + break; |
| + |
| + case 2: |
| + { |
| + const int b = xbits[1]; |
| + const int B = b | (b << 2) | (b << 6); |
| + const int C = 23; |
| + T = unquantize_value<0x20>(A, B, C, D); |
| + } |
| + break; |
| + |
| + case 3: |
| + { |
| + const int cb = xbits(2, 1); |
| + const int B = cb | (cb << 5); |
| + const int C = 11; |
| + T = unquantize_value<0x20>(A, B, C, D); |
| + } |
| + break; |
| + |
| + default: |
| + SkDEBUGFAIL("Too many bits for trit encoding"); |
| + break; |
| + } |
| + |
| + } else if (fWeightQuints > 0) { |
| + SkASSERT(0 == fWeightTrits); |
| + switch (fWeightBits) { |
| + case 0: |
| + { |
| + // x is a single quint |
| + SkASSERT(x < 5); |
| + |
| + static const int kUnquantizationTable[5] = { 0, 16, 32, 47, 63 }; |
| + T = kUnquantizationTable[x]; |
| + } |
| + break; |
| + |
| + case 1: |
| + { |
| + const int B = 0; |
| + const int C = 28; |
| + T = unquantize_value<0x20>(A, B, C, D); |
| + } |
| + break; |
| + |
| + case 2: |
| + { |
| + const int b = xbits[1]; |
| + const int B = (b << 1) | (b << 6); |
| + const int C = 13; |
| + T = unquantize_value<0x20>(A, B, C, D); |
| + } |
| + break; |
| + |
| + default: |
| + SkDEBUGFAIL("Too many bits for quint encoding"); |
| + break; |
| + } |
| + } else { |
| + SkASSERT(0 == fWeightTrits); |
| + SkASSERT(0 == fWeightQuints); |
| + |
| + T = replicate_bits(x, fWeightBits, 6); |
| + } |
| + |
| + // This should bring the value within [0, 63].. |
| + SkASSERT(T <= 63); |
| + |
| + if (T > 32) { |
| + T += 1; |
| + } |
| + |
| + SkASSERT(T <= 64); |
| + |
| + return T; |
| + } |
| + |
| + // Returns the weight at the associated index. If the index is out of bounds, it |
| + // returns zero. It also chooses the weight appropriately based on the given dual |
| + // plane. |
| + int getWeight(const int* unquantizedWeights, int idx, bool dualPlane) const { |
| + const int maxIdx = ((fDualPlaneEnabled)? 2 : 1) * fWeightDimX * fWeightDimY - 1; |
| + if (fDualPlaneEnabled) { |
|
robertphillips
2014/08/05 17:30:01
space before '?' ?
krajcevski
2014/08/05 20:55:05
Done.
|
| + const int effectiveIdx = 2*idx + (dualPlane? 1 : 0); |
| + if (effectiveIdx > maxIdx) { |
| + return 0; |
| + } |
| + return unquantizedWeights[effectiveIdx]; |
| + } |
| + |
| + SkASSERT(!dualPlane); |
| + |
| + if (idx > maxIdx) { |
| + return 0; |
| + } else { |
| + return unquantizedWeights[idx]; |
| + } |
| + } |
| + |
| + // This computes the effective weight at location (s, t) of the block. This |
| + // weight is computed by sampling the texel weight grid (it's usually not 1-1), and |
| + // then applying a bilerp. The algorithm outlined here follows the algorithm |
| + // defined in section C.2.18 of the ASTC spec. |
| + int infillWeight(const int* unquantizedValues, int s, int t, bool dualPlane) const { |
| + const int Ds = (1024 + fDimX/2) / (fDimX - 1); |
| + const int Dt = (1024 + fDimY/2) / (fDimY - 1); |
| + |
| + const int cs = Ds * s; |
| + const int ct = Dt * t; |
| + |
| + const int gs = (cs*(fWeightDimX - 1) + 32) >> 6; |
| + const int gt = (ct*(fWeightDimY - 1) + 32) >> 6; |
| + |
| + const int js = gs >> 4; |
| + const int jt = gt >> 4; |
| + |
| + const int fs = gs & 0xF; |
| + const int ft = gt & 0xF; |
| + |
| + const int idx = js + jt*fWeightDimX; |
| + const int p00 = this->getWeight(unquantizedValues, idx, dualPlane); |
| + const int p01 = this->getWeight(unquantizedValues, idx + 1, dualPlane); |
| + const int p10 = this->getWeight(unquantizedValues, idx + fWeightDimX, dualPlane); |
| + const int p11 = this->getWeight(unquantizedValues, idx + fWeightDimX + 1, dualPlane); |
| + |
| + const int w11 = (fs*ft + 8) >> 4; |
| + const int w10 = ft - w11; |
| + const int w01 = fs - w11; |
| + const int w00 = 16 - fs - ft + w11; |
| + |
| + const int weight = (p00*w00 + p01*w01 + p10*w10 + p11*w11 + 8) >> 4; |
| + SkASSERT(weight <= 64); |
| + return weight; |
| + } |
| + |
| + // Unquantizes the decoded texel weights as described in section C.2.17 of |
| + // the ASTC specification. Additionally, it populates texelWeights with |
| + // the expanded weight grid, which is computed according to section C.2.18 |
| + void texelWeights(int texelWeights[2][12][12], const int* texelValues) const { |
| + // Unquantized texel weights... |
| + int unquantizedValues[144*2]; // 12x12 blocks with dual plane decoding... |
| + SkASSERT(this->numWeights() <= 144*2); |
| + |
| + // Unquantize the weights and cache them |
| + for (int j = 0; j < this->numWeights(); ++j) { |
| + unquantizedValues[j] = this->unquantizeWeight(texelValues[j]); |
| + } |
| + |
| + // Do weight infill... |
|
robertphillips
2014/08/05 20:37:53
Is there a reason to use s,t over x,y ?
krajcevski
2014/08/05 20:55:08
s,t are what's used in the spec. They also map bet
|
| + for (int t = 0; t < fDimY; ++t) { |
| + for (int s = 0; s < fDimX; ++s) { |
|
robertphillips
2014/08/05 20:37:53
Would it be clearer to unroll the plane loop and j
krajcevski
2014/08/05 20:55:07
Yes, I think my brain was fried by this point.
|
| + for (int i = 0; i < 2; ++i) { |
| + bool bUseDualPlane = static_cast<bool>(i); |
| + if (bUseDualPlane && !fDualPlaneEnabled) { |
| + continue; |
| + } |
| + |
| + texelWeights[i][s][t] = |
| + this->infillWeight(unquantizedValues, s, t, bUseDualPlane); |
| + } |
| + } |
| + } |
| + } |
| + |
| + // Returns the partition for the texel located at position (x, y). |
|
robertphillips
2014/08/05 20:37:53
Adapted from ... ?
krajcevski
2014/08/05 20:55:05
Not sure what you mean? The next comment says wher
robertphillips
2014/08/06 12:31:52
I think we should replace "Copied directly from" w
krajcevski
2014/08/06 14:15:57
Done.
|
| + // Copied directly from C.2.21 of the ASTC specification |
| + int getPartition(int x, int y) const { |
|
robertphillips
2014/08/05 20:37:53
bSmallBlock -> isSmallBlock ?
krajcevski
2014/08/05 20:55:08
Done.
|
| + bool bSmallBlock = (fDimX * fDimY) < 31; |
| + const int partitionCount = this->numPartitions(); |
| + int seed = fPartIndex; |
| + if (bSmallBlock) { |
| + x <<= 1; |
| + y <<= 1; |
| + } |
| + |
| + seed += (partitionCount - 1) * 1024; |
| + |
| + uint32_t p = seed; |
| + p ^= p >> 15; p -= p << 17; p += p << 7; p += p << 4; |
| + p ^= p >> 5; p += p << 16; p ^= p >> 7; p ^= p >> 3; |
| + p ^= p << 6; p ^= p >> 17; |
| + |
| + uint32_t rnum = p; |
| + uint8_t seed1 = rnum & 0xF; |
| + uint8_t seed2 = (rnum >> 4) & 0xF; |
| + uint8_t seed3 = (rnum >> 8) & 0xF; |
| + uint8_t seed4 = (rnum >> 12) & 0xF; |
| + uint8_t seed5 = (rnum >> 16) & 0xF; |
| + uint8_t seed6 = (rnum >> 20) & 0xF; |
| + uint8_t seed7 = (rnum >> 24) & 0xF; |
| + uint8_t seed8 = (rnum >> 28) & 0xF; |
| + uint8_t seed9 = (rnum >> 18) & 0xF; |
| + uint8_t seed10 = (rnum >> 22) & 0xF; |
| + uint8_t seed11 = (rnum >> 26) & 0xF; |
| + uint8_t seed12 = ((rnum >> 30) | (rnum << 2)) & 0xF; |
| + |
| + seed1 *= seed1; seed2 *= seed2; |
| + seed3 *= seed3; seed4 *= seed4; |
| + seed5 *= seed5; seed6 *= seed6; |
| + seed7 *= seed7; seed8 *= seed8; |
| + seed9 *= seed9; seed10 *= seed10; |
| + seed11 *= seed11; seed12 *= seed12; |
| + |
| + int sh1, sh2, sh3; |
| + if (0 != (seed & 1)) { |
| + sh1 = (0 != (seed & 2))? 4 : 5; |
| + sh2 = (partitionCount == 3)? 6 : 5; |
| + } else { |
| + sh1 = (partitionCount==3)? 6 : 5; |
| + sh2 = (0 != (seed & 2))? 4 : 5; |
| + } |
| + sh3 = (0 != (seed & 0x10))? sh1 : sh2; |
| + |
| + seed1 >>= sh1; seed2 >>= sh2; seed3 >>= sh1; seed4 >>= sh2; |
| + seed5 >>= sh1; seed6 >>= sh2; seed7 >>= sh1; seed8 >>= sh2; |
| + seed9 >>= sh3; seed10 >>= sh3; seed11 >>= sh3; seed12 >>= sh3; |
| + |
| + const int z = 0; |
| + int a = seed1*x + seed2*y + seed11*z + (rnum >> 14); |
| + int b = seed3*x + seed4*y + seed12*z + (rnum >> 10); |
| + int c = seed5*x + seed6*y + seed9 *z + (rnum >> 6); |
| + int d = seed7*x + seed8*y + seed10*z + (rnum >> 2); |
| + |
| + a &= 0x3F; |
| + b &= 0x3F; |
| + c &= 0x3F; |
| + d &= 0x3F; |
| + |
| + if (partitionCount < 4) { |
| + d = 0; |
| + } |
| + |
| + if (partitionCount < 3) { |
| + c = 0; |
| + } |
| + |
| + if (a >= b && a >= c && a >= d) { |
| + return 0; |
| + } else if (b >= c && b >= d) { |
| + return 1; |
| + } else if (c >= d) { |
| + return 2; |
| + } else { |
| + return 3; |
| + } |
| + } |
| + |
| + // Performs the proper interpolation of the texel based on the |
| + // endpoints and weights. |
| + SkColor getTexel(const SkColor endpoints[4][2], |
| + const int weights[2][12][12], |
| + int x, int y) const { |
| + int part = 0; |
| + if (this->numPartitions() > 1) { |
| + part = this->getPartition(x, y); |
| + } |
| + |
| + SkColor result; |
| + if (fDualPlaneEnabled) { |
| + result = interpolate_dual_endpoints( |
| + endpoints[part], weights[0][x][y], weights[1][x][y], fDualPlane); |
| + } else { |
| + result = interpolate_endpoints(endpoints[part], weights[0][x][y]); |
| + } |
| + |
| +#if 1 |
| + // !FIXME! if we're writing directly to a bitmap, then we don't need |
| + // to swap the red and blue channels, but since we're usually being used |
| + // by the SkImageDecoder_astc module, the results are expected to be in RGBA. |
| + result = SkColorSetARGB( |
| + SkColorGetA(result), SkColorGetB(result), SkColorGetG(result), SkColorGetR(result)); |
| +#endif |
| + |
| + return result; |
| + } |
| + |
| + void decode() { |
| + // First decode the block mode. |
| + this->decodeBlockMode(); |
| + |
| + // Now we can decode the partition information. |
| + fPartIndex = read_astc_bits(fBlock, 11, 23); |
| + fPartCount = (fPartIndex & 0x3) + 1; |
| + fPartIndex >>= 2; |
| + |
| + // This is illegal |
| + if (fDualPlaneEnabled && this->numPartitions() == 4) { |
| + fError = true; |
|
robertphillips
2014/08/05 20:37:53
Can we early out here?
krajcevski
2014/08/05 20:55:06
Yes. If fError is true then none of the other valu
|
| + } |
| + |
| + // Based on the partition info, we can decode the color information. |
| + this->decodeColorData(); |
| + } |
| + |
| + // Decodes the dual plane based on the given bit location. The final |
| + // location, if the dual plane is enabled, is also the end of our color data. |
| + // This function is only meant to be used from this->decodeColorData() |
| + void decodeDualPlane(int bitLoc) { |
| + if (fDualPlaneEnabled) { |
| + fDualPlane = read_astc_bits(fBlock, bitLoc - 2, bitLoc); |
| + fColorEndBit = bitLoc - 2; |
| + } else { |
| + fColorEndBit = bitLoc; |
| + } |
| + } |
| + |
| + // Decodes the color information based on the ASTC spec. |
| + void decodeColorData() { |
| + |
| + // By default, the last color bit is at the end of the texel weights |
| + const int lastWeight = 128 - this->numWeightBits(); |
| + |
| + // If we have a dual plane then it will be at this location, too. |
| + int dualPlaneBitLoc = lastWeight; |
| + |
| + // If there's only one partition, then our job is (relatively) easy. |
| + if (this->numPartitions() == 1) { |
| + fCEM[0] = read_astc_bits(fBlock, 13, 17); |
| + fColorStartBit = 17; |
| + |
| + // Handle dual plane mode... |
| + this->decodeDualPlane(dualPlaneBitLoc); |
| + |
| + return; |
| + } |
| + |
| + // If we have more than one partition, then we need to make |
| + // room for the partition index. |
| + fColorStartBit = 29; |
| + |
| + // Read the base CEM. If it's zero, then we have no additional |
| + // CEM data and the endpoints for each partition share the same CEM. |
| + const int baseCEM = read_astc_bits(fBlock, 23, 25); |
| + if (0 == baseCEM) { |
| + const int sameCEM = read_astc_bits(fBlock, 25, 29); |
| + for (int i = 0; i < kMaxPartitions; ++i) { |
| + fCEM[i] = sameCEM; |
| + } |
| + |
| + // Handle dual plane mode... |
| + this->decodeDualPlane(dualPlaneBitLoc); |
| + |
| + return; |
| + } |
| + |
| + // Move the dual plane selector bits down based on how many |
| + // partitions the block contains. |
| + switch (this->numPartitions()) { |
| + case 2: |
| + dualPlaneBitLoc -= 2; |
| + break; |
| + |
| + case 3: |
| + dualPlaneBitLoc -= 5; |
| + break; |
| + |
| + case 4: |
| + dualPlaneBitLoc -= 8; |
| + break; |
| + |
| + default: |
| + SkDEBUGFAIL("Internal ASTC decoding error."); |
| + break; |
| + } |
| + |
| + // The rest of the CEM config will be between the dual plane bit selector |
| + // and the texel weight grid. |
| + const int lowCEM = read_astc_bits(fBlock, 23, 29); |
| + int fullCEM = read_astc_bits(fBlock, dualPlaneBitLoc, lastWeight); |
| + |
| + // Attach the config at the end of the weight grid to the CEM values |
| + // in the beginning of the block. |
| + fullCEM = (fullCEM << 6) | lowCEM; |
| + |
| + // Ignore the two least significant bits, since those are our baseCEM above. |
| + fullCEM = fullCEM >> 2; |
| + |
| + int C[kMaxPartitions]; // Next, decode C and M from the spec (Table C.2.12) |
| + for (int i = 0; i < this->numPartitions(); ++i) { |
| + C[i] = fullCEM & 1; |
| + fullCEM = fullCEM >> 1; |
| + } |
| + |
| + int M[kMaxPartitions]; |
| + for (int i = 0; i < this->numPartitions(); ++i) { |
| + M[i] = fullCEM & 0x3; |
| + fullCEM = fullCEM >> 2; |
| + } |
| + |
| + // Construct our CEMs.. |
| + SkASSERT(baseCEM > 0); |
| + for (int i = 0; i < this->numPartitions(); ++i) { |
| + int cem = (baseCEM - 1) * 4; |
| + cem += (0 == C[i])? 0 : 4; |
| + cem += M[i]; |
| + |
| + SkASSERT(cem < 16); |
| + fCEM[i] = cem; |
| + } |
| + |
| + // Finally, if we have dual plane mode, then read the plane selector. |
| + this->decodeDualPlane(dualPlaneBitLoc); |
| + } |
| + |
| + // Decodes the block mode. This function determines whether or not we use |
| + // dual plane encoding, the size of the texel weight grid, and the number of |
| + // bits, trits and quints that are used to encode it. For more information, |
| + // see section C.2.10 of the ASTC spec. |
| + void decodeBlockMode() { |
| + const int blockMode = read_astc_bits(fBlock, 0, 11); |
| + |
| + // Check for special void extent encoding |
| + fVoidExtent = (blockMode & 0x1FF) == 0x1FC; |
| + |
| + // Check for reserved block modes |
| + fError = (blockMode & 0x1C3) == 0x1C0; |
| + |
| + // Neither reserved nor void-extent, decode as usual |
| + // This code corresponds to table C.2.8 of the ASTC spec |
|
robertphillips
2014/08/05 20:37:53
highPrecision ?
krajcevski
2014/08/05 20:55:06
Done.
|
| + bool bHighPrecision = false; |
| + int R = 0; |
| + if ((blockMode & 0x3) == 0) { |
| + R = ((0xC & blockMode) >> 1) | ((0x10 & blockMode) >> 4); |
| + const int bitsSevenAndEight = (blockMode & 0x180) >> 7; |
| + SkASSERT(0 <= bitsSevenAndEight && bitsSevenAndEight < 4); |
| + |
| + const int A = (blockMode >> 5) & 0x3; |
| + const int B = (blockMode >> 9) & 0x3; |
| + |
|
robertphillips
2014/08/05 20:37:53
Would it be any better to define these two as flag
krajcevski
2014/08/05 20:55:06
If life were easy, yes. Some block modes don't enc
|
| + fDualPlaneEnabled = (blockMode >> 10) & 0x1; |
| + bHighPrecision = (blockMode >> 9) & 0x1; |
| + |
| + switch (bitsSevenAndEight) { |
| + default: |
|
robertphillips
2014/08/05 20:37:53
Do these 0..4 values have some clear symolic inter
krajcevski
2014/08/05 20:55:07
I'm not sure that there's a meaningful way to comm
|
| + case 0: |
| + fWeightDimX = 12; |
| + fWeightDimY = A + 2; |
| + break; |
| + |
| + case 1: |
| + fWeightDimX = A + 2; |
| + fWeightDimY = 12; |
| + break; |
| + |
| + case 2: |
| + fWeightDimX = A + 6; |
| + fWeightDimY = B + 6; |
| + fDualPlaneEnabled = false; |
| + bHighPrecision = false; |
| + break; |
| + |
| + case 3: |
| + if (0 == A) { |
| + fWeightDimX = 6; |
| + fWeightDimY = 10; |
| + } else { |
| + fWeightDimX = 10; |
| + fWeightDimY = 6; |
| + } |
| + break; |
| + } |
| + } else { // (blockMode & 0x3) != 0 |
| + R = ((blockMode & 0x3) << 1) | ((blockMode & 0x10) >> 4); |
| + |
| + const int bitsTwoAndThree = (blockMode >> 2) & 0x3; |
| + SkASSERT(0 <= bitsTwoAndThree && bitsTwoAndThree < 4); |
| + |
| + const int A = (blockMode >> 5) & 0x3; |
| + const int B = (blockMode >> 7) & 0x3; |
| + |
| + fDualPlaneEnabled = (blockMode >> 10) & 0x1; |
| + bHighPrecision = (blockMode >> 9) & 0x1; |
| + |
| + switch (bitsTwoAndThree) { |
| + case 0: |
| + fWeightDimX = B + 4; |
| + fWeightDimY = A + 2; |
| + break; |
| + case 1: |
| + fWeightDimX = B + 8; |
| + fWeightDimY = A + 2; |
| + break; |
| + case 2: |
| + fWeightDimX = A + 2; |
| + fWeightDimY = B + 8; |
| + break; |
| + case 3: |
| + if ((B & 0x2) == 0) { |
| + fWeightDimX = A + 2; |
| + fWeightDimY = (B & 1) + 6; |
| + } else { |
| + fWeightDimX = (B & 1) + 2; |
| + fWeightDimY = A + 2; |
| + } |
| + break; |
| + } |
| + } |
| + |
| + // We should have set the values of R and bHighPrecision |
| + // from decoding the block mode, these are used to determine |
| + // the proper dimensions of our weight grid. |
| + if ((R & 0x6) == 0) { |
| + fError = true; |
| + } else { |
| + static const int kBitAllocationTable[2][6][3] = { |
| + { |
| + { 1, 0, 0 }, |
| + { 0, 1, 0 }, |
| + { 2, 0, 0 }, |
| + { 0, 0, 1 }, |
| + { 1, 1, 0 }, |
| + { 3, 0, 0 } |
| + }, |
| + { |
| + { 1, 0, 1 }, |
| + { 2, 1, 0 }, |
| + { 4, 0, 0 }, |
| + { 2, 0, 1 }, |
| + { 3, 1, 0 }, |
| + { 5, 0, 0 } |
| + } |
| + }; |
| + |
| + fWeightBits = kBitAllocationTable[bHighPrecision][R - 2][0]; |
| + fWeightTrits = kBitAllocationTable[bHighPrecision][R - 2][1]; |
| + fWeightQuints = kBitAllocationTable[bHighPrecision][R - 2][2]; |
| + } |
| + } |
| +}; |
| + |
| +// Take a known void-extent block, and write out the values as a constant color. |
| +static void decompress_void_extent(uint8_t* dst, int dstRowBytes, |
| + const ASTCDecompressionData &data) { |
|
robertphillips
2014/08/05 15:29:03
0x100 ?
krajcevski
2014/08/05 20:55:08
I think this is remains of a decompressor past.
|
| + if ((0x100 & data.fBlockMode) == 0) { |
| + write_error_color(dst, dstRowBytes); |
| + return; |
| + } |
| + |
| + // The top 64 bits contain 4 16-bit RGBA values. |
| + int a = (read_astc_bits(data.fBlock, 112, 128) + 255) >> 8; |
| + int b = (read_astc_bits(data.fBlock, 96, 112) + 255) >> 8; |
| + int g = (read_astc_bits(data.fBlock, 80, 96) + 255) >> 8; |
| + int r = (read_astc_bits(data.fBlock, 64, 80) + 255) >> 8; |
| + |
| + write_constant_color(dst, dstRowBytes, SkColorSetARGB(a, r, g, b)); |
| +} |
| + |
| +// Decompresses a single ASTC block. It's assumed that data.fDimX and data.fDimY are |
| +// set and that the block has already been decoded (i.e. data.decode() has been called) |
| +static void decompress_astc_block(uint8_t* dst, int dstRowBytes, |
| + const ASTCDecompressionData &data) { |
| + if (data.fError) { |
| + write_error_color(dst, dstRowBytes); |
| + return; |
| + } |
| + |
| + if(data.fVoidExtent) { |
| + decompress_void_extent(dst, dstRowBytes, data); |
| + return; |
| + } |
| + |
| + // Decode the texel weights. |
|
robertphillips
2014/08/05 15:29:03
64?
krajcevski
2014/08/05 20:55:05
Done.
|
| + int texelValues[64]; |
| + bool success = decode_integer_sequence( |
| + texelValues, 64, data.numWeights(), |
|
robertphillips
2014/08/05 15:29:02
128?
krajcevski
2014/08/05 20:55:07
Done.
|
| + data.fBlock, 128, 128 - data.numWeightBits(), false, |
| + data.fWeightBits, data.fWeightTrits, data.fWeightQuints); |
| + |
| + if (!success) { |
| + write_error_color(dst, dstRowBytes); |
| + return; |
| + } |
| + |
| + // Decode the color endpoints |
| + int colorBits, colorTrits, colorQuints; |
| + if (!data.getColorValueEncoding(&colorBits, &colorTrits, &colorQuints)) { |
| + write_error_color(dst, dstRowBytes); |
| + return; |
| + } |
| + |
| + int colorValues[18]; |
| + success = decode_integer_sequence( |
| + colorValues, 18, data.numColorValues(), |
| + data.fBlock, data.fColorStartBit, data.fColorEndBit, true, |
| + colorBits, colorTrits, colorQuints); |
| + |
| + if (!success) { |
| + write_error_color(dst, dstRowBytes); |
| + return; |
| + } |
| + |
| + // Unquantize the color values after they've been decoded. |
| + unquantize_colors(colorValues, data.numColorValues(), colorBits, colorTrits, colorQuints); |
| + |
| + // Decode the colors into the appropriate endpoints. |
| + SkColor endpoints[4][2]; |
| + data.colorEndpoints(endpoints, colorValues); |
| + |
| + // Do texel infill and decode the texel values. |
| + int texelWeights[2][12][12]; |
| + data.texelWeights(texelWeights, texelValues); |
| + |
| + // Write the texels by interpolating them based on the information |
| + // stored in the block. |
| + dst += data.fDimY * dstRowBytes; |
|
robertphillips
2014/08/05 15:29:03
i,j -> x,y ?
krajcevski
2014/08/05 20:55:07
Done.
|
| + for (int j = 0; j < data.fDimY; ++j) { |
| + dst -= dstRowBytes; |
| + SkColor* colorPtr = reinterpret_cast<SkColor*>(dst); |
| + for (int i = 0; i < data.fDimX; ++i) { |
| + colorPtr[i] = data.getTexel(endpoints, texelWeights, i, j); |
| + } |
| + } |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| namespace SkTextureCompressor { |
| @@ -285,4 +1932,21 @@ SkBlitter* CreateASTCBlitter(int width, int height, void* outputBuffer) { |
| (width, height, outputBuffer); |
| } |
| +void DecompressASTC(uint8_t* dst, int dstRowBytes, const uint8_t* src, |
| + int width, int height, int blockDimX, int blockDimY) { |
|
robertphillips
2014/08/05 15:29:02
// ASTC is encoded upside down so we need to walk
krajcevski
2014/08/05 20:55:06
Done.
|
| + dst += height * dstRowBytes; |
| + |
| + ASTCDecompressionData data(blockDimX, blockDimY); |
|
robertphillips
2014/08/05 15:29:03
Do we need (or already have) and assert somewhere
krajcevski
2014/08/05 20:55:05
Yes, that should be caught in SkTextureCompressor.
|
| + for (int j = 0; j < height; j += blockDimY) { |
| + dst -= blockDimY * dstRowBytes; |
| + SkColor *colorPtr = reinterpret_cast<SkColor*>(dst); |
| + for (int i = 0; i < width; i += blockDimX) { |
|
robertphillips
2014/08/05 15:29:02
Should we just pass in data here (rather then data
krajcevski
2014/08/05 20:55:05
Done.
|
| + read_astc_block(&(data.fBlock), src); |
| + data.decode(); |
| + decompress_astc_block(reinterpret_cast<uint8_t*>(colorPtr + i), dstRowBytes, data); |
|
robertphillips
2014/08/05 15:29:02
16?
krajcevski
2014/08/05 20:55:05
Done.
|
| + src += 16; |
| + } |
| + } |
| +} |
| + |
| } // SkTextureCompressor |