Evas ETC2: Add ETC2 encoder skeletton
authorJean-Philippe Andre <jp.andre@samsung.com>
Tue, 27 May 2014 06:02:23 +0000 (15:02 +0900)
committerJean-Philippe Andre <jp.andre@samsung.com>
Tue, 10 Jun 2014 05:58:27 +0000 (14:58 +0900)
Implement Alpha encoding, brute force way, but doesn't scan
all possibilities either (only based on average alpha).

RGB encoding is still entirely left to the rg-etc1 encoder.
T, H and Planar mode will come in the next commits.

@feature: Implement an ETC2 encoder from scratch for RGB8 and RGBA8

src/Makefile_Evas.am
src/static_libs/rg_etc/etc2_encoder.c [new file with mode: 0644]
src/static_libs/rg_etc/rg_etc1.h

index 39a00ae..c98f80c 100644 (file)
@@ -1816,6 +1816,7 @@ modules/evas/savers/tgv/evas_image_save_tgv.c \
 static_libs/rg_etc/rg_etc1.c \
 static_libs/rg_etc/rg_etc2.c \
 static_libs/rg_etc/rg_etc1.h \
+static_libs/rg_etc/etc2_encoder.c \
 static_libs/lz4/lz4.c \
 static_libs/lz4/lz4.h \
 static_libs/lz4/lz4hc.c \
@@ -1871,6 +1872,7 @@ modules_evas_savers_tgv_module_la_SOURCES = \
 modules/evas/savers/tgv/evas_image_save_tgv.c \
 static_libs/rg_etc/rg_etc1.c \
 static_libs/rg_etc/rg_etc1.h \
+static_libs/rg_etc/etc2_encoder.c \
 static_libs/lz4/lz4.c \
 static_libs/lz4/lz4.h \
 static_libs/lz4/lz4hc.c \
diff --git a/src/static_libs/rg_etc/etc2_encoder.c b/src/static_libs/rg_etc/etc2_encoder.c
new file mode 100644 (file)
index 0000000..f70385c
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+Copyright (C) 2014 Jean-Philippe ANDRE
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <Eina.h>
+#include "rg_etc1.h"
+
+// For alpha support
+static const int kAlphaModifiers[16][8] = {
+   {  -3,  -6,  -9,  -15,  2,  5,  8,  14},
+   {  -3,  -7, -10,  -13,  2,  6,  9,  12},
+   {  -2,  -5,  -8,  -13,  1,  4,  7,  12},
+   {  -2,  -4,  -6,  -13,  1,  3,  5,  12},
+   {  -3,  -6,  -8,  -12,  2,  5,  7,  11},
+   {  -3,  -7,  -9,  -11,  2,  6,  8,  10},
+   {  -4,  -7,  -8,  -11,  3,  6,  7,  10},
+   {  -3,  -5,  -8,  -11,  2,  4,  7,  10},
+   {  -2,  -6,  -8,  -10,  1,  5,  7,   9},
+   {  -2,  -5,  -8,  -10,  1,  4,  7,   9},
+   {  -2,  -4,  -8,  -10,  1,  3,  7,   9},
+   {  -2,  -5,  -7,  -10,  1,  4,  6,   9},
+   {  -3,  -4,  -7,  -10,  2,  3,  6,   9},
+   {  -1,  -2,  -3,  -10,  0,  1,  2,   9},
+   {  -4,  -6,  -8,   -9,  3,  5,  7,   8},
+   {  -3,  -5,  -7,   -9,  2,  4,  6,   8}
+};
+
+// Damn OpenGL people, why don't you just pack data as on a CPU???
+static const int kBlockWalk[16] = {
+   0, 4,  8, 12,
+   1, 5,  9, 13,
+   2, 6, 10, 14,
+   3, 7, 11, 15
+};
+
+// Use with static constants so the compiler can optimize everything
+#define BITS(byteval, lowbit, highbit) \
+   (((byteval) >> (lowbit)) & ((1 << ((highbit) - (lowbit) + 1)) - 1))
+
+#define BIT(byteval, bit) \
+   (((byteval) >> (bit)) & 0x1)
+
+// Clamps only if value is > 255
+#define CLAMPDOWN(a) ({ int _z = (a); ((_z <= 255) ? _z : 255); })
+
+// Clamps only if value is < 0
+#define CLAMPUP(a) ({ int _z = (a); ((_z >= 0) ? _z : 0); })
+
+// Real clamp
+#define CLAMP(a) ({ int _b = (a); (((_b) >= 0) ? (((_b) < 256) ? (_b) : 255) : 0); })
+
+// Simple min
+#define MIN(a,b) ({ int _z = (a), _y = (b); ((_z <= _y) ? _z : _y); })
+
+// Simple abs
+#define ABS(a) ({ int _a = (a); ((_a >= 0) ? _a : (-_a)); })
+
+#ifndef WORDS_BIGENDIAN
+/* x86 */
+#define A_VAL(p) (((uint8_t *)(p))[3])
+#define R_VAL(p) (((uint8_t *)(p))[2])
+#define G_VAL(p) (((uint8_t *)(p))[1])
+#define B_VAL(p) (((uint8_t *)(p))[0])
+#else
+/* ppc */
+#define A_VAL(p) (((uint8_t *)(p))[0])
+#define R_VAL(p) (((uint8_t *)(p))[1])
+#define G_VAL(p) (((uint8_t *)(p))[2])
+#define B_VAL(p) (((uint8_t *)(p))[3])
+#endif
+
+#ifndef DBG
+# define DBG(fmt, ...) fprintf(stderr, fmt "\n", ## __VA_ARGS__)
+#endif
+
+/** Pack alpha block given a modifier table and a multiplier
+ * @returns Squared error
+ */
+static int
+_etc2_alpha_block_pack(uint8_t *etc2_alpha,
+                       const int base_codeword,
+                       const int multiplier,
+                       const int modifierIdx,
+                       const uint32_t *bgra,
+                       const Eina_Bool write)
+{
+   const int *alphaModifiers = kAlphaModifiers[modifierIdx];
+   uint8_t alphaIndexes[16];
+   int errAcc2 = 0;
+
+   // Header
+   if (write)
+     {
+        etc2_alpha[0] = base_codeword & 0xFF;
+        etc2_alpha[1] = ((multiplier << 4) & 0xF0) | (modifierIdx & 0x0F);
+     }
+
+   // Compute alphas now
+   for (int i = 0; i < 16; i++)
+     {
+        const int realA = A_VAL(bgra + kBlockWalk[i]);
+        int minErr = INT_MAX, idx = 0;
+
+        // Brute force -- find modifier index
+        for (int k = 0; (k < 8) && minErr; k++)
+          {
+             int tryA = CLAMP(base_codeword + alphaModifiers[k] * multiplier);
+             int err = ABS(realA - tryA);
+             if (err < minErr)
+               {
+                  minErr = err;
+                  idx = k;
+                  if (!minErr) break;
+               }
+          }
+
+        alphaIndexes[i] = idx;
+
+        // Keep some stats
+        errAcc2 += minErr * minErr;
+     }
+
+   if (write)
+     for (int k = 0; k < 2; k++)
+       {
+          etc2_alpha[2 + 3 * k]  =  alphaIndexes[0 + 8 * k] << 5;        // A
+          etc2_alpha[2 + 3 * k] |=  alphaIndexes[1 + 8 * k] << 2;        // B
+          etc2_alpha[2 + 3 * k] |= (alphaIndexes[2 + 8 * k] >> 1) & 0x3; // C01
+          etc2_alpha[3 + 3 * k]  = (alphaIndexes[2 + 8 * k] & 0x1) << 7; // C2
+          etc2_alpha[3 + 3 * k] |=  alphaIndexes[3 + 8 * k] << 4;        // D
+          etc2_alpha[3 + 3 * k] |=  alphaIndexes[4 + 8 * k] << 1;        // E
+          etc2_alpha[3 + 3 * k] |= (alphaIndexes[5 + 8 * k] >> 2) & 0x1; // F0
+          etc2_alpha[4 + 3 * k]  = (alphaIndexes[5 + 8 * k] & 0x3) << 6; // F12
+          etc2_alpha[4 + 3 * k] |=  alphaIndexes[6 + 8 * k] << 3;        // G
+          etc2_alpha[4 + 3 * k] |=  alphaIndexes[7 + 8 * k];             // H
+       }
+
+   return errAcc2;
+}
+
+static int
+_etc2_alpha_encode(uint8_t *etc2_alpha, const uint32_t *bgra,
+                   rg_etc1_pack_params *params EINA_UNUSED)
+{
+   int alphas[16], avg = 0, diff = 0, maxDiff = INT_MAX, minErr = INT_MAX;
+   int base_codeword;
+   int multiplier, bestMult = 0;
+   int modifierIdx, bestIdx = 0;
+   int err, base_range, base_step = 1, max_error = 0;
+
+   // Try to select the best alpha value (avg)
+   for (int i = 0; i < 16; i++)
+     {
+        alphas[i] = A_VAL(bgra + kBlockWalk[i]);
+        avg += alphas[i];
+     }
+   avg /= 16;
+
+   for (int i = 0; i < 16; i++)
+     {
+        int thisDiff = ABS(alphas[i] - avg);
+        maxDiff = MIN(thisDiff, maxDiff);
+        diff += thisDiff;
+     }
+
+   base_codeword = alphas[0];
+   if (!diff)
+     {
+        // All same alphas
+        etc2_alpha[0] = base_codeword;
+        memset(etc2_alpha + 1, 0, 7);
+        return 0;
+     }
+
+   // Bruteforce -- try all tables and all multipliers, oh my god this will be slow.
+
+   switch (params->m_quality)
+     {
+      // The follow parameters are completely arbitrary.
+      // Need some real testing.
+      case rg_etc1_high_quality:
+        base_range = 15;
+        base_step = 0;
+        max_error = 0;
+        break;
+      case rg_etc1_medium_quality:
+        base_range = 6;
+        base_step = 2;
+        max_error = 2 * 2 * 16; // 42dB
+        break;
+      case rg_etc1_low_quality:
+        base_range = 0;
+        max_error = 5 * 5 * 16; // 34dB
+        break;
+     }
+
+   // for loop avg, avg-1, avg+1, avg-2, avg+2, ...
+   for (int step = 0; step < base_range; step += base_step)
+     for (base_codeword = avg - step; base_codeword <= avg + step; base_codeword += 2 * step)
+       {
+          for (modifierIdx = 0; modifierIdx < 16; modifierIdx++)
+            for (multiplier = 0; multiplier < 16; multiplier++)
+              {
+                 if ((ABS(multiplier * kAlphaModifiers[modifierIdx][3])) < maxDiff)
+                   continue;
+
+                 err = _etc2_alpha_block_pack(etc2_alpha, base_codeword,
+                                              multiplier, modifierIdx, bgra, EINA_FALSE);
+                 if (err < minErr)
+                   {
+                      minErr = err;
+                      bestMult = multiplier;
+                      bestIdx = modifierIdx;
+                      if (err < max_error)
+                        goto pack_now;
+
+                   }
+              }
+          if (step <= 0) break;
+       }
+
+pack_now:
+   err = _etc2_alpha_block_pack(etc2_alpha, base_codeword,
+                                bestMult, bestIdx, bgra, EINA_TRUE);
+   return err;
+}
+
+unsigned int
+etc2_rgba8_block_pack(unsigned char *etc2, const unsigned int *bgra,
+                      rg_etc1_pack_params *params)
+{
+   unsigned int error;
+
+   // FIXME/TODO: For now, encode use rg_etc1 only!
+   error = rg_etc1_pack_block(etc2 + 8, bgra, params);
+   error += _etc2_alpha_encode(etc2, bgra, params);
+
+   return error;
+}
+
+unsigned int
+etc2_rgb8_block_pack(unsigned char *etc2, const unsigned int *bgra,
+                     rg_etc1_pack_params *params)
+{
+  unsigned int error;
+
+  // FIXME/TODO: For now, encode use rg_etc1 only!
+  error = rg_etc1_pack_block(etc2, bgra, params);
+
+  return error;
+}
index 275b0db..6f4b944 100644 (file)
@@ -37,6 +37,12 @@ void rg_etc1_pack_block_init();
 // pack_etc1_block() does not currently support "perceptual" colorspace metrics - it primarily optimizes for RGB RMSE.
 unsigned int rg_etc1_pack_block(void* pETC1_block, const unsigned int* pSrc_pixels_BGRA, rg_etc1_pack_params *pack_params);
 
+// Pack a 4x4 block of 32bpp BGRA pixels to a 16-byte RGBA8_ETC2_EAC block (supports alpha).
+unsigned int etc2_rgba8_block_pack(unsigned char *etc2, const unsigned int *bgra, rg_etc1_pack_params *params);
+
+// Pack a 4x4 block of 32bpp BGRA pixels to a 8-byte RGB8_ETC2 block (opaque).
+unsigned int etc2_rgb8_block_pack(unsigned char *etc2, const unsigned int *bgra, rg_etc1_pack_params *params);
+
 // ETC2 support: RGB8_ETC2
 void rg_etc2_rgb8_decode_block(const unsigned char *etc_block, unsigned int *bgra);