Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(160)

Side by Side Diff: src/utils/SkTextureCompressor_ASTC.cpp

Issue 444433002: Initial ASTC decoder -- currently only supports 2D LDR decomrpession modes. (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/utils/SkTextureCompressor_ASTC.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright 2014 Google Inc. 2 * Copyright 2014 Google Inc.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license that can be 4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file. 5 * found in the LICENSE file.
6 */ 6 */
7 7
8 #include "SkTextureCompressor_ASTC.h" 8 #include "SkTextureCompressor_ASTC.h"
9 #include "SkTextureCompressor_Blitter.h" 9 #include "SkTextureCompressor_Blitter.h"
10 10
(...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after
254 top = top ^ t ^ (t << 2); 254 top = top ^ t ^ (t << 2);
255 255
256 send_packing(dst, SkEndian_SwapLE64(top), SkEndian_SwapLE64(bottom)); 256 send_packing(dst, SkEndian_SwapLE64(top), SkEndian_SwapLE64(bottom));
257 } 257 }
258 258
259 inline void CompressA8ASTCBlockVertical(uint8_t* dst, const uint8_t* src) { 259 inline void CompressA8ASTCBlockVertical(uint8_t* dst, const uint8_t* src) {
260 compress_a8_astc_block<GetAlphaTranspose>(&dst, src, 12); 260 compress_a8_astc_block<GetAlphaTranspose>(&dst, src, 12);
261 } 261 }
262 262
263 //////////////////////////////////////////////////////////////////////////////// 263 ////////////////////////////////////////////////////////////////////////////////
264 //
265 // ASTC Decoder
266 //
267 // Full details available in the spec:
268 // http://www.khronos.org/registry/gles/extensions/OES/OES_texture_compression_a stc.txt
269 //
270 ////////////////////////////////////////////////////////////////////////////////
271
272 #define ASSERT_ASTC_DECODE_ERROR 0
273
274 // An ASTC block is 128 bits. We represent it as two 64-bit integers in order
275 // 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.
276 struct astcBlock_t {
277 uint64_t low;
278 uint64_t high;
279 };
280
281 // Writes the given color to every pixel in the block. This is used by void-exte nt
282 // blocks (a special constant-color encoding of a block) and by the error functi on.
283 static inline void write_constant_color(uint8_t* dst, int dstRowBytes, SkColor c olor) {
robertphillips 2014/08/05 15:29:03 12? i,j -> x,y ?
krajcevski 2014/08/05 20:55:05 Done.
284 for (int j = 0; j < 12; ++j) {
285 SkColor *dstColors = reinterpret_cast<SkColor*>(dst + j*dstRowBytes);
286 for (int i = 0; i < 12; ++i) {
287 dstColors[i] = color;
288 }
289 }
290 }
291
292 // Sets the entire block to the ASTC "error" color, a disgusting magenta
293 // that's not supposed to appear in natural images.
294 static inline void write_error_color(uint8_t* dst, int dstRowBytes) {
295 static const SkColor kASTCErrorColor = SkColorSetRGB(0xFF, 0, 0xFF);
296
297 #if ASSERT_ASTC_DECODE_ERROR
298 SkDEBUGFAIL("ASTC decoding error!\n");
299 #endif
300
301 write_constant_color(dst, dstRowBytes, kASTCErrorColor);
302 }
303
304 // Reads an ASTC block from the given pointer.
305 static inline void read_astc_block(astcBlock_t *dst, const uint8_t* src) {
306 const uint64_t* qword = reinterpret_cast<const uint64_t*>(src);
307 dst->low = SkEndian_SwapLE64(qword[0]);
308 dst->high = SkEndian_SwapLE64(qword[1]);
309 }
310
311 // Reads up to 64 bits of the ASTC block starting from bit
312 // 'from' and going up to but not including bit 'to'. 'from' starts
313 // counting from the LSB, counting up to the MSB. Returns -1 on
314 // error.
315 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.
316 const int nBits = to - from;
317 if (0 == nBits) {
318 return 0;
319 }
320
321 if (nBits < 0 || 64 <= nBits) {
322 SkDEBUGFAIL("ASTC -- shouldn't read more than 64 bits");
323 return -1;
324 }
325
robertphillips 2014/08/05 15:29:03 I don't think we need these two asserts.
krajcevski 2014/08/05 20:55:07 Done.
326 SkASSERT(to > from);
327 SkASSERT(nBits > 0);
328
329 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.
330 if (to <= 64) {
robertphillips 2014/08/05 15:29:02 // All desired bits are in low ?
krajcevski 2014/08/05 20:55:08 Done.
331 result = (block.low >> from) & ((1ULL << nBits) - 1);
332 } 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.
333 result = (block.high >> (from - 64)) & ((1ULL << nBits) - 1);
334 } else {
335 // from < 64 && to > 64
336 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.
337 const int nHigh = nBits - (64 - from);
338 const int nLow = nBits - nHigh;
339 SkASSERT(nLow + nHigh == nBits);
340 result =
341 ((block.low >> from) & ((1ULL << nLow) - 1)) |
342 ((block.high & ((1ULL << nHigh) - 1)) << nLow);
343 }
344
345 return result;
346 }
347
348 // Reverse 64-bit integer taken from TAOCP 4a, although it's better
349 // documented at this site:
350 // http://matthewarcus.wordpress.com/2012/11/18/reversing-a-64-bit-word/
351
352 template <typename T, T m, int k>
353 static inline T swap_bits(T p) {
354 T q = ((p>>k)^p) & m;
355 return p^q^(q<<k);
356 }
357
358 static inline uint64_t reverse64(uint64_t n) {
359 static const uint64_t m0 = 0x5555555555555555LLU;
360 static const uint64_t m1 = 0x0300c0303030c303LLU;
361 static const uint64_t m2 = 0x00c0300c03f0003fLLU;
362 static const uint64_t m3 = 0x00000ffc00003fffLLU;
363 n = ((n>>1)&m0) | (n&m0)<<1;
364 n = swap_bits<uint64_t, m1, 4>(n);
365 n = swap_bits<uint64_t, m2, 8>(n);
366 n = swap_bits<uint64_t, m3, 20>(n);
367 n = (n >> 34) | (n << 30);
368 return n;
369 }
370
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.
371 // Reverses the bits of an ASTC block, making the LSB of the
372 // 128 bit block the MSB.
robertphillips 2014/08/05 15:29:03 x -> block ?
krajcevski 2014/08/05 20:55:05 Done.
373 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.
374 const astcBlock_t b = { reverse64(x->high), reverse64(x->low) };
375 x->high = b.high;
376 x->low = b.low;
377 }
378
robertphillips 2014/08/05 15:29:02 SkIsPow2 in SkMath.h ?
krajcevski 2014/08/05 20:55:06 Done.
379 // Helper function that returns true if x is a power of two.
380 static inline bool is_power_of_two(int x) {
381 return (x & (x - 1)) == 0;
382 }
383
robertphillips 2014/08/05 15:29:04 Could SkNextLog2 serve here ?
krajcevski 2014/08/05 20:55:08 Done.
384 // Returns the number of bits needed to represent a number
385 // in the given power-of-two range.
386 static inline int bits_for_range(int x) {
387 SkASSERT(is_power_of_two(x));
388 SkASSERT(0 != x);
389
390 int cnt = 0;
391 while (x >>= 1) {
392 cnt++;
393 }
394
395 return cnt;
396 }
397
robertphillips 2014/08/05 15:29:02 Use SkClampMax ?
krajcevski 2014/08/05 20:55:09 Done.
398 // Clamps an integer to the range [0, 255]
399 static inline int clamp_byte(int x) {
400 return (x > 255)? 255 : ((x < 0)? 0 : x);
401 }
402
403 // 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.
404 static inline void bit_transfer_signed(int *a, int *b) {
405 *b >>= 1;
406 *b |= *a & 0x80;
407 *a >>= 1;
408 *a &= 0x3F;
409 if ( (*a & 0x20) != 0 ) {
410 *a -= 0x40;
411 }
412 }
413
414 // 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.
415 static inline SkColor blue_contract(int a, int r, int g, int b) {
416 return SkColorSetARGB(a, (r + b) >> 1, (g + b) >> 1, b);
417 }
418
419 // A helper class used to decode bit values from standard integer values.
420 // We can't use this class with astcBlock_t because then it would need to
421 // handle multi-value ranges, and it's non-trivial to lookup a range of bits
422 // that splits across two different ints.
423 template <typename T>
424 class SkTBits {
425 public:
426 SkTBits(const T val) : fVal(val) { }
427
428 // Returns the bit at the given position
429 T operator [](const int idx) const {
430 return (fVal >> idx) & 1;
431 }
432
433 // 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.
434 T operator ()(const int end, const int start) const {
435 SkASSERT(end >= start);
436 return (fVal >> start) & ((1ULL << ((end - start) + 1)) - 1);
437 }
438
439 private:
440 const T fVal;
441 };
442
443 // This algorithm matches the trit block decoding in the spec (Table C.2.14)
444 static void decode_trit_block(int* dst, int nBits, const uint64_t &block) {
445
446 SkTBits<uint64_t> blockBits(block);
447
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.
448 int m[5];
449 if (0 == nBits) {
450 memset(m, 0, sizeof(m));
451 } else {
robertphillips 2014/08/05 15:29:02 // The trits are arranged ...
krajcevski 2014/08/05 20:55:06 Done.
452 m[0] = blockBits(nBits - 1, 0);
453 m[1] = blockBits(2*nBits - 1 + 2, nBits + 2);
454 m[2] = blockBits(3*nBits - 1 + 4, 2*nBits + 4);
455 m[3] = blockBits(4*nBits - 1 + 5, 3*nBits + 5);
456 m[4] = blockBits(5*nBits - 1 + 7, 4*nBits + 7);
457 }
458
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
459 int T =
460 blockBits(nBits + 1, nBits) |
461 (blockBits(2*nBits + 2 + 1, 2*nBits + 2) << 2) |
462 (blockBits[3*nBits + 4] << 4) |
463 (blockBits(4*nBits + 5 + 1, 4*nBits + 5) << 5) |
464 (blockBits[5*nBits + 7] << 7);
465
466 int t[5];
467
468 int C;
469 SkTBits<int> Tbits(T);
470 if (0x7 == Tbits(4, 2)) {
471 C = (Tbits(7, 5) << 2) | Tbits(1, 0);
472 t[3] = t[4] = 2;
473 } else {
474 C = Tbits(4, 0);
475 if (Tbits(6, 5) == 0x3) {
476 t[4] = 2; t[3] = Tbits[7];
477 } else {
478 t[4] = Tbits[7]; t[3] = Tbits(6, 5);
479 }
480 }
481
482 SkTBits<int> Cbits(C);
483 if (Cbits(1, 0) == 0x3) {
484 t[2] = 2;
485 t[1] = Cbits[4];
486 t[0] = (Cbits[3] << 1) | (Cbits[2] & (0x1 & ~(Cbits[3])));
487 } else if (Cbits(3, 2) == 0x3) {
488 t[2] = 2;
489 t[1] = 2;
490 t[0] = Cbits(1, 0);
491 } else {
492 t[2] = Cbits[4];
493 t[1] = Cbits(3, 2);
494 t[0] = (Cbits[1] << 1) | (Cbits[0] & (0x1 & ~(Cbits[1])));
495 }
496
497 #if SK_DEBUG
498 for (int i = 0; i < 5; ++i) {
499 SkASSERT(t[i] < 3);
500 SkASSERT(m[i] < (1 << nBits));
501 }
502 #endif
503
504 for (int i = 0; i < 5; ++i) {
505 *dst = (t[i] << nBits) + m[i];
506 ++dst;
507 }
508 }
509
robertphillips 2014/08/05 15:29:02 trit -> quint ?
krajcevski 2014/08/05 20:55:05 Done.
510 // This algorithm matches the trit block decoding in the spec (Table C.2.15)
511 static void decode_quint_block(int* dst, int nBits, const uint64_t &block) {
512 SkTBits<uint64_t> blockBits(block);
513
514 int m[3];
515 if (0 == nBits) {
516 memset(m, 0, sizeof(m));
517 } else {
robertphillips 2014/08/05 15:29:02 // The quints are arranged ...
krajcevski 2014/08/05 20:55:08 Done.
518 m[0] = blockBits(nBits - 1, 0);
519 m[1] = blockBits(2*nBits - 1 + 3, nBits + 3);
520 m[2] = blockBits(3*nBits - 1 + 5, 2*nBits + 5);
521 }
522
robertphillips 2014/08/05 15:29:03 Blarg
krajcevski 2014/08/05 20:55:07 Blarg. :(
523 int Q =
524 blockBits(nBits + 2, nBits) |
525 (blockBits(2*nBits + 3 + 1, 2*nBits + 3) << 3) |
526 (blockBits(3*nBits + 5 + 1, 3*nBits + 5) << 5);
527
528 int q[3];
529 SkTBits<int> Qbits(Q); // quantum?
530
531 if (Qbits(2, 1) == 0x3 && Qbits(6, 5) == 0) {
532 const int notBitZero = (0x1 & ~(Qbits[0]));
533 q[2] = (Qbits[0] << 2) | ((Qbits[4] & notBitZero) << 1) | (Qbits[3] & no tBitZero);
534 q[1] = 4;
535 q[0] = 4;
536 } else {
537 int C;
538 if (Qbits(2, 1) == 0x3) {
539 q[2] = 4;
540 C = (Qbits(4, 3) << 3) | ((0x3 & ~(Qbits(6, 5))) << 1) | Qbits[0];
541 } else {
542 q[2] = Qbits(6, 5);
543 C = Qbits(4, 0);
544 }
545
546 SkTBits<int> Cbits(C);
547 if (Cbits(2, 0) == 0x5) {
548 q[1] = 4;
549 q[0] = Cbits(4, 3);
550 } else {
551 q[1] = Cbits(4, 3);
552 q[0] = Cbits(2, 0);
553 }
554 }
555
556 #if SK_DEBUG
557 for (int i = 0; i < 3; ++i) {
558 SkASSERT(q[i] < 5);
559 SkASSERT(m[i] < (1 << nBits));
560 }
561 #endif
562
563 for (int i = 0; i < 3; ++i) {
564 *dst = (q[i] << nBits) + m[i];
565 ++dst;
566 }
567 }
568
569 // Function that decodes a sequence of integers stored as an ISE (Integer
570 // Sequence Encoding) bit stream. The full details of this function are outlined
571 // in section C.2.12 of the ASTC spec. A brief overview is as follows:
572 //
573 // - Each integer in the sequence is bounded by a specific range r.
574 // - The range of each value determines the way the bit stream is interpreted,
575 // - If the range is a power of two, then the sequence is a sequence of bits
576 // - If the range is of the form 3*2^n, then the sequence is stored as a
577 // sequence of blocks, each block contains 5 trits and 5 bit sequences, which
578 // decodes into 5 values.
579 // - Similarly, if the range is of the form 5*2^n, then the sequence is stored a s a
robertphillips 2014/08/05 15:29:02 trits -> quints ?
krajcevski 2014/08/05 20:55:06 Done.
580 // 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.
581 // decodes into 5 values.
582 static bool decode_integer_sequence(
583 int* dst, // The array holding the destination bits
584 int dstSize, // The maximum size of the array
585 int nVals, // The number of values that we'd like to decode
586 const astcBlock_t &block, // The block that we're decoding from
587 int startBit, // The bit from which we're going to do the readin g
robertphillips 2014/08/05 15:29:03 inclusive or not ?
krajcevski 2014/08/05 20:55:08 Done.
588 int endBit, // The bit at which we stop reading
589 bool bReadForward, // If true, then read LSB -> MSB, else read MSB -> LSB
590 int nBits, // The number of bits representing this encoding
591 int nTrits, // The number of trits representing this encoding
592 int nQuints // The number of quints representing this encoding
593 ) {
594 // If we want more values than we have, then fail.
595 if (nVals > dstSize) {
596 return false;
597 }
598
599 astcBlock_t src = block;
600
601 if (!bReadForward) {
602 reverse_block(&src);
603 startBit = 128 - startBit;
604 endBit = 128 - endBit;
605 }
606
607 while (nVals > 0) {
608
609 if (nTrits > 0) {
610 SkASSERT(0 == nQuints);
611
612 int endBlockBit = startBit + 8 + 5*nBits;
613 if (endBlockBit > endBit) {
614 endBlockBit = endBit;
615 }
616
617 decode_trit_block(dst, nBits, read_astc_bits(src, startBit, endBlock Bit));
618 dst += 5;
619 nVals -= 5;
620 startBit = endBlockBit;
621
622 } else if (nQuints > 0) {
623 SkASSERT(0 == nTrits);
624
625 int endBlockBit = startBit + 7 + 3*nBits;
626 if (endBlockBit > endBit) {
627 endBlockBit = endBit;
628 }
629
630 decode_quint_block(dst, nBits, read_astc_bits(src, startBit, endBloc kBit));
631 dst += 3;
632 nVals -= 3;
633 startBit = endBlockBit;
634
635 } else {
636 // Just read the bits, but don't read more than we have...
637 int endValBit = startBit + nBits;
638 if (endValBit > endBit) {
639 endValBit = endBit;
640 }
641
642 *dst = read_astc_bits(src, startBit, endValBit);
643 ++dst;
644 --nVals;
645 startBit = endValBit;
646 }
647 }
648
649 return true;
650 }
651
652 // Helper function that unquantizes some (seemingly random) generated
653 // 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.
654 // 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.
655 template<unsigned mask>
656 static inline int unquantize_value(int A, int B, int C, int D) {
657 int T = D * C + B;
658 T = T ^ A;
659 T = (A & mask) | (T >> 2);
660 SkASSERT(T < 256);
661 return T;
662 }
663
robertphillips 2014/08/05 15:29:04 replicated -> replicate ?
krajcevski 2014/08/05 20:55:05 Done.
664 // Helper function to replicated the bits in x that represents an oldPrec
665 // precision integer into a prec precision integer. For example:
666 // 255 == replicate_bits(7, 3, 8);
667 static inline int replicate_bits(int x, int oldPrec, int prec) {
668 while (oldPrec < prec) {
669 const int toShift = SkMin32(prec-oldPrec, oldPrec);
670 x = (x << toShift) | (x >> (oldPrec - toShift));
671 oldPrec += toShift;
672 }
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.
673 return x;
674 }
675
676 // Returns the unquantized value of a color that's represented only as
677 // a set of bits.
678 static inline int unquantize_bits_color(int val, int nBits) {
679 return replicate_bits(val, nBits, 8);
680 }
681
682 // Returns the unquantized value of a color that's represented as a
683 // trit followed by nBits bits. This algorithm follows the sequence
684 // defined in section C.2.13 of the ASTC spec.
685 static inline int unquantize_trit_color(int val, int nBits) {
686 const int D = (val >> nBits) & 0x3;
687 SkASSERT(D < 3);
688
689 const int A = -(val & 0x1) & 0x1FF;
690
691 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.
692 SkASSERT(nBits > 0);
693 SkASSERT(nBits < 7);
694
695 const int C = Cvals[nBits - 1];
696
697 int B = 0;
698 const SkTBits<int> valBits(val);
699 switch (nBits) {
700 case 1:
701 B = 0;
702 break;
703
robertphillips 2014/08/05 17:30:00 In Skia this is formatted as: case kGaussian_
krajcevski 2014/08/05 20:55:06 Done.
704 case 2:
705 {
706 const int b = valBits[1];
707 B = (b << 1) | (b << 2) | (b << 4) | (b << 8);
708 }
709 break;
710
711 case 3:
712 {
713 const int cb = valBits(2, 1);
714 B = cb | (cb << 2) | (cb << 7);
715 }
716 break;
717
718 case 4:
719 {
720 const int dcb = valBits(3, 1);
721 B = dcb | (dcb << 6);
722 }
723 break;
724
725 case 5:
726 {
727 const int edcb = valBits(4, 1);
728 B = (edcb << 5) | (edcb >> 2);
729 }
730 break;
731
732 case 6:
733 {
734 const int fedcb = valBits(5, 1);
735 B = (fedcb << 4) | (fedcb >> 4);
736 }
737 break;
738 }
739
740 return unquantize_value<0x80>(A, B, C, D);
741 }
742
743 // Returns the unquantized value of a color that's represented as a
744 // quint followed by nBits bits. This algorithm follows the sequence
745 // defined in section C.2.13 of the ASTC spec.
746 static inline int unquantize_quint_color(int val, int nBits) {
747 const int D = (val >> nBits) & 0x7;
748 SkASSERT(D < 5);
749
750 const int A = -(val & 0x1) & 0x1FF;
751
752 static const int Cvals[5] = { 113, 54, 26, 13, 6 };
753 SkASSERT(nBits > 0);
754 SkASSERT(nBits < 6);
755
756 const int C = Cvals[nBits - 1];
757
758 int B = 0;
759 const SkTBits<int> valBits(val);
760 switch (nBits) {
761 case 1:
762 B = 0;
763 break;
764
765 case 2:
766 {
767 const int b = valBits[1];
768 B = (b << 2) | (b << 3) | (b << 8);
769 }
770 break;
771
772 case 3:
773 {
774 const int cb = valBits(2, 1);
775 B = (cb >> 1) | (cb << 1) | (cb << 7);
776 }
777 break;
778
779 case 4:
780 {
781 const int dcb = valBits(3, 1);
782 B = (dcb >> 1) | (dcb << 6);
783 }
784 break;
785
786 case 5:
787 {
788 const int edcb = valBits(4, 1);
789 B = (edcb << 5) | (edcb >> 3);
790 }
791 break;
792 }
793
794 return unquantize_value<0x80>(A, B, C, D);
795 }
796
797 // This algorithm takes a list of integers, stored in vals, and unquantizes them
798 // in place. This follows the algorithm laid out in section C.2.13 of the ASTC s pec.
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
799 static void unquantize_colors(int *vals, int nVals, int nBits, int nTrits, int n Quints) {
800 for (int i = 0; i < nVals; ++i) {
801 if (nTrits > 0) {
802 SkASSERT(nQuints == 0);
robertphillips 2014/08/05 17:30:00 extra space after ',' ?
krajcevski 2014/08/05 20:55:07 Done.
803 vals[i] = unquantize_trit_color(vals[i], nBits);
804 } else if (nQuints > 0) {
805 SkASSERT(nTrits == 0);
806 vals[i] = unquantize_quint_color(vals[i], nBits);
807 } else {
808 SkASSERT(nQuints == 0 && nTrits == 0);
809 vals[i] = unquantize_bits_color(vals[i], nBits);
810 }
811 }
812 }
813
814 // Returns an interpolated value between c0 and c1 based on the weight. This
815 // follows the algorithm laid out in section C.2.19 of the ASTC spec.
816 static int interpolate_channel(int c0, int c1, int weight) {
817 SkASSERT(0 <= c0 && c0 < 256);
818 SkASSERT(0 <= c1 && c1 < 256);
819
820 c0 = (c0 << 8) | c0;
821 c1 = (c1 << 8) | c1;
822
823 const int result = ((c0*(64 - weight) + c1*weight + 32) / 64) >> 8;
824
825 if (result > 255) {
826 return 255;
827 }
828
829 SkASSERT(result >= 0);
830 return result;
831 }
832
833 // Returns an interpolated color between the two endpoints based on the weight.
834 static SkColor interpolate_endpoints(const SkColor endpoints[2], int weight) {
835 return SkColorSetARGB(
836 interpolate_channel(SkColorGetA(endpoints[0]), SkColorGetA(endpoints[1]) , weight),
837 interpolate_channel(SkColorGetR(endpoints[0]), SkColorGetR(endpoints[1]) , weight),
838 interpolate_channel(SkColorGetG(endpoints[0]), SkColorGetG(endpoints[1]) , weight),
839 interpolate_channel(SkColorGetB(endpoints[0]), SkColorGetB(endpoints[1]) , weight));
840 }
841
842 // Returns an interpolated color between the two endpoints based on the weight.
843 // It uses separate weights for the channel depending on the value of the 'plane '
844 // variable. By default, all channels will use weight 0, and the value of plane
845 // means that weight1 will be used for:
846 // 0: red
847 // 1: green
848 // 2: blue
849 // 3: alpha
850 static SkColor interpolate_dual_endpoints(
851 const SkColor endpoints[2], int weight0, int weight1, int plane) {
852 int a = interpolate_channel(SkColorGetA(endpoints[0]), SkColorGetA(endpoints [1]), weight0);
853 int r = interpolate_channel(SkColorGetR(endpoints[0]), SkColorGetR(endpoints [1]), weight0);
854 int g = interpolate_channel(SkColorGetG(endpoints[0]), SkColorGetG(endpoints [1]), weight0);
855 int b = interpolate_channel(SkColorGetB(endpoints[0]), SkColorGetB(endpoints [1]), weight0);
856
857 switch (plane) {
858
859 case 0:
860 r = interpolate_channel(
861 SkColorGetR(endpoints[0]), SkColorGetR(endpoints[1]), weight1);
862 break;
863
864 case 1:
865 g = interpolate_channel(
866 SkColorGetG(endpoints[0]), SkColorGetG(endpoints[1]), weight1);
867 break;
868
869 case 2:
870 b = interpolate_channel(
871 SkColorGetB(endpoints[0]), SkColorGetB(endpoints[1]), weight1);
872 break;
873
874 case 3:
875 a = interpolate_channel(
876 SkColorGetA(endpoints[0]), SkColorGetA(endpoints[1]), weight1);
877 break;
878
879 default:
880 SkDEBUGFAIL("Plane should be 0-3");
881 break;
882 }
883
884 return SkColorSetARGB(a, r, g, b);
885 }
886
887 // A struct of decoded values that we use to carry around information
888 // about the block. dimX and dimY are the dimension in texels of the block,
889 // 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.
890 struct ASTCDecompressionData {
891 ASTCDecompressionData(int dimX, int dimY) : fDimX(dimX), fDimY(dimY) { }
892 const int fDimX; // the X dimension of the decompressed block
893 const int fDimY; // the Y dimension of the decompressed block
894 astcBlock_t fBlock; // the block data
895 int fBlockMode; // the block header that contains the block mode.
896
897 bool fDualPlaneEnabled; // is this block compressing dual weight planes?
898 int fDualPlane; // the independent plane in dual plane mode.
899
900 bool fVoidExtent; // is this block a single color?
901 bool fError; // does this block have an error encoding?
902
903 int fWeightDimX; // the x dimension of the weight grid
904 int fWeightDimY; // the y dimension of the weight grid
905
906 int fWeightBits; // the number of bits used for each weight value
907 int fWeightTrits; // the number of trits used for each weight value
908 int fWeightQuints; // the number of quints used for each weight value
909
910 int fPartCount; // the number of partitions in this block
911 int fPartIndex; // the partition index: only relevant if fPartCount > 0
912
913 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.
914 int fCEM[kMaxPartitions]; // the color endpoint modes for this block.
915
916 int fColorStartBit; // The bit position of the first bit of the color da ta
917 int fColorEndBit; // The bit position of the last *possible* bit of th e color data
918
919 // Returns the number of partitions for this block.
920 int numPartitions() const {
921 return fPartCount;
922 }
923
924 // Returns the total number of weight values that are stored in this block
925 int numWeights() const {
robertphillips 2014/08/05 17:30:01 Add a space before the '?' ?
krajcevski 2014/08/05 20:55:07 Done.
926 return fWeightDimX * fWeightDimY * (fDualPlaneEnabled? 2 : 1);
927 }
928
robertphillips 2014/08/05 17:30:00 #ifdef SK_DEBUG ?
krajcevski 2014/08/05 20:55:07 Done.
929 // Returns the maximum value that any weight can take. We really only use
930 // this function for debugging.
931 int maxWeightValue() const {
932 int maxVal = (1 << fWeightBits);
933 if (fWeightTrits > 0) {
934 SkASSERT(0 == fWeightQuints);
935 maxVal *= 3;
936 } else if (fWeightQuints > 0) {
937 SkASSERT(0 == fWeightTrits);
938 maxVal *= 5;
939 }
940 return maxVal - 1;
941 }
robertphillips 2014/08/05 17:30:00 #endif ?
krajcevski 2014/08/05 20:55:05 Done.
942
943 // The number of bits needed to represent the texel weight data. This
944 // comes from the 'data size determination' section of the ASTC spec (C.2.22 )
945 int numWeightBits() const {
946 const int nWeights = this->numWeights();
947 return
948 ((nWeights*8*fWeightTrits + 4) / 5) +
949 ((nWeights*7*fWeightQuints + 2) / 3) +
950 (nWeights*fWeightBits);
951 }
952
953 // Returns the number of color values stored in this block. The number of
954 // values stored is directly a function of the color endpoint modes.
955 int numColorValues() const {
956 int numValues = 0;
957 for (int i = 0; i < this->numPartitions(); ++i) {
958 numValues += ((fCEM[i] >> 2) + 1) * 2;
959 }
960
961 return numValues;
962 }
963
964 // Figures out the number of bits available for color values, and fills
965 // in the maximum encoding that will fit the number of color values that
966 // we need. Returns false on error. (See section C.2.22 of the spec)
967 bool getColorValueEncoding(int *nBits, int *nTrits, int *nQuints) const {
968 if (NULL == nBits || NULL == nTrits || NULL == nQuints) {
969 return false;
970 }
971
972 const int nColorVals = this->numColorValues();
973 if (nColorVals <= 0) {
974 return false;
975 }
976
977 const int colorBits = fColorEndBit - fColorStartBit;
978 SkASSERT(colorBits > 0);
979
980 // This is the minimum amount of accuracy required by the spec.
981 if (colorBits < ((13 * nColorVals + 4) / 5)) {
982 return false;
983 }
984
985 // Values can be represented as at most 8-bit values.
986 // !SPEED! place this in a lookup table based on colorBits and nColorVal s
987 for (int i = 255; i > 0; --i) {
988 int range = i + 1;
989 int bits = 0, trits = 0, quints = 0;
990 bool valid = false;
991 if (is_power_of_two(range)) {
992 bits = bits_for_range(range);
993 valid = true;
994 } else if ((range % 3) == 0 && is_power_of_two(range/3)) {
995 trits = 1;
996 bits = bits_for_range(range/3);
997 valid = true;
998 } else if ((range % 5) == 0 && is_power_of_two(range/5)) {
999 quints = 1;
1000 bits = bits_for_range(range/5);
1001 valid = true;
1002 }
1003
1004 if (valid) {
1005 const int actualColorBits =
1006 ((nColorVals*8*trits + 4) / 5) +
1007 ((nColorVals*7*quints + 2) / 3) +
1008 (nColorVals*bits);
1009 if (actualColorBits <= colorBits) {
1010 *nTrits = trits;
1011 *nQuints = quints;
1012 *nBits = bits;
1013 return true;
1014 }
1015 }
1016 }
1017
1018 return false;
1019 }
1020
1021 // Converts the sequence of color values into endpoints. The algorithm here
1022 // corresponds to the values determined by section C.2.14 of the ASTC spec
1023 void colorEndpoints(SkColor endpoints[4][2], const int* colorValues) const {
1024 for (int i = 0; i < this->numPartitions(); ++i) {
1025 switch (fCEM[i]) {
1026 // 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.
1027 case 0:
robertphillips 2014/08/05 17:30:00 formatting ?
krajcevski 2014/08/05 20:55:06 Done.
1028 {
1029 const int* v = colorValues;
1030 endpoints[i][0] = SkColorSetARGB(0xFF, v[0], v[0], v[0]);
1031 endpoints[i][1] = SkColorSetARGB(0xFF, v[1], v[1], v[1]);
1032
1033 colorValues += 2;
1034 }
1035 break;
1036
1037 // LDR Luminance, base+offset
1038 case 1:
1039 {
1040 const int* v = colorValues;
1041 const int L0 = (v[0] >> 2) | (v[1] & 0xC0);
1042 const int L1 = clamp_byte(L0 + (v[1] & 0x3F));
1043
1044 endpoints[i][0] = SkColorSetARGB(0xFF, L0, L0, L0);
1045 endpoints[i][1] = SkColorSetARGB(0xFF, L1, L1, L1);
1046
1047 colorValues += 2;
1048 }
1049 break;
1050
1051 // LDR Luminance + Alpha, direct
1052 case 4:
1053 {
1054 const int* v = colorValues;
1055
1056 endpoints[i][0] = SkColorSetARGB(v[2], v[0], v[0], v[0]);
1057 endpoints[i][1] = SkColorSetARGB(v[3], v[1], v[1], v[1]);
1058
1059 colorValues += 4;
1060 }
1061 break;
1062
1063 // LDR Luminance + Alpha, base+offset
1064 case 5:
1065 {
1066 int v0 = colorValues[0];
1067 int v1 = colorValues[1];
1068 int v2 = colorValues[2];
1069 int v3 = colorValues[3];
1070
1071 bit_transfer_signed(&v1, &v0);
1072 bit_transfer_signed(&v3, &v2);
1073
1074 endpoints[i][0] = SkColorSetARGB(v2, v0, v0, v0);
1075 endpoints[i][1] = SkColorSetARGB(
1076 clamp_byte(v3+v2),
1077 clamp_byte(v1+v0),
1078 clamp_byte(v1+v0),
1079 clamp_byte(v1+v0));
1080
1081 colorValues += 4;
1082 }
1083 break;
1084
1085 // LDR RGB, base+scale
1086 case 6:
1087 {
1088 const int* v = colorValues;
1089
1090 endpoints[i][0] = SkColorSetARGB(
1091 0xFF, v[0]*v[3] >> 8, v[1]*v[3] >> 8, v[2]*v[3] >> 8);
1092 endpoints[i][1] = SkColorSetARGB(0xFF, v[0], v[1], v[2]);
1093
1094 colorValues += 4;
1095 }
1096 break;
1097
1098 // LDR RGB, direct
1099 case 8:
robertphillips 2014/08/05 17:30:00 Could 8 & 12 share a sub-routine ?
krajcevski 2014/08/05 20:55:07 Done.
1100 {
1101 const int* v = colorValues;
1102
1103 const int s0 = v[0] + v[2] + v[4];
1104 const int s1 = v[1] + v[3] + v[5];
1105
1106 if (s1 >= s0) {
1107 endpoints[i][0] = SkColorSetARGB(0xFF, v[0], v[2], v[4]) ;
1108 endpoints[i][1] = SkColorSetARGB(0xFF, v[1], v[3], v[5]) ;
1109 } else {
1110 endpoints[i][0] = blue_contract(0xFF, v[1], v[3], v[5]);
1111 endpoints[i][1] = blue_contract(0xFF, v[0], v[2], v[4]);
1112 }
1113
1114 colorValues += 6;
1115 }
1116 break;
1117
1118 // LDR RGB, base+offset
1119 case 9:
robertphillips 2014/08/05 17:30:00 Could 9 & 13 share a subroutine ?
krajcevski 2014/08/05 20:55:07 Done.
1120 {
1121 int v0 = colorValues[0];
1122 int v1 = colorValues[1];
1123 int v2 = colorValues[2];
1124 int v3 = colorValues[3];
1125 int v4 = colorValues[4];
1126 int v5 = colorValues[5];
1127
1128 bit_transfer_signed(&v1, &v0);
1129 bit_transfer_signed(&v3, &v2);
1130 bit_transfer_signed(&v5, &v4);
1131
1132 int c[2][4];
1133 if (v1 + v3 + v5 >= 0) {
1134 c[0][0] = 0xFF;
1135 c[0][1] = v0;
1136 c[0][2] = v2;
1137 c[0][3] = v4;
1138
1139 c[1][0] = 0xFF;
1140 c[1][1] = v0 + v1;
1141 c[1][2] = v2 + v3;
1142 c[1][3] = v4 + v5;
1143 } else {
1144 c[0][0] = 0xFF;
1145 c[0][1] = (v0 + v1 + v4 + v5) >> 1;
1146 c[0][2] = (v2 + v3 + v4 + v5) >> 1;
1147 c[0][3] = v4 + v5;
1148
1149 c[1][0] = 0xFF;
1150 c[1][1] = (v0 + v4) >> 1;
1151 c[1][2] = (v2 + v4) >> 1;
1152 c[1][3] = v4;
1153 }
1154
1155 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. :)
1156 clamp_byte(c[0][0]),
1157 clamp_byte(c[0][1]),
1158 clamp_byte(c[0][2]),
1159 clamp_byte(c[0][3]));
1160
1161 endpoints[i][1] = SkColorSetARGB(
1162 clamp_byte(c[1][0]),
1163 clamp_byte(c[1][1]),
1164 clamp_byte(c[1][2]),
1165 clamp_byte(c[1][3]));
1166
1167 colorValues += 6;
1168 }
1169 break;
1170
1171 // LDR RGBA, base+scale and two alpha
1172 case 10:
1173 {
1174 const int* v = colorValues;
1175
1176 endpoints[i][0] = SkColorSetARGB(v[4],
1177 (v[0]*v[3]) >> 8,
1178 (v[1]*v[3]) >> 8,
1179 (v[2]*v[3]) >> 8);
1180 endpoints[i][1] = SkColorSetARGB(v[5], v[0], v[1], v[2]);
1181
1182 colorValues += 6;
1183 }
1184 break;
1185
1186 // LDR RGBA, direct
1187 case 12:
1188 {
1189 const int* v = colorValues;
1190
1191 if ((v[0] + v[2] + v[4]) >= (v[1] + v[3] + v[5])) {
1192 endpoints[i][0] = SkColorSetARGB(v[6], v[0], v[2], v[4]) ;
1193 endpoints[i][1] = SkColorSetARGB(v[7], v[1], v[3], v[5]) ;
1194 } else {
1195 endpoints[i][0] = blue_contract(v[7], v[1], v[3], v[5]);
1196 endpoints[i][1] = blue_contract(v[6], v[0], v[2], v[4]);
1197 }
1198
1199 colorValues += 8;
1200 }
1201 break;
1202
1203 // LDR RGBA, base+offset
1204 case 13:
1205 {
1206 int v0 = colorValues[0];
1207 int v1 = colorValues[1];
1208 int v2 = colorValues[2];
1209 int v3 = colorValues[3];
1210 int v4 = colorValues[4];
1211 int v5 = colorValues[5];
1212 int v6 = colorValues[6];
1213 int v7 = colorValues[7];
1214
1215 bit_transfer_signed(&v1, &v0);
1216 bit_transfer_signed(&v3, &v2);
1217 bit_transfer_signed(&v5, &v4);
1218 bit_transfer_signed(&v7, &v6);
1219
1220 int c[2][4];
1221 if ((v1 + v3 + v5) >= 0) {
1222 c[0][0] = v6;
1223 c[0][1] = v0;
1224 c[0][2] = v2;
1225 c[0][3] = v4;
1226
1227 c[1][0] = v6 + v7;
1228 c[1][1] = v0 + v1;
1229 c[1][2] = v2 + v3;
1230 c[1][3] = v4 + v5;
1231 } else {
1232 c[0][0] = v6 + v7;
1233 c[0][1] = (v0 + v1 + v4 + v5) >> 1;
1234 c[0][2] = (v2 + v3 + v4 + v5) >> 1;
1235 c[0][3] = v4 + v5;
1236
1237 c[1][0] = v6;
1238 c[1][1] = (v0 + v4) >> 1;
1239 c[1][2] = (v2 + v4) >> 1;
1240 c[1][3] = v4;
1241 }
1242
1243 endpoints[i][0] = SkColorSetARGB(
1244 clamp_byte(c[0][0]),
1245 clamp_byte(c[0][1]),
1246 clamp_byte(c[0][2]),
1247 clamp_byte(c[0][3]));
1248
1249 endpoints[i][1] = SkColorSetARGB(
1250 clamp_byte(c[1][0]),
1251 clamp_byte(c[1][1]),
1252 clamp_byte(c[1][2]),
1253 clamp_byte(c[1][3]));
1254
1255 colorValues += 8;
1256 }
1257 break;
1258
1259 default:
1260 SkDEBUGFAIL("HDR mode unsupported! This should be caught soo ner.");
1261 break;
1262 }
1263 }
1264 }
1265
1266 // Follows the procedure from section C.2.17 of the ASTC specification
1267 int unquantizeWeight(int x) const {
1268 SkASSERT(x <= this->maxWeightValue());
1269
1270 const int D = (x >> fWeightBits) & 0x7;
1271 const int A = -(x & 0x1) & 0x7F;
1272
1273 SkTBits<int> xbits(x);
1274
1275 int T = 0;
1276 if (fWeightTrits > 0) {
1277 SkASSERT(0 == fWeightQuints);
1278 switch (fWeightBits) {
1279 case 0:
1280 {
1281 // x is a single trit
1282 SkASSERT(x < 3);
1283
1284 static const int kUnquantizationTable[3] = { 0, 32, 63 };
1285 T = kUnquantizationTable[x];
1286 }
1287 break;
1288
1289 case 1:
1290 {
1291 const int B = 0;
1292 const int C = 50;
1293 T = unquantize_value<0x20>(A, B, C, D);
1294 }
1295 break;
1296
1297 case 2:
1298 {
1299 const int b = xbits[1];
1300 const int B = b | (b << 2) | (b << 6);
1301 const int C = 23;
1302 T = unquantize_value<0x20>(A, B, C, D);
1303 }
1304 break;
1305
1306 case 3:
1307 {
1308 const int cb = xbits(2, 1);
1309 const int B = cb | (cb << 5);
1310 const int C = 11;
1311 T = unquantize_value<0x20>(A, B, C, D);
1312 }
1313 break;
1314
1315 default:
1316 SkDEBUGFAIL("Too many bits for trit encoding");
1317 break;
1318 }
1319
1320 } else if (fWeightQuints > 0) {
1321 SkASSERT(0 == fWeightTrits);
1322 switch (fWeightBits) {
1323 case 0:
1324 {
1325 // x is a single quint
1326 SkASSERT(x < 5);
1327
1328 static const int kUnquantizationTable[5] = { 0, 16, 32, 47, 63 };
1329 T = kUnquantizationTable[x];
1330 }
1331 break;
1332
1333 case 1:
1334 {
1335 const int B = 0;
1336 const int C = 28;
1337 T = unquantize_value<0x20>(A, B, C, D);
1338 }
1339 break;
1340
1341 case 2:
1342 {
1343 const int b = xbits[1];
1344 const int B = (b << 1) | (b << 6);
1345 const int C = 13;
1346 T = unquantize_value<0x20>(A, B, C, D);
1347 }
1348 break;
1349
1350 default:
1351 SkDEBUGFAIL("Too many bits for quint encoding");
1352 break;
1353 }
1354 } else {
1355 SkASSERT(0 == fWeightTrits);
1356 SkASSERT(0 == fWeightQuints);
1357
1358 T = replicate_bits(x, fWeightBits, 6);
1359 }
1360
1361 // This should bring the value within [0, 63]..
1362 SkASSERT(T <= 63);
1363
1364 if (T > 32) {
1365 T += 1;
1366 }
1367
1368 SkASSERT(T <= 64);
1369
1370 return T;
1371 }
1372
1373 // Returns the weight at the associated index. If the index is out of bounds , it
1374 // returns zero. It also chooses the weight appropriately based on the given dual
1375 // plane.
1376 int getWeight(const int* unquantizedWeights, int idx, bool dualPlane) const {
1377 const int maxIdx = ((fDualPlaneEnabled)? 2 : 1) * fWeightDimX * fWeightD imY - 1;
1378 if (fDualPlaneEnabled) {
robertphillips 2014/08/05 17:30:01 space before '?' ?
krajcevski 2014/08/05 20:55:05 Done.
1379 const int effectiveIdx = 2*idx + (dualPlane? 1 : 0);
1380 if (effectiveIdx > maxIdx) {
1381 return 0;
1382 }
1383 return unquantizedWeights[effectiveIdx];
1384 }
1385
1386 SkASSERT(!dualPlane);
1387
1388 if (idx > maxIdx) {
1389 return 0;
1390 } else {
1391 return unquantizedWeights[idx];
1392 }
1393 }
1394
1395 // This computes the effective weight at location (s, t) of the block. This
1396 // weight is computed by sampling the texel weight grid (it's usually not 1- 1), and
1397 // then applying a bilerp. The algorithm outlined here follows the algorithm
1398 // defined in section C.2.18 of the ASTC spec.
1399 int infillWeight(const int* unquantizedValues, int s, int t, bool dualPlane) const {
1400 const int Ds = (1024 + fDimX/2) / (fDimX - 1);
1401 const int Dt = (1024 + fDimY/2) / (fDimY - 1);
1402
1403 const int cs = Ds * s;
1404 const int ct = Dt * t;
1405
1406 const int gs = (cs*(fWeightDimX - 1) + 32) >> 6;
1407 const int gt = (ct*(fWeightDimY - 1) + 32) >> 6;
1408
1409 const int js = gs >> 4;
1410 const int jt = gt >> 4;
1411
1412 const int fs = gs & 0xF;
1413 const int ft = gt & 0xF;
1414
1415 const int idx = js + jt*fWeightDimX;
1416 const int p00 = this->getWeight(unquantizedValues, idx, dualPlane);
1417 const int p01 = this->getWeight(unquantizedValues, idx + 1, dualPlane);
1418 const int p10 = this->getWeight(unquantizedValues, idx + fWeightDimX, du alPlane);
1419 const int p11 = this->getWeight(unquantizedValues, idx + fWeightDimX + 1 , dualPlane);
1420
1421 const int w11 = (fs*ft + 8) >> 4;
1422 const int w10 = ft - w11;
1423 const int w01 = fs - w11;
1424 const int w00 = 16 - fs - ft + w11;
1425
1426 const int weight = (p00*w00 + p01*w01 + p10*w10 + p11*w11 + 8) >> 4;
1427 SkASSERT(weight <= 64);
1428 return weight;
1429 }
1430
1431 // Unquantizes the decoded texel weights as described in section C.2.17 of
1432 // the ASTC specification. Additionally, it populates texelWeights with
1433 // the expanded weight grid, which is computed according to section C.2.18
1434 void texelWeights(int texelWeights[2][12][12], const int* texelValues) const {
1435 // Unquantized texel weights...
1436 int unquantizedValues[144*2]; // 12x12 blocks with dual plane decoding.. .
1437 SkASSERT(this->numWeights() <= 144*2);
1438
1439 // Unquantize the weights and cache them
1440 for (int j = 0; j < this->numWeights(); ++j) {
1441 unquantizedValues[j] = this->unquantizeWeight(texelValues[j]);
1442 }
1443
1444 // 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
1445 for (int t = 0; t < fDimY; ++t) {
1446 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.
1447 for (int i = 0; i < 2; ++i) {
1448 bool bUseDualPlane = static_cast<bool>(i);
1449 if (bUseDualPlane && !fDualPlaneEnabled) {
1450 continue;
1451 }
1452
1453 texelWeights[i][s][t] =
1454 this->infillWeight(unquantizedValues, s, t, bUseDualPlan e);
1455 }
1456 }
1457 }
1458 }
1459
1460 // 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.
1461 // Copied directly from C.2.21 of the ASTC specification
1462 int getPartition(int x, int y) const {
robertphillips 2014/08/05 20:37:53 bSmallBlock -> isSmallBlock ?
krajcevski 2014/08/05 20:55:08 Done.
1463 bool bSmallBlock = (fDimX * fDimY) < 31;
1464 const int partitionCount = this->numPartitions();
1465 int seed = fPartIndex;
1466 if (bSmallBlock) {
1467 x <<= 1;
1468 y <<= 1;
1469 }
1470
1471 seed += (partitionCount - 1) * 1024;
1472
1473 uint32_t p = seed;
1474 p ^= p >> 15; p -= p << 17; p += p << 7; p += p << 4;
1475 p ^= p >> 5; p += p << 16; p ^= p >> 7; p ^= p >> 3;
1476 p ^= p << 6; p ^= p >> 17;
1477
1478 uint32_t rnum = p;
1479 uint8_t seed1 = rnum & 0xF;
1480 uint8_t seed2 = (rnum >> 4) & 0xF;
1481 uint8_t seed3 = (rnum >> 8) & 0xF;
1482 uint8_t seed4 = (rnum >> 12) & 0xF;
1483 uint8_t seed5 = (rnum >> 16) & 0xF;
1484 uint8_t seed6 = (rnum >> 20) & 0xF;
1485 uint8_t seed7 = (rnum >> 24) & 0xF;
1486 uint8_t seed8 = (rnum >> 28) & 0xF;
1487 uint8_t seed9 = (rnum >> 18) & 0xF;
1488 uint8_t seed10 = (rnum >> 22) & 0xF;
1489 uint8_t seed11 = (rnum >> 26) & 0xF;
1490 uint8_t seed12 = ((rnum >> 30) | (rnum << 2)) & 0xF;
1491
1492 seed1 *= seed1; seed2 *= seed2;
1493 seed3 *= seed3; seed4 *= seed4;
1494 seed5 *= seed5; seed6 *= seed6;
1495 seed7 *= seed7; seed8 *= seed8;
1496 seed9 *= seed9; seed10 *= seed10;
1497 seed11 *= seed11; seed12 *= seed12;
1498
1499 int sh1, sh2, sh3;
1500 if (0 != (seed & 1)) {
1501 sh1 = (0 != (seed & 2))? 4 : 5;
1502 sh2 = (partitionCount == 3)? 6 : 5;
1503 } else {
1504 sh1 = (partitionCount==3)? 6 : 5;
1505 sh2 = (0 != (seed & 2))? 4 : 5;
1506 }
1507 sh3 = (0 != (seed & 0x10))? sh1 : sh2;
1508
1509 seed1 >>= sh1; seed2 >>= sh2; seed3 >>= sh1; seed4 >>= sh2;
1510 seed5 >>= sh1; seed6 >>= sh2; seed7 >>= sh1; seed8 >>= sh2;
1511 seed9 >>= sh3; seed10 >>= sh3; seed11 >>= sh3; seed12 >>= sh3;
1512
1513 const int z = 0;
1514 int a = seed1*x + seed2*y + seed11*z + (rnum >> 14);
1515 int b = seed3*x + seed4*y + seed12*z + (rnum >> 10);
1516 int c = seed5*x + seed6*y + seed9 *z + (rnum >> 6);
1517 int d = seed7*x + seed8*y + seed10*z + (rnum >> 2);
1518
1519 a &= 0x3F;
1520 b &= 0x3F;
1521 c &= 0x3F;
1522 d &= 0x3F;
1523
1524 if (partitionCount < 4) {
1525 d = 0;
1526 }
1527
1528 if (partitionCount < 3) {
1529 c = 0;
1530 }
1531
1532 if (a >= b && a >= c && a >= d) {
1533 return 0;
1534 } else if (b >= c && b >= d) {
1535 return 1;
1536 } else if (c >= d) {
1537 return 2;
1538 } else {
1539 return 3;
1540 }
1541 }
1542
1543 // Performs the proper interpolation of the texel based on the
1544 // endpoints and weights.
1545 SkColor getTexel(const SkColor endpoints[4][2],
1546 const int weights[2][12][12],
1547 int x, int y) const {
1548 int part = 0;
1549 if (this->numPartitions() > 1) {
1550 part = this->getPartition(x, y);
1551 }
1552
1553 SkColor result;
1554 if (fDualPlaneEnabled) {
1555 result = interpolate_dual_endpoints(
1556 endpoints[part], weights[0][x][y], weights[1][x][y], fDualPlane) ;
1557 } else {
1558 result = interpolate_endpoints(endpoints[part], weights[0][x][y]);
1559 }
1560
1561 #if 1
1562 // !FIXME! if we're writing directly to a bitmap, then we don't need
1563 // to swap the red and blue channels, but since we're usually being used
1564 // by the SkImageDecoder_astc module, the results are expected to be in RGBA.
1565 result = SkColorSetARGB(
1566 SkColorGetA(result), SkColorGetB(result), SkColorGetG(result), SkCol orGetR(result));
1567 #endif
1568
1569 return result;
1570 }
1571
1572 void decode() {
1573 // First decode the block mode.
1574 this->decodeBlockMode();
1575
1576 // Now we can decode the partition information.
1577 fPartIndex = read_astc_bits(fBlock, 11, 23);
1578 fPartCount = (fPartIndex & 0x3) + 1;
1579 fPartIndex >>= 2;
1580
1581 // This is illegal
1582 if (fDualPlaneEnabled && this->numPartitions() == 4) {
1583 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
1584 }
1585
1586 // Based on the partition info, we can decode the color information.
1587 this->decodeColorData();
1588 }
1589
1590 // Decodes the dual plane based on the given bit location. The final
1591 // location, if the dual plane is enabled, is also the end of our color data .
1592 // This function is only meant to be used from this->decodeColorData()
1593 void decodeDualPlane(int bitLoc) {
1594 if (fDualPlaneEnabled) {
1595 fDualPlane = read_astc_bits(fBlock, bitLoc - 2, bitLoc);
1596 fColorEndBit = bitLoc - 2;
1597 } else {
1598 fColorEndBit = bitLoc;
1599 }
1600 }
1601
1602 // Decodes the color information based on the ASTC spec.
1603 void decodeColorData() {
1604
1605 // By default, the last color bit is at the end of the texel weights
1606 const int lastWeight = 128 - this->numWeightBits();
1607
1608 // If we have a dual plane then it will be at this location, too.
1609 int dualPlaneBitLoc = lastWeight;
1610
1611 // If there's only one partition, then our job is (relatively) easy.
1612 if (this->numPartitions() == 1) {
1613 fCEM[0] = read_astc_bits(fBlock, 13, 17);
1614 fColorStartBit = 17;
1615
1616 // Handle dual plane mode...
1617 this->decodeDualPlane(dualPlaneBitLoc);
1618
1619 return;
1620 }
1621
1622 // If we have more than one partition, then we need to make
1623 // room for the partition index.
1624 fColorStartBit = 29;
1625
1626 // Read the base CEM. If it's zero, then we have no additional
1627 // CEM data and the endpoints for each partition share the same CEM.
1628 const int baseCEM = read_astc_bits(fBlock, 23, 25);
1629 if (0 == baseCEM) {
1630 const int sameCEM = read_astc_bits(fBlock, 25, 29);
1631 for (int i = 0; i < kMaxPartitions; ++i) {
1632 fCEM[i] = sameCEM;
1633 }
1634
1635 // Handle dual plane mode...
1636 this->decodeDualPlane(dualPlaneBitLoc);
1637
1638 return;
1639 }
1640
1641 // Move the dual plane selector bits down based on how many
1642 // partitions the block contains.
1643 switch (this->numPartitions()) {
1644 case 2:
1645 dualPlaneBitLoc -= 2;
1646 break;
1647
1648 case 3:
1649 dualPlaneBitLoc -= 5;
1650 break;
1651
1652 case 4:
1653 dualPlaneBitLoc -= 8;
1654 break;
1655
1656 default:
1657 SkDEBUGFAIL("Internal ASTC decoding error.");
1658 break;
1659 }
1660
1661 // The rest of the CEM config will be between the dual plane bit selecto r
1662 // and the texel weight grid.
1663 const int lowCEM = read_astc_bits(fBlock, 23, 29);
1664 int fullCEM = read_astc_bits(fBlock, dualPlaneBitLoc, lastWeight);
1665
1666 // Attach the config at the end of the weight grid to the CEM values
1667 // in the beginning of the block.
1668 fullCEM = (fullCEM << 6) | lowCEM;
1669
1670 // Ignore the two least significant bits, since those are our baseCEM ab ove.
1671 fullCEM = fullCEM >> 2;
1672
1673 int C[kMaxPartitions]; // Next, decode C and M from the spec (Table C.2. 12)
1674 for (int i = 0; i < this->numPartitions(); ++i) {
1675 C[i] = fullCEM & 1;
1676 fullCEM = fullCEM >> 1;
1677 }
1678
1679 int M[kMaxPartitions];
1680 for (int i = 0; i < this->numPartitions(); ++i) {
1681 M[i] = fullCEM & 0x3;
1682 fullCEM = fullCEM >> 2;
1683 }
1684
1685 // Construct our CEMs..
1686 SkASSERT(baseCEM > 0);
1687 for (int i = 0; i < this->numPartitions(); ++i) {
1688 int cem = (baseCEM - 1) * 4;
1689 cem += (0 == C[i])? 0 : 4;
1690 cem += M[i];
1691
1692 SkASSERT(cem < 16);
1693 fCEM[i] = cem;
1694 }
1695
1696 // Finally, if we have dual plane mode, then read the plane selector.
1697 this->decodeDualPlane(dualPlaneBitLoc);
1698 }
1699
1700 // Decodes the block mode. This function determines whether or not we use
1701 // dual plane encoding, the size of the texel weight grid, and the number of
1702 // bits, trits and quints that are used to encode it. For more information,
1703 // see section C.2.10 of the ASTC spec.
1704 void decodeBlockMode() {
1705 const int blockMode = read_astc_bits(fBlock, 0, 11);
1706
1707 // Check for special void extent encoding
1708 fVoidExtent = (blockMode & 0x1FF) == 0x1FC;
1709
1710 // Check for reserved block modes
1711 fError = (blockMode & 0x1C3) == 0x1C0;
1712
1713 // Neither reserved nor void-extent, decode as usual
1714 // 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.
1715 bool bHighPrecision = false;
1716 int R = 0;
1717 if ((blockMode & 0x3) == 0) {
1718 R = ((0xC & blockMode) >> 1) | ((0x10 & blockMode) >> 4);
1719 const int bitsSevenAndEight = (blockMode & 0x180) >> 7;
1720 SkASSERT(0 <= bitsSevenAndEight && bitsSevenAndEight < 4);
1721
1722 const int A = (blockMode >> 5) & 0x3;
1723 const int B = (blockMode >> 9) & 0x3;
1724
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
1725 fDualPlaneEnabled = (blockMode >> 10) & 0x1;
1726 bHighPrecision = (blockMode >> 9) & 0x1;
1727
1728 switch (bitsSevenAndEight) {
1729 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
1730 case 0:
1731 fWeightDimX = 12;
1732 fWeightDimY = A + 2;
1733 break;
1734
1735 case 1:
1736 fWeightDimX = A + 2;
1737 fWeightDimY = 12;
1738 break;
1739
1740 case 2:
1741 fWeightDimX = A + 6;
1742 fWeightDimY = B + 6;
1743 fDualPlaneEnabled = false;
1744 bHighPrecision = false;
1745 break;
1746
1747 case 3:
1748 if (0 == A) {
1749 fWeightDimX = 6;
1750 fWeightDimY = 10;
1751 } else {
1752 fWeightDimX = 10;
1753 fWeightDimY = 6;
1754 }
1755 break;
1756 }
1757 } else { // (blockMode & 0x3) != 0
1758 R = ((blockMode & 0x3) << 1) | ((blockMode & 0x10) >> 4);
1759
1760 const int bitsTwoAndThree = (blockMode >> 2) & 0x3;
1761 SkASSERT(0 <= bitsTwoAndThree && bitsTwoAndThree < 4);
1762
1763 const int A = (blockMode >> 5) & 0x3;
1764 const int B = (blockMode >> 7) & 0x3;
1765
1766 fDualPlaneEnabled = (blockMode >> 10) & 0x1;
1767 bHighPrecision = (blockMode >> 9) & 0x1;
1768
1769 switch (bitsTwoAndThree) {
1770 case 0:
1771 fWeightDimX = B + 4;
1772 fWeightDimY = A + 2;
1773 break;
1774 case 1:
1775 fWeightDimX = B + 8;
1776 fWeightDimY = A + 2;
1777 break;
1778 case 2:
1779 fWeightDimX = A + 2;
1780 fWeightDimY = B + 8;
1781 break;
1782 case 3:
1783 if ((B & 0x2) == 0) {
1784 fWeightDimX = A + 2;
1785 fWeightDimY = (B & 1) + 6;
1786 } else {
1787 fWeightDimX = (B & 1) + 2;
1788 fWeightDimY = A + 2;
1789 }
1790 break;
1791 }
1792 }
1793
1794 // We should have set the values of R and bHighPrecision
1795 // from decoding the block mode, these are used to determine
1796 // the proper dimensions of our weight grid.
1797 if ((R & 0x6) == 0) {
1798 fError = true;
1799 } else {
1800 static const int kBitAllocationTable[2][6][3] = {
1801 {
1802 { 1, 0, 0 },
1803 { 0, 1, 0 },
1804 { 2, 0, 0 },
1805 { 0, 0, 1 },
1806 { 1, 1, 0 },
1807 { 3, 0, 0 }
1808 },
1809 {
1810 { 1, 0, 1 },
1811 { 2, 1, 0 },
1812 { 4, 0, 0 },
1813 { 2, 0, 1 },
1814 { 3, 1, 0 },
1815 { 5, 0, 0 }
1816 }
1817 };
1818
1819 fWeightBits = kBitAllocationTable[bHighPrecision][R - 2][0];
1820 fWeightTrits = kBitAllocationTable[bHighPrecision][R - 2][1];
1821 fWeightQuints = kBitAllocationTable[bHighPrecision][R - 2][2];
1822 }
1823 }
1824 };
1825
1826 // Take a known void-extent block, and write out the values as a constant color.
1827 static void decompress_void_extent(uint8_t* dst, int dstRowBytes,
1828 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.
1829 if ((0x100 & data.fBlockMode) == 0) {
1830 write_error_color(dst, dstRowBytes);
1831 return;
1832 }
1833
1834 // The top 64 bits contain 4 16-bit RGBA values.
1835 int a = (read_astc_bits(data.fBlock, 112, 128) + 255) >> 8;
1836 int b = (read_astc_bits(data.fBlock, 96, 112) + 255) >> 8;
1837 int g = (read_astc_bits(data.fBlock, 80, 96) + 255) >> 8;
1838 int r = (read_astc_bits(data.fBlock, 64, 80) + 255) >> 8;
1839
1840 write_constant_color(dst, dstRowBytes, SkColorSetARGB(a, r, g, b));
1841 }
1842
1843 // Decompresses a single ASTC block. It's assumed that data.fDimX and data.fDimY are
1844 // set and that the block has already been decoded (i.e. data.decode() has been called)
1845 static void decompress_astc_block(uint8_t* dst, int dstRowBytes,
1846 const ASTCDecompressionData &data) {
1847 if (data.fError) {
1848 write_error_color(dst, dstRowBytes);
1849 return;
1850 }
1851
1852 if(data.fVoidExtent) {
1853 decompress_void_extent(dst, dstRowBytes, data);
1854 return;
1855 }
1856
1857 // Decode the texel weights.
robertphillips 2014/08/05 15:29:03 64?
krajcevski 2014/08/05 20:55:05 Done.
1858 int texelValues[64];
1859 bool success = decode_integer_sequence(
1860 texelValues, 64, data.numWeights(),
robertphillips 2014/08/05 15:29:02 128?
krajcevski 2014/08/05 20:55:07 Done.
1861 data.fBlock, 128, 128 - data.numWeightBits(), false,
1862 data.fWeightBits, data.fWeightTrits, data.fWeightQuints);
1863
1864 if (!success) {
1865 write_error_color(dst, dstRowBytes);
1866 return;
1867 }
1868
1869 // Decode the color endpoints
1870 int colorBits, colorTrits, colorQuints;
1871 if (!data.getColorValueEncoding(&colorBits, &colorTrits, &colorQuints)) {
1872 write_error_color(dst, dstRowBytes);
1873 return;
1874 }
1875
1876 int colorValues[18];
1877 success = decode_integer_sequence(
1878 colorValues, 18, data.numColorValues(),
1879 data.fBlock, data.fColorStartBit, data.fColorEndBit, true,
1880 colorBits, colorTrits, colorQuints);
1881
1882 if (!success) {
1883 write_error_color(dst, dstRowBytes);
1884 return;
1885 }
1886
1887 // Unquantize the color values after they've been decoded.
1888 unquantize_colors(colorValues, data.numColorValues(), colorBits, colorTrits, colorQuints);
1889
1890 // Decode the colors into the appropriate endpoints.
1891 SkColor endpoints[4][2];
1892 data.colorEndpoints(endpoints, colorValues);
1893
1894 // Do texel infill and decode the texel values.
1895 int texelWeights[2][12][12];
1896 data.texelWeights(texelWeights, texelValues);
1897
1898 // Write the texels by interpolating them based on the information
1899 // stored in the block.
1900 dst += data.fDimY * dstRowBytes;
robertphillips 2014/08/05 15:29:03 i,j -> x,y ?
krajcevski 2014/08/05 20:55:07 Done.
1901 for (int j = 0; j < data.fDimY; ++j) {
1902 dst -= dstRowBytes;
1903 SkColor* colorPtr = reinterpret_cast<SkColor*>(dst);
1904 for (int i = 0; i < data.fDimX; ++i) {
1905 colorPtr[i] = data.getTexel(endpoints, texelWeights, i, j);
1906 }
1907 }
1908 }
1909
1910 ////////////////////////////////////////////////////////////////////////////////
264 1911
265 namespace SkTextureCompressor { 1912 namespace SkTextureCompressor {
266 1913
267 bool CompressA8To12x12ASTC(uint8_t* dst, const uint8_t* src, int width, int heig ht, int rowBytes) { 1914 bool CompressA8To12x12ASTC(uint8_t* dst, const uint8_t* src, int width, int heig ht, int rowBytes) {
268 if (width < 0 || ((width % 12) != 0) || height < 0 || ((height % 12) != 0)) { 1915 if (width < 0 || ((width % 12) != 0) || height < 0 || ((height % 12) != 0)) {
269 return false; 1916 return false;
270 } 1917 }
271 1918
272 uint8_t** dstPtr = &dst; 1919 uint8_t** dstPtr = &dst;
273 for (int y = 0; y < height; y += 12) { 1920 for (int y = 0; y < height; y += 12) {
274 for (int x = 0; x < width; x += 12) { 1921 for (int x = 0; x < width; x += 12) {
275 compress_a8_astc_block<GetAlpha>(dstPtr, src + y*rowBytes + x, rowBy tes); 1922 compress_a8_astc_block<GetAlpha>(dstPtr, src + y*rowBytes + x, rowBy tes);
276 } 1923 }
277 } 1924 }
278 1925
279 return true; 1926 return true;
280 } 1927 }
281 1928
282 SkBlitter* CreateASTCBlitter(int width, int height, void* outputBuffer) { 1929 SkBlitter* CreateASTCBlitter(int width, int height, void* outputBuffer) {
283 return new 1930 return new
284 SkTCompressedAlphaBlitter<12, 16, CompressA8ASTCBlockVertical> 1931 SkTCompressedAlphaBlitter<12, 16, CompressA8ASTCBlockVertical>
285 (width, height, outputBuffer); 1932 (width, height, outputBuffer);
286 } 1933 }
287 1934
1935 void DecompressASTC(uint8_t* dst, int dstRowBytes, const uint8_t* src,
1936 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.
1937 dst += height * dstRowBytes;
1938
1939 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.
1940 for (int j = 0; j < height; j += blockDimY) {
1941 dst -= blockDimY * dstRowBytes;
1942 SkColor *colorPtr = reinterpret_cast<SkColor*>(dst);
1943 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.
1944 read_astc_block(&(data.fBlock), src);
1945 data.decode();
1946 decompress_astc_block(reinterpret_cast<uint8_t*>(colorPtr + i), dstR owBytes, data);
robertphillips 2014/08/05 15:29:02 16?
krajcevski 2014/08/05 20:55:05 Done.
1947 src += 16;
1948 }
1949 }
1950 }
1951
288 } // SkTextureCompressor 1952 } // SkTextureCompressor
OLDNEW
« no previous file with comments | « src/utils/SkTextureCompressor_ASTC.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698