Files
basis_universal/basisu_etc.cpp
richgel999 ab722fa2e1 - Adding support for skip blocks, i-frames and p-frames to the backend and transcoder, for texture video use.
- For PVRTC1, we now memset() any extra blocks after the blocks we know will be transcoded into. This is done because in GL there is padding that occurs on textures smaller than 8x8.
- Removing all OpenMP usage and replacing it with a basis C++11 job system.
- Fixing TSVQ class so it can split very long "thin" clusters that before weren't being split. This improves endpoint quantization on videos and complex 2D textures.
- More frontend perf. optimizations.

Tested on OSX, Linux with gcc/clang, and Win using MSVC 2019.
2019-06-07 20:35:38 -07:00

1970 lines
76 KiB
C++

// basis_etc.cpp
// Copyright (C) 2017-2019 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "basisu_etc.h"
#define BASISU_DEBUG_ETC_ENCODER 0
#define BASISU_DEBUG_ETC_ENCODER_DEEPER 0
namespace basisu
{
const uint32_t BASISU_ETC1_CLUSTER_FIT_ORDER_TABLE_SIZE = 165;
static const struct { uint8_t m_v[4]; } g_cluster_fit_order_tab[BASISU_ETC1_CLUSTER_FIT_ORDER_TABLE_SIZE] =
{
{ { 0, 0, 0, 8 } },{ { 0, 5, 2, 1 } },{ { 0, 6, 1, 1 } },{ { 0, 7, 0, 1 } },{ { 0, 7, 1, 0 } },
{ { 0, 0, 8, 0 } },{ { 0, 0, 3, 5 } },{ { 0, 1, 7, 0 } },{ { 0, 0, 4, 4 } },{ { 0, 0, 2, 6 } },
{ { 0, 0, 7, 1 } },{ { 0, 0, 1, 7 } },{ { 0, 0, 5, 3 } },{ { 1, 6, 0, 1 } },{ { 0, 0, 6, 2 } },
{ { 0, 2, 6, 0 } },{ { 2, 4, 2, 0 } },{ { 0, 3, 5, 0 } },{ { 3, 3, 1, 1 } },{ { 4, 2, 0, 2 } },
{ { 1, 5, 2, 0 } },{ { 0, 5, 3, 0 } },{ { 0, 6, 2, 0 } },{ { 2, 4, 1, 1 } },{ { 5, 1, 0, 2 } },
{ { 6, 1, 1, 0 } },{ { 3, 3, 0, 2 } },{ { 6, 0, 0, 2 } },{ { 0, 8, 0, 0 } },{ { 6, 1, 0, 1 } },
{ { 0, 1, 6, 1 } },{ { 1, 6, 1, 0 } },{ { 4, 1, 3, 0 } },{ { 0, 2, 5, 1 } },{ { 5, 0, 3, 0 } },
{ { 5, 3, 0, 0 } },{ { 0, 1, 5, 2 } },{ { 0, 3, 4, 1 } },{ { 2, 5, 1, 0 } },{ { 1, 7, 0, 0 } },
{ { 0, 1, 4, 3 } },{ { 6, 0, 2, 0 } },{ { 0, 4, 4, 0 } },{ { 2, 6, 0, 0 } },{ { 0, 2, 4, 2 } },
{ { 0, 5, 1, 2 } },{ { 0, 6, 0, 2 } },{ { 3, 5, 0, 0 } },{ { 0, 4, 3, 1 } },{ { 3, 4, 1, 0 } },
{ { 4, 3, 1, 0 } },{ { 1, 5, 0, 2 } },{ { 0, 3, 3, 2 } },{ { 1, 4, 1, 2 } },{ { 0, 4, 2, 2 } },
{ { 2, 3, 3, 0 } },{ { 4, 4, 0, 0 } },{ { 1, 2, 4, 1 } },{ { 0, 5, 0, 3 } },{ { 0, 1, 3, 4 } },
{ { 1, 5, 1, 1 } },{ { 1, 4, 2, 1 } },{ { 1, 3, 2, 2 } },{ { 5, 2, 1, 0 } },{ { 1, 3, 3, 1 } },
{ { 0, 1, 2, 5 } },{ { 1, 1, 5, 1 } },{ { 0, 3, 2, 3 } },{ { 2, 5, 0, 1 } },{ { 3, 2, 2, 1 } },
{ { 2, 3, 0, 3 } },{ { 1, 4, 3, 0 } },{ { 2, 2, 1, 3 } },{ { 6, 2, 0, 0 } },{ { 1, 0, 6, 1 } },
{ { 3, 3, 2, 0 } },{ { 7, 1, 0, 0 } },{ { 3, 1, 4, 0 } },{ { 0, 2, 3, 3 } },{ { 0, 4, 1, 3 } },
{ { 0, 4, 0, 4 } },{ { 0, 1, 0, 7 } },{ { 2, 0, 5, 1 } },{ { 2, 0, 4, 2 } },{ { 3, 0, 2, 3 } },
{ { 2, 2, 4, 0 } },{ { 2, 2, 3, 1 } },{ { 4, 0, 3, 1 } },{ { 3, 2, 3, 0 } },{ { 2, 3, 2, 1 } },
{ { 1, 3, 4, 0 } },{ { 7, 0, 1, 0 } },{ { 3, 0, 4, 1 } },{ { 1, 0, 5, 2 } },{ { 8, 0, 0, 0 } },
{ { 3, 0, 1, 4 } },{ { 4, 1, 1, 2 } },{ { 4, 0, 2, 2 } },{ { 1, 2, 5, 0 } },{ { 4, 2, 1, 1 } },
{ { 3, 4, 0, 1 } },{ { 2, 0, 3, 3 } },{ { 5, 0, 1, 2 } },{ { 5, 0, 0, 3 } },{ { 2, 4, 0, 2 } },
{ { 2, 1, 4, 1 } },{ { 4, 0, 1, 3 } },{ { 2, 1, 5, 0 } },{ { 4, 2, 2, 0 } },{ { 4, 0, 4, 0 } },
{ { 1, 0, 4, 3 } },{ { 1, 4, 0, 3 } },{ { 3, 0, 3, 2 } },{ { 4, 3, 0, 1 } },{ { 0, 1, 1, 6 } },
{ { 1, 3, 1, 3 } },{ { 0, 2, 2, 4 } },{ { 2, 0, 2, 4 } },{ { 5, 1, 1, 1 } },{ { 3, 0, 5, 0 } },
{ { 2, 3, 1, 2 } },{ { 3, 0, 0, 5 } },{ { 0, 3, 1, 4 } },{ { 5, 0, 2, 1 } },{ { 2, 1, 3, 2 } },
{ { 2, 0, 6, 0 } },{ { 3, 1, 3, 1 } },{ { 5, 1, 2, 0 } },{ { 1, 0, 3, 4 } },{ { 1, 1, 6, 0 } },
{ { 4, 0, 0, 4 } },{ { 2, 0, 1, 5 } },{ { 0, 3, 0, 5 } },{ { 1, 3, 0, 4 } },{ { 4, 1, 2, 1 } },
{ { 1, 2, 3, 2 } },{ { 3, 1, 0, 4 } },{ { 5, 2, 0, 1 } },{ { 1, 2, 2, 3 } },{ { 3, 2, 1, 2 } },
{ { 2, 2, 2, 2 } },{ { 6, 0, 1, 1 } },{ { 1, 2, 1, 4 } },{ { 1, 1, 4, 2 } },{ { 3, 2, 0, 3 } },
{ { 1, 2, 0, 5 } },{ { 1, 0, 7, 0 } },{ { 3, 1, 2, 2 } },{ { 1, 0, 2, 5 } },{ { 2, 0, 0, 6 } },
{ { 2, 1, 1, 4 } },{ { 2, 2, 0, 4 } },{ { 1, 1, 3, 3 } },{ { 7, 0, 0, 1 } },{ { 1, 0, 0, 7 } },
{ { 2, 1, 2, 3 } },{ { 4, 1, 0, 3 } },{ { 3, 1, 1, 3 } },{ { 1, 1, 2, 4 } },{ { 2, 1, 0, 5 } },
{ { 1, 0, 1, 6 } },{ { 0, 2, 1, 5 } },{ { 0, 2, 0, 6 } },{ { 1, 1, 1, 5 } },{ { 1, 1, 0, 6 } }
};
const int g_etc1_inten_tables[cETC1IntenModifierValues][cETC1SelectorValues] =
{
{ -8, -2, 2, 8 }, { -17, -5, 5, 17 }, { -29, -9, 9, 29 }, { -42, -13, 13, 42 },
{ -60, -18, 18, 60 }, { -80, -24, 24, 80 }, { -106, -33, 33, 106 }, { -183, -47, 47, 183 }
};
const uint8_t g_etc1_to_selector_index[cETC1SelectorValues] = { 2, 3, 1, 0 };
const uint8_t g_selector_index_to_etc1[cETC1SelectorValues] = { 3, 2, 0, 1 };
// [flip][subblock][pixel_index]
const etc_coord2 g_etc1_pixel_coords[2][2][8] =
{
{
{
{ 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 },
{ 1, 0 }, { 1, 1 }, { 1, 2 }, { 1, 3 }
},
{
{ 2, 0 }, { 2, 1 }, { 2, 2 }, { 2, 3 },
{ 3, 0 }, { 3, 1 }, { 3, 2 }, { 3, 3 }
}
},
{
{
{ 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 },
{ 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 }
},
{
{ 0, 2 }, { 1, 2 }, { 2, 2 }, { 3, 2 },
{ 0, 3 }, { 1, 3 }, { 2, 3 }, { 3, 3 }
},
}
};
// [flip][subblock][pixel_index]
const uint32_t g_etc1_pixel_indices[2][2][8] =
{
{
{
0 + 4 * 0, 0 + 4 * 1, 0 + 4 * 2, 0 + 4 * 3,
1 + 4 * 0, 1 + 4 * 1, 1 + 4 * 2, 1 + 4 * 3
},
{
2 + 4 * 0, 2 + 4 * 1, 2 + 4 * 2, 2 + 4 * 3,
3 + 4 * 0, 3 + 4 * 1, 3 + 4 * 2, 3 + 4 * 3
}
},
{
{
0 + 4 * 0, 1 + 4 * 0, 2 + 4 * 0, 3 + 4 * 0,
0 + 4 * 1, 1 + 4 * 1, 2 + 4 * 1, 3 + 4 * 1
},
{
0 + 4 * 2, 1 + 4 * 2, 2 + 4 * 2, 3 + 4 * 2,
0 + 4 * 3, 1 + 4 * 3, 2 + 4 * 3, 3 + 4 * 3
},
}
};
// Given an ETC1 diff/inten_table/selector, and an 8-bit desired color, this table encodes the best packed_color in the low byte, and the abs error in the high byte.
static uint16_t g_etc1_inverse_lookup[2 * 8 * 4][256]; // [diff/inten_table/selector][desired_color]
// g_color8_to_etc_block_config[color][table_index] = Supplies for each 8-bit color value a list of packed ETC1 diff/intensity table/selectors/packed_colors that map to that color.
// To pack: diff | (inten << 1) | (selector << 4) | (packed_c << 8)
static const uint16_t g_color8_to_etc_block_config_0_255[2][33] =
{
{ 0x0000, 0x0010, 0x0002, 0x0012, 0x0004, 0x0014, 0x0006, 0x0016, 0x0008, 0x0018, 0x000A, 0x001A, 0x000C, 0x001C, 0x000E, 0x001E,
0x0001, 0x0011, 0x0003, 0x0013, 0x0005, 0x0015, 0x0007, 0x0017, 0x0009, 0x0019, 0x000B, 0x001B, 0x000D, 0x001D, 0x000F, 0x001F, 0xFFFF },
{ 0x0F20, 0x0F30, 0x0E32, 0x0F22, 0x0E34, 0x0F24, 0x0D36, 0x0F26, 0x0C38, 0x0E28, 0x0B3A, 0x0E2A, 0x093C, 0x0E2C, 0x053E, 0x0D2E,
0x1E31, 0x1F21, 0x1D33, 0x1F23, 0x1C35, 0x1E25, 0x1A37, 0x1E27, 0x1839, 0x1D29, 0x163B, 0x1C2B, 0x133D, 0x1B2D, 0x093F, 0x1A2F, 0xFFFF },
};
// Really only [254][11].
static const uint16_t g_color8_to_etc_block_config_1_to_254[254][12] =
{
{ 0x021C, 0x0D0D, 0xFFFF }, { 0x0020, 0x0021, 0x0A0B, 0x061F, 0xFFFF }, { 0x0113, 0x0217, 0xFFFF }, { 0x0116, 0x031E,
0x0B0E, 0x0405, 0xFFFF }, { 0x0022, 0x0204, 0x050A, 0x0023, 0xFFFF }, { 0x0111, 0x0319, 0x0809, 0x170F, 0xFFFF }, {
0x0303, 0x0215, 0x0607, 0xFFFF }, { 0x0030, 0x0114, 0x0408, 0x0031, 0x0201, 0x051D, 0xFFFF }, { 0x0100, 0x0024, 0x0306,
0x0025, 0x041B, 0x0E0D, 0xFFFF }, { 0x021A, 0x0121, 0x0B0B, 0x071F, 0xFFFF }, { 0x0213, 0x0317, 0xFFFF }, { 0x0112,
0x0505, 0xFFFF }, { 0x0026, 0x070C, 0x0123, 0x0027, 0xFFFF }, { 0x0211, 0x0909, 0xFFFF }, { 0x0110, 0x0315, 0x0707,
0x0419, 0x180F, 0xFFFF }, { 0x0218, 0x0131, 0x0301, 0x0403, 0x061D, 0xFFFF }, { 0x0032, 0x0202, 0x0033, 0x0125, 0x051B,
0x0F0D, 0xFFFF }, { 0x0028, 0x031C, 0x0221, 0x0029, 0xFFFF }, { 0x0120, 0x0313, 0x0C0B, 0x081F, 0xFFFF }, { 0x0605,
0x0417, 0xFFFF }, { 0x0216, 0x041E, 0x0C0E, 0x0223, 0x0127, 0xFFFF }, { 0x0122, 0x0304, 0x060A, 0x0311, 0x0A09, 0xFFFF
}, { 0x0519, 0x190F, 0xFFFF }, { 0x002A, 0x0231, 0x0503, 0x0415, 0x0807, 0x002B, 0x071D, 0xFFFF }, { 0x0130, 0x0214,
0x0508, 0x0401, 0x0133, 0x0225, 0x061B, 0xFFFF }, { 0x0200, 0x0124, 0x0406, 0x0321, 0x0129, 0x100D, 0xFFFF }, { 0x031A,
0x0D0B, 0x091F, 0xFFFF }, { 0x0413, 0x0705, 0x0517, 0xFFFF }, { 0x0212, 0x0034, 0x0323, 0x0035, 0x0227, 0xFFFF }, {
0x0126, 0x080C, 0x0B09, 0xFFFF }, { 0x0411, 0x0619, 0x1A0F, 0xFFFF }, { 0x0210, 0x0331, 0x0603, 0x0515, 0x0907, 0x012B,
0xFFFF }, { 0x0318, 0x002C, 0x0501, 0x0233, 0x0325, 0x071B, 0x002D, 0x081D, 0xFFFF }, { 0x0132, 0x0302, 0x0229, 0x110D,
0xFFFF }, { 0x0128, 0x041C, 0x0421, 0x0E0B, 0x0A1F, 0xFFFF }, { 0x0220, 0x0513, 0x0617, 0xFFFF }, { 0x0135, 0x0805,
0x0327, 0xFFFF }, { 0x0316, 0x051E, 0x0D0E, 0x0423, 0xFFFF }, { 0x0222, 0x0404, 0x070A, 0x0511, 0x0719, 0x0C09, 0x1B0F,
0xFFFF }, { 0x0703, 0x0615, 0x0A07, 0x022B, 0xFFFF }, { 0x012A, 0x0431, 0x0601, 0x0333, 0x012D, 0x091D, 0xFFFF }, {
0x0230, 0x0314, 0x0036, 0x0608, 0x0425, 0x0037, 0x0329, 0x081B, 0x120D, 0xFFFF }, { 0x0300, 0x0224, 0x0506, 0x0521,
0x0F0B, 0x0B1F, 0xFFFF }, { 0x041A, 0x0613, 0x0717, 0xFFFF }, { 0x0235, 0x0905, 0xFFFF }, { 0x0312, 0x0134, 0x0523,
0x0427, 0xFFFF }, { 0x0226, 0x090C, 0x002E, 0x0611, 0x0D09, 0x002F, 0xFFFF }, { 0x0715, 0x0B07, 0x0819, 0x032B, 0x1C0F,
0xFFFF }, { 0x0310, 0x0531, 0x0701, 0x0803, 0x022D, 0x0A1D, 0xFFFF }, { 0x0418, 0x012C, 0x0433, 0x0525, 0x0137, 0x091B,
0x130D, 0xFFFF }, { 0x0232, 0x0402, 0x0621, 0x0429, 0xFFFF }, { 0x0228, 0x051C, 0x0713, 0x100B, 0x0C1F, 0xFFFF }, {
0x0320, 0x0335, 0x0A05, 0x0817, 0xFFFF }, { 0x0623, 0x0527, 0xFFFF }, { 0x0416, 0x061E, 0x0E0E, 0x0711, 0x0E09, 0x012F,
0xFFFF }, { 0x0322, 0x0504, 0x080A, 0x0919, 0x1D0F, 0xFFFF }, { 0x0631, 0x0903, 0x0815, 0x0C07, 0x042B, 0x032D, 0x0B1D,
0xFFFF }, { 0x022A, 0x0801, 0x0533, 0x0625, 0x0237, 0x0A1B, 0xFFFF }, { 0x0330, 0x0414, 0x0136, 0x0708, 0x0721, 0x0529,
0x140D, 0xFFFF }, { 0x0400, 0x0324, 0x0606, 0x0038, 0x0039, 0x110B, 0x0D1F, 0xFFFF }, { 0x051A, 0x0813, 0x0B05, 0x0917,
0xFFFF }, { 0x0723, 0x0435, 0x0627, 0xFFFF }, { 0x0412, 0x0234, 0x0F09, 0x022F, 0xFFFF }, { 0x0326, 0x0A0C, 0x012E,
0x0811, 0x0A19, 0x1E0F, 0xFFFF }, { 0x0731, 0x0A03, 0x0915, 0x0D07, 0x052B, 0xFFFF }, { 0x0410, 0x0901, 0x0633, 0x0725,
0x0337, 0x0B1B, 0x042D, 0x0C1D, 0xFFFF }, { 0x0518, 0x022C, 0x0629, 0x150D, 0xFFFF }, { 0x0332, 0x0502, 0x0821, 0x0139,
0x120B, 0x0E1F, 0xFFFF }, { 0x0328, 0x061C, 0x0913, 0x0A17, 0xFFFF }, { 0x0420, 0x0535, 0x0C05, 0x0727, 0xFFFF }, {
0x0823, 0x032F, 0xFFFF }, { 0x0516, 0x071E, 0x0F0E, 0x0911, 0x0B19, 0x1009, 0x1F0F, 0xFFFF }, { 0x0422, 0x0604, 0x090A,
0x0B03, 0x0A15, 0x0E07, 0x062B, 0xFFFF }, { 0x0831, 0x0A01, 0x0733, 0x052D, 0x0D1D, 0xFFFF }, { 0x032A, 0x0825, 0x0437,
0x0729, 0x0C1B, 0x160D, 0xFFFF }, { 0x0430, 0x0514, 0x0236, 0x0808, 0x0921, 0x0239, 0x130B, 0x0F1F, 0xFFFF }, { 0x0500,
0x0424, 0x0706, 0x0138, 0x0A13, 0x0B17, 0xFFFF }, { 0x061A, 0x0635, 0x0D05, 0xFFFF }, { 0x0923, 0x0827, 0xFFFF }, {
0x0512, 0x0334, 0x003A, 0x0A11, 0x1109, 0x003B, 0x042F, 0xFFFF }, { 0x0426, 0x0B0C, 0x022E, 0x0B15, 0x0F07, 0x0C19,
0x072B, 0xFFFF }, { 0x0931, 0x0B01, 0x0C03, 0x062D, 0x0E1D, 0xFFFF }, { 0x0510, 0x0833, 0x0925, 0x0537, 0x0D1B, 0x170D,
0xFFFF }, { 0x0618, 0x032C, 0x0A21, 0x0339, 0x0829, 0xFFFF }, { 0x0432, 0x0602, 0x0B13, 0x140B, 0x101F, 0xFFFF }, {
0x0428, 0x071C, 0x0735, 0x0E05, 0x0C17, 0xFFFF }, { 0x0520, 0x0A23, 0x0927, 0xFFFF }, { 0x0B11, 0x1209, 0x013B, 0x052F,
0xFFFF }, { 0x0616, 0x081E, 0x0D19, 0xFFFF }, { 0x0522, 0x0704, 0x0A0A, 0x0A31, 0x0D03, 0x0C15, 0x1007, 0x082B, 0x072D,
0x0F1D, 0xFFFF }, { 0x0C01, 0x0933, 0x0A25, 0x0637, 0x0E1B, 0xFFFF }, { 0x042A, 0x0B21, 0x0929, 0x180D, 0xFFFF }, {
0x0530, 0x0614, 0x0336, 0x0908, 0x0439, 0x150B, 0x111F, 0xFFFF }, { 0x0600, 0x0524, 0x0806, 0x0238, 0x0C13, 0x0F05,
0x0D17, 0xFFFF }, { 0x071A, 0x0B23, 0x0835, 0x0A27, 0xFFFF }, { 0x1309, 0x023B, 0x062F, 0xFFFF }, { 0x0612, 0x0434,
0x013A, 0x0C11, 0x0E19, 0xFFFF }, { 0x0526, 0x0C0C, 0x032E, 0x0B31, 0x0E03, 0x0D15, 0x1107, 0x092B, 0xFFFF }, { 0x0D01,
0x0A33, 0x0B25, 0x0737, 0x0F1B, 0x082D, 0x101D, 0xFFFF }, { 0x0610, 0x0A29, 0x190D, 0xFFFF }, { 0x0718, 0x042C, 0x0C21,
0x0539, 0x160B, 0x121F, 0xFFFF }, { 0x0532, 0x0702, 0x0D13, 0x0E17, 0xFFFF }, { 0x0528, 0x081C, 0x0935, 0x1005, 0x0B27,
0xFFFF }, { 0x0620, 0x0C23, 0x033B, 0x072F, 0xFFFF }, { 0x0D11, 0x0F19, 0x1409, 0xFFFF }, { 0x0716, 0x003C, 0x091E,
0x0F03, 0x0E15, 0x1207, 0x0A2B, 0x003D, 0xFFFF }, { 0x0622, 0x0804, 0x0B0A, 0x0C31, 0x0E01, 0x0B33, 0x092D, 0x111D,
0xFFFF }, { 0x0C25, 0x0837, 0x0B29, 0x101B, 0x1A0D, 0xFFFF }, { 0x052A, 0x0D21, 0x0639, 0x170B, 0x131F, 0xFFFF }, {
0x0630, 0x0714, 0x0436, 0x0A08, 0x0E13, 0x0F17, 0xFFFF }, { 0x0700, 0x0624, 0x0906, 0x0338, 0x0A35, 0x1105, 0xFFFF }, {
0x081A, 0x0D23, 0x0C27, 0xFFFF }, { 0x0E11, 0x1509, 0x043B, 0x082F, 0xFFFF }, { 0x0712, 0x0534, 0x023A, 0x0F15, 0x1307,
0x1019, 0x0B2B, 0x013D, 0xFFFF }, { 0x0626, 0x0D0C, 0x042E, 0x0D31, 0x0F01, 0x1003, 0x0A2D, 0x121D, 0xFFFF }, { 0x0C33,
0x0D25, 0x0937, 0x111B, 0x1B0D, 0xFFFF }, { 0x0710, 0x0E21, 0x0739, 0x0C29, 0xFFFF }, { 0x0818, 0x052C, 0x0F13, 0x180B,
0x141F, 0xFFFF }, { 0x0632, 0x0802, 0x0B35, 0x1205, 0x1017, 0xFFFF }, { 0x0628, 0x091C, 0x0E23, 0x0D27, 0xFFFF }, {
0x0720, 0x0F11, 0x1609, 0x053B, 0x092F, 0xFFFF }, { 0x1119, 0x023D, 0xFFFF }, { 0x0816, 0x013C, 0x0A1E, 0x0E31, 0x1103,
0x1015, 0x1407, 0x0C2B, 0x0B2D, 0x131D, 0xFFFF }, { 0x0722, 0x0904, 0x0C0A, 0x1001, 0x0D33, 0x0E25, 0x0A37, 0x121B,
0xFFFF }, { 0x0F21, 0x0D29, 0x1C0D, 0xFFFF }, { 0x062A, 0x0839, 0x190B, 0x151F, 0xFFFF }, { 0x0730, 0x0814, 0x0536,
0x0B08, 0x1013, 0x1305, 0x1117, 0xFFFF }, { 0x0800, 0x0724, 0x0A06, 0x0438, 0x0F23, 0x0C35, 0x0E27, 0xFFFF }, { 0x091A,
0x1709, 0x063B, 0x0A2F, 0xFFFF }, { 0x1011, 0x1219, 0x033D, 0xFFFF }, { 0x0812, 0x0634, 0x033A, 0x0F31, 0x1203, 0x1115,
0x1507, 0x0D2B, 0xFFFF }, { 0x0726, 0x0E0C, 0x052E, 0x1101, 0x0E33, 0x0F25, 0x0B37, 0x131B, 0x0C2D, 0x141D, 0xFFFF }, {
0x0E29, 0x1D0D, 0xFFFF }, { 0x0810, 0x1021, 0x0939, 0x1A0B, 0x161F, 0xFFFF }, { 0x0918, 0x062C, 0x1113, 0x1217, 0xFFFF
}, { 0x0732, 0x0902, 0x0D35, 0x1405, 0x0F27, 0xFFFF }, { 0x0728, 0x0A1C, 0x1023, 0x073B, 0x0B2F, 0xFFFF }, { 0x0820,
0x1111, 0x1319, 0x1809, 0xFFFF }, { 0x1303, 0x1215, 0x1607, 0x0E2B, 0x043D, 0xFFFF }, { 0x0916, 0x023C, 0x0B1E, 0x1031,
0x1201, 0x0F33, 0x0D2D, 0x151D, 0xFFFF }, { 0x0822, 0x0A04, 0x0D0A, 0x1025, 0x0C37, 0x0F29, 0x141B, 0x1E0D, 0xFFFF }, {
0x1121, 0x0A39, 0x1B0B, 0x171F, 0xFFFF }, { 0x072A, 0x1213, 0x1317, 0xFFFF }, { 0x0830, 0x0914, 0x0636, 0x0C08, 0x0E35,
0x1505, 0xFFFF }, { 0x0900, 0x0824, 0x0B06, 0x0538, 0x1123, 0x1027, 0xFFFF }, { 0x0A1A, 0x1211, 0x1909, 0x083B, 0x0C2F,
0xFFFF }, { 0x1315, 0x1707, 0x1419, 0x0F2B, 0x053D, 0xFFFF }, { 0x0912, 0x0734, 0x043A, 0x1131, 0x1301, 0x1403, 0x0E2D,
0x161D, 0xFFFF }, { 0x0826, 0x0F0C, 0x062E, 0x1033, 0x1125, 0x0D37, 0x151B, 0x1F0D, 0xFFFF }, { 0x1221, 0x0B39, 0x1029,
0xFFFF }, { 0x0910, 0x1313, 0x1C0B, 0x181F, 0xFFFF }, { 0x0A18, 0x072C, 0x0F35, 0x1605, 0x1417, 0xFFFF }, { 0x0832,
0x0A02, 0x1223, 0x1127, 0xFFFF }, { 0x0828, 0x0B1C, 0x1311, 0x1A09, 0x093B, 0x0D2F, 0xFFFF }, { 0x0920, 0x1519, 0x063D,
0xFFFF }, { 0x1231, 0x1503, 0x1415, 0x1807, 0x102B, 0x0F2D, 0x171D, 0xFFFF }, { 0x0A16, 0x033C, 0x0C1E, 0x1401, 0x1133,
0x1225, 0x0E37, 0x161B, 0xFFFF }, { 0x0922, 0x0B04, 0x0E0A, 0x1321, 0x1129, 0xFFFF }, { 0x0C39, 0x1D0B, 0x191F, 0xFFFF
}, { 0x082A, 0x1413, 0x1705, 0x1517, 0xFFFF }, { 0x0930, 0x0A14, 0x0736, 0x0D08, 0x1323, 0x1035, 0x1227, 0xFFFF }, {
0x0A00, 0x0924, 0x0C06, 0x0638, 0x1B09, 0x0A3B, 0x0E2F, 0xFFFF }, { 0x0B1A, 0x1411, 0x1619, 0x073D, 0xFFFF }, { 0x1331,
0x1603, 0x1515, 0x1907, 0x112B, 0xFFFF }, { 0x0A12, 0x0834, 0x053A, 0x1501, 0x1233, 0x1325, 0x0F37, 0x171B, 0x102D,
0x181D, 0xFFFF }, { 0x0926, 0x072E, 0x1229, 0xFFFF }, { 0x1421, 0x0D39, 0x1E0B, 0x1A1F, 0xFFFF }, { 0x0A10, 0x1513,
0x1617, 0xFFFF }, { 0x0B18, 0x082C, 0x1135, 0x1805, 0x1327, 0xFFFF }, { 0x0932, 0x0B02, 0x1423, 0x0B3B, 0x0F2F, 0xFFFF
}, { 0x0928, 0x0C1C, 0x1511, 0x1719, 0x1C09, 0xFFFF }, { 0x0A20, 0x1703, 0x1615, 0x1A07, 0x122B, 0x083D, 0xFFFF }, {
0x1431, 0x1601, 0x1333, 0x112D, 0x191D, 0xFFFF }, { 0x0B16, 0x043C, 0x0D1E, 0x1425, 0x1037, 0x1329, 0x181B, 0xFFFF }, {
0x0A22, 0x0C04, 0x0F0A, 0x1521, 0x0E39, 0x1F0B, 0x1B1F, 0xFFFF }, { 0x1613, 0x1717, 0xFFFF }, { 0x092A, 0x1235, 0x1905,
0xFFFF }, { 0x0A30, 0x0B14, 0x0836, 0x0E08, 0x1523, 0x1427, 0xFFFF }, { 0x0B00, 0x0A24, 0x0D06, 0x0738, 0x1611, 0x1D09,
0x0C3B, 0x102F, 0xFFFF }, { 0x0C1A, 0x1715, 0x1B07, 0x1819, 0x132B, 0x093D, 0xFFFF }, { 0x1531, 0x1701, 0x1803, 0x122D,
0x1A1D, 0xFFFF }, { 0x0B12, 0x0934, 0x063A, 0x1433, 0x1525, 0x1137, 0x191B, 0xFFFF }, { 0x0A26, 0x003E, 0x082E, 0x1621,
0x0F39, 0x1429, 0x003F, 0xFFFF }, { 0x1713, 0x1C1F, 0xFFFF }, { 0x0B10, 0x1335, 0x1A05, 0x1817, 0xFFFF }, { 0x0C18,
0x092C, 0x1623, 0x1527, 0xFFFF }, { 0x0A32, 0x0C02, 0x1711, 0x1E09, 0x0D3B, 0x112F, 0xFFFF }, { 0x0A28, 0x0D1C, 0x1919,
0x0A3D, 0xFFFF }, { 0x0B20, 0x1631, 0x1903, 0x1815, 0x1C07, 0x142B, 0x132D, 0x1B1D, 0xFFFF }, { 0x1801, 0x1533, 0x1625,
0x1237, 0x1A1B, 0xFFFF }, { 0x0C16, 0x053C, 0x0E1E, 0x1721, 0x1529, 0x013F, 0xFFFF }, { 0x0B22, 0x0D04, 0x1039, 0x1D1F,
0xFFFF }, { 0x1813, 0x1B05, 0x1917, 0xFFFF }, { 0x0A2A, 0x1723, 0x1435, 0x1627, 0xFFFF }, { 0x0B30, 0x0C14, 0x0936,
0x0F08, 0x1F09, 0x0E3B, 0x122F, 0xFFFF }, { 0x0C00, 0x0B24, 0x0E06, 0x0838, 0x1811, 0x1A19, 0x0B3D, 0xFFFF }, { 0x0D1A,
0x1731, 0x1A03, 0x1915, 0x1D07, 0x152B, 0xFFFF }, { 0x1901, 0x1633, 0x1725, 0x1337, 0x1B1B, 0x142D, 0x1C1D, 0xFFFF }, {
0x0C12, 0x0A34, 0x073A, 0x1629, 0x023F, 0xFFFF }, { 0x0B26, 0x013E, 0x092E, 0x1821, 0x1139, 0x1E1F, 0xFFFF }, { 0x1913,
0x1A17, 0xFFFF }, { 0x0C10, 0x1535, 0x1C05, 0x1727, 0xFFFF }, { 0x0D18, 0x0A2C, 0x1823, 0x0F3B, 0x132F, 0xFFFF }, {
0x0B32, 0x0D02, 0x1911, 0x1B19, 0xFFFF }, { 0x0B28, 0x0E1C, 0x1B03, 0x1A15, 0x1E07, 0x162B, 0x0C3D, 0xFFFF }, { 0x0C20,
0x1831, 0x1A01, 0x1733, 0x152D, 0x1D1D, 0xFFFF }, { 0x1825, 0x1437, 0x1729, 0x1C1B, 0x033F, 0xFFFF }, { 0x0D16, 0x063C,
0x0F1E, 0x1921, 0x1239, 0x1F1F, 0xFFFF }, { 0x0C22, 0x0E04, 0x1A13, 0x1B17, 0xFFFF }, { 0x1635, 0x1D05, 0xFFFF }, {
0x0B2A, 0x1923, 0x1827, 0xFFFF }, { 0x0C30, 0x0D14, 0x0A36, 0x1A11, 0x103B, 0x142F, 0xFFFF }, { 0x0D00, 0x0C24, 0x0F06,
0x0938, 0x1B15, 0x1F07, 0x1C19, 0x172B, 0x0D3D, 0xFFFF }, { 0x0E1A, 0x1931, 0x1B01, 0x1C03, 0x162D, 0x1E1D, 0xFFFF }, {
0x1833, 0x1925, 0x1537, 0x1D1B, 0xFFFF }, { 0x0D12, 0x0B34, 0x083A, 0x1A21, 0x1339, 0x1829, 0x043F, 0xFFFF }, { 0x0C26,
0x023E, 0x0A2E, 0x1B13, 0xFFFF }, { 0x1735, 0x1E05, 0x1C17, 0xFFFF }, { 0x0D10, 0x1A23, 0x1927, 0xFFFF }, { 0x0E18,
0x0B2C, 0x1B11, 0x113B, 0x152F, 0xFFFF }, { 0x0C32, 0x0E02, 0x1D19, 0x0E3D, 0xFFFF }, { 0x0C28, 0x0F1C, 0x1A31, 0x1D03,
0x1C15, 0x182B, 0x172D, 0x1F1D, 0xFFFF }, { 0x0D20, 0x1C01, 0x1933, 0x1A25, 0x1637, 0x1E1B, 0xFFFF }, { 0x1B21, 0x1929,
0x053F, 0xFFFF }, { 0x0E16, 0x073C, 0x1439, 0xFFFF }, { 0x0D22, 0x0F04, 0x1C13, 0x1F05, 0x1D17, 0xFFFF }, { 0x1B23,
0x1835, 0x1A27, 0xFFFF }, { 0x0C2A, 0x123B, 0x162F, 0xFFFF }, { 0x0D30, 0x0E14, 0x0B36, 0x1C11, 0x1E19, 0x0F3D, 0xFFFF
}, { 0x0E00, 0x0D24, 0x0A38, 0x1B31, 0x1E03, 0x1D15, 0x192B, 0xFFFF }, { 0x0F1A, 0x1D01, 0x1A33, 0x1B25, 0x1737, 0x1F1B,
0x182D, 0xFFFF }, { 0x1A29, 0x063F, 0xFFFF }, { 0x0E12, 0x0C34, 0x093A, 0x1C21, 0x1539, 0xFFFF }, { 0x0D26, 0x033E,
0x0B2E, 0x1D13, 0x1E17, 0xFFFF }, { 0x1935, 0x1B27, 0xFFFF }, { 0x0E10, 0x1C23, 0x133B, 0x172F, 0xFFFF }, { 0x0F18,
0x0C2C, 0x1D11, 0x1F19, 0xFFFF }, { 0x0D32, 0x0F02, 0x1F03, 0x1E15, 0x1A2B, 0x103D, 0xFFFF }, { 0x0D28, 0x1C31, 0x1E01,
0x1B33, 0x192D, 0xFFFF }, { 0x0E20, 0x1C25, 0x1837, 0x1B29, 0x073F, 0xFFFF }, { 0x1D21, 0x1639, 0xFFFF }, { 0x0F16,
0x083C, 0x1E13, 0x1F17, 0xFFFF }, { 0x0E22, 0x1A35, 0xFFFF }, { 0x1D23, 0x1C27, 0xFFFF }, { 0x0D2A, 0x1E11, 0x143B,
0x182F, 0xFFFF }, { 0x0E30, 0x0F14, 0x0C36, 0x1F15, 0x1B2B, 0x113D, 0xFFFF }, { 0x0F00, 0x0E24, 0x0B38, 0x1D31, 0x1F01,
0x1A2D, 0xFFFF }, { 0x1C33, 0x1D25, 0x1937, 0xFFFF }, { 0x1E21, 0x1739, 0x1C29, 0x083F, 0xFFFF }, { 0x0F12, 0x0D34,
0x0A3A, 0x1F13, 0xFFFF }, { 0x0E26, 0x043E, 0x0C2E, 0x1B35, 0xFFFF }, { 0x1E23, 0x1D27, 0xFFFF }, { 0x0F10, 0x1F11,
0x153B, 0x192F, 0xFFFF }, { 0x0D2C, 0x123D, 0xFFFF },
};
uint16_t etc_block::pack_color5(const color_rgba& color, bool scaled, uint32_t bias)
{
return pack_color5(color.r, color.g, color.b, scaled, bias);
}
uint16_t etc_block::pack_color5(uint32_t r, uint32_t g, uint32_t b, bool scaled, uint32_t bias)
{
if (scaled)
{
r = (r * 31U + bias) / 255U;
g = (g * 31U + bias) / 255U;
b = (b * 31U + bias) / 255U;
}
r = minimum(r, 31U);
g = minimum(g, 31U);
b = minimum(b, 31U);
return static_cast<uint16_t>(b | (g << 5U) | (r << 10U));
}
color_rgba etc_block::unpack_color5(uint16_t packed_color5, bool scaled, uint32_t alpha)
{
uint32_t b = packed_color5 & 31U;
uint32_t g = (packed_color5 >> 5U) & 31U;
uint32_t r = (packed_color5 >> 10U) & 31U;
if (scaled)
{
b = (b << 3U) | (b >> 2U);
g = (g << 3U) | (g >> 2U);
r = (r << 3U) | (r >> 2U);
}
return color_rgba(cNoClamp, r, g, b, minimum(alpha, 255U));
}
void etc_block::unpack_color5(color_rgba& result, uint16_t packed_color5, bool scaled)
{
result = unpack_color5(packed_color5, scaled, 255);
}
void etc_block::unpack_color5(uint32_t& r, uint32_t& g, uint32_t& b, uint16_t packed_color5, bool scaled)
{
color_rgba c(unpack_color5(packed_color5, scaled, 0));
r = c.r;
g = c.g;
b = c.b;
}
bool etc_block::unpack_color5(color_rgba& result, uint16_t packed_color5, uint16_t packed_delta3, bool scaled, uint32_t alpha)
{
color_rgba_i16 dc(unpack_delta3(packed_delta3));
int b = (packed_color5 & 31U) + dc.b;
int g = ((packed_color5 >> 5U) & 31U) + dc.g;
int r = ((packed_color5 >> 10U) & 31U) + dc.r;
bool success = true;
if (static_cast<uint32_t>(r | g | b) > 31U)
{
success = false;
r = clamp<int>(r, 0, 31);
g = clamp<int>(g, 0, 31);
b = clamp<int>(b, 0, 31);
}
if (scaled)
{
b = (b << 3U) | (b >> 2U);
g = (g << 3U) | (g >> 2U);
r = (r << 3U) | (r >> 2U);
}
result.set_noclamp_rgba(r, g, b, minimum(alpha, 255U));
return success;
}
bool etc_block::unpack_color5(uint32_t& r, uint32_t& g, uint32_t& b, uint16_t packed_color5, uint16_t packed_delta3, bool scaled, uint32_t alpha)
{
color_rgba result;
const bool success = unpack_color5(result, packed_color5, packed_delta3, scaled, alpha);
r = result.r;
g = result.g;
b = result.b;
return success;
}
uint16_t etc_block::pack_delta3(const color_rgba_i16& color)
{
return pack_delta3(color.r, color.g, color.b);
}
uint16_t etc_block::pack_delta3(int r, int g, int b)
{
assert((r >= cETC1ColorDeltaMin) && (r <= cETC1ColorDeltaMax));
assert((g >= cETC1ColorDeltaMin) && (g <= cETC1ColorDeltaMax));
assert((b >= cETC1ColorDeltaMin) && (b <= cETC1ColorDeltaMax));
if (r < 0) r += 8;
if (g < 0) g += 8;
if (b < 0) b += 8;
return static_cast<uint16_t>(b | (g << 3) | (r << 6));
}
color_rgba_i16 etc_block::unpack_delta3(uint16_t packed_delta3)
{
int r = (packed_delta3 >> 6) & 7;
int g = (packed_delta3 >> 3) & 7;
int b = packed_delta3 & 7;
if (r >= 4) r -= 8;
if (g >= 4) g -= 8;
if (b >= 4) b -= 8;
return color_rgba_i16(r, g, b, 255);
}
void etc_block::unpack_delta3(int& r, int& g, int& b, uint16_t packed_delta3)
{
r = (packed_delta3 >> 6) & 7;
g = (packed_delta3 >> 3) & 7;
b = packed_delta3 & 7;
if (r >= 4) r -= 8;
if (g >= 4) g -= 8;
if (b >= 4) b -= 8;
}
uint16_t etc_block::pack_color4(const color_rgba& color, bool scaled, uint32_t bias)
{
return pack_color4(color.r, color.g, color.b, scaled, bias);
}
uint16_t etc_block::pack_color4(uint32_t r, uint32_t g, uint32_t b, bool scaled, uint32_t bias)
{
if (scaled)
{
r = (r * 15U + bias) / 255U;
g = (g * 15U + bias) / 255U;
b = (b * 15U + bias) / 255U;
}
r = minimum(r, 15U);
g = minimum(g, 15U);
b = minimum(b, 15U);
return static_cast<uint16_t>(b | (g << 4U) | (r << 8U));
}
color_rgba etc_block::unpack_color4(uint16_t packed_color4, bool scaled, uint32_t alpha)
{
uint32_t b = packed_color4 & 15U;
uint32_t g = (packed_color4 >> 4U) & 15U;
uint32_t r = (packed_color4 >> 8U) & 15U;
if (scaled)
{
b = (b << 4U) | b;
g = (g << 4U) | g;
r = (r << 4U) | r;
}
return color_rgba(cNoClamp, r, g, b, minimum(alpha, 255U));
}
void etc_block::unpack_color4(uint32_t& r, uint32_t& g, uint32_t& b, uint16_t packed_color4, bool scaled)
{
color_rgba c(unpack_color4(packed_color4, scaled, 0));
r = c.r;
g = c.g;
b = c.b;
}
void etc_block::get_diff_subblock_colors(color_rgba* pDst, uint16_t packed_color5, uint32_t table_idx)
{
assert(table_idx < cETC1IntenModifierValues);
const int *pInten_modifer_table = &g_etc1_inten_tables[table_idx][0];
uint32_t r, g, b;
unpack_color5(r, g, b, packed_color5, true);
const int ir = static_cast<int>(r), ig = static_cast<int>(g), ib = static_cast<int>(b);
const int y0 = pInten_modifer_table[0];
pDst[0].set(ir + y0, ig + y0, ib + y0, 255);
const int y1 = pInten_modifer_table[1];
pDst[1].set(ir + y1, ig + y1, ib + y1, 255);
const int y2 = pInten_modifer_table[2];
pDst[2].set(ir + y2, ig + y2, ib + y2, 255);
const int y3 = pInten_modifer_table[3];
pDst[3].set(ir + y3, ig + y3, ib + y3, 255);
}
bool etc_block::get_diff_subblock_colors(color_rgba* pDst, uint16_t packed_color5, uint16_t packed_delta3, uint32_t table_idx)
{
assert(table_idx < cETC1IntenModifierValues);
const int *pInten_modifer_table = &g_etc1_inten_tables[table_idx][0];
uint32_t r, g, b;
bool success = unpack_color5(r, g, b, packed_color5, packed_delta3, true);
const int ir = static_cast<int>(r), ig = static_cast<int>(g), ib = static_cast<int>(b);
const int y0 = pInten_modifer_table[0];
pDst[0].set(ir + y0, ig + y0, ib + y0, 255);
const int y1 = pInten_modifer_table[1];
pDst[1].set(ir + y1, ig + y1, ib + y1, 255);
const int y2 = pInten_modifer_table[2];
pDst[2].set(ir + y2, ig + y2, ib + y2, 255);
const int y3 = pInten_modifer_table[3];
pDst[3].set(ir + y3, ig + y3, ib + y3, 255);
return success;
}
void etc_block::get_abs_subblock_colors(color_rgba* pDst, uint16_t packed_color4, uint32_t table_idx)
{
assert(table_idx < cETC1IntenModifierValues);
const int *pInten_modifer_table = &g_etc1_inten_tables[table_idx][0];
uint32_t r, g, b;
unpack_color4(r, g, b, packed_color4, true);
const int ir = static_cast<int>(r), ig = static_cast<int>(g), ib = static_cast<int>(b);
const int y0 = pInten_modifer_table[0];
pDst[0].set(ir + y0, ig + y0, ib + y0, 255);
const int y1 = pInten_modifer_table[1];
pDst[1].set(ir + y1, ig + y1, ib + y1, 255);
const int y2 = pInten_modifer_table[2];
pDst[2].set(ir + y2, ig + y2, ib + y2, 255);
const int y3 = pInten_modifer_table[3];
pDst[3].set(ir + y3, ig + y3, ib + y3, 255);
}
bool unpack_etc1(const etc_block& block, color_rgba *pDst, bool preserve_alpha)
{
const bool diff_flag = block.get_diff_bit();
const bool flip_flag = block.get_flip_bit();
const uint32_t table_index0 = block.get_inten_table(0);
const uint32_t table_index1 = block.get_inten_table(1);
color_rgba subblock_colors0[4];
color_rgba subblock_colors1[4];
if (diff_flag)
{
const uint16_t base_color5 = block.get_base5_color();
const uint16_t delta_color3 = block.get_delta3_color();
etc_block::get_diff_subblock_colors(subblock_colors0, base_color5, table_index0);
if (!etc_block::get_diff_subblock_colors(subblock_colors1, base_color5, delta_color3, table_index1))
return false;
}
else
{
const uint16_t base_color4_0 = block.get_base4_color(0);
etc_block::get_abs_subblock_colors(subblock_colors0, base_color4_0, table_index0);
const uint16_t base_color4_1 = block.get_base4_color(1);
etc_block::get_abs_subblock_colors(subblock_colors1, base_color4_1, table_index1);
}
if (preserve_alpha)
{
if (flip_flag)
{
for (uint32_t y = 0; y < 2; y++)
{
pDst[0].set_rgb(subblock_colors0[block.get_selector(0, y)]);
pDst[1].set_rgb(subblock_colors0[block.get_selector(1, y)]);
pDst[2].set_rgb(subblock_colors0[block.get_selector(2, y)]);
pDst[3].set_rgb(subblock_colors0[block.get_selector(3, y)]);
pDst += 4;
}
for (uint32_t y = 2; y < 4; y++)
{
pDst[0].set_rgb(subblock_colors1[block.get_selector(0, y)]);
pDst[1].set_rgb(subblock_colors1[block.get_selector(1, y)]);
pDst[2].set_rgb(subblock_colors1[block.get_selector(2, y)]);
pDst[3].set_rgb(subblock_colors1[block.get_selector(3, y)]);
pDst += 4;
}
}
else
{
for (uint32_t y = 0; y < 4; y++)
{
pDst[0].set_rgb(subblock_colors0[block.get_selector(0, y)]);
pDst[1].set_rgb(subblock_colors0[block.get_selector(1, y)]);
pDst[2].set_rgb(subblock_colors1[block.get_selector(2, y)]);
pDst[3].set_rgb(subblock_colors1[block.get_selector(3, y)]);
pDst += 4;
}
}
}
else
{
if (flip_flag)
{
// 0000
// 0000
// 1111
// 1111
for (uint32_t y = 0; y < 2; y++)
{
pDst[0] = subblock_colors0[block.get_selector(0, y)];
pDst[1] = subblock_colors0[block.get_selector(1, y)];
pDst[2] = subblock_colors0[block.get_selector(2, y)];
pDst[3] = subblock_colors0[block.get_selector(3, y)];
pDst += 4;
}
for (uint32_t y = 2; y < 4; y++)
{
pDst[0] = subblock_colors1[block.get_selector(0, y)];
pDst[1] = subblock_colors1[block.get_selector(1, y)];
pDst[2] = subblock_colors1[block.get_selector(2, y)];
pDst[3] = subblock_colors1[block.get_selector(3, y)];
pDst += 4;
}
}
else
{
// 0011
// 0011
// 0011
// 0011
for (uint32_t y = 0; y < 4; y++)
{
pDst[0] = subblock_colors0[block.get_selector(0, y)];
pDst[1] = subblock_colors0[block.get_selector(1, y)];
pDst[2] = subblock_colors1[block.get_selector(2, y)];
pDst[3] = subblock_colors1[block.get_selector(3, y)];
pDst += 4;
}
}
}
return true;
}
inline int extend_6_to_8(uint32_t n)
{
return (n << 2) | (n >> 4);
}
inline int extend_7_to_8(uint32_t n)
{
return (n << 1) | (n >> 6);
}
inline int extend_4_to_8(uint32_t n)
{
return (n << 4) | n;
}
uint64_t etc_block::evaluate_etc1_error(const color_rgba* pBlock_pixels, bool perceptual, int subblock_index) const
{
color_rgba unpacked_block[16];
unpack_etc1(*this, unpacked_block);
uint64_t total_error = 0;
if (subblock_index < 0)
{
for (uint32_t i = 0; i < 16; i++)
total_error += color_distance(perceptual, pBlock_pixels[i], unpacked_block[i], false);
}
else
{
const bool flip_bit = get_flip_bit();
for (uint32_t i = 0; i < 8; i++)
{
const uint32_t idx = g_etc1_pixel_indices[flip_bit][subblock_index][i];
total_error += color_distance(perceptual, pBlock_pixels[idx], unpacked_block[idx], false);
}
}
return total_error;
}
void etc_block::get_subblock_pixels(color_rgba* pPixels, int subblock_index) const
{
if (subblock_index < 0)
unpack_etc1(*this, pPixels);
else
{
color_rgba unpacked_block[16];
unpack_etc1(*this, unpacked_block);
const bool flip_bit = get_flip_bit();
for (uint32_t i = 0; i < 8; i++)
{
const uint32_t idx = g_etc1_pixel_indices[flip_bit][subblock_index][i];
pPixels[i] = unpacked_block[idx];
}
}
}
bool etc1_optimizer::compute()
{
assert(m_pResult->m_pSelectors);
if ((m_pParams->m_pForce_selectors) || (m_pParams->m_pEval_solution_override))
{
assert(m_pParams->m_quality >= cETCQualitySlow);
}
const uint32_t n = m_pParams->m_num_src_pixels;
if (m_pParams->m_cluster_fit)
{
if (m_pParams->m_quality == cETCQualityFast)
compute_internal_cluster_fit(4);
else if (m_pParams->m_quality == cETCQualityMedium)
compute_internal_cluster_fit(32);
else if (m_pParams->m_quality == cETCQualitySlow)
compute_internal_cluster_fit(64);
else
compute_internal_cluster_fit(BASISU_ETC1_CLUSTER_FIT_ORDER_TABLE_SIZE);
}
else
compute_internal_neighborhood(m_br, m_bg, m_bb);
if (!m_best_solution.m_valid)
{
m_pResult->m_error = UINT32_MAX;
return false;
}
const uint8_t* pSelectors = &m_best_solution.m_selectors[0];
#ifdef BASISU_BUILD_DEBUG
if (m_pParams->m_pEval_solution_override == nullptr)
{
color_rgba block_colors[4];
m_best_solution.m_coords.get_block_colors(block_colors);
const color_rgba* pSrc_pixels = m_pParams->m_pSrc_pixels;
uint64_t actual_error = 0;
for (uint32_t i = 0; i < n; i++)
{
if ((m_pParams->m_perceptual) && (m_pParams->m_quality >= cETCQualitySlow))
actual_error += color_distance(true, pSrc_pixels[i], block_colors[pSelectors[i]], false);
else
actual_error += color_distance(pSrc_pixels[i], block_colors[pSelectors[i]], false);
}
assert(actual_error == m_best_solution.m_error);
}
#endif
m_pResult->m_error = m_best_solution.m_error;
m_pResult->m_block_color_unscaled = m_best_solution.m_coords.m_unscaled_color;
m_pResult->m_block_color4 = m_best_solution.m_coords.m_color4;
m_pResult->m_block_inten_table = m_best_solution.m_coords.m_inten_table;
memcpy(m_pResult->m_pSelectors, pSelectors, n);
m_pResult->m_n = n;
return true;
}
void etc1_optimizer::refine_solution(uint32_t max_refinement_trials)
{
// Now we have the input block, the avg. color of the input pixels, a set of trial selector indices, and the block color+intensity index.
// Now, for each component, attempt to refine the current solution by solving a simple linear equation. For example, for 4 colors:
// The goal is:
// pixel0 - (block_color+inten_table[selector0]) + pixel1 - (block_color+inten_table[selector1]) + pixel2 - (block_color+inten_table[selector2]) + pixel3 - (block_color+inten_table[selector3]) = 0
// Rearranging this:
// (pixel0 + pixel1 + pixel2 + pixel3) - (block_color+inten_table[selector0]) - (block_color+inten_table[selector1]) - (block_color+inten_table[selector2]) - (block_color+inten_table[selector3]) = 0
// (pixel0 + pixel1 + pixel2 + pixel3) - block_color - inten_table[selector0] - block_color-inten_table[selector1] - block_color-inten_table[selector2] - block_color-inten_table[selector3] = 0
// (pixel0 + pixel1 + pixel2 + pixel3) - 4*block_color - inten_table[selector0] - inten_table[selector1] - inten_table[selector2] - inten_table[selector3] = 0
// (pixel0 + pixel1 + pixel2 + pixel3) - 4*block_color - (inten_table[selector0] + inten_table[selector1] + inten_table[selector2] + inten_table[selector3]) = 0
// (pixel0 + pixel1 + pixel2 + pixel3)/4 - block_color - (inten_table[selector0] + inten_table[selector1] + inten_table[selector2] + inten_table[selector3])/4 = 0
// block_color = (pixel0 + pixel1 + pixel2 + pixel3)/4 - (inten_table[selector0] + inten_table[selector1] + inten_table[selector2] + inten_table[selector3])/4
// So what this means:
// optimal_block_color = avg_input - avg_inten_delta
// So the optimal block color can be computed by taking the average block color and subtracting the current average of the intensity delta.
// Unfortunately, optimal_block_color must then be quantized to 555 or 444 so it's not always possible to improve matters using this formula.
// Also, the above formula is for unclamped intensity deltas. The actual implementation takes into account clamping.
const uint32_t n = m_pParams->m_num_src_pixels;
for (uint32_t refinement_trial = 0; refinement_trial < max_refinement_trials; refinement_trial++)
{
const uint8_t* pSelectors = &m_best_solution.m_selectors[0];
const int* pInten_table = g_etc1_inten_tables[m_best_solution.m_coords.m_inten_table];
int delta_sum_r = 0, delta_sum_g = 0, delta_sum_b = 0;
const color_rgba base_color(m_best_solution.m_coords.get_scaled_color());
for (uint32_t r = 0; r < n; r++)
{
const uint32_t s = *pSelectors++;
const int yd_temp = pInten_table[s];
// Compute actual delta being applied to each pixel, taking into account clamping.
delta_sum_r += clamp<int>(base_color.r + yd_temp, 0, 255) - base_color.r;
delta_sum_g += clamp<int>(base_color.g + yd_temp, 0, 255) - base_color.g;
delta_sum_b += clamp<int>(base_color.b + yd_temp, 0, 255) - base_color.b;
}
if ((!delta_sum_r) && (!delta_sum_g) && (!delta_sum_b))
break;
const float avg_delta_r_f = static_cast<float>(delta_sum_r) / n;
const float avg_delta_g_f = static_cast<float>(delta_sum_g) / n;
const float avg_delta_b_f = static_cast<float>(delta_sum_b) / n;
const int br1 = clamp<int>(static_cast<uint32_t>((m_avg_color[0] - avg_delta_r_f) * m_limit / 255.0f + .5f), 0, m_limit);
const int bg1 = clamp<int>(static_cast<uint32_t>((m_avg_color[1] - avg_delta_g_f) * m_limit / 255.0f + .5f), 0, m_limit);
const int bb1 = clamp<int>(static_cast<uint32_t>((m_avg_color[2] - avg_delta_b_f) * m_limit / 255.0f + .5f), 0, m_limit);
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Refinement trial %u, avg_delta %f %f %f\n", refinement_trial, avg_delta_r_f, avg_delta_g_f, avg_delta_b_f);
#endif
if (!evaluate_solution(etc1_solution_coordinates(br1, bg1, bb1, 0, m_pParams->m_use_color4), m_trial_solution, &m_best_solution))
break;
} // refinement_trial
}
void etc1_optimizer::compute_internal_neighborhood(int scan_r, int scan_g, int scan_b)
{
if (m_best_solution.m_error == 0)
return;
const uint32_t n = m_pParams->m_num_src_pixels;
const int scan_delta_size = m_pParams->m_scan_delta_size;
// Scan through a subset of the 3D lattice centered around the avg block color trying each 3D (555 or 444) lattice point as a potential block color.
// Each time a better solution is found try to refine the current solution's block color based of the current selectors and intensity table index.
for (int zdi = 0; zdi < scan_delta_size; zdi++)
{
const int zd = m_pParams->m_pScan_deltas[zdi];
const int mbb = scan_b + zd;
if (mbb < 0) continue; else if (mbb > m_limit) break;
for (int ydi = 0; ydi < scan_delta_size; ydi++)
{
const int yd = m_pParams->m_pScan_deltas[ydi];
const int mbg = scan_g + yd;
if (mbg < 0) continue; else if (mbg > m_limit) break;
for (int xdi = 0; xdi < scan_delta_size; xdi++)
{
const int xd = m_pParams->m_pScan_deltas[xdi];
const int mbr = scan_r + xd;
if (mbr < 0) continue; else if (mbr > m_limit) break;
etc1_solution_coordinates coords(mbr, mbg, mbb, 0, m_pParams->m_use_color4);
if (!evaluate_solution(coords, m_trial_solution, &m_best_solution))
continue;
if (m_pParams->m_refinement)
{
refine_solution((m_pParams->m_quality == cETCQualityFast) ? 2 : (((xd | yd | zd) == 0) ? 4 : 2));
}
} // xdi
} // ydi
} // zdi
}
void etc1_optimizer::compute_internal_cluster_fit(uint32_t total_perms_to_try)
{
if ((!m_best_solution.m_valid) || ((m_br != m_best_solution.m_coords.m_unscaled_color.r) || (m_bg != m_best_solution.m_coords.m_unscaled_color.g) || (m_bb != m_best_solution.m_coords.m_unscaled_color.b)))
{
evaluate_solution(etc1_solution_coordinates(m_br, m_bg, m_bb, 0, m_pParams->m_use_color4), m_trial_solution, &m_best_solution);
}
if ((m_best_solution.m_error == 0) || (!m_best_solution.m_valid))
return;
for (uint32_t i = 0; i < total_perms_to_try; i++)
{
int delta_sum_r = 0, delta_sum_g = 0, delta_sum_b = 0;
const int *pInten_table = g_etc1_inten_tables[m_best_solution.m_coords.m_inten_table];
const color_rgba base_color(m_best_solution.m_coords.get_scaled_color());
const uint8_t *pNum_selectors = g_cluster_fit_order_tab[i].m_v;
for (uint32_t q = 0; q < 4; q++)
{
const int yd_temp = pInten_table[q];
delta_sum_r += pNum_selectors[q] * (clamp<int>(base_color.r + yd_temp, 0, 255) - base_color.r);
delta_sum_g += pNum_selectors[q] * (clamp<int>(base_color.g + yd_temp, 0, 255) - base_color.g);
delta_sum_b += pNum_selectors[q] * (clamp<int>(base_color.b + yd_temp, 0, 255) - base_color.b);
}
if ((!delta_sum_r) && (!delta_sum_g) && (!delta_sum_b))
continue;
const float avg_delta_r_f = static_cast<float>(delta_sum_r) / 8;
const float avg_delta_g_f = static_cast<float>(delta_sum_g) / 8;
const float avg_delta_b_f = static_cast<float>(delta_sum_b) / 8;
const int br1 = clamp<int>(static_cast<uint32_t>((m_avg_color[0] - avg_delta_r_f) * m_limit / 255.0f + .5f), 0, m_limit);
const int bg1 = clamp<int>(static_cast<uint32_t>((m_avg_color[1] - avg_delta_g_f) * m_limit / 255.0f + .5f), 0, m_limit);
const int bb1 = clamp<int>(static_cast<uint32_t>((m_avg_color[2] - avg_delta_b_f) * m_limit / 255.0f + .5f), 0, m_limit);
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Second refinement trial %u, avg_delta %f %f %f\n", i, avg_delta_r_f, avg_delta_g_f, avg_delta_b_f);
#endif
evaluate_solution(etc1_solution_coordinates(br1, bg1, bb1, 0, m_pParams->m_use_color4), m_trial_solution, &m_best_solution);
if (m_best_solution.m_error == 0)
break;
}
}
void etc1_optimizer::init(const params& params, results& result)
{
m_pParams = &params;
m_pResult = &result;
const uint32_t n = m_pParams->m_num_src_pixels;
m_selectors.resize(n);
m_best_selectors.resize(n);
m_temp_selectors.resize(n);
m_trial_solution.m_selectors.resize(n);
m_best_solution.m_selectors.resize(n);
m_limit = m_pParams->m_use_color4 ? 15 : 31;
vec3F avg_color(0.0f);
m_luma.resize(n);
m_sorted_luma_indices.resize(n);
m_sorted_luma.resize(n);
for (uint32_t i = 0; i < n; i++)
{
const color_rgba& c = m_pParams->m_pSrc_pixels[i];
const vec3F fc(c.r, c.g, c.b);
avg_color += fc;
m_luma[i] = static_cast<uint16_t>(c.r + c.g + c.b);
m_sorted_luma_indices[i] = i;
}
avg_color /= static_cast<float>(n);
m_avg_color = avg_color;
m_br = clamp<int>(static_cast<uint32_t>(m_avg_color[0] * m_limit / 255.0f + .5f), 0, m_limit);
m_bg = clamp<int>(static_cast<uint32_t>(m_avg_color[1] * m_limit / 255.0f + .5f), 0, m_limit);
m_bb = clamp<int>(static_cast<uint32_t>(m_avg_color[2] * m_limit / 255.0f + .5f), 0, m_limit);
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Avg block color: %u %u %u\n", m_br, m_bg, m_bb);
#endif
if (m_pParams->m_quality <= cETCQualityMedium)
{
indirect_sort(n, &m_sorted_luma_indices[0], &m_luma[0]);
m_pSorted_luma = &m_sorted_luma[0];
m_pSorted_luma_indices = &m_sorted_luma_indices[0];
for (uint32_t i = 0; i < n; i++)
m_pSorted_luma[i] = m_luma[m_pSorted_luma_indices[i]];
}
m_best_solution.m_coords.clear();
m_best_solution.m_valid = false;
m_best_solution.m_error = UINT64_MAX;
m_solutions_tried.clear();
}
bool etc1_optimizer::evaluate_solution_slow(const etc1_solution_coordinates& coords, potential_solution& trial_solution, potential_solution* pBest_solution)
{
uint32_t k = coords.m_unscaled_color.r | (coords.m_unscaled_color.g << 8) | (coords.m_unscaled_color.b << 16);
if (!m_solutions_tried.insert(k).second)
return false;
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Eval solution: %u %u %u\n", coords.m_unscaled_color.r, coords.m_unscaled_color.g, coords.m_unscaled_color.b);
#endif
trial_solution.m_valid = false;
if (m_pParams->m_constrain_against_base_color5)
{
const int dr = (int)coords.m_unscaled_color.r - (int)m_pParams->m_base_color5.r;
const int dg = (int)coords.m_unscaled_color.g - (int)m_pParams->m_base_color5.g;
const int db = (int)coords.m_unscaled_color.b - (int)m_pParams->m_base_color5.b;
if ((minimum(dr, dg, db) < cETC1ColorDeltaMin) || (maximum(dr, dg, db) > cETC1ColorDeltaMax))
{
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Eval failed due to constraint from %u %u %u\n", m_pParams->m_base_color5.r, m_pParams->m_base_color5.g, m_pParams->m_base_color5.b);
#endif
return false;
}
}
const color_rgba base_color(coords.get_scaled_color());
const uint32_t n = m_pParams->m_num_src_pixels;
assert(trial_solution.m_selectors.size() == n);
trial_solution.m_error = UINT64_MAX;
const uint8_t *pSelectors_to_use = m_pParams->m_pForce_selectors;
for (uint32_t inten_table = 0; inten_table < cETC1IntenModifierValues; inten_table++)
{
const int* pInten_table = g_etc1_inten_tables[inten_table];
color_rgba block_colors[4];
for (uint32_t s = 0; s < 4; s++)
{
const int yd = pInten_table[s];
block_colors[s].set(base_color.r + yd, base_color.g + yd, base_color.b + yd, 255);
}
uint64_t total_error = 0;
const color_rgba* pSrc_pixels = m_pParams->m_pSrc_pixels;
for (uint32_t c = 0; c < n; c++)
{
const color_rgba& src_pixel = *pSrc_pixels++;
uint32_t best_selector_index = 0;
uint32_t best_error = 0;
if (pSelectors_to_use)
{
best_selector_index = pSelectors_to_use[c];
best_error = color_distance(m_pParams->m_perceptual, src_pixel, block_colors[best_selector_index], false);
}
else
{
best_error = color_distance(m_pParams->m_perceptual, src_pixel, block_colors[0], false);
uint32_t trial_error = color_distance(m_pParams->m_perceptual, src_pixel, block_colors[1], false);
if (trial_error < best_error)
{
best_error = trial_error;
best_selector_index = 1;
}
trial_error = color_distance(m_pParams->m_perceptual, src_pixel, block_colors[2], false);
if (trial_error < best_error)
{
best_error = trial_error;
best_selector_index = 2;
}
trial_error = color_distance(m_pParams->m_perceptual, src_pixel, block_colors[3], false);
if (trial_error < best_error)
{
best_error = trial_error;
best_selector_index = 3;
}
}
m_temp_selectors[c] = static_cast<uint8_t>(best_selector_index);
total_error += best_error;
if ((m_pParams->m_pEval_solution_override == nullptr) && (total_error >= trial_solution.m_error))
break;
}
if (m_pParams->m_pEval_solution_override)
{
if (!(*m_pParams->m_pEval_solution_override)(total_error, *m_pParams, block_colors, &m_temp_selectors[0], coords))
return false;
}
if (total_error < trial_solution.m_error)
{
trial_solution.m_error = total_error;
trial_solution.m_coords.m_inten_table = inten_table;
trial_solution.m_selectors.swap(m_temp_selectors);
trial_solution.m_valid = true;
}
}
trial_solution.m_coords.m_unscaled_color = coords.m_unscaled_color;
trial_solution.m_coords.m_color4 = m_pParams->m_use_color4;
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Eval done: %u error: %I64u best error so far: %I64u\n", (trial_solution.m_error < pBest_solution->m_error), trial_solution.m_error, pBest_solution->m_error);
#endif
bool success = false;
if (pBest_solution)
{
if (trial_solution.m_error < pBest_solution->m_error)
{
*pBest_solution = trial_solution;
success = true;
}
}
return success;
}
bool etc1_optimizer::evaluate_solution_fast(const etc1_solution_coordinates& coords, potential_solution& trial_solution, potential_solution* pBest_solution)
{
uint32_t k = coords.m_unscaled_color.r | (coords.m_unscaled_color.g << 8) | (coords.m_unscaled_color.b << 16);
if (!m_solutions_tried.insert(k).second)
return false;
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Eval solution fast: %u %u %u\n", coords.m_unscaled_color.r, coords.m_unscaled_color.g, coords.m_unscaled_color.b);
#endif
if (m_pParams->m_constrain_against_base_color5)
{
const int dr = (int)coords.m_unscaled_color.r - (int)m_pParams->m_base_color5.r;
const int dg = (int)coords.m_unscaled_color.g - (int)m_pParams->m_base_color5.g;
const int db = (int)coords.m_unscaled_color.b - (int)m_pParams->m_base_color5.b;
if ((minimum(dr, dg, db) < cETC1ColorDeltaMin) || (maximum(dr, dg, db) > cETC1ColorDeltaMax))
{
trial_solution.m_valid = false;
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Eval failed due to constraint from %u %u %u\n", m_pParams->m_base_color5.r, m_pParams->m_base_color5.g, m_pParams->m_base_color5.b);
#endif
return false;
}
}
const color_rgba base_color(coords.get_scaled_color());
const uint32_t n = m_pParams->m_num_src_pixels;
assert(trial_solution.m_selectors.size() == n);
trial_solution.m_error = UINT64_MAX;
for (int inten_table = cETC1IntenModifierValues - 1; inten_table >= 0; --inten_table)
{
const int* pInten_table = g_etc1_inten_tables[inten_table];
uint32_t block_inten[4];
color_rgba block_colors[4];
for (uint32_t s = 0; s < 4; s++)
{
const int yd = pInten_table[s];
color_rgba block_color(base_color.r + yd, base_color.g + yd, base_color.b + yd, 255);
block_colors[s] = block_color;
block_inten[s] = block_color.r + block_color.g + block_color.b;
}
// evaluate_solution_fast() enforces/assumesd a total ordering of the input colors along the intensity (1,1,1) axis to more quickly classify the inputs to selectors.
// The inputs colors have been presorted along the projection onto this axis, and ETC1 block colors are always ordered along the intensity axis, so this classification is fast.
// 0 1 2 3
// 01 12 23
const uint32_t block_inten_midpoints[3] = { block_inten[0] + block_inten[1], block_inten[1] + block_inten[2], block_inten[2] + block_inten[3] };
uint64_t total_error = 0;
const color_rgba* pSrc_pixels = m_pParams->m_pSrc_pixels;
if ((m_pSorted_luma[n - 1] * 2) < block_inten_midpoints[0])
{
if (block_inten[0] > m_pSorted_luma[n - 1])
{
const uint32_t min_error = iabs((int)block_inten[0] - (int)m_pSorted_luma[n - 1]);
if (min_error >= trial_solution.m_error)
continue;
}
memset(&m_temp_selectors[0], 0, n);
for (uint32_t c = 0; c < n; c++)
total_error += color_distance(block_colors[0], pSrc_pixels[c], false);
}
else if ((m_pSorted_luma[0] * 2) >= block_inten_midpoints[2])
{
if (m_pSorted_luma[0] > block_inten[3])
{
const uint32_t min_error = iabs((int)m_pSorted_luma[0] - (int)block_inten[3]);
if (min_error >= trial_solution.m_error)
continue;
}
memset(&m_temp_selectors[0], 3, n);
for (uint32_t c = 0; c < n; c++)
total_error += color_distance(block_colors[3], pSrc_pixels[c], false);
}
else
{
uint32_t cur_selector = 0, c;
for (c = 0; c < n; c++)
{
const uint32_t y = m_pSorted_luma[c];
while ((y * 2) >= block_inten_midpoints[cur_selector])
if (++cur_selector > 2)
goto done;
const uint32_t sorted_pixel_index = m_pSorted_luma_indices[c];
m_temp_selectors[sorted_pixel_index] = static_cast<uint8_t>(cur_selector);
total_error += color_distance(block_colors[cur_selector], pSrc_pixels[sorted_pixel_index], false);
}
done:
while (c < n)
{
const uint32_t sorted_pixel_index = m_pSorted_luma_indices[c];
m_temp_selectors[sorted_pixel_index] = 3;
total_error += color_distance(block_colors[3], pSrc_pixels[sorted_pixel_index], false);
++c;
}
}
if (total_error < trial_solution.m_error)
{
trial_solution.m_error = total_error;
trial_solution.m_coords.m_inten_table = inten_table;
trial_solution.m_selectors.swap(m_temp_selectors);
trial_solution.m_valid = true;
if (!total_error)
break;
}
}
trial_solution.m_coords.m_unscaled_color = coords.m_unscaled_color;
trial_solution.m_coords.m_color4 = m_pParams->m_use_color4;
#if BASISU_DEBUG_ETC_ENCODER_DEEPER
printf("Eval done: %u error: %I64u best error so far: %I64u\n", (trial_solution.m_error < pBest_solution->m_error), trial_solution.m_error, pBest_solution->m_error);
#endif
bool success = false;
if (pBest_solution)
{
if (trial_solution.m_error < pBest_solution->m_error)
{
*pBest_solution = trial_solution;
success = true;
}
}
return success;
}
static uint32_t etc1_decode_value(uint32_t diff, uint32_t inten, uint32_t selector, uint32_t packed_c)
{
const uint32_t limit = diff ? 32 : 16; limit;
assert((diff < 2) && (inten < 8) && (selector < 4) && (packed_c < limit));
int c;
if (diff)
c = (packed_c >> 2) | (packed_c << 3);
else
c = packed_c | (packed_c << 4);
c += g_etc1_inten_tables[inten][selector];
c = clamp<int>(c, 0, 255);
return c;
}
void pack_etc1_block_init()
{
for (uint32_t diff = 0; diff < 2; diff++)
{
const uint32_t limit = diff ? 32 : 16;
for (uint32_t inten = 0; inten < 8; inten++)
{
for (uint32_t selector = 0; selector < 4; selector++)
{
const uint32_t inverse_table_index = diff + (inten << 1) + (selector << 4);
for (uint32_t color = 0; color < 256; color++)
{
uint32_t best_error = UINT32_MAX, best_packed_c = 0;
for (uint32_t packed_c = 0; packed_c < limit; packed_c++)
{
int v = etc1_decode_value(diff, inten, selector, packed_c);
uint32_t err = iabs(v - static_cast<int>(color));
if (err < best_error)
{
best_error = err;
best_packed_c = packed_c;
if (!best_error)
break;
}
}
assert(best_error <= 255);
g_etc1_inverse_lookup[inverse_table_index][color] = static_cast<uint16_t>(best_packed_c | (best_error << 8));
}
}
}
}
}
// Packs solid color blocks efficiently using a set of small precomputed tables.
// For random 888 inputs, MSE results are better than Erricson's ETC1 packer in "slow" mode ~9.5% of the time, is slightly worse only ~.01% of the time, and is equal the rest of the time.
uint64_t pack_etc1_block_solid_color(etc_block& block, const uint8_t* pColor, bool diff_only)
{
assert(g_etc1_inverse_lookup[0][255]);
static uint32_t s_next_comp[4] = { 1, 2, 0, 1 };
uint32_t best_error = UINT32_MAX, best_i = 0;
int best_x = 0, best_packed_c1 = 0, best_packed_c2 = 0;
// For each possible 8-bit value, there is a precomputed list of diff/inten/selector configurations that allow that 8-bit value to be encoded with no error.
for (uint32_t i = 0; i < 3; i++)
{
const uint32_t c1 = pColor[s_next_comp[i]], c2 = pColor[s_next_comp[i + 1]];
const int delta_range = 1;
for (int delta = -delta_range; delta <= delta_range; delta++)
{
const int c_plus_delta = clamp<int>(pColor[i] + delta, 0, 255);
const uint16_t* pTable;
if (!c_plus_delta)
pTable = g_color8_to_etc_block_config_0_255[0];
else if (c_plus_delta == 255)
pTable = g_color8_to_etc_block_config_0_255[1];
else
pTable = g_color8_to_etc_block_config_1_to_254[c_plus_delta - 1];
do
{
const uint32_t x = *pTable++;
const bool diff = (x & 1) != 0;
if ((diff_only) && (!diff))
continue;
#ifdef BASISU_BUILD_DEBUG
const uint32_t diff = x & 1;
const uint32_t inten = (x >> 1) & 7;
const uint32_t selector = (x >> 4) & 3;
const uint32_t p0 = (x >> 8) & 255;
assert(etc1_decode_value(diff, inten, selector, p0) == (uint32_t)c_plus_delta);
#endif
const uint16_t* pInverse_table = g_etc1_inverse_lookup[x & 0xFF];
uint16_t p1 = pInverse_table[c1];
uint16_t p2 = pInverse_table[c2];
const uint32_t trial_error = square(c_plus_delta - pColor[i]) + square(p1 >> 8) + square(p2 >> 8);
if (trial_error < best_error)
{
best_error = trial_error;
best_x = x;
best_packed_c1 = p1 & 0xFF;
best_packed_c2 = p2 & 0xFF;
best_i = i;
if (!best_error)
goto found_perfect_match;
}
} while (*pTable != 0xFFFF);
}
}
found_perfect_match:
if (best_error == UINT32_MAX)
return UINT64_MAX;
const uint32_t diff = best_x & 1;
const uint32_t inten = (best_x >> 1) & 7;
block.m_bytes[3] = static_cast<uint8_t>(((inten | (inten << 3)) << 2) | (diff << 1));
const uint32_t etc1_selector = g_selector_index_to_etc1[(best_x >> 4) & 3];
*reinterpret_cast<uint16_t*>(&block.m_bytes[4]) = (etc1_selector & 2) ? 0xFFFF : 0;
*reinterpret_cast<uint16_t*>(&block.m_bytes[6]) = (etc1_selector & 1) ? 0xFFFF : 0;
const uint32_t best_packed_c0 = (best_x >> 8) & 255;
if (diff)
{
block.m_bytes[best_i] = static_cast<uint8_t>(best_packed_c0 << 3);
block.m_bytes[s_next_comp[best_i]] = static_cast<uint8_t>(best_packed_c1 << 3);
block.m_bytes[s_next_comp[best_i + 1]] = static_cast<uint8_t>(best_packed_c2 << 3);
}
else
{
block.m_bytes[best_i] = static_cast<uint8_t>(best_packed_c0 | (best_packed_c0 << 4));
block.m_bytes[s_next_comp[best_i]] = static_cast<uint8_t>(best_packed_c1 | (best_packed_c1 << 4));
block.m_bytes[s_next_comp[best_i + 1]] = static_cast<uint8_t>(best_packed_c2 | (best_packed_c2 << 4));
}
return best_error;
}
static uint32_t pack_etc1_block_solid_color_constrained(
etc1_optimizer::results& results,
uint32_t num_colors, const uint8_t *pColor,
bool use_diff,
const color_rgba* pBase_color5_unscaled)
{
assert(g_etc1_inverse_lookup[0][255]);
static uint32_t s_next_comp[4] = { 1, 2, 0, 1 };
uint32_t best_error = UINT32_MAX, best_i = 0;
int best_x = 0, best_packed_c1 = 0, best_packed_c2 = 0;
// For each possible 8-bit value, there is a precomputed list of diff/inten/selector configurations that allow that 8-bit value to be encoded with no error.
for (uint32_t i = 0; i < 3; i++)
{
const uint32_t c1 = pColor[s_next_comp[i]], c2 = pColor[s_next_comp[i + 1]];
const int delta_range = 1;
for (int delta = -delta_range; delta <= delta_range; delta++)
{
const int c_plus_delta = clamp<int>(pColor[i] + delta, 0, 255);
const uint16_t* pTable;
if (!c_plus_delta)
pTable = g_color8_to_etc_block_config_0_255[0];
else if (c_plus_delta == 255)
pTable = g_color8_to_etc_block_config_0_255[1];
else
pTable = g_color8_to_etc_block_config_1_to_254[c_plus_delta - 1];
do
{
const uint32_t x = *pTable++;
const uint32_t diff = x & 1;
if (static_cast<uint32_t>(use_diff) != diff)
{
if (*pTable == 0xFFFF)
break;
continue;
}
if ((diff) && (pBase_color5_unscaled))
{
const int p0 = (x >> 8) & 255;
int delta_temp = p0 - static_cast<int>(pBase_color5_unscaled->m_comps[i]);
if ((delta_temp < cETC1ColorDeltaMin) || (delta_temp > cETC1ColorDeltaMax))
{
if (*pTable == 0xFFFF)
break;
continue;
}
}
#ifdef BASISU_BUILD_DEBUG
{
const uint32_t inten = (x >> 1) & 7;
const uint32_t selector = (x >> 4) & 3;
const uint32_t p0 = (x >> 8) & 255;
assert(etc1_decode_value(diff, inten, selector, p0) == (uint32_t)c_plus_delta);
}
#endif
const uint16_t* pInverse_table = g_etc1_inverse_lookup[x & 0xFF];
uint16_t p1 = pInverse_table[c1];
uint16_t p2 = pInverse_table[c2];
if ((diff) && (pBase_color5_unscaled))
{
int delta1 = (p1 & 0xFF) - static_cast<int>(pBase_color5_unscaled->m_comps[s_next_comp[i]]);
int delta2 = (p2 & 0xFF) - static_cast<int>(pBase_color5_unscaled->m_comps[s_next_comp[i + 1]]);
if ((delta1 < cETC1ColorDeltaMin) || (delta1 > cETC1ColorDeltaMax) || (delta2 < cETC1ColorDeltaMin) || (delta2 > cETC1ColorDeltaMax))
{
if (*pTable == 0xFFFF)
break;
continue;
}
}
const uint32_t trial_error = square(c_plus_delta - pColor[i]) + square(p1 >> 8) + square(p2 >> 8);
if (trial_error < best_error)
{
best_error = trial_error;
best_x = x;
best_packed_c1 = p1 & 0xFF;
best_packed_c2 = p2 & 0xFF;
best_i = i;
if (!best_error)
goto found_perfect_match;
}
} while (*pTable != 0xFFFF);
}
}
found_perfect_match:
if (best_error == UINT32_MAX)
return best_error;
best_error *= num_colors;
results.m_n = num_colors;
results.m_block_color4 = !(best_x & 1);
results.m_block_inten_table = (best_x >> 1) & 7;
memset(results.m_pSelectors, (best_x >> 4) & 3, num_colors);
const uint32_t best_packed_c0 = (best_x >> 8) & 255;
results.m_block_color_unscaled[best_i] = static_cast<uint8_t>(best_packed_c0);
results.m_block_color_unscaled[s_next_comp[best_i]] = static_cast<uint8_t>(best_packed_c1);
results.m_block_color_unscaled[s_next_comp[best_i + 1]] = static_cast<uint8_t>(best_packed_c2);
results.m_error = best_error;
return best_error;
}
static bool invoke_optimizer(etc1_optimizer::results &results, pack_etc1_block_context &context, etc1_optimizer::params &params, const basis_etc1_pack_params &pack_params)
{
if (!pack_params.m_cluster_fit)
{
if (params.m_quality >= cETCQualitySlow)
{
static const int s_scan_delta_0_to_4[] = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
params.m_scan_delta_size = BASISU_ARRAY_SIZE(s_scan_delta_0_to_4);
params.m_pScan_deltas = s_scan_delta_0_to_4;
}
else if (params.m_quality == cETCQualityMedium)
{
static const int s_scan_delta_0_to_1[] = { -1, 0, 1 };
params.m_scan_delta_size = BASISU_ARRAY_SIZE(s_scan_delta_0_to_1);
params.m_pScan_deltas = s_scan_delta_0_to_1;
}
else
{
static const int s_scan_delta_0[] = { 0 };
params.m_scan_delta_size = BASISU_ARRAY_SIZE(s_scan_delta_0);
params.m_pScan_deltas = s_scan_delta_0;
}
}
context.m_optimizer.init(params, results);
bool optimizer_succeeded = context.m_optimizer.compute();
#if BASISU_DEBUG_ETC_ENCODER
printf("Optimizer succeeded: %u, First optimization pass error: %I64u\n", optimizer_succeeded, results);
#endif
if (!pack_params.m_cluster_fit)
{
// Fairly arbitrary/unrefined thresholds that control how far away to scan for potentially better solutions.
const uint32_t refinement_error_thresh0 = 3000 * (results.m_n / 8);
const uint32_t refinement_error_thresh1 = 6000 * (results.m_n / 8);
if ((params.m_quality >= cETCQualityMedium) && ((results.m_error > refinement_error_thresh0) || (!optimizer_succeeded)))
{
if (params.m_quality == cETCQualityMedium)
{
static const int s_scan_delta_2_to_3[] = { -3, -2, 2, 3 };
params.m_scan_delta_size = BASISU_ARRAY_SIZE(s_scan_delta_2_to_3);
params.m_pScan_deltas = s_scan_delta_2_to_3;
}
else
{
static const int s_scan_delta_5_to_5[] = { -5, 5 };
static const int s_scan_delta_5_to_8[] = { -8, -7, -6, -5, 5, 6, 7, 8 };
if (results.m_error > refinement_error_thresh1)
{
params.m_scan_delta_size = BASISU_ARRAY_SIZE(s_scan_delta_5_to_8);
params.m_pScan_deltas = s_scan_delta_5_to_8;
}
else
{
params.m_scan_delta_size = BASISU_ARRAY_SIZE(s_scan_delta_5_to_5);
params.m_pScan_deltas = s_scan_delta_5_to_5;
}
}
if (context.m_optimizer.compute())
optimizer_succeeded = true;
#if BASISU_DEBUG_ETC_ENCODER
printf("Second optimization pass error: %I64u\n", results.m_error);
#endif
}
}
return optimizer_succeeded;
}
static vec3F color_quad_u8_to_f(const color_rgba& c)
{
return vec3F(static_cast<float>(c.r), static_cast<float>(c.g), static_cast<float>(c.b));
}
// Returns distance from color c to the gray line going through color d.
static inline float gray_distance2(const color_rgba& c, const vec3F& d)
{
float gray_dist = static_cast<float>(((c.r - d[0]) + (c.g - d[1]) + (c.b - d[2]))) * (1.0f / 3.0f);
// Project onto the gray line
float gray_point_r = clamp(d[0] + gray_dist, 0.0f, 255.0f);
float gray_point_g = clamp(d[1] + gray_dist, 0.0f, 255.0f);
float gray_point_b = clamp(d[2] + gray_dist, 0.0f, 255.0f);
// Compute distance^2
float dist_to_gray_point_r = static_cast<float>(c.r) - gray_point_r;
float dist_to_gray_point_g = static_cast<float>(c.g) - gray_point_g;
float dist_to_gray_point_b = static_cast<float>(c.b) - gray_point_b;
return dist_to_gray_point_r * dist_to_gray_point_r + dist_to_gray_point_g * dist_to_gray_point_g + dist_to_gray_point_b * dist_to_gray_point_b;
}
bool pack_etc1_estimate_flipped(const color_rgba* pSrc_pixels)
{
vec3F sums[2][2];
#define GET_XY(x, y) color_quad_u8_to_f(pSrc_pixels[(x) + ((y) * 4)])
sums[0][0] = GET_XY(0, 0) + GET_XY(0, 1) + GET_XY(1, 0) + GET_XY(1, 1);
sums[1][0] = GET_XY(2, 0) + GET_XY(2, 1) + GET_XY(3, 0) + GET_XY(3, 1);
sums[0][1] = GET_XY(0, 2) + GET_XY(0, 3) + GET_XY(1, 2) + GET_XY(1, 3);
sums[1][1] = GET_XY(2, 2) + GET_XY(2, 3) + GET_XY(3, 2) + GET_XY(3, 3);
vec3F upper_avg((sums[0][0] + sums[1][0]) * (1.0f / 8.0f));
vec3F lower_avg((sums[0][1] + sums[1][1]) * (1.0f / 8.0f));
vec3F left_avg((sums[0][0] + sums[0][1]) * (1.0f / 8.0f));
vec3F right_avg((sums[1][0] + sums[1][1]) * (1.0f / 8.0f));
#undef GET_XY
#define GET_XY(x, y, a) gray_distance2(pSrc_pixels[(x) + ((y) * 4)], a)
float upper_gray_dist = 0.0f, lower_gray_dist = 0.0f, left_gray_dist = 0.0f, right_gray_dist = 0.0f;
for (uint32_t i = 0; i < 4; i++)
{
for (uint32_t j = 0; j < 2; j++)
{
upper_gray_dist += GET_XY(i, j, upper_avg);
lower_gray_dist += GET_XY(i, 2 + j, lower_avg);
left_gray_dist += GET_XY(j, i, left_avg);
right_gray_dist += GET_XY(2 + j, i, right_avg);
}
}
#undef GET_XY
float upper_lower_sum = upper_gray_dist + lower_gray_dist;
float left_right_sum = left_gray_dist + right_gray_dist;
return upper_lower_sum < left_right_sum;
}
uint64_t pack_etc1_block(etc_block& dst_block, const color_rgba* pSrc_pixels, basis_etc1_pack_params& pack_params, pack_etc1_block_context& context, const uint8_t* pForce_selectors)
{
#if BASISU_DEBUG_ETC_ENCODER
printf("** Start of block\n");
#endif
color_rgba src_pixel0(pSrc_pixels[0]);
if (!pForce_selectors)
{
int r;
for (r = 15; r >= 1; --r)
if ((pSrc_pixels[r].r != src_pixel0.r) || (pSrc_pixels[r].g != src_pixel0.g) || (pSrc_pixels[r].b != src_pixel0.b))
break;
if (!r)
{
#if BASISU_DEBUG_ETC_ENCODER
printf("** Block is a single solid color\n");
#endif
uint64_t err = 16 * pack_etc1_block_solid_color(dst_block, &pSrc_pixels[0].r);
dst_block.set_flip_bit(true);
return err;
}
}
uint64_t best_error = UINT64_MAX;
uint32_t best_flip = false, best_use_color4 = false, best_is_subset = false;
uint8_t best_selectors[2][8];
etc1_optimizer::results best_results[2];
for (uint32_t i = 0; i < 2; i++)
{
best_results[i].m_n = 8;
best_results[i].m_pSelectors = best_selectors[i];
best_results[i].m_block_color_unscaled.set(0, 0, 0, 255);
best_results[i].m_block_inten_table = 0;
best_results[i].m_error = 0;
best_results[i].m_block_color4 = false;
}
uint8_t best_subset_selectors[16];
etc1_optimizer::results best_subset_results;
best_subset_results.m_n = 16;
best_subset_results.m_pSelectors = best_subset_selectors;
if (((pForce_selectors) && (pack_params.m_quality >= cETCQualitySlow)) || (pack_params.m_force_etc1s))
{
// TODO: This may only be useful in force selector mode.
uint8_t subset_selectors[16];
etc1_optimizer::params subset_params(pack_params);
subset_params.m_num_src_pixels = 16;
subset_params.m_pSrc_pixels = pSrc_pixels;
subset_params.m_pForce_selectors = pForce_selectors;
etc1_optimizer::results subset_results;
subset_results.m_n = 16;
subset_results.m_pSelectors = subset_selectors;
for (uint32_t use_color4 = 0; use_color4 < (uint32_t)(pack_params.m_use_color4 ? 2 : 1); use_color4++)
{
subset_params.m_use_color4 = (use_color4 != 0);
if (!invoke_optimizer(subset_results, context, subset_params, subset_params))
break;
if (subset_results.m_error < best_error)
{
best_error = subset_results.m_error;
best_flip = true;
best_use_color4 = use_color4;
best_subset_results = subset_results;
best_results[0] = subset_results;
best_results[1] = subset_results;
best_is_subset = true;
}
}
}
if ((best_error > 0) && (!pack_params.m_force_etc1s))
{
uint8_t selectors[3][8];
etc1_optimizer::results results[3];
for (uint32_t i = 0; i < 3; i++)
{
results[i].m_n = 8;
results[i].m_pSelectors = selectors[i];
}
color_rgba subblock_pixels[8];
etc1_optimizer::params params(pack_params);
params.m_num_src_pixels = 8;
params.m_pSrc_pixels = subblock_pixels;
uint8_t forced_selectors[8];
params.m_pForce_selectors = pForce_selectors ? forced_selectors : nullptr;
uint32_t first_flip = 0;
uint32_t last_flip = 2;
if (pack_params.m_quality < cETCQualitySlow)
{
const bool should_flip = pack_etc1_estimate_flipped(pSrc_pixels);
first_flip = should_flip;
last_flip = first_flip + 1;
}
for (uint32_t flip = first_flip; flip < last_flip; flip++)
{
for (uint32_t use_color4 = 0; use_color4 < (uint32_t)(pack_params.m_use_color4 ? 2 : 1); use_color4++)
{
uint64_t trial_error = 0;
uint32_t subblock;
for (subblock = 0; subblock < 2; subblock++)
{
#if BASISU_DEBUG_ETC_ENCODER
printf("** Flip: %u, Color4: %u, Subblock: %u, Best error so far: %I64u\n", flip, use_color4, subblock, best_error);
#endif
if (flip)
{
memcpy(subblock_pixels, pSrc_pixels + subblock * 8, sizeof(color_rgba) * 8);
if (pForce_selectors)
memcpy(forced_selectors, pForce_selectors + subblock * 8, 8);
}
else
{
const color_rgba* pSrc_col = pSrc_pixels + subblock * 2;
subblock_pixels[0] = pSrc_col[0]; subblock_pixels[1] = pSrc_col[4]; subblock_pixels[2] = pSrc_col[8]; subblock_pixels[3] = pSrc_col[12];
subblock_pixels[4] = pSrc_col[1]; subblock_pixels[5] = pSrc_col[5]; subblock_pixels[6] = pSrc_col[9]; subblock_pixels[7] = pSrc_col[13];
if (pForce_selectors)
{
const uint8_t* pSrc_sel = pForce_selectors + subblock * 2;
forced_selectors[0] = pSrc_sel[0]; forced_selectors[1] = pSrc_sel[4]; forced_selectors[2] = pSrc_sel[8]; forced_selectors[3] = pSrc_sel[12];
forced_selectors[4] = pSrc_sel[1]; forced_selectors[5] = pSrc_sel[5]; forced_selectors[6] = pSrc_sel[9]; forced_selectors[7] = pSrc_sel[13];
}
}
results[2].m_error = UINT64_MAX;
if ((params.m_quality >= cETCQualityMedium) && ((subblock) || (use_color4)) && (pForce_selectors == nullptr))
{
color_rgba subblock_pixel0(subblock_pixels[0]);
int r;
for (r = 7; r >= 1; --r)
if ((subblock_pixels[r].r != subblock_pixel0.r) || (subblock_pixels[r].g != subblock_pixel0.g) || (subblock_pixels[r].b != subblock_pixel0.b))
break;
if (!r)
{
pack_etc1_block_solid_color_constrained(results[2], 8, &subblock_pixel0.r, !use_color4, (subblock && !use_color4) ? &results[0].m_block_color_unscaled : nullptr);
}
}
params.m_use_color4 = (use_color4 != 0);
params.m_constrain_against_base_color5 = false;
if ((!use_color4) && (subblock))
{
params.m_constrain_against_base_color5 = true;
params.m_base_color5 = results[0].m_block_color_unscaled;
}
if (!invoke_optimizer(results[subblock], context, params, pack_params))
{
#if BASISU_DEBUG_ETC_ENCODER
printf("Optimizer failed\n");
#endif
break;
#if 0
if (!pack_params.m_cluster_fit)
break;
// TODO: Test this, is it worth it?
basis_etc1_pack_params temp_pack_params(pack_params);
temp_pack_params.m_cluster_fit = false;
if (!invoke_optimizer(results[subblock], context, params, temp_pack_params))
{
#if BASISU_DEBUG_ETC_ENCODER
printf("Optimizer failed again\n");
#endif
break;
}
else
{
#if BASISU_DEBUG_ETC_ENCODER
printf("Optimizer succeeded once cluster fit was disabled\n");
#endif
}
#endif
}
#if BASISU_DEBUG_ETC_ENCODER
printf("First optimization pass error: %I64u\n", results[subblock].m_error);
#endif
if (results[2].m_error < results[subblock].m_error)
{
#if BASISU_DEBUG_ETC_ENCODER
printf("Switching to solid block results for sublock (%I64u vs. %I64u error)\n", results[2].m_error, results[subblock].m_error);
#endif
results[subblock] = results[2];
}
trial_error += results[subblock].m_error;
if (trial_error >= best_error)
{
#if BASISU_DEBUG_ETC_ENCODER
printf("Early out, trial error %I64u vs. current best error %I64u\n", trial_error, best_error);
#endif
break;
}
} // subblock
if (subblock < 2)
continue;
if ((pack_params.m_flip_bias > 0.0f) && (flip) && (pack_params.m_quality >= cETCQualitySlow))
{
uint64_t nonflip_error = best_error;
uint64_t flip_error = trial_error;
if (!(flip_error < nonflip_error * pack_params.m_flip_bias))
continue;
}
best_error = trial_error;
best_results[0] = results[0];
best_results[1] = results[1];
best_flip = flip;
best_use_color4 = use_color4;
best_is_subset = false;
} // use_color4
} // flip
}
#if BASISU_DEBUG_ETC_ENCODER
printf("Best error: %I64u\n", best_error);
#endif
int dr = best_results[1].m_block_color_unscaled.r - best_results[0].m_block_color_unscaled.r;
int dg = best_results[1].m_block_color_unscaled.g - best_results[0].m_block_color_unscaled.g;
int db = best_results[1].m_block_color_unscaled.b - best_results[0].m_block_color_unscaled.b;
if (!best_use_color4)
{
if ((minimum(dr, dg, db) < cETC1ColorDeltaMin) || (maximum(dr, dg, db) > cETC1ColorDeltaMax))
{
assert(0);
#if 0
printf("%i %i %i, %i %i %i\n",
best_results[0].m_block_color_unscaled.r,
best_results[0].m_block_color_unscaled.g,
best_results[0].m_block_color_unscaled.b,
best_results[1].m_block_color_unscaled.r,
best_results[1].m_block_color_unscaled.g,
best_results[1].m_block_color_unscaled.b);
#endif
// Shouldn't ever happen!
}
}
if (best_use_color4)
{
dst_block.m_bytes[0] = static_cast<uint8_t>(best_results[1].m_block_color_unscaled.r | (best_results[0].m_block_color_unscaled.r << 4));
dst_block.m_bytes[1] = static_cast<uint8_t>(best_results[1].m_block_color_unscaled.g | (best_results[0].m_block_color_unscaled.g << 4));
dst_block.m_bytes[2] = static_cast<uint8_t>(best_results[1].m_block_color_unscaled.b | (best_results[0].m_block_color_unscaled.b << 4));
}
else
{
if (dr < 0) dr += 8;
if (dg < 0) dg += 8;
if (db < 0) db += 8;
dst_block.m_bytes[0] = static_cast<uint8_t>((best_results[0].m_block_color_unscaled.r << 3) | dr);
dst_block.m_bytes[1] = static_cast<uint8_t>((best_results[0].m_block_color_unscaled.g << 3) | dg);
dst_block.m_bytes[2] = static_cast<uint8_t>((best_results[0].m_block_color_unscaled.b << 3) | db);
}
dst_block.m_bytes[3] = static_cast<uint8_t>((best_results[1].m_block_inten_table << 2) | (best_results[0].m_block_inten_table << 5) | ((~best_use_color4 & 1) << 1) | best_flip);
uint32_t selector0 = 0, selector1 = 0;
if (best_is_subset)
{
const uint8_t* pSelectors0 = best_subset_results.m_pSelectors;
const uint8_t* pSelectors1 = best_subset_results.m_pSelectors + 8;
for (int x = 3; x >= 0; --x)
{
uint32_t b;
b = g_selector_index_to_etc1[pSelectors1[4 + x]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors1[x]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors0[4 + x]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors0[x]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
}
}
else if (best_flip)
{
// flipped:
// { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 },
// { 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 }
//
// { 0, 2 }, { 1, 2 }, { 2, 2 }, { 3, 2 },
// { 0, 3 }, { 1, 3 }, { 2, 3 }, { 3, 3 }
const uint8_t* pSelectors0 = best_results[0].m_pSelectors;
const uint8_t* pSelectors1 = best_results[1].m_pSelectors;
for (int x = 3; x >= 0; --x)
{
uint32_t b;
b = g_selector_index_to_etc1[pSelectors1[4 + x]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors1[x]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors0[4 + x]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors0[x]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
}
}
else
{
// non-flipped:
// { 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 },
// { 1, 0 }, { 1, 1 }, { 1, 2 }, { 1, 3 }
//
// { 2, 0 }, { 2, 1 }, { 2, 2 }, { 2, 3 },
// { 3, 0 }, { 3, 1 }, { 3, 2 }, { 3, 3 }
for (int subblock = 1; subblock >= 0; --subblock)
{
const uint8_t* pSelectors = best_results[subblock].m_pSelectors + 4;
for (uint32_t i = 0; i < 2; i++)
{
uint32_t b;
b = g_selector_index_to_etc1[pSelectors[3]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors[2]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors[1]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
b = g_selector_index_to_etc1[pSelectors[0]];
selector0 = (selector0 << 1) | (b & 1); selector1 = (selector1 << 1) | (b >> 1);
pSelectors -= 4;
}
}
}
dst_block.m_bytes[4] = static_cast<uint8_t>(selector1 >> 8);
dst_block.m_bytes[5] = static_cast<uint8_t>(selector1 & 0xFF);
dst_block.m_bytes[6] = static_cast<uint8_t>(selector0 >> 8);
dst_block.m_bytes[7] = static_cast<uint8_t>(selector0 & 0xFF);
return best_error;
}
} // namespace basisu