// bc7e_scalar.cpp - Pure scalar C++17 port of bc7e.ispc (auto-derived, then // hand-fixed). De-SIMD'd: the single SPMD foreach over blocks becomes a plain // for loop; uniform/varying are stripped (one lane/block, no gang), and the // ISPC stdlib surface is provided by ispc_compat.h. Encodes one 4x4 BC7 block // per iteration. Logic/math and the public API are preserved verbatim. #include "basisu_bc7e_scalar.h" #include #include // memset, memcpy #include #include #include // --- ISPC compatibility shim (formerly ispc_compat.h, inlined here so this .cpp // --- plus basisu_bc7e_scalar.h are fully self-contained). Supplies the small ISPC // --- stdlib surface the de-SIMD'd code uses, the base integer type aliases, and // --- the coherent control-flow keyword macros. Kept in the .cpp (not the public // --- header) so these global templates/macros don't leak into the rest of the project. // ISPC base integer type aliases (the fixed-width *_t names come from ). typedef int8_t int8; typedef int16_t int16; typedef int32_t int32; typedef int64_t int64; // ISPC "coherent" control-flow keywords -> plain C++ control flow (no-op at gang width 1). #define cif if #define cfor for #define cwhile while // Mark an intentionally-unused variable/parameter (silences C4100/C4189). #define NOTE_UNUSED(x) (void)(x) // Range-checked cast for a float that is known to hold a small non-negative integral // value (e.g. an 8-bit channel min/max). Asserts the value is in [0,255] in debug, then // narrows to uint32_t. Used at the few float->uint sites so the truncation is explicit // and guarded rather than an implicit (and portability-warning-triggering) conversion. static inline uint32_t float_to_uint8(float v) { assert((v >= 0.0f) && (v <= 255.0f)); return (uint32_t)v; } // ISPC stdlib scalar equivalents. min/max match minss/maxss semantics (the second // operand is returned on an unordered/NaN comparison), and clamp() is defined as // min(max(v,lo),hi) -- NOT a direct ternary -- so clamp(NaN,lo,hi) yields lo just as // it does under ISPC (a direct ternary would yield NaN, which casts to INT_MIN and // becomes an out-of-bounds index). template static inline T min(T a, T b) { return a < b ? a : b; } template static inline T max(T a, T b) { return a > b ? a : b; } template static inline T clamp(T v, T lo, T hi) { return min(max(v, lo), hi); } template static inline T select(bool c, T a, T b) { return c ? a : b; } template static inline bool all(T v) { return (bool)v; } template static inline bool any(T v) { return (bool)v; } template static inline bool none(T v) { return !(bool)v; } template static inline T abs(T v) { return v < 0 ? -v : v; } // floor()/sqrt() on floats resolve to the global float overloads provides under MSVC. namespace bc7e_scalar { // TEMP debug checkpoint (single-threaded runs only). Prints the source line so // the last value before a crash pinpoints the failing statement. Remove once verified. #define HERE() do { fprintf(stderr, "L%d\n", __LINE__); fflush(stderr); } while(0) // Tiny bias added to data-dependent denominators that can legitimately be zero // (flat blocks / flat channels -> zero endpoint deltas). The original SIMD code // let the divide produce +/-Inf/NaN and relied on a later min/max clamp to // sanitize it; in scalar code an unguarded 0/0 NaN can become an INT_MIN array // index. Biasing keeps the divisor finite and nonzero with negligible effect on // the result (denominators are otherwise >= 1 in these spots). static const float BC7E_DENOM_BIAS = 0.0000125f; // bc7e.ispc - Fast high quality SIMD BC7 encoder // Copyright (C) 2018-2020 Binomial LLC, All rights reserved. Apache 2.0 license - see LICENSE. // Typically compiled as: ispc -g -O2 "%(Filename).ispc" -o "$(TargetDir)%(Filename).obj" -h "$(ProjectDir)%(Filename)_ispc.h" --target=sse2,sse4,avx,avx2 --opt=fast-math --opt=disable-assertions // --opt=fast-math is optional (doesn't make much if any measurable difference). // Thanks to ArasP for the determinism fix. #define BC7E_NON_DETERMINISTIC (0) #define BC7E_2SUBSET_CHECKERBOARD_PARTITION_INDEX (34) #define BC7E_BLOCK_SIZE (16) #define BC7E_MAX_PARTITIONS0 (16) #define BC7E_MAX_PARTITIONS1 (64) #define BC7E_MAX_PARTITIONS2 (64) #define BC7E_MAX_PARTITIONS3 (64) #define BC7E_MAX_PARTITIONS7 (64) #define BC7E_MAX_UBER_LEVEL (4) // endpoint_err::m_error is a real uint16_t (was a 64-bit alias inherited from the ISPC source). // Verified safe to narrow: all stores are single-channel squared errors (k-c)^2 <= 65025, // guarded by safe_cast_uint16(); reads only compare or sum 3-4 of them (integer-promoted to // int, max ~260100), so 16 bits is sufficient and no 64-bit width is relied upon. #ifndef UINT16_MAX #define UINT16_MAX (0xFFFF) #endif #ifndef UINT_MAX #define UINT_MAX (0xFFFFFFFFU) #endif #ifndef UINT64_MAX #define UINT64_MAX (0xFFFFFFFFFFFFFFFFULL) #endif #ifndef INT64_MAX #define INT64_MAX (0x7FFFFFFFFFFFFFFFULL) #endif static inline int32_t clampi( int32_t value, int32_t low, int32_t high) { return clamp(value, low, high); } [[maybe_unused]] static inline uint32_t clampu( uint32_t value, uint32_t low, uint32_t high) { return clamp(value, low, high); } static inline float clampf( float value, float low, float high) { return clamp(value, low, high); } static inline float saturate( float value) { return clampf(value, 0, 1.0f); } [[maybe_unused]] static inline float saturate255( float value) { return clampf(value, 0, 255.0f); } [[maybe_unused]] static inline uint8_t minimumub( uint8_t a, uint8_t b) { return min(a, b); } static inline int32_t minimumi( int32_t a, int32_t b) { return min(a, b); } static inline uint32_t minimumu( uint32_t a, uint32_t b) { return min(a, b); } [[maybe_unused]] static inline uint64_t minimumu64( uint64_t a, uint64_t b) { return min(a, b); } static inline float minimumf( float a, float b) { return min(a, b); } [[maybe_unused]] static inline uint8_t maximumub( uint8_t a, uint8_t b) { return max(a, b); } static inline int32_t maximumi( int32_t a, int32_t b) { return max(a, b); } static inline uint32_t maximumu( uint32_t a, uint32_t b) { return max(a, b); } static inline float maximumf( float a, float b) { return max(a, b); } static inline int32_t iabs32( int32_t v) { uint32_t msk = v >> 31; return (v ^ msk) - msk; } [[maybe_unused]] static inline void swapub( uint8_t * a, uint8_t * b) { uint8_t t = *a; *a = *b; *b = t; } static inline void swapu( uint32_t * a, uint32_t * b) { uint32_t t = *a; *a = *b; *b = t; } static inline void swapi( int32_t * a, int32_t * b) { int32_t t = *a; *a = *b; *b = t; } static inline void swapf( float * a, float * b) { float t = *a; *a = *b; *b = t; } static inline float square(float s) { return s * s; } [[maybe_unused]] static inline int square(int s) { return s * s; } struct color_quad_u8 { uint8_t m_c[4]; }; struct color_quad_i { int32_t m_c[4]; }; struct color_quad_f { float m_c[4]; }; [[maybe_unused]] static inline color_quad_i component_min_rgb(const color_quad_i * pA, const color_quad_i * pB) { color_quad_i res; res.m_c[0] = minimumi(pA->m_c[0], pB->m_c[0]); res.m_c[1] = minimumi(pA->m_c[1], pB->m_c[1]); res.m_c[2] = minimumi(pA->m_c[2], pB->m_c[2]); res.m_c[3] = 255; return res; } [[maybe_unused]] static inline color_quad_i component_max_rgb(const color_quad_i * pA, const color_quad_i * pB) { color_quad_i res; res.m_c[0] = maximumi(pA->m_c[0], pB->m_c[0]); res.m_c[1] = maximumi(pA->m_c[1], pB->m_c[1]); res.m_c[2] = maximumi(pA->m_c[2], pB->m_c[2]); res.m_c[3] = 255; return res; } static inline color_quad_i *color_quad_i_set_clamped( color_quad_i * pRes, int32_t r, int32_t g, int32_t b, int32_t a) { pRes->m_c[0] = clampi(r, 0, 255); pRes->m_c[1] = clampi(g, 0, 255); pRes->m_c[2] = clampi(b, 0, 255); pRes->m_c[3] = clampi(a, 0, 255); return pRes; } static inline color_quad_i *color_quad_i_set( color_quad_i * pRes, int32_t r, int32_t g, int32_t b, int32_t a) { pRes->m_c[0] = r; pRes->m_c[1] = g; pRes->m_c[2] = b; pRes->m_c[3] = a; return pRes; } static inline bool color_quad_i_equals(const color_quad_i * pLHS, const color_quad_i * pRHS) { return (pLHS->m_c[0] == pRHS->m_c[0]) && (pLHS->m_c[1] == pRHS->m_c[1]) && (pLHS->m_c[2] == pRHS->m_c[2]) && (pLHS->m_c[3] == pRHS->m_c[3]); } static inline bool color_quad_i_notequals(const color_quad_i * pLHS, const color_quad_i * pRHS) { return !color_quad_i_equals(pLHS, pRHS); } struct vec4F { float m_c[4]; }; static inline vec4F * vec4F_set_scalar( vec4F * pV, float x) { pV->m_c[0] = x; pV->m_c[1] = x; pV->m_c[2] = x; pV->m_c[3] = x; return pV; } static inline vec4F * vec4F_set( vec4F * pV, float x, float y, float z, float w) { pV->m_c[0] = x; pV->m_c[1] = y; pV->m_c[2] = z; pV->m_c[3] = w; return pV; } static inline vec4F * vec4F_saturate_in_place( vec4F * pV) { pV->m_c[0] = saturate(pV->m_c[0]); pV->m_c[1] = saturate(pV->m_c[1]); pV->m_c[2] = saturate(pV->m_c[2]); pV->m_c[3] = saturate(pV->m_c[3]); return pV; } static inline vec4F vec4F_saturate(const vec4F * pV) { vec4F res; res.m_c[0] = saturate(pV->m_c[0]); res.m_c[1] = saturate(pV->m_c[1]); res.m_c[2] = saturate(pV->m_c[2]); res.m_c[3] = saturate(pV->m_c[3]); return res; } static inline vec4F vec4F_from_color(const color_quad_i * pC) { vec4F res; vec4F_set(&res, (float)pC->m_c[0], (float)pC->m_c[1], (float)pC->m_c[2], (float)pC->m_c[3]); return res; } static inline vec4F vec4F_add(const vec4F * pLHS, const vec4F * pRHS) { vec4F res; vec4F_set(&res, pLHS->m_c[0] + pRHS->m_c[0], pLHS->m_c[1] + pRHS->m_c[1], pLHS->m_c[2] + pRHS->m_c[2], pLHS->m_c[3] + pRHS->m_c[3]); return res; } static inline vec4F vec4F_sub(const vec4F * pLHS, const vec4F * pRHS) { vec4F res; vec4F_set(&res, pLHS->m_c[0] - pRHS->m_c[0], pLHS->m_c[1] - pRHS->m_c[1], pLHS->m_c[2] - pRHS->m_c[2], pLHS->m_c[3] - pRHS->m_c[3]); return res; } static inline float vec4F_dot(const vec4F * pLHS, const vec4F * pRHS) { return pLHS->m_c[0] * pRHS->m_c[0] + pLHS->m_c[1] * pRHS->m_c[1] + pLHS->m_c[2] * pRHS->m_c[2] + pLHS->m_c[3] * pRHS->m_c[3]; } static inline vec4F vec4F_mul(const vec4F * pLHS, float s) { vec4F res; vec4F_set(&res, pLHS->m_c[0] * s, pLHS->m_c[1] * s, pLHS->m_c[2] * s, pLHS->m_c[3] * s); return res; } static inline vec4F *vec4F_normalize_in_place( vec4F * pV) { float s = pV->m_c[0] * pV->m_c[0] + pV->m_c[1] * pV->m_c[1] + pV->m_c[2] * pV->m_c[2] + pV->m_c[3] * pV->m_c[3]; if (s != 0.0f) { s = 1.0f / sqrt(s); pV->m_c[0] *= s; pV->m_c[1] *= s; pV->m_c[2] *= s; pV->m_c[3] *= s; } return pV; } static const uint32_t g_bc7_weights2[4] = { 0, 21, 43, 64 }; static const uint32_t g_bc7_weights3[8] = { 0, 9, 18, 27, 37, 46, 55, 64 }; static const uint32_t g_bc7_weights4[16] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; // Precomputed weight constants used during least fit determination. For each entry in g_bc7_weights[]: w * w, (1.0f - w) * w, (1.0f - w) * (1.0f - w), w static const float g_bc7_weights2x[4 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.107666f, 0.220459f, 0.451416f, 0.328125f, 0.451416f, 0.220459f, 0.107666f, 0.671875f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; static const float g_bc7_weights3x[8 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.019775f, 0.120850f, 0.738525f, 0.140625f, 0.079102f, 0.202148f, 0.516602f, 0.281250f, 0.177979f, 0.243896f, 0.334229f, 0.421875f, 0.334229f, 0.243896f, 0.177979f, 0.578125f, 0.516602f, 0.202148f, 0.079102f, 0.718750f, 0.738525f, 0.120850f, 0.019775f, 0.859375f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; static const float g_bc7_weights4x[16 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.003906f, 0.058594f, 0.878906f, 0.062500f, 0.019775f, 0.120850f, 0.738525f, 0.140625f, 0.041260f, 0.161865f, 0.635010f, 0.203125f, 0.070557f, 0.195068f, 0.539307f, 0.265625f, 0.107666f, 0.220459f, 0.451416f, 0.328125f, 0.165039f, 0.241211f, 0.352539f, 0.406250f, 0.219727f, 0.249023f, 0.282227f, 0.468750f, 0.282227f, 0.249023f, 0.219727f, 0.531250f, 0.352539f, 0.241211f, 0.165039f, 0.593750f, 0.451416f, 0.220459f, 0.107666f, 0.671875f, 0.539307f, 0.195068f, 0.070557f, 0.734375f, 0.635010f, 0.161865f, 0.041260f, 0.796875f, 0.738525f, 0.120850f, 0.019775f, 0.859375f, 0.878906f, 0.058594f, 0.003906f, 0.937500f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; static const int g_bc7_partition1[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; static const int g_bc7_partition2[64 * 16] = { 0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1, 0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1, 0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1, 0,0,0,1,0,0,1,1,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1, 0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1, 0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1, 0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,1, 0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,0, 0,1,1,1,0,0,1,1,0,0,0,1,0,0,0,0, 0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,1,0,0,0,1,1,0,0,1,1,1,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1, 0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0, 0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0, 0,0,0,1,0,1,1,1,1,1,1,0,1,0,0,0, 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0, 0,1,1,1,0,0,0,1,1,0,0,0,1,1,1,0, 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0, 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1, 0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1, 0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0, 0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0, 0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0, 0,1,0,1,0,1,0,1,1,0,1,0,1,0,1,0, 0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1, 0,1,0,1,1,0,1,0,1,0,1,0,0,1,0,1, 0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,0, 0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0, 0,0,1,1,0,0,1,0,0,1,0,0,1,1,0,0, 0,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0, 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, 0,0,1,1,1,1,0,0,1,1,0,0,0,0,1,1, 0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1, 0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0, 0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,0, 0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0, 0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0, 0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,0, 0,1,1,0,1,1,0,0,1,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,1,0,0,1,1,1,0,0, 0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0, 0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,0,0,1,1,1,0,0,1, 0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1, 0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,1, 0,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1, 0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0, 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0, 0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1 }; static const int g_bc7_table_anchor_index_second_subset[64] = { 15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15, 15, 2, 8, 2, 2, 8, 8,15, 2, 8, 2, 2, 8, 8, 2, 2, 15,15, 6, 8, 2, 8,15,15, 2, 8, 2, 2, 2,15,15, 6, 6, 2, 6, 8,15,15, 2, 2, 15,15,15,15,15, 2, 2,15 }; static const int g_bc7_partition3[64 * 16] = { 0,0,1,1,0,0,1,1,0,2,2,1,2,2,2,2, 0,0,0,1,0,0,1,1,2,2,1,1,2,2,2,1, 0,0,0,0,2,0,0,1,2,2,1,1,2,2,1,1, 0,2,2,2,0,0,2,2,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,0,1,1,2,2,1,1,2,2, 0,0,1,1,0,0,1,1,0,0,2,2,0,0,2,2, 0,0,2,2,0,0,2,2,1,1,1,1,1,1,1,1, 0,0,1,1,0,0,1,1,2,2,1,1,2,2,1,1, 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2, 0,0,1,2,0,0,1,2,0,0,1,2,0,0,1,2, 0,1,1,2,0,1,1,2,0,1,1,2,0,1,1,2, 0,1,2,2,0,1,2,2,0,1,2,2,0,1,2,2, 0,0,1,1,0,1,1,2,1,1,2,2,1,2,2,2, 0,0,1,1,2,0,0,1,2,2,0,0,2,2,2,0, 0,0,0,1,0,0,1,1,0,1,1,2,1,1,2,2, 0,1,1,1,0,0,1,1,2,0,0,1,2,2,0,0, 0,0,0,0,1,1,2,2,1,1,2,2,1,1,2,2, 0,0,2,2,0,0,2,2,0,0,2,2,1,1,1,1, 0,1,1,1,0,1,1,1,0,2,2,2,0,2,2,2, 0,0,0,1,0,0,0,1,2,2,2,1,2,2,2,1, 0,0,0,0,0,0,1,1,0,1,2,2,0,1,2,2, 0,0,0,0,1,1,0,0,2,2,1,0,2,2,1,0, 0,1,2,2,0,1,2,2,0,0,1,1,0,0,0,0, 0,0,1,2,0,0,1,2,1,1,2,2,2,2,2,2, 0,1,1,0,1,2,2,1,1,2,2,1,0,1,1,0, 0,0,0,0,0,1,1,0,1,2,2,1,1,2,2,1, 0,0,2,2,1,1,0,2,1,1,0,2,0,0,2,2, 0,1,1,0,0,1,1,0,2,0,0,2,2,2,2,2, 0,0,1,1,0,1,2,2,0,1,2,2,0,0,1,1, 0,0,0,0,2,0,0,0,2,2,1,1,2,2,2,1, 0,0,0,0,0,0,0,2,1,1,2,2,1,2,2,2, 0,2,2,2,0,0,2,2,0,0,1,2,0,0,1,1, 0,0,1,1,0,0,1,2,0,0,2,2,0,2,2,2, 0,1,2,0,0,1,2,0,0,1,2,0,0,1,2,0, 0,0,0,0,1,1,1,1,2,2,2,2,0,0,0,0, 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0, 0,1,2,0,2,0,1,2,1,2,0,1,0,1,2,0, 0,0,1,1,2,2,0,0,1,1,2,2,0,0,1,1, 0,0,1,1,1,1,2,2,2,2,0,0,0,0,1,1, 0,1,0,1,0,1,0,1,2,2,2,2,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,2,1,2,1,2,1, 0,0,2,2,1,1,2,2,0,0,2,2,1,1,2,2, 0,0,2,2,0,0,1,1,0,0,2,2,0,0,1,1, 0,2,2,0,1,2,2,1,0,2,2,0,1,2,2,1, 0,1,0,1,2,2,2,2,2,2,2,2,0,1,0,1, 0,0,0,0,2,1,2,1,2,1,2,1,2,1,2,1, 0,1,0,1,0,1,0,1,0,1,0,1,2,2,2,2, 0,2,2,2,0,1,1,1,0,2,2,2,0,1,1,1, 0,0,0,2,1,1,1,2,0,0,0,2,1,1,1,2, 0,0,0,0,2,1,1,2,2,1,1,2,2,1,1,2, 0,2,2,2,0,1,1,1,0,1,1,1,0,2,2,2, 0,0,0,2,1,1,1,2,1,1,1,2,0,0,0,2, 0,1,1,0,0,1,1,0,0,1,1,0,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,1,2,2,1,1,2, 0,1,1,0,0,1,1,0,2,2,2,2,2,2,2,2, 0,0,2,2,0,0,1,1,0,0,1,1,0,0,2,2, 0,0,2,2,1,1,2,2,1,1,2,2,0,0,2,2, 0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,2, 0,0,0,2,0,0,0,1,0,0,0,2,0,0,0,1, 0,2,2,2,1,2,2,2,0,2,2,2,1,2,2,2, 0,1,0,1,2,2,2,2,2,2,2,2,2,2,2,2, 0,1,1,1,2,0,1,1,2,2,0,1,2,2,2,0, }; static const int g_bc7_table_anchor_index_third_subset_1[64] = { 3, 3,15,15, 8, 3,15,15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8,15, 3, 3, 6,10, 5, 8, 8, 6, 8, 5,15,15, 8,15, 3, 5, 6,10, 8,15, 15, 3,15, 5,15,15,15,15, 3,15, 5, 5, 5, 8, 5,10, 5,10, 8,13,15,12, 3, 3 }; static const int g_bc7_table_anchor_index_third_subset_2[64] = { 15, 8, 8, 3,15,15, 3, 8, 15,15,15,15,15,15,15, 8, 15, 8,15, 3,15, 8,15, 8, 3,15, 6,10,15,15,10, 8, 15, 3,15,10,10, 8, 9,10, 6,15, 8,15, 3, 6, 6, 8, 15, 3,15,15,15,15,15,15, 15,15,15,15, 3,15,15, 8 }; static const int g_bc7_num_subsets[8] = { 3, 2, 3, 2, 1, 1, 1, 2 }; static const int g_bc7_partition_bits[8] = { 4, 6, 6, 6, 0, 0, 0, 6 }; [[maybe_unused]] static const int g_bc7_rotation_bits[8] = { 0, 0, 0, 0, 2, 2, 0, 0 }; static const int g_bc7_color_index_bitcount[8] = { 3, 3, 2, 2, 2, 2, 4, 2 }; static int get_bc7_color_index_size( int mode, int index_selection_bit) { return g_bc7_color_index_bitcount[mode] + index_selection_bit; } static int g_bc7_alpha_index_bitcount[8] = { 0, 0, 0, 0, 3, 2, 4, 2 }; static int get_bc7_alpha_index_size( int mode, int index_selection_bit) { return g_bc7_alpha_index_bitcount[mode] - index_selection_bit; } static const int g_bc7_mode_has_p_bits[8] = { 1, 1, 0, 1, 0, 0, 1, 1 }; static const int g_bc7_mode_has_shared_p_bits[8] = { 0, 1, 0, 0, 0, 0, 0, 0 }; static const int g_bc7_color_precision_table[8] = { 4, 6, 5, 7, 5, 7, 7, 5 }; [[maybe_unused]] static const int g_bc7_color_precision_plus_pbit_table[8] = { 5, 7, 5, 8, 5, 7, 8, 6 }; static const int g_bc7_alpha_precision_table[8] = { 0, 0, 0, 0, 6, 8, 7, 5 }; [[maybe_unused]] static const int g_bc7_alpha_precision_plus_pbit_table[8] = { 0, 0, 0, 0, 6, 8, 8, 6 }; static bool get_bc7_mode_has_seperate_alpha_selectors( int mode) { return (mode == 4) || (mode == 5); } struct endpoint_err { uint16_t m_error; uint8_t m_lo; uint8_t m_hi; }; // Guarded store into endpoint_err::m_error (a real uint16_t). The stored quantity // is always a single-channel squared error (k-c)^2 with k,c in [0,255], so it is // <= 255^2 = 65025 <= UINT16_MAX by construction. This assert verifies that // invariant on every store; if it ever fires (e.g. a weighted error gets routed // here), the value would silently truncate at 16 bits, so the assert must hold. static inline uint16_t safe_cast_uint16(int v) { assert(v >= 0 && v <= (int)UINT16_MAX); return (uint16_t)v; } static endpoint_err g_bc7_mode_1_optimal_endpoints[256][2]; // [c][pbit] const uint32_t BC7E_MODE_1_OPTIMAL_INDEX = 2; static endpoint_err g_bc7_mode_7_optimal_endpoints[256][2][2]; // [c][pbit][hp][lp] const uint32_t BC7E_MODE_7_OPTIMAL_INDEX = 1; static endpoint_err g_bc7_mode_6_optimal_endpoints[256][2][2]; // [c][hp][lp] const uint32_t BC7E_MODE_6_OPTIMAL_INDEX = 5; static uint32_t g_bc7_mode_4_optimal_endpoints3[256]; // [c] static uint32_t g_bc7_mode_4_optimal_endpoints2[256]; // [c] const uint32_t BC7E_MODE_4_OPTIMAL_INDEX3 = 2; const uint32_t BC7E_MODE_4_OPTIMAL_INDEX2 = 1; static uint32_t g_bc7_mode_5_optimal_endpoints[256]; // [c] const uint32_t BC7E_MODE_5_OPTIMAL_INDEX = 1; static endpoint_err g_bc7_mode_0_optimal_endpoints[256][2][2]; // [c][hp][lp] const uint32_t BC7E_MODE_0_OPTIMAL_INDEX = 2; static bool g_codec_initialized; void bc7e_compress_block_init() { if (g_codec_initialized) return; // Mode 0: 444.1 for ( int c = 0; c < 256; c++) { for ( uint32_t hp = 0; hp < 2; hp++) { for ( uint32_t lp = 0; lp < 2; lp++) { endpoint_err best; best.m_error = safe_cast_uint16(UINT16_MAX); for ( uint32_t l = 0; l < 16; l++) { uint32_t low = ((l << 1) | lp) << 3; low |= (low >> 5); for ( uint32_t h = 0; h < 16; h++) { uint32_t high = ((h << 1) | hp) << 3; high |= (high >> 5); const int k = (low * (64 - g_bc7_weights3[BC7E_MODE_0_OPTIMAL_INDEX]) + high * g_bc7_weights3[BC7E_MODE_0_OPTIMAL_INDEX] + 32) >> 6; const int err = (k - c) * (k - c); if (err < best.m_error) { best.m_error = safe_cast_uint16(err); best.m_lo = (uint8_t)l; best.m_hi = (uint8_t)h; } } // h } // l g_bc7_mode_0_optimal_endpoints[c][hp][lp] = best; } // lp } // hp } // c // Mode 1: 666.1 for ( int c = 0; c < 256; c++) { for ( uint32_t lp = 0; lp < 2; lp++) { endpoint_err best; best.m_error = safe_cast_uint16(UINT16_MAX); for ( uint32_t l = 0; l < 64; l++) { uint32_t low = ((l << 1) | lp) << 1; low |= (low >> 7); for ( uint32_t h = 0; h < 64; h++) { uint32_t high = ((h << 1) | lp) << 1; high |= (high >> 7); const int k = (low * (64 - g_bc7_weights3[BC7E_MODE_1_OPTIMAL_INDEX]) + high * g_bc7_weights3[BC7E_MODE_1_OPTIMAL_INDEX] + 32) >> 6; const int err = (k - c) * (k - c); if (err < best.m_error) { best.m_error = safe_cast_uint16(err); best.m_lo = (uint8_t)l; best.m_hi = (uint8_t)h; } } // h } // l g_bc7_mode_1_optimal_endpoints[c][lp] = best; } // lp } // c // Mode 6: 777.1 4-bit indices for ( int c = 0; c < 256; c++) { for ( uint32_t hp = 0; hp < 2; hp++) { for ( uint32_t lp = 0; lp < 2; lp++) { endpoint_err best; best.m_error = safe_cast_uint16(UINT16_MAX); for ( uint32_t l = 0; l < 128; l++) { uint32_t low = (l << 1) | lp; for ( uint32_t h = 0; h < 128; h++) { uint32_t high = (h << 1) | hp; const int k = (low * (64 - g_bc7_weights4[BC7E_MODE_6_OPTIMAL_INDEX]) + high * g_bc7_weights4[BC7E_MODE_6_OPTIMAL_INDEX] + 32) >> 6; const int err = (k - c) * (k - c); if (err < best.m_error) { best.m_error = safe_cast_uint16(err); best.m_lo = (uint8_t)l; best.m_hi = (uint8_t)h; } } // h } // l g_bc7_mode_6_optimal_endpoints[c][hp][lp] = best; } // lp } // hp } // c // Mode 5: 777 2-bit indices for ( int c = 0; c < 256; c++) { endpoint_err best; best.m_error = safe_cast_uint16(UINT16_MAX); best.m_lo = 0; best.m_hi = 0; for ( uint32_t l = 0; l < 128; l++) { uint32_t low = l << 1; low |= (low >> 7); for ( uint32_t h = 0; h < 128; h++) { uint32_t high = h << 1; high |= (high >> 7); const int k = (low * (64 - g_bc7_weights2[BC7E_MODE_5_OPTIMAL_INDEX]) + high * g_bc7_weights2[BC7E_MODE_5_OPTIMAL_INDEX] + 32) >> 6; const int err = (k - c) * (k - c); if (err < best.m_error) { best.m_error = safe_cast_uint16(err); best.m_lo = (uint8_t)l; best.m_hi = (uint8_t)h; } } // h } // l g_bc7_mode_5_optimal_endpoints[c] = (uint32_t)best.m_lo | (((uint32_t)best.m_hi) << 8); } // c //Mode 4: 555 3-bit indices for ( int c = 0; c < 256; c++) { endpoint_err best; best.m_error = safe_cast_uint16(UINT16_MAX); best.m_lo = 0; best.m_hi = 0; for ( uint32_t l = 0; l < 32; l++) { uint32_t low = l << 3; low |= (low >> 5); for ( uint32_t h = 0; h < 32; h++) { uint32_t high = h << 3; high |= (high >> 5); const int k = (low * (64 - g_bc7_weights3[BC7E_MODE_4_OPTIMAL_INDEX3]) + high * g_bc7_weights3[BC7E_MODE_4_OPTIMAL_INDEX3] + 32) >> 6; const int err = (k - c) * (k - c); if (err < best.m_error) { best.m_error = safe_cast_uint16(err); best.m_lo = (uint8_t)l; best.m_hi = (uint8_t)h; } } // h } // l g_bc7_mode_4_optimal_endpoints3[c] = (uint32_t)best.m_lo | (((uint32_t)best.m_hi) << 8); } // c // Mode 4: 555 2-bit indices for ( int c = 0; c < 256; c++) { endpoint_err best; best.m_error = safe_cast_uint16(UINT16_MAX); best.m_lo = 0; best.m_hi = 0; for ( uint32_t l = 0; l < 32; l++) { uint32_t low = l << 3; low |= (low >> 5); for ( uint32_t h = 0; h < 32; h++) { uint32_t high = h << 3; high |= (high >> 5); const int k = (low * (64 - g_bc7_weights2[BC7E_MODE_4_OPTIMAL_INDEX2]) + high * g_bc7_weights2[BC7E_MODE_4_OPTIMAL_INDEX2] + 32) >> 6; const int err = (k - c) * (k - c); if (err < best.m_error) { best.m_error = safe_cast_uint16(err); best.m_lo = (uint8_t)l; best.m_hi = (uint8_t)h; } } // h } // l g_bc7_mode_4_optimal_endpoints2[c] = (uint32_t)best.m_lo | (((uint32_t)best.m_hi) << 8); } // c // Mode 7: 555.1 2-bit indices for ( int c = 0; c < 256; c++) { endpoint_err best; best.m_error = safe_cast_uint16(UINT16_MAX); best.m_lo = 0; best.m_hi = 0; for ( uint32_t hp = 0; hp < 2; hp++) { for ( uint32_t lp = 0; lp < 2; lp++) { for ( uint32_t l = 0; l < 32; l++) { uint32_t low = ((l << 1) | lp) << 2; low |= (low >> 6); for ( uint32_t h = 0; h < 32; h++) { uint32_t high = ((h << 1) | hp) << 2; high |= (high >> 6); const int k = (low * (64 - g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX]) + high * g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX] + 32) >> 6; const int err = (k - c) * (k - c); if (err < best.m_error) { best.m_error = safe_cast_uint16(err); best.m_lo = (uint8_t)l; best.m_hi = (uint8_t)h; } } // h } // l g_bc7_mode_7_optimal_endpoints[c][hp][lp] = best; } // hp } // lp } // c g_codec_initialized = true; } static void compute_least_squares_endpoints_rgba(uint32_t N, const int * pSelectors, const vec4F * pSelector_weights, vec4F * pXl, vec4F * pXh, const color_quad_i * pColors) { // Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf // I did this in matrix form first, expanded out all the ops, then optimized it a bit. float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; float q00_r = 0.0f, q10_r = 0.0f, t_r = 0.0f; float q00_g = 0.0f, q10_g = 0.0f, t_g = 0.0f; float q00_b = 0.0f, q10_b = 0.0f, t_b = 0.0f; float q00_a = 0.0f, q10_a = 0.0f, t_a = 0.0f; for ( uint32_t i = 0; i < N; i++) { const uint32_t sel = pSelectors[i]; z00 += pSelector_weights[sel].m_c[0]; z10 += pSelector_weights[sel].m_c[1]; z11 += pSelector_weights[sel].m_c[2]; float w = pSelector_weights[sel].m_c[3]; q00_r += w * (int)pColors[i].m_c[0]; t_r += (int)pColors[i].m_c[0]; q00_g += w * (int)pColors[i].m_c[1]; t_g += (int)pColors[i].m_c[1]; q00_b += w * (int)pColors[i].m_c[2]; t_b += (int)pColors[i].m_c[2]; q00_a += w * (int)pColors[i].m_c[3]; t_a += (int)pColors[i].m_c[3]; } q10_r = t_r - q00_r; q10_g = t_g - q00_g; q10_b = t_b - q00_b; q10_a = t_a - q00_a; z01 = z10; float det = z00 * z11 - z01 * z10; if (det != 0.0f) det = 1.0f / det; float iz00, iz01, iz10, iz11; iz00 = z11 * det; iz01 = -z01 * det; iz10 = -z10 * det; iz11 = z00 * det; pXl->m_c[0] = (float)(iz00 * q00_r + iz01 * q10_r); pXh->m_c[0] = (float)(iz10 * q00_r + iz11 * q10_r); pXl->m_c[1] = (float)(iz00 * q00_g + iz01 * q10_g); pXh->m_c[1] = (float)(iz10 * q00_g + iz11 * q10_g); pXl->m_c[2] = (float)(iz00 * q00_b + iz01 * q10_b); pXh->m_c[2] = (float)(iz10 * q00_b + iz11 * q10_b); pXl->m_c[3] = (float)(iz00 * q00_a + iz01 * q10_a); pXh->m_c[3] = (float)(iz10 * q00_a + iz11 * q10_a); } static void compute_least_squares_endpoints_rgb(uint32_t N, const int * pSelectors, const vec4F * pSelector_weights, vec4F * pXl, vec4F * pXh, const color_quad_i * pColors) { // Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf // I did this in matrix form first, expanded out all the ops, then optimized it a bit. float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; float q00_r = 0.0f, q10_r = 0.0f, t_r = 0.0f; float q00_g = 0.0f, q10_g = 0.0f, t_g = 0.0f; float q00_b = 0.0f, q10_b = 0.0f, t_b = 0.0f; for ( uint32_t i = 0; i < N; i++) { const uint32_t sel = pSelectors[i]; z00 += pSelector_weights[sel].m_c[0]; z10 += pSelector_weights[sel].m_c[1]; z11 += pSelector_weights[sel].m_c[2]; float w = pSelector_weights[sel].m_c[3]; q00_r += w * (int)pColors[i].m_c[0]; t_r += (int)pColors[i].m_c[0]; q00_g += w * (int)pColors[i].m_c[1]; t_g += (int)pColors[i].m_c[1]; q00_b += w * (int)pColors[i].m_c[2]; t_b += (int)pColors[i].m_c[2]; } q10_r = t_r - q00_r; q10_g = t_g - q00_g; q10_b = t_b - q00_b; z01 = z10; float det = z00 * z11 - z01 * z10; if (det != 0.0f) det = 1.0f / det; float iz00, iz01, iz10, iz11; iz00 = z11 * det; iz01 = -z01 * det; iz10 = -z10 * det; iz11 = z00 * det; pXl->m_c[0] = (float)(iz00 * q00_r + iz01 * q10_r); pXh->m_c[0] = (float)(iz10 * q00_r + iz11 * q10_r); pXl->m_c[1] = (float)(iz00 * q00_g + iz01 * q10_g); pXh->m_c[1] = (float)(iz10 * q00_g + iz11 * q10_g); pXl->m_c[2] = (float)(iz00 * q00_b + iz01 * q10_b); pXh->m_c[2] = (float)(iz10 * q00_b + iz11 * q10_b); } static void compute_least_squares_endpoints_a(uint32_t N, const int * pSelectors, const vec4F * pSelector_weights, float * pXl, float * pXh, const color_quad_i * pColors) { // Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf // I did this in matrix form first, expanded out all the ops, then optimized it a bit. float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; float q00_a = 0.0f, q10_a = 0.0f, t_a = 0.0f; for ( uint32_t i = 0; i < N; i++) { const uint32_t sel = pSelectors[i]; z00 += pSelector_weights[sel].m_c[0]; z10 += pSelector_weights[sel].m_c[1]; z11 += pSelector_weights[sel].m_c[2]; float w = pSelector_weights[sel].m_c[3]; q00_a += w * (int)pColors[i].m_c[3]; t_a += (int)pColors[i].m_c[3]; } q10_a = t_a - q00_a; z01 = z10; float det = z00 * z11 - z01 * z10; if (det != 0.0f) det = 1.0f / det; float iz00, iz01, iz10, iz11; iz00 = z11 * det; iz01 = -z01 * det; iz10 = -z10 * det; iz11 = z00 * det; *pXl = (float)(iz00 * q00_a + iz01 * q10_a); *pXh = (float)(iz10 * q00_a + iz11 * q10_a); } struct color_cell_compressor_params { uint32_t m_num_selector_weights; const uint32_t * m_pSelector_weights; const vec4F * m_pSelector_weightsx; uint32_t m_comp_bits; uint32_t m_weights[4]; bool m_has_alpha; bool m_has_pbits; bool m_endpoints_share_pbit; bool m_perceptual; }; static inline void color_cell_compressor_params_clear( color_cell_compressor_params * p) { p->m_num_selector_weights = 0; p->m_pSelector_weights = NULL; p->m_pSelector_weightsx = NULL; p->m_comp_bits = 0; p->m_perceptual = false; p->m_weights[0] = 1; p->m_weights[1] = 1; p->m_weights[2] = 1; p->m_weights[3] = 1; p->m_has_alpha = false; p->m_has_pbits = false; p->m_endpoints_share_pbit = false; } struct color_cell_compressor_results { uint64_t m_best_overall_err; color_quad_i m_low_endpoint; color_quad_i m_high_endpoint; uint32_t m_pbits[2]; int *m_pSelectors; int *m_pSelectors_temp; // True if this subset's result was produced by the precomputed "one color" // optimal-endpoint lookup tables (the solid/allSame path or the average-color // candidate). Those place endpoints at extreme positions that only land at one // fixed weight -- a hint to callers that this subset is "weird" (fragile under // lossy weight recoding). bool m_used_lut; }; static inline color_quad_i scale_color(const color_quad_i * pC, const color_cell_compressor_params * pParams) { color_quad_i results; const uint32_t n = pParams->m_comp_bits + (pParams->m_has_pbits ? 1 : 0); assert((n >= 4) && (n <= 8)); for ( uint32_t i = 0; i < 4; i++) { uint32_t v = pC->m_c[i] << (8 - n); v |= (v >> n); assert(v <= 255); results.m_c[i] = v; } return results; } static const float pr_weight = (.5f / (1.0f - .2126f)) * (.5f / (1.0f - .2126f)); static const float pb_weight = (.5f / (1.0f - .0722f)) * (.5f / (1.0f - .0722f)); static inline uint64_t compute_color_distance_rgb(const color_quad_i * pE1, const color_quad_i * pE2, bool perceptual, const uint32_t weights[4]) { if (perceptual) { const float l1 = pE1->m_c[0] * .2126f + pE1->m_c[1] * .7152f + pE1->m_c[2] * .0722f; const float cr1 = pE1->m_c[0] - l1; const float cb1 = pE1->m_c[2] - l1; const float l2 = pE2->m_c[0] * .2126f + pE2->m_c[1] * .7152f + pE2->m_c[2] * .0722f; const float cr2 = pE2->m_c[0] - l2; const float cb2 = pE2->m_c[2] - l2; float dl = l1 - l2; float dcr = cr1 - cr2; float dcb = cb1 - cb2; return (int64_t)(weights[0] * (dl * dl) + weights[1] * pr_weight * (dcr * dcr) + weights[2] * pb_weight * (dcb * dcb)); } else { float dr = (float)pE1->m_c[0] - (float)pE2->m_c[0]; float dg = (float)pE1->m_c[1] - (float)pE2->m_c[1]; float db = (float)pE1->m_c[2] - (float)pE2->m_c[2]; return (int64_t)(weights[0] * dr * dr + weights[1] * dg * dg + weights[2] * db * db); } } static inline uint64_t compute_color_distance_rgba(const color_quad_i * pE1, const color_quad_i * pE2, bool perceptual, const uint32_t weights[4]) { float da = (float)pE1->m_c[3] - (float)pE2->m_c[3]; float a_err = weights[3] * (da * da); if (perceptual) { const float l1 = pE1->m_c[0] * .2126f + pE1->m_c[1] * .7152f + pE1->m_c[2] * .0722f; const float cr1 = pE1->m_c[0] - l1; const float cb1 = pE1->m_c[2] - l1; const float l2 = pE2->m_c[0] * .2126f + pE2->m_c[1] * .7152f + pE2->m_c[2] * .0722f; const float cr2 = pE2->m_c[0] - l2; const float cb2 = pE2->m_c[2] - l2; float dl = l1 - l2; float dcr = cr1 - cr2; float dcb = cb1 - cb2; return (int64_t)(weights[0] * (dl * dl) + weights[1] * pr_weight * (dcr * dcr) + weights[2] * pb_weight * (dcb * dcb) + a_err); } else { float dr = (float)pE1->m_c[0] - (float)pE2->m_c[0]; float dg = (float)pE1->m_c[1] - (float)pE2->m_c[1]; float db = (float)pE1->m_c[2] - (float)pE2->m_c[2]; return (int64_t)(weights[0] * dr * dr + weights[1] * dg * dg + weights[2] * db * db + a_err); } } static uint64_t pack_mode1_to_one_color(const color_cell_compressor_params * pParams, color_cell_compressor_results * pResults, uint32_t r, uint32_t g, uint32_t b, int * pSelectors, uint32_t num_pixels, const color_quad_i * pPixels) { uint32_t best_err = UINT_MAX; uint32_t best_p = 0; for ( uint32_t p = 0; p < 2; p++) { uint32_t err = g_bc7_mode_1_optimal_endpoints[r][p].m_error + g_bc7_mode_1_optimal_endpoints[g][p].m_error + g_bc7_mode_1_optimal_endpoints[b][p].m_error; if (err < best_err) { best_err = err; best_p = p; } } const endpoint_err *pEr = &g_bc7_mode_1_optimal_endpoints[r][best_p]; const endpoint_err *pEg = &g_bc7_mode_1_optimal_endpoints[g][best_p]; const endpoint_err *pEb = &g_bc7_mode_1_optimal_endpoints[b][best_p]; color_quad_i_set(&pResults->m_low_endpoint, pEr->m_lo, pEg->m_lo, pEb->m_lo, 0); color_quad_i_set(&pResults->m_high_endpoint, pEr->m_hi, pEg->m_hi, pEb->m_hi, 0); pResults->m_pbits[0] = best_p; pResults->m_pbits[1] = 0; for ( uint32_t i = 0; i < num_pixels; i++) pSelectors[i] = BC7E_MODE_1_OPTIMAL_INDEX; color_quad_i p; for ( uint32_t i = 0; i < 3; i++) { uint32_t low = ((pResults->m_low_endpoint.m_c[i] << 1) | pResults->m_pbits[0]) << 1; low |= (low >> 7); uint32_t high = ((pResults->m_high_endpoint.m_c[i] << 1) | pResults->m_pbits[0]) << 1; high |= (high >> 7); p.m_c[i] = (low * (64 - g_bc7_weights3[BC7E_MODE_1_OPTIMAL_INDEX]) + high * g_bc7_weights3[BC7E_MODE_1_OPTIMAL_INDEX] + 32) >> 6; } p.m_c[3] = 255; uint64_t total_err = 0; for ( uint32_t i = 0; i < num_pixels; i++) total_err += compute_color_distance_rgb(&p, &pPixels[i], pParams->m_perceptual, pParams->m_weights); pResults->m_best_overall_err = total_err; return total_err; } static uint64_t pack_mode24_to_one_color(const color_cell_compressor_params * pParams, color_cell_compressor_results * pResults, uint32_t r, uint32_t g, uint32_t b, int * pSelectors, uint32_t num_pixels, const color_quad_i * pPixels) { uint32_t er, eg, eb; if (pParams->m_num_selector_weights == 8) { er = g_bc7_mode_4_optimal_endpoints3[r]; eg = g_bc7_mode_4_optimal_endpoints3[g]; eb = g_bc7_mode_4_optimal_endpoints3[b]; } else { er = g_bc7_mode_4_optimal_endpoints2[r]; eg = g_bc7_mode_4_optimal_endpoints2[g]; eb = g_bc7_mode_4_optimal_endpoints2[b]; } color_quad_i_set(&pResults->m_low_endpoint, er & 0xFF, eg & 0xFF, eb & 0xFF, 0); color_quad_i_set(&pResults->m_high_endpoint, er >> 8, eg >> 8, eb >> 8, 0); for ( uint32_t i = 0; i < num_pixels; i++) pSelectors[i] = (pParams->m_num_selector_weights == 8) ? BC7E_MODE_4_OPTIMAL_INDEX3 : BC7E_MODE_4_OPTIMAL_INDEX2; color_quad_i p; for ( uint32_t i = 0; i < 3; i++) { uint32_t low = pResults->m_low_endpoint.m_c[i] << 3; low |= (low >> 5); uint32_t high = pResults->m_high_endpoint.m_c[i] << 3; high |= (high >> 5); if (pParams->m_num_selector_weights == 8) p.m_c[i] = (low * (64 - g_bc7_weights3[BC7E_MODE_4_OPTIMAL_INDEX3]) + high * g_bc7_weights3[BC7E_MODE_4_OPTIMAL_INDEX3] + 32) >> 6; else p.m_c[i] = (low * (64 - g_bc7_weights2[BC7E_MODE_4_OPTIMAL_INDEX2]) + high * g_bc7_weights2[BC7E_MODE_4_OPTIMAL_INDEX2] + 32) >> 6; } p.m_c[3] = 255; uint64_t total_err = 0; for ( uint32_t i = 0; i < num_pixels; i++) total_err += compute_color_distance_rgb(&p, &pPixels[i], pParams->m_perceptual, pParams->m_weights); pResults->m_best_overall_err = total_err; return total_err; } static uint64_t pack_mode0_to_one_color(const color_cell_compressor_params * pParams, color_cell_compressor_results * pResults, uint32_t r, uint32_t g, uint32_t b, int * pSelectors, uint32_t num_pixels, const color_quad_i * pPixels) { uint32_t best_err = UINT_MAX; uint32_t best_p = 0; for ( uint32_t p = 0; p < 4; p++) { uint32_t err = g_bc7_mode_0_optimal_endpoints[r][p >> 1][p & 1].m_error + g_bc7_mode_0_optimal_endpoints[g][p >> 1][p & 1].m_error + g_bc7_mode_0_optimal_endpoints[b][p >> 1][p & 1].m_error; if (err < best_err) { best_err = err; best_p = p; } } const endpoint_err *pEr = &g_bc7_mode_0_optimal_endpoints[r][best_p >> 1][best_p & 1]; const endpoint_err *pEg = &g_bc7_mode_0_optimal_endpoints[g][best_p >> 1][best_p & 1]; const endpoint_err *pEb = &g_bc7_mode_0_optimal_endpoints[b][best_p >> 1][best_p & 1]; color_quad_i_set(&pResults->m_low_endpoint, pEr->m_lo, pEg->m_lo, pEb->m_lo, 0); color_quad_i_set(&pResults->m_high_endpoint, pEr->m_hi, pEg->m_hi, pEb->m_hi, 0); pResults->m_pbits[0] = best_p & 1; pResults->m_pbits[1] = best_p >> 1; for ( uint32_t i = 0; i < num_pixels; i++) pSelectors[i] = BC7E_MODE_0_OPTIMAL_INDEX; color_quad_i p; for ( uint32_t i = 0; i < 3; i++) { uint32_t low = ((pResults->m_low_endpoint.m_c[i] << 1) | pResults->m_pbits[0]) << 3; low |= (low >> 5); uint32_t high = ((pResults->m_high_endpoint.m_c[i] << 1) | pResults->m_pbits[1]) << 3; high |= (high >> 5); p.m_c[i] = (low * (64 - g_bc7_weights3[BC7E_MODE_0_OPTIMAL_INDEX]) + high * g_bc7_weights3[BC7E_MODE_0_OPTIMAL_INDEX] + 32) >> 6; } p.m_c[3] = 255; uint64_t total_err = 0; for ( uint32_t i = 0; i < num_pixels; i++) total_err += compute_color_distance_rgb(&p, &pPixels[i], pParams->m_perceptual, pParams->m_weights); pResults->m_best_overall_err = total_err; return total_err; } static uint64_t pack_mode6_to_one_color(const color_cell_compressor_params * pParams, color_cell_compressor_results * pResults, uint32_t r, uint32_t g, uint32_t b, uint32_t a, int * pSelectors, uint32_t num_pixels, const color_quad_i * pPixels) { uint32_t best_err = UINT_MAX; uint32_t best_p = 0; for ( uint32_t p = 0; p < 4; p++) { uint32_t hi_p = p >> 1; uint32_t lo_p = p & 1; uint32_t err = g_bc7_mode_6_optimal_endpoints[r][hi_p][lo_p].m_error + g_bc7_mode_6_optimal_endpoints[g][hi_p][lo_p].m_error + g_bc7_mode_6_optimal_endpoints[b][hi_p][lo_p].m_error + g_bc7_mode_6_optimal_endpoints[a][hi_p][lo_p].m_error; if (err < best_err) { best_err = err; best_p = p; } } uint32_t best_hi_p = best_p >> 1; uint32_t best_lo_p = best_p & 1; const endpoint_err *pEr = &g_bc7_mode_6_optimal_endpoints[r][best_hi_p][best_lo_p]; const endpoint_err *pEg = &g_bc7_mode_6_optimal_endpoints[g][best_hi_p][best_lo_p]; const endpoint_err *pEb = &g_bc7_mode_6_optimal_endpoints[b][best_hi_p][best_lo_p]; const endpoint_err *pEa = &g_bc7_mode_6_optimal_endpoints[a][best_hi_p][best_lo_p]; color_quad_i_set(&pResults->m_low_endpoint, pEr->m_lo, pEg->m_lo, pEb->m_lo, pEa->m_lo); color_quad_i_set(&pResults->m_high_endpoint, pEr->m_hi, pEg->m_hi, pEb->m_hi, pEa->m_hi); pResults->m_pbits[0] = best_lo_p; pResults->m_pbits[1] = best_hi_p; for ( uint32_t i = 0; i < num_pixels; i++) pSelectors[i] = BC7E_MODE_6_OPTIMAL_INDEX; color_quad_i p; for ( uint32_t i = 0; i < 4; i++) { uint32_t low = (pResults->m_low_endpoint.m_c[i] << 1) | pResults->m_pbits[0]; uint32_t high = (pResults->m_high_endpoint.m_c[i] << 1) | pResults->m_pbits[1]; p.m_c[i] = (low * (64 - g_bc7_weights4[BC7E_MODE_6_OPTIMAL_INDEX]) + high * g_bc7_weights4[BC7E_MODE_6_OPTIMAL_INDEX] + 32) >> 6; } uint64_t total_err = 0; for ( uint32_t i = 0; i < num_pixels; i++) total_err += compute_color_distance_rgba(&p, &pPixels[i], pParams->m_perceptual, pParams->m_weights); pResults->m_best_overall_err = total_err; return total_err; } static uint64_t pack_mode7_to_one_color(const color_cell_compressor_params * pParams, color_cell_compressor_results * pResults, uint32_t r, uint32_t g, uint32_t b, uint32_t a, int * pSelectors, uint32_t num_pixels, const color_quad_i * pPixels) { uint32_t best_err = UINT_MAX; uint32_t best_p = 0; for ( uint32_t p = 0; p < 4; p++) { uint32_t hi_p = p >> 1; uint32_t lo_p = p & 1; uint32_t err = g_bc7_mode_7_optimal_endpoints[r][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[g][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[b][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[a][hi_p][lo_p].m_error; if (err < best_err) { best_err = err; best_p = p; } } uint32_t best_hi_p = best_p >> 1; uint32_t best_lo_p = best_p & 1; const endpoint_err *pEr = &g_bc7_mode_7_optimal_endpoints[r][best_hi_p][best_lo_p]; const endpoint_err *pEg = &g_bc7_mode_7_optimal_endpoints[g][best_hi_p][best_lo_p]; const endpoint_err *pEb = &g_bc7_mode_7_optimal_endpoints[b][best_hi_p][best_lo_p]; const endpoint_err *pEa = &g_bc7_mode_7_optimal_endpoints[a][best_hi_p][best_lo_p]; color_quad_i_set(&pResults->m_low_endpoint, pEr->m_lo, pEg->m_lo, pEb->m_lo, pEa->m_lo); color_quad_i_set(&pResults->m_high_endpoint, pEr->m_hi, pEg->m_hi, pEb->m_hi, pEa->m_hi); pResults->m_pbits[0] = best_lo_p; pResults->m_pbits[1] = best_hi_p; for ( uint32_t i = 0; i < num_pixels; i++) pSelectors[i] = BC7E_MODE_7_OPTIMAL_INDEX; color_quad_i p; for ( uint32_t i = 0; i < 4; i++) { uint32_t low = (pResults->m_low_endpoint.m_c[i] << 1) | pResults->m_pbits[0]; uint32_t high = (pResults->m_high_endpoint.m_c[i] << 1) | pResults->m_pbits[1]; p.m_c[i] = (low * (64 - g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX]) + high * g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX] + 32) >> 6; } uint64_t total_err = 0; for ( uint32_t i = 0; i < num_pixels; i++) total_err += compute_color_distance_rgba(&p, &pPixels[i], pParams->m_perceptual, pParams->m_weights); pResults->m_best_overall_err = total_err; return total_err; } static uint64_t evaluate_solution(const color_quad_i * pLow, const color_quad_i * pHigh, const uint32_t * pbits, const color_cell_compressor_params * pParams, color_cell_compressor_results * pResults, uint32_t num_pixels, const color_quad_i * pPixels) { color_quad_i quantMinColor = *pLow; color_quad_i quantMaxColor = *pHigh; if (pParams->m_has_pbits) { uint32_t minPBit, maxPBit; if (pParams->m_endpoints_share_pbit) maxPBit = minPBit = pbits[0]; else { minPBit = pbits[0]; maxPBit = pbits[1]; } quantMinColor.m_c[0] = (pLow->m_c[0] << 1) | minPBit; quantMinColor.m_c[1] = (pLow->m_c[1] << 1) | minPBit; quantMinColor.m_c[2] = (pLow->m_c[2] << 1) | minPBit; quantMinColor.m_c[3] = (pLow->m_c[3] << 1) | minPBit; quantMaxColor.m_c[0] = (pHigh->m_c[0] << 1) | maxPBit; quantMaxColor.m_c[1] = (pHigh->m_c[1] << 1) | maxPBit; quantMaxColor.m_c[2] = (pHigh->m_c[2] << 1) | maxPBit; quantMaxColor.m_c[3] = (pHigh->m_c[3] << 1) | maxPBit; } color_quad_i actualMinColor = scale_color(&quantMinColor, pParams); color_quad_i actualMaxColor = scale_color(&quantMaxColor, pParams); const uint32_t N = pParams->m_num_selector_weights; const uint32_t nc = pParams->m_has_alpha ? 4 : 3; float total_errf = 0; float wr = (float)(pParams->m_weights[0]); float wg = (float)(pParams->m_weights[1]); float wb = (float)(pParams->m_weights[2]); float wa = (float)(pParams->m_weights[3]); color_quad_f weightedColors[16]; weightedColors[0].m_c[0] = (float)(actualMinColor.m_c[0]); weightedColors[0].m_c[1] = (float)(actualMinColor.m_c[1]); weightedColors[0].m_c[2] = (float)(actualMinColor.m_c[2]); weightedColors[0].m_c[3] = (float)(actualMinColor.m_c[3]); weightedColors[N - 1].m_c[0] = (float)(actualMaxColor.m_c[0]); weightedColors[N - 1].m_c[1] = (float)(actualMaxColor.m_c[1]); weightedColors[N - 1].m_c[2] = (float)(actualMaxColor.m_c[2]); weightedColors[N - 1].m_c[3] = (float)(actualMaxColor.m_c[3]); for ( uint32_t i = 1; i < (N - 1); i++) for ( uint32_t j = 0; j < nc; j++) weightedColors[i].m_c[j] = floor((weightedColors[0].m_c[j] * (64.0f - pParams->m_pSelector_weights[i]) + weightedColors[N - 1].m_c[j] * pParams->m_pSelector_weights[i] + 32) * (1.0f / 64.0f)); if (!pParams->m_perceptual) { if (!pParams->m_has_alpha) { if (N == 16) { float lr = (float)(actualMinColor.m_c[0]); float lg = (float)(actualMinColor.m_c[1]); float lb = (float)(actualMinColor.m_c[2]); float dr = actualMaxColor.m_c[0] - lr; float dg = actualMaxColor.m_c[1] - lg; float db = actualMaxColor.m_c[2] - lb; const float f = N / (dr * dr + dg * dg + db * db + BC7E_DENOM_BIAS); lr *= -dr; lg *= -dg; lb *= -db; cfor ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pC = &pPixels[i]; float r = (float)(pC->m_c[0]); float g = (float)(pC->m_c[1]); float b = (float)(pC->m_c[2]); float best_sel = floor(((r * dr + lr) + (g * dg + lg) + (b * db + lb)) * f + .5f); best_sel = clamp(best_sel, (float)1, (float)(N - 1)); float best_sel0 = best_sel - 1; float dr0 = weightedColors[(int)best_sel0].m_c[0] - r; float dg0 = weightedColors[(int)best_sel0].m_c[1] - g; float db0 = weightedColors[(int)best_sel0].m_c[2] - b; float err0 = wr * dr0 * dr0 + wg * dg0 * dg0 + wb * db0 * db0; float dr1 = weightedColors[(int)best_sel].m_c[0] - r; float dg1 = weightedColors[(int)best_sel].m_c[1] - g; float db1 = weightedColors[(int)best_sel].m_c[2] - b; float err1 = wr * dr1 * dr1 + wg * dg1 * dg1 + wb * db1 * db1; float min_err = min(err0, err1); total_errf += min_err; pResults->m_pSelectors_temp[i] = (int)select(min_err == err0, best_sel0, best_sel); } } else if (N == 8) { cfor ( uint32_t i = 0; i < num_pixels; i++) { float pr = (float)pPixels[i].m_c[0]; float pg = (float)pPixels[i].m_c[1]; float pb = (float)pPixels[i].m_c[2]; float best_err; int best_sel; { float dr0 = weightedColors[0].m_c[0] - pr; float dg0 = weightedColors[0].m_c[1] - pg; float db0 = weightedColors[0].m_c[2] - pb; float err0 = wr * dr0 * dr0 + wg * dg0 * dg0 + wb * db0 * db0; float dr1 = weightedColors[1].m_c[0] - pr; float dg1 = weightedColors[1].m_c[1] - pg; float db1 = weightedColors[1].m_c[2] - pb; float err1 = wr * dr1 * dr1 + wg * dg1 * dg1 + wb * db1 * db1; float dr2 = weightedColors[2].m_c[0] - pr; float dg2 = weightedColors[2].m_c[1] - pg; float db2 = weightedColors[2].m_c[2] - pb; float err2 = wr * dr2 * dr2 + wg * dg2 * dg2 + wb * db2 * db2; float dr3 = weightedColors[3].m_c[0] - pr; float dg3 = weightedColors[3].m_c[1] - pg; float db3 = weightedColors[3].m_c[2] - pb; float err3 = wr * dr3 * dr3 + wg * dg3 * dg3 + wb * db3 * db3; best_err = min(min(min(err0, err1), err2), err3); best_sel = select(best_err == err1, 1, 0); best_sel = select(best_err == err2, 2, best_sel); best_sel = select(best_err == err3, 3, best_sel); } { float dr0 = weightedColors[4].m_c[0] - pr; float dg0 = weightedColors[4].m_c[1] - pg; float db0 = weightedColors[4].m_c[2] - pb; float err0 = wr * dr0 * dr0 + wg * dg0 * dg0 + wb * db0 * db0; float dr1 = weightedColors[5].m_c[0] - pr; float dg1 = weightedColors[5].m_c[1] - pg; float db1 = weightedColors[5].m_c[2] - pb; float err1 = wr * dr1 * dr1 + wg * dg1 * dg1 + wb * db1 * db1; float dr2 = weightedColors[6].m_c[0] - pr; float dg2 = weightedColors[6].m_c[1] - pg; float db2 = weightedColors[6].m_c[2] - pb; float err2 = wr * dr2 * dr2 + wg * dg2 * dg2 + wb * db2 * db2; float dr3 = weightedColors[7].m_c[0] - pr; float dg3 = weightedColors[7].m_c[1] - pg; float db3 = weightedColors[7].m_c[2] - pb; float err3 = wr * dr3 * dr3 + wg * dg3 * dg3 + wb * db3 * db3; best_err = min(best_err, min(min(min(err0, err1), err2), err3)); best_sel = select(best_err == err0, 4, best_sel); best_sel = select(best_err == err1, 5, best_sel); best_sel = select(best_err == err2, 6, best_sel); best_sel = select(best_err == err3, 7, best_sel); } total_errf += best_err; pResults->m_pSelectors_temp[i] = best_sel; } } else // if (N == 4) { cfor ( uint32_t i = 0; i < num_pixels; i++) { float pr = (float)pPixels[i].m_c[0]; float pg = (float)pPixels[i].m_c[1]; float pb = (float)pPixels[i].m_c[2]; float dr0 = weightedColors[0].m_c[0] - pr; float dg0 = weightedColors[0].m_c[1] - pg; float db0 = weightedColors[0].m_c[2] - pb; float err0 = wr * dr0 * dr0 + wg * dg0 * dg0 + wb * db0 * db0; float dr1 = weightedColors[1].m_c[0] - pr; float dg1 = weightedColors[1].m_c[1] - pg; float db1 = weightedColors[1].m_c[2] - pb; float err1 = wr * dr1 * dr1 + wg * dg1 * dg1 + wb * db1 * db1; float dr2 = weightedColors[2].m_c[0] - pr; float dg2 = weightedColors[2].m_c[1] - pg; float db2 = weightedColors[2].m_c[2] - pb; float err2 = wr * dr2 * dr2 + wg * dg2 * dg2 + wb * db2 * db2; float dr3 = weightedColors[3].m_c[0] - pr; float dg3 = weightedColors[3].m_c[1] - pg; float db3 = weightedColors[3].m_c[2] - pb; float err3 = wr * dr3 * dr3 + wg * dg3 * dg3 + wb * db3 * db3; float best_err = min(min(min(err0, err1), err2), err3); int best_sel = select(best_err == err1, 1, 0); best_sel = select(best_err == err2, 2, best_sel); best_sel = select(best_err == err3, 3, best_sel); total_errf += best_err; pResults->m_pSelectors_temp[i] = best_sel; } } } else { // alpha if (N == 16) { float lr = (float)(actualMinColor.m_c[0]); float lg = (float)(actualMinColor.m_c[1]); float lb = (float)(actualMinColor.m_c[2]); float la = (float)(actualMinColor.m_c[3]); float dr = actualMaxColor.m_c[0] - lr; float dg = actualMaxColor.m_c[1] - lg; float db = actualMaxColor.m_c[2] - lb; float da = actualMaxColor.m_c[3] - la; const float f = N / (dr * dr + dg * dg + db * db + da * da + BC7E_DENOM_BIAS); lr *= -dr; lg *= -dg; lb *= -db; la *= -da; cfor ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pC = &pPixels[i]; float r = (float)(pC->m_c[0]); float g = (float)(pC->m_c[1]); float b = (float)(pC->m_c[2]); float a = (float)(pC->m_c[3]); float best_sel = floor(((r * dr + lr) + (g * dg + lg) + (b * db + lb) + (a * da + la)) * f + .5f); best_sel = clamp(best_sel, (float)1, (float)(N - 1)); float best_sel0 = best_sel - 1; float dr0 = weightedColors[(int)best_sel0].m_c[0] - r; float dg0 = weightedColors[(int)best_sel0].m_c[1] - g; float db0 = weightedColors[(int)best_sel0].m_c[2] - b; float da0 = weightedColors[(int)best_sel0].m_c[3] - a; float err0 = (wr * dr0 * dr0) + (wg * dg0 * dg0) + (wb * db0 * db0) + (wa * da0 * da0); float dr1 = weightedColors[(int)best_sel].m_c[0] - r; float dg1 = weightedColors[(int)best_sel].m_c[1] - g; float db1 = weightedColors[(int)best_sel].m_c[2] - b; float da1 = weightedColors[(int)best_sel].m_c[3] - a; float err1 = (wr * dr1 * dr1) + (wg * dg1 * dg1) + (wb * db1 * db1) + (wa * da1 * da1); float min_err = min(err0, err1); total_errf += min_err; pResults->m_pSelectors_temp[i] = (int)select(min_err == err0, best_sel0, best_sel); } } else if (N == 8) { cfor ( uint32_t i = 0; i < num_pixels; i++) { float pr = (float)pPixels[i].m_c[0]; float pg = (float)pPixels[i].m_c[1]; float pb = (float)pPixels[i].m_c[2]; float pa = (float)pPixels[i].m_c[3]; float best_err; int best_sel; { float dr0 = weightedColors[0].m_c[0] - pr; float dg0 = weightedColors[0].m_c[1] - pg; float db0 = weightedColors[0].m_c[2] - pb; float da0 = weightedColors[0].m_c[3] - pa; float err0 = wr * dr0 * dr0 + wg * dg0 * dg0 + wb * db0 * db0 + wa * da0 * da0; float dr1 = weightedColors[1].m_c[0] - pr; float dg1 = weightedColors[1].m_c[1] - pg; float db1 = weightedColors[1].m_c[2] - pb; float da1 = weightedColors[1].m_c[3] - pa; float err1 = wr * dr1 * dr1 + wg * dg1 * dg1 + wb * db1 * db1 + wa * da1 * da1; float dr2 = weightedColors[2].m_c[0] - pr; float dg2 = weightedColors[2].m_c[1] - pg; float db2 = weightedColors[2].m_c[2] - pb; float da2 = weightedColors[2].m_c[3] - pa; float err2 = wr * dr2 * dr2 + wg * dg2 * dg2 + wb * db2 * db2 + wa * da2 * da2; float dr3 = weightedColors[3].m_c[0] - pr; float dg3 = weightedColors[3].m_c[1] - pg; float db3 = weightedColors[3].m_c[2] - pb; float da3 = weightedColors[3].m_c[3] - pa; float err3 = wr * dr3 * dr3 + wg * dg3 * dg3 + wb * db3 * db3 + wa * da3 * da3; best_err = min(min(min(err0, err1), err2), err3); best_sel = select(best_err == err1, 1, 0); best_sel = select(best_err == err2, 2, best_sel); best_sel = select(best_err == err3, 3, best_sel); } { float dr0 = weightedColors[4].m_c[0] - pr; float dg0 = weightedColors[4].m_c[1] - pg; float db0 = weightedColors[4].m_c[2] - pb; float da0 = weightedColors[4].m_c[3] - pa; float err0 = wr * dr0 * dr0 + wg * dg0 * dg0 + wb * db0 * db0 + wa * da0 * da0; float dr1 = weightedColors[5].m_c[0] - pr; float dg1 = weightedColors[5].m_c[1] - pg; float db1 = weightedColors[5].m_c[2] - pb; float da1 = weightedColors[5].m_c[3] - pa; float err1 = wr * dr1 * dr1 + wg * dg1 * dg1 + wb * db1 * db1 + wa * da1 * da1; float dr2 = weightedColors[6].m_c[0] - pr; float dg2 = weightedColors[6].m_c[1] - pg; float db2 = weightedColors[6].m_c[2] - pb; float da2 = weightedColors[6].m_c[3] - pa; float err2 = wr * dr2 * dr2 + wg * dg2 * dg2 + wb * db2 * db2 + wa * da2 * da2; float dr3 = weightedColors[7].m_c[0] - pr; float dg3 = weightedColors[7].m_c[1] - pg; float db3 = weightedColors[7].m_c[2] - pb; float da3 = weightedColors[7].m_c[3] - pa; float err3 = wr * dr3 * dr3 + wg * dg3 * dg3 + wb * db3 * db3 + wa * da3 * da3; best_err = min(best_err, min(min(min(err0, err1), err2), err3)); best_sel = select(best_err == err0, 4, best_sel); best_sel = select(best_err == err1, 5, best_sel); best_sel = select(best_err == err2, 6, best_sel); best_sel = select(best_err == err3, 7, best_sel); } total_errf += best_err; pResults->m_pSelectors_temp[i] = best_sel; } } else // if (N == 4) { cfor ( uint32_t i = 0; i < num_pixels; i++) { float pr = (float)pPixels[i].m_c[0]; float pg = (float)pPixels[i].m_c[1]; float pb = (float)pPixels[i].m_c[2]; float pa = (float)pPixels[i].m_c[3]; float dr0 = weightedColors[0].m_c[0] - pr; float dg0 = weightedColors[0].m_c[1] - pg; float db0 = weightedColors[0].m_c[2] - pb; float da0 = weightedColors[0].m_c[3] - pa; float err0 = wr * dr0 * dr0 + wg * dg0 * dg0 + wb * db0 * db0 + wa * da0 * da0; float dr1 = weightedColors[1].m_c[0] - pr; float dg1 = weightedColors[1].m_c[1] - pg; float db1 = weightedColors[1].m_c[2] - pb; float da1 = weightedColors[1].m_c[3] - pa; float err1 = wr * dr1 * dr1 + wg * dg1 * dg1 + wb * db1 * db1 + wa * da1 * da1; float dr2 = weightedColors[2].m_c[0] - pr; float dg2 = weightedColors[2].m_c[1] - pg; float db2 = weightedColors[2].m_c[2] - pb; float da2 = weightedColors[2].m_c[3] - pa; float err2 = wr * dr2 * dr2 + wg * dg2 * dg2 + wb * db2 * db2 + wa * da2 * da2; float dr3 = weightedColors[3].m_c[0] - pr; float dg3 = weightedColors[3].m_c[1] - pg; float db3 = weightedColors[3].m_c[2] - pb; float da3 = weightedColors[3].m_c[3] - pa; float err3 = wr * dr3 * dr3 + wg * dg3 * dg3 + wb * db3 * db3 + wa * da3 * da3; float best_err = min(min(min(err0, err1), err2), err3); int best_sel = select(best_err == err1, 1, 0); best_sel = select(best_err == err2, 2, best_sel); best_sel = select(best_err == err3, 3, best_sel); total_errf += best_err; pResults->m_pSelectors_temp[i] = best_sel; } } } } else { wg *= pr_weight; wb *= pb_weight; float weightedColorsY[16], weightedColorsCr[16], weightedColorsCb[16]; for ( uint32_t i = 0; i < N; i++) { float r = weightedColors[i].m_c[0]; float g = weightedColors[i].m_c[1]; float b = weightedColors[i].m_c[2]; float y = r * .2126f + g * .7152f + b * .0722f; weightedColorsY[i] = y; weightedColorsCr[i] = r - y; weightedColorsCb[i] = b - y; } if (pParams->m_has_alpha) { for ( uint32_t i = 0; i < num_pixels; i++) { float r = (float)(pPixels[i].m_c[0]); float g = (float)(pPixels[i].m_c[1]); float b = (float)(pPixels[i].m_c[2]); float a = (float)(pPixels[i].m_c[3]); float y = r * .2126f + g * .7152f + b * .0722f; float cr = r - y; float cb = b - y; float best_err = 1e+10f; int32_t best_sel = 0; for ( uint32_t j = 0; j < N; j++) { float dl = y - weightedColorsY[j]; float dcr = cr - weightedColorsCr[j]; float dcb = cb - weightedColorsCb[j]; float da = a - weightedColors[j].m_c[3]; float err = (wr * dl * dl) + (wg * dcr * dcr) + (wb * dcb * dcb) + (wa * da * da); if (err < best_err) { best_err = err; best_sel = j; } } total_errf += best_err; pResults->m_pSelectors_temp[i] = best_sel; } } else { for ( uint32_t i = 0; i < num_pixels; i++) { float r = (float)(pPixels[i].m_c[0]); float g = (float)(pPixels[i].m_c[1]); float b = (float)(pPixels[i].m_c[2]); float y = r * .2126f + g * .7152f + b * .0722f; float cr = r - y; float cb = b - y; float best_err = 1e+10f; int32_t best_sel = 0; for ( uint32_t j = 0; j < N; j++) { float dl = y - weightedColorsY[j]; float dcr = cr - weightedColorsCr[j]; float dcb = cb - weightedColorsCb[j]; float err = (wr * dl * dl) + (wg * dcr * dcr) + (wb * dcb * dcb); if (err < best_err) { best_err = err; best_sel = j; } } total_errf += best_err; pResults->m_pSelectors_temp[i] = best_sel; } } } uint64_t total_err = (int64)total_errf; if (total_err < pResults->m_best_overall_err) { pResults->m_best_overall_err = total_err; pResults->m_low_endpoint = *pLow; pResults->m_high_endpoint = *pHigh; pResults->m_pbits[0] = pbits[0]; pResults->m_pbits[1] = pbits[1]; for ( uint32_t i = 0; i < num_pixels; i++) pResults->m_pSelectors[i] = pResults->m_pSelectors_temp[i]; } return total_err; } static void fixDegenerateEndpoints( uint32_t mode, color_quad_i * pTrialMinColor, color_quad_i * pTrialMaxColor, const vec4F * pXl, const vec4F * pXh, uint32_t iscale) { if ((mode == 1) || (mode == 4)) // also mode 2 { // fix degenerate case where the input collapses to a single colorspace voxel, and we loose all freedom (test with grayscale ramps) for ( uint32_t i = 0; i < 3; i++) { if (pTrialMinColor->m_c[i] == pTrialMaxColor->m_c[i]) { if (abs(pXl->m_c[i] - pXh->m_c[i]) > 0.0f) { if (pTrialMinColor->m_c[i] > (int)(iscale >> 1)) { if (pTrialMinColor->m_c[i] > 0) pTrialMinColor->m_c[i]--; else if (pTrialMaxColor->m_c[i] < (int)iscale) pTrialMaxColor->m_c[i]++; } else { if (pTrialMaxColor->m_c[i] < (int)iscale) pTrialMaxColor->m_c[i]++; else if (pTrialMinColor->m_c[i] > 0) pTrialMinColor->m_c[i]--; } if (mode == 4) { if (pTrialMinColor->m_c[i] > (int)(iscale >> 1)) { if (pTrialMaxColor->m_c[i] < (int)iscale) pTrialMaxColor->m_c[i]++; else if (pTrialMinColor->m_c[i] > 0) pTrialMinColor->m_c[i]--; } else { if (pTrialMinColor->m_c[i] > 0) pTrialMinColor->m_c[i]--; else if (pTrialMaxColor->m_c[i] < (int)iscale) pTrialMaxColor->m_c[i]++; } } } } } } } static uint64_t find_optimal_solution( uint32_t mode, vec4F * pXl, vec4F * pXh, const color_cell_compressor_params * pParams, color_cell_compressor_results * pResults, bool pbit_search, uint32_t num_pixels, const color_quad_i * pPixels) { vec4F xl = *pXl; vec4F xh = *pXh; vec4F_saturate_in_place(&xl); vec4F_saturate_in_place(&xh); if (pParams->m_has_pbits) { if (pbit_search) { // compensated rounding+pbit search const int iscalep = (1 << (pParams->m_comp_bits + 1)) - 1; const float scalep = (float)iscalep; const int32_t totalComps = pParams->m_has_alpha ? 4 : 3; NOTE_UNUSED(totalComps); if (!pParams->m_endpoints_share_pbit) { color_quad_i lo[2], hi[2]; for ( int p = 0; p < 2; p++) { color_quad_i xMinColor, xMaxColor; // Notes: The pbit controls which quantization intervals are selected. // total_levels=2^(comp_bits+1), where comp_bits=4 for mode 0, etc. // pbit 0: v=(b*2)/(total_levels-1), pbit 1: v=(b*2+1)/(total_levels-1) where b is the component bin from [0,total_levels/2-1] and v is the [0,1] component value // rearranging you get for pbit 0: b=floor(v*(total_levels-1)/2+.5) // rearranging you get for pbit 1: b=floor((v*(total_levels-1)-1)/2+.5) for ( uint32_t c = 0; c < 4; c++) { xMinColor.m_c[c] = (int)((xl.m_c[c] * scalep - p) / 2.0f + .5f) * 2 + p; xMinColor.m_c[c] = clamp(xMinColor.m_c[c], p, iscalep - 1 + p); xMaxColor.m_c[c] = (int)((xh.m_c[c] * scalep - p) / 2.0f + .5f) * 2 + p; xMaxColor.m_c[c] = clamp(xMaxColor.m_c[c], p, iscalep - 1 + p); } lo[p] = xMinColor; hi[p] = xMaxColor; for ( int c = 0; c < 4; c++) { lo[p].m_c[c] >>= 1; hi[p].m_c[c] >>= 1; } } fixDegenerateEndpoints(mode, &lo[0], &hi[0], &xl, &xh, iscalep >> 1); fixDegenerateEndpoints(mode, &lo[1], &hi[1], &xl, &xh, iscalep >> 1); uint32_t pbits[2]; pbits[0] = 0; pbits[1] = 0; evaluate_solution(&lo[0], &hi[0], pbits, pParams, pResults, num_pixels, pPixels); pbits[0] = 0; pbits[1] = 1; evaluate_solution(&lo[0], &hi[1], pbits, pParams, pResults, num_pixels, pPixels); pbits[0] = 1; pbits[1] = 0; evaluate_solution(&lo[1], &hi[0], pbits, pParams, pResults, num_pixels, pPixels); pbits[0] = 1; pbits[1] = 1; evaluate_solution(&lo[1], &hi[1], pbits, pParams, pResults, num_pixels, pPixels); } else { // Endpoints share pbits color_quad_i lo[2], hi[2]; for ( int p = 0; p < 2; p++) { color_quad_i xMinColor, xMaxColor; for ( uint32_t c = 0; c < 4; c++) { xMinColor.m_c[c] = (int)((xl.m_c[c] * scalep - p) / 2.0f + .5f) * 2 + p; xMinColor.m_c[c] = clamp(xMinColor.m_c[c], p, iscalep - 1 + p); xMaxColor.m_c[c] = (int)((xh.m_c[c] * scalep - p) / 2.0f + .5f) * 2 + p; xMaxColor.m_c[c] = clamp(xMaxColor.m_c[c], p, iscalep - 1 + p); } lo[p] = xMinColor; hi[p] = xMaxColor; for ( int c = 0; c < 4; c++) { lo[p].m_c[c] >>= 1; hi[p].m_c[c] >>= 1; } } fixDegenerateEndpoints(mode, &lo[0], &hi[0], &xl, &xh, iscalep >> 1); fixDegenerateEndpoints(mode, &lo[1], &hi[1], &xl, &xh, iscalep >> 1); uint32_t pbits[2]; pbits[0] = 0; pbits[1] = 0; evaluate_solution(&lo[0], &hi[0], pbits, pParams, pResults, num_pixels, pPixels); pbits[0] = 1; pbits[1] = 1; evaluate_solution(&lo[1], &hi[1], pbits, pParams, pResults, num_pixels, pPixels); } } else { // compensated rounding const int iscalep = (1 << (pParams->m_comp_bits + 1)) - 1; const float scalep = (float)iscalep; const int32_t totalComps = pParams->m_has_alpha ? 4 : 3; uint32_t best_pbits[2]; color_quad_i bestMinColor, bestMaxColor; if (!pParams->m_endpoints_share_pbit) { float best_err0 = 1e+9; float best_err1 = 1e+9; for ( int p = 0; p < 2; p++) { color_quad_i xMinColor, xMaxColor; // Notes: The pbit controls which quantization intervals are selected. // total_levels=2^(comp_bits+1), where comp_bits=4 for mode 0, etc. // pbit 0: v=(b*2)/(total_levels-1), pbit 1: v=(b*2+1)/(total_levels-1) where b is the component bin from [0,total_levels/2-1] and v is the [0,1] component value // rearranging you get for pbit 0: b=floor(v*(total_levels-1)/2+.5) // rearranging you get for pbit 1: b=floor((v*(total_levels-1)-1)/2+.5) for ( uint32_t c = 0; c < 4; c++) { xMinColor.m_c[c] = (int)((xl.m_c[c] * scalep - p) / 2.0f + .5f) * 2 + p; xMinColor.m_c[c] = clamp(xMinColor.m_c[c], p, iscalep - 1 + p); xMaxColor.m_c[c] = (int)((xh.m_c[c] * scalep - p) / 2.0f + .5f) * 2 + p; xMaxColor.m_c[c] = clamp(xMaxColor.m_c[c], p, iscalep - 1 + p); } color_quad_i scaledLow = scale_color(&xMinColor, pParams); color_quad_i scaledHigh = scale_color(&xMaxColor, pParams); float err0 = 0; float err1 = 0; for ( int i = 0; i < totalComps; i++) { err0 += square(scaledLow.m_c[i] - xl.m_c[i]*255.0f); err1 += square(scaledHigh.m_c[i] - xh.m_c[i]*255.0f); } if (err0 < best_err0) { best_err0 = err0; best_pbits[0] = p; bestMinColor.m_c[0] = xMinColor.m_c[0] >> 1; bestMinColor.m_c[1] = xMinColor.m_c[1] >> 1; bestMinColor.m_c[2] = xMinColor.m_c[2] >> 1; bestMinColor.m_c[3] = xMinColor.m_c[3] >> 1; } if (err1 < best_err1) { best_err1 = err1; best_pbits[1] = p; bestMaxColor.m_c[0] = xMaxColor.m_c[0] >> 1; bestMaxColor.m_c[1] = xMaxColor.m_c[1] >> 1; bestMaxColor.m_c[2] = xMaxColor.m_c[2] >> 1; bestMaxColor.m_c[3] = xMaxColor.m_c[3] >> 1; } } } else { // Endpoints share pbits float best_err = 1e+9; for ( int p = 0; p < 2; p++) { color_quad_i xMinColor, xMaxColor; for ( uint32_t c = 0; c < 4; c++) { xMinColor.m_c[c] = (int)((xl.m_c[c] * scalep - p) / 2.0f + .5f) * 2 + p; xMinColor.m_c[c] = clamp(xMinColor.m_c[c], p, iscalep - 1 + p); xMaxColor.m_c[c] = (int)((xh.m_c[c] * scalep - p) / 2.0f + .5f) * 2 + p; xMaxColor.m_c[c] = clamp(xMaxColor.m_c[c], p, iscalep - 1 + p); } color_quad_i scaledLow = scale_color(&xMinColor, pParams); color_quad_i scaledHigh = scale_color(&xMaxColor, pParams); float err = 0; for ( int i = 0; i < totalComps; i++) err += square((scaledLow.m_c[i]/255.0f) - xl.m_c[i]) + square((scaledHigh.m_c[i]/255.0f) - xh.m_c[i]); if (err < best_err) { best_err = err; best_pbits[0] = p; best_pbits[1] = p; bestMinColor.m_c[0] = xMinColor.m_c[0] >> 1; bestMinColor.m_c[1] = xMinColor.m_c[1] >> 1; bestMinColor.m_c[2] = xMinColor.m_c[2] >> 1; bestMinColor.m_c[3] = xMinColor.m_c[3] >> 1; bestMaxColor.m_c[0] = xMaxColor.m_c[0] >> 1; bestMaxColor.m_c[1] = xMaxColor.m_c[1] >> 1; bestMaxColor.m_c[2] = xMaxColor.m_c[2] >> 1; bestMaxColor.m_c[3] = xMaxColor.m_c[3] >> 1; } } } fixDegenerateEndpoints(mode, &bestMinColor, &bestMaxColor, &xl, &xh, iscalep >> 1); if ((pResults->m_best_overall_err == UINT64_MAX) || color_quad_i_notequals(&bestMinColor, &pResults->m_low_endpoint) || color_quad_i_notequals(&bestMaxColor, &pResults->m_high_endpoint) || (best_pbits[0] != pResults->m_pbits[0]) || (best_pbits[1] != pResults->m_pbits[1])) { evaluate_solution(&bestMinColor, &bestMaxColor, best_pbits, pParams, pResults, num_pixels, pPixels); } } } else { const int iscale = (1 << pParams->m_comp_bits) - 1; const float scale = (float)iscale; color_quad_i trialMinColor, trialMaxColor; color_quad_i_set_clamped(&trialMinColor, (int)(xl.m_c[0] * scale + .5f), (int)(xl.m_c[1] * scale + .5f), (int)(xl.m_c[2] * scale + .5f), (int)(xl.m_c[3] * scale + .5f)); color_quad_i_set_clamped(&trialMaxColor, (int)(xh.m_c[0] * scale + .5f), (int)(xh.m_c[1] * scale + .5f), (int)(xh.m_c[2] * scale + .5f), (int)(xh.m_c[3] * scale + .5f)); fixDegenerateEndpoints(mode, &trialMinColor, &trialMaxColor, &xl, &xh, iscale); if ((pResults->m_best_overall_err == UINT64_MAX) || color_quad_i_notequals(&trialMinColor, &pResults->m_low_endpoint) || color_quad_i_notequals(&trialMaxColor, &pResults->m_high_endpoint)) { uint32_t pbits[2]; pbits[0] = 0; pbits[1] = 0; evaluate_solution(&trialMinColor, &trialMaxColor, pbits, pParams, pResults, num_pixels, pPixels); } } return pResults->m_best_overall_err; } // Note: In mode 6, m_has_alpha will only be true for transparent blocks. static uint64_t color_cell_compression( uint32_t mode, const color_cell_compressor_params * pParams, color_cell_compressor_results * pResults, const bc7e_compress_block_params * pComp_params, uint32_t num_pixels, const color_quad_i * pPixels, bool refinement) { pResults->m_best_overall_err = UINT64_MAX; pResults->m_used_lut = false; // set true below only if a one-color LUT result wins if ((mode != 6) && (mode != 7)) { assert(!pParams->m_has_alpha); } if ((mode <= 2) || (mode == 4) || (mode >= 6)) { const uint32_t cr = pPixels[0].m_c[0]; const uint32_t cg = pPixels[0].m_c[1]; const uint32_t cb = pPixels[0].m_c[2]; const uint32_t ca = pPixels[0].m_c[3]; bool allSame = true; for ( uint32_t i = 1; i < num_pixels; i++) { if ((cr != (uint32_t)pPixels[i].m_c[0]) || (cg != (uint32_t)pPixels[i].m_c[1]) || (cb != (uint32_t)pPixels[i].m_c[2]) || (ca != (uint32_t)pPixels[i].m_c[3])) { allSame = false; break; } } cif (allSame && pComp_params->m_use_luts) { pResults->m_used_lut = true; // solid/allSame subset -> one-color optimal-endpoint LUT if (mode == 0) return pack_mode0_to_one_color(pParams, pResults, cr, cg, cb, pResults->m_pSelectors, num_pixels, pPixels); if (mode == 1) return pack_mode1_to_one_color(pParams, pResults, cr, cg, cb, pResults->m_pSelectors, num_pixels, pPixels); else if (mode == 6) return pack_mode6_to_one_color(pParams, pResults, cr, cg, cb, ca, pResults->m_pSelectors, num_pixels, pPixels); else if (mode == 7) return pack_mode7_to_one_color(pParams, pResults, cr, cg, cb, ca, pResults->m_pSelectors, num_pixels, pPixels); else return pack_mode24_to_one_color(pParams, pResults, cr, cg, cb, pResults->m_pSelectors, num_pixels, pPixels); } } vec4F meanColor, axis; vec4F_set_scalar(&meanColor, 0.0f); for ( uint32_t i = 0; i < num_pixels; i++) { vec4F color = vec4F_from_color(&pPixels[i]); meanColor = vec4F_add(&meanColor, &color); } vec4F meanColorScaled = vec4F_mul(&meanColor, 1.0f / (float)((int)num_pixels)); meanColor = vec4F_mul(&meanColor, 1.0f / (float)((int)num_pixels * 255.0f)); vec4F_saturate_in_place(&meanColor); if (pParams->m_has_alpha) { vec4F v; vec4F_set_scalar(&v, 0.0f); cfor ( uint32_t i = 0; i < num_pixels; i++) { vec4F color = vec4F_from_color(&pPixels[i]); color = vec4F_sub(&color, &meanColorScaled); vec4F a = vec4F_mul(&color, color.m_c[0]); vec4F b = vec4F_mul(&color, color.m_c[1]); vec4F c = vec4F_mul(&color, color.m_c[2]); vec4F d = vec4F_mul(&color, color.m_c[3]); vec4F n = i ? v : color; vec4F_normalize_in_place(&n); v.m_c[0] += vec4F_dot(&a, &n); v.m_c[1] += vec4F_dot(&b, &n); v.m_c[2] += vec4F_dot(&c, &n); v.m_c[3] += vec4F_dot(&d, &n); } axis = v; vec4F_normalize_in_place(&axis); } else { float cov[6]; cov[0] = 0; cov[1] = 0; cov[2] = 0; cov[3] = 0; cov[4] = 0; cov[5] = 0; cfor ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pV = &pPixels[i]; float r = pV->m_c[0] - meanColorScaled.m_c[0]; float g = pV->m_c[1] - meanColorScaled.m_c[1]; float b = pV->m_c[2] - meanColorScaled.m_c[2]; cov[0] += r*r; cov[1] += r*g; cov[2] += r*b; cov[3] += g*g; cov[4] += g*b; cov[5] += b*b; } float vfr, vfg, vfb; //vfr = hi[0] - lo[0]; //vfg = hi[1] - lo[1]; //vfb = hi[2] - lo[2]; // This is more stable. vfr = .9f; vfg = 1.0f; vfb = .7f; for ( uint32_t iter = 0; iter < 3; iter++) { float r = vfr*cov[0] + vfg*cov[1] + vfb*cov[2]; float g = vfr*cov[1] + vfg*cov[3] + vfb*cov[4]; float b = vfr*cov[2] + vfg*cov[4] + vfb*cov[5]; float m = maximumf(maximumf(abs(r), abs(g)), abs(b)); if (m > 1e-10f) { m = 1.0f / m; r *= m; g *= m; b *= m; } //float delta = square(vfr - r) + square(vfg - g) + square(vfb - b); vfr = r; vfg = g; vfb = b; //if ((iter > 1) && (delta < 1e-8f)) // break; } float len = vfr*vfr + vfg*vfg + vfb*vfb; if (len < 1e-10f) vec4F_set_scalar(&axis, 0.0f); else { len = 1.0f / sqrt(len); vfr *= len; vfg *= len; vfb *= len; vec4F_set(&axis, vfr, vfg, vfb, 0); } } cif (vec4F_dot(&axis, &axis) < .5f) { if (pParams->m_perceptual) vec4F_set(&axis, .213f, .715f, .072f, pParams->m_has_alpha ? .715f : 0); else vec4F_set(&axis, 1.0f, 1.0f, 1.0f, pParams->m_has_alpha ? 1.0f : 0); vec4F_normalize_in_place(&axis); } float l = 1e+9f, h = -1e+9f; cfor ( uint32_t i = 0; i < num_pixels; i++) { vec4F color = vec4F_from_color(&pPixels[i]); vec4F q = vec4F_sub(&color, &meanColorScaled); float d = vec4F_dot(&q, &axis); l = minimumf(l, d); h = maximumf(h, d); } l *= (1.0f / 255.0f); h *= (1.0f / 255.0f); vec4F b0 = vec4F_mul(&axis, l); vec4F b1 = vec4F_mul(&axis, h); vec4F c0 = vec4F_add(&meanColor, &b0); vec4F c1 = vec4F_add(&meanColor, &b1); vec4F minColor = vec4F_saturate(&c0); vec4F maxColor = vec4F_saturate(&c1); vec4F whiteVec; vec4F_set_scalar(&whiteVec, 1.0f); if (vec4F_dot(&minColor, &whiteVec) > vec4F_dot(&maxColor, &whiteVec)) { vec4F temp = minColor; minColor = maxColor; maxColor = temp; } if (!find_optimal_solution(mode, &minColor, &maxColor, pParams, pResults, pComp_params->m_pbit_search, num_pixels, pPixels)) return 0; if (!refinement) return pResults->m_best_overall_err; for ( uint32_t i = 0; i < pComp_params->m_refinement_passes; i++) { vec4F xl, xh; vec4F_set_scalar(&xl, 0.0f); vec4F_set_scalar(&xh, 0.0f); if (pParams->m_has_alpha) compute_least_squares_endpoints_rgba(num_pixels, pResults->m_pSelectors, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); else { compute_least_squares_endpoints_rgb(num_pixels, pResults->m_pSelectors, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); xl.m_c[3] = 255.0f; xh.m_c[3] = 255.0f; } xl = vec4F_mul(&xl, (1.0f / 255.0f)); xh = vec4F_mul(&xh, (1.0f / 255.0f)); if (!find_optimal_solution(mode, &xl, &xh, pParams, pResults, pComp_params->m_pbit_search, num_pixels, pPixels)) return 0; } if (pComp_params->m_uber_level > 0) { int selectors_temp[16], selectors_temp1[16]; for ( uint32_t i = 0; i < num_pixels; i++) selectors_temp[i] = pResults->m_pSelectors[i]; const int max_selector = pParams->m_num_selector_weights - 1; uint32_t min_sel = 16; uint32_t max_sel = 0; for ( uint32_t i = 0; i < num_pixels; i++) { uint32_t sel = selectors_temp[i]; min_sel = minimumu(min_sel, sel); max_sel = maximumu(max_sel, sel); } vec4F xl, xh; vec4F_set_scalar(&xl, 0.0f); vec4F_set_scalar(&xh, 0.0f); if (pComp_params->m_uber1_mask & 1) { for ( uint32_t i = 0; i < num_pixels; i++) { uint32_t sel = selectors_temp[i]; if ((sel == min_sel) && (sel < (pParams->m_num_selector_weights - 1))) sel++; selectors_temp1[i] = sel; } if (pParams->m_has_alpha) compute_least_squares_endpoints_rgba(num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); else { compute_least_squares_endpoints_rgb(num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); xl.m_c[3] = 255.0f; xh.m_c[3] = 255.0f; } xl = vec4F_mul(&xl, (1.0f / 255.0f)); xh = vec4F_mul(&xh, (1.0f / 255.0f)); if (!find_optimal_solution(mode, &xl, &xh, pParams, pResults, pComp_params->m_pbit_search, num_pixels, pPixels)) return 0; } if (pComp_params->m_uber1_mask & 2) { for ( uint32_t i = 0; i < num_pixels; i++) { uint32_t sel = selectors_temp[i]; if ((sel == max_sel) && (sel > 0)) sel--; selectors_temp1[i] = sel; } if (pParams->m_has_alpha) compute_least_squares_endpoints_rgba(num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); else { compute_least_squares_endpoints_rgb(num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); xl.m_c[3] = 255.0f; xh.m_c[3] = 255.0f; } xl = vec4F_mul(&xl, (1.0f / 255.0f)); xh = vec4F_mul(&xh, (1.0f / 255.0f)); if (!find_optimal_solution(mode, &xl, &xh, pParams, pResults, pComp_params->m_pbit_search, num_pixels, pPixels)) return 0; } if (pComp_params->m_uber1_mask & 4) { for ( uint32_t i = 0; i < num_pixels; i++) { uint32_t sel = selectors_temp[i]; if ((sel == min_sel) && (sel < (pParams->m_num_selector_weights - 1))) sel++; else if ((sel == max_sel) && (sel > 0)) sel--; selectors_temp1[i] = sel; } if (pParams->m_has_alpha) compute_least_squares_endpoints_rgba(num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); else { compute_least_squares_endpoints_rgb(num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); xl.m_c[3] = 255.0f; xh.m_c[3] = 255.0f; } xl = vec4F_mul(&xl, (1.0f / 255.0f)); xh = vec4F_mul(&xh, (1.0f / 255.0f)); if (!find_optimal_solution(mode, &xl, &xh, pParams, pResults, pComp_params->m_pbit_search, num_pixels, pPixels)) return 0; } const uint32_t uber_err_thresh = (num_pixels * 56) >> 4; if ((pComp_params->m_uber_level >= 2) && (pResults->m_best_overall_err > uber_err_thresh)) { const int Q = (pComp_params->m_uber_level >= 4) ? (pComp_params->m_uber_level - 2) : 1; for ( int ly = -Q; ly <= 1; ly++) { for ( int hy = max_selector - 1; hy <= (max_selector + Q); hy++) { if ((ly == 0) && (hy == max_selector)) continue; for ( uint32_t i = 0; i < num_pixels; i++) selectors_temp1[i] = (int)clampf(floor((float)max_selector * ((float)(int)selectors_temp[i] - (float)ly) / ((float)hy - (float)ly) + .5f), 0, (float)max_selector); vec4F_set_scalar(&xl, 0.0f); vec4F_set_scalar(&xh, 0.0f); if (pParams->m_has_alpha) compute_least_squares_endpoints_rgba(num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); else { compute_least_squares_endpoints_rgb(num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pPixels); xl.m_c[3] = 255.0f; xh.m_c[3] = 255.0f; } xl = vec4F_mul(&xl, (1.0f / 255.0f)); xh = vec4F_mul(&xh, (1.0f / 255.0f)); if (!find_optimal_solution(mode, &xl, &xh, pParams, pResults, pComp_params->m_pbit_search && (pComp_params->m_uber_level >= 2), num_pixels, pPixels)) return 0; } } } } if (((mode <= 2) || (mode == 4) || (mode >= 6)) && pComp_params->m_use_luts) { color_cell_compressor_results avg_results; memset(&avg_results, 0, sizeof(avg_results)); avg_results.m_best_overall_err = pResults->m_best_overall_err; avg_results.m_pSelectors = pResults->m_pSelectors; avg_results.m_pSelectors_temp = pResults->m_pSelectors_temp; const uint32_t r = (int)(.5f + meanColor.m_c[0] * 255.0f); const uint32_t g = (int)(.5f + meanColor.m_c[1] * 255.0f); const uint32_t b = (int)(.5f + meanColor.m_c[2] * 255.0f); const uint32_t a = (int)(.5f + meanColor.m_c[3] * 255.0f); uint64_t avg_err; if (mode == 0) avg_err = pack_mode0_to_one_color(pParams, &avg_results, r, g, b, pResults->m_pSelectors_temp, num_pixels, pPixels); else if (mode == 1) avg_err = pack_mode1_to_one_color(pParams, &avg_results, r, g, b, pResults->m_pSelectors_temp, num_pixels, pPixels); else if (mode == 6) avg_err = pack_mode6_to_one_color(pParams, &avg_results, r, g, b, a, pResults->m_pSelectors_temp, num_pixels, pPixels); else if (mode == 7) avg_err = pack_mode7_to_one_color(pParams, &avg_results, r, g, b, a, pResults->m_pSelectors_temp, num_pixels, pPixels); else avg_err = pack_mode24_to_one_color(pParams, &avg_results, r, g, b, pResults->m_pSelectors_temp, num_pixels, pPixels); if (avg_err < pResults->m_best_overall_err) { pResults->m_best_overall_err = avg_err; pResults->m_low_endpoint = avg_results.m_low_endpoint; pResults->m_high_endpoint = avg_results.m_high_endpoint; pResults->m_pbits[0] = avg_results.m_pbits[0]; pResults->m_pbits[1] = avg_results.m_pbits[1]; for ( uint32_t i = 0; i < num_pixels; i++) pResults->m_pSelectors[i] = pResults->m_pSelectors_temp[i]; pResults->m_used_lut = true; // average-color one-color LUT candidate won } } return pResults->m_best_overall_err; } static uint64_t color_cell_compression_est( uint32_t mode, const color_cell_compressor_params * pParams, uint64_t best_err_so_far, uint32_t num_pixels, const color_quad_i * pPixels) { NOTE_UNUSED(best_err_so_far); assert((pParams->m_num_selector_weights == 4) || (pParams->m_num_selector_weights == 8)); float lr = 255, lg = 255, lb = 255; float hr = 0, hg = 0, hb = 0; for ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pC = &pPixels[i]; float r = (float)(pC->m_c[0]); float g = (float)(pC->m_c[1]); float b = (float)(pC->m_c[2]); lr = min(lr, r); lg = min(lg, g); lb = min(lb, b); hr = max(hr, r); hg = max(hg, g); hb = max(hb, b); } const uint32_t N = 1 << g_bc7_color_index_bitcount[mode]; uint64_t total_err = 0; float sr = lr; float sg = lg; float sb = lb; float dir = hr - lr; float dig = hg - lg; float dib = hb - lb; float far = dir; float fag = dig; float fab = dib; float low = far * sr + fag * sg + fab * sb; float high = far * hr + fag * hg + fab * hb; float scale = ((float)N - 1) / ((float)(high - low) + BC7E_DENOM_BIAS); float inv_n = 1.0f / ((float)N - 1); float total_errf = 0; // We don't handle perceptual very well here, but the difference is very slight (<.05 dB avg Luma PSNR across a large corpus) and the perf lost was high (2x slower). if ((pParams->m_weights[0] != 1) || (pParams->m_weights[1] != 1) || (pParams->m_weights[2] != 1)) { float wr = (float)(pParams->m_weights[0]); float wg = (float)(pParams->m_weights[1]); float wb = (float)(pParams->m_weights[2]); for ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pC = &pPixels[i]; float d = far * (float)pC->m_c[0] + fag * (float)pC->m_c[1] + fab * (float)pC->m_c[2]; float s = clamp(floorf((d - low) * scale + .5f) * inv_n, 0.0f, 1.0f); float itr = sr + dir * s; float itg = sg + dig * s; float itb = sb + dib * s; float dr = itr - (float)pC->m_c[0]; float dg = itg - (float)pC->m_c[1]; float db = itb - (float)pC->m_c[2]; total_errf += wr * dr * dr + wg * dg * dg + wb * db * db; } } else { for ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pC = &pPixels[i]; float d = far * (float)pC->m_c[0] + fag * (float)pC->m_c[1] + fab * (float)pC->m_c[2]; float s = clamp(floorf((d - low) * scale + .5f) * inv_n, 0.0f, 1.0f); float itr = sr + dir * s; float itg = sg + dig * s; float itb = sb + dib * s; float dr = itr - (float)pC->m_c[0]; float dg = itg - (float)pC->m_c[1]; float db = itb - (float)pC->m_c[2]; total_errf += dr * dr + dg * dg + db * db; } } total_err = (int64_t)total_errf; return total_err; } static uint64_t color_cell_compression_est_mode7( uint32_t mode, const color_cell_compressor_params * pParams, uint64_t best_err_so_far, uint32_t num_pixels, const color_quad_i * pPixels) { NOTE_UNUSED(best_err_so_far); NOTE_UNUSED(mode); // only referenced by the assert below (compiled out under NDEBUG) assert((mode == 7) && (pParams->m_num_selector_weights == 4)); float lr = 255, lg = 255, lb = 255, la = 255; float hr = 0, hg = 0, hb = 0, ha = 0; for ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pC = &pPixels[i]; float r = (float)(pC->m_c[0]); float g = (float)(pC->m_c[1]); float b = (float)(pC->m_c[2]); float a = (float)(pC->m_c[3]); lr = min(lr, r); lg = min(lg, g); lb = min(lb, b); la = min(la, a); hr = max(hr, r); hg = max(hg, g); hb = max(hb, b); ha = max(ha, a); } const uint32_t N = 4; uint64_t total_err = 0; float sr = lr; float sg = lg; float sb = lb; float sa = la; float dir = hr - lr; float dig = hg - lg; float dib = hb - lb; float dia = ha - la; float far = dir; float fag = dig; float fab = dib; float faa = dia; float low = far * sr + fag * sg + fab * sb + faa * sa; float high = far * hr + fag * hg + fab * hb + faa * ha; float scale = ((float)N - 1) / ((float)(high - low) + BC7E_DENOM_BIAS); float inv_n = 1.0f / ((float)N - 1); float total_errf = 0; // We don't handle perceptual very well here, but the difference is very slight (<.05 dB avg Luma PSNR across a large corpus) and the perf lost was high (2x slower). if ( (!pParams->m_perceptual) && ((pParams->m_weights[0] != 1) || (pParams->m_weights[1] != 1) || (pParams->m_weights[2] != 1) || (pParams->m_weights[3] != 1)) ) { float wr = (float)(pParams->m_weights[0]); float wg = (float)(pParams->m_weights[1]); float wb = (float)(pParams->m_weights[2]); float wa = (float)(pParams->m_weights[3]); for ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pC = &pPixels[i]; float d = far * (float)pC->m_c[0] + fag * (float)pC->m_c[1] + fab * (float)pC->m_c[2] + faa * (float)pC->m_c[3]; float s = clamp(floorf((d - low) * scale + .5f) * inv_n, 0.0f, 1.0f); float itr = sr + dir * s; float itg = sg + dig * s; float itb = sb + dib * s; float ita = sa + dia * s; float dr = itr - (float)pC->m_c[0]; float dg = itg - (float)pC->m_c[1]; float db = itb - (float)pC->m_c[2]; float da = ita - (float)pC->m_c[3]; total_errf += wr * dr * dr + wg * dg * dg + wb * db * db + wa * da * da; } } else { for ( uint32_t i = 0; i < num_pixels; i++) { const color_quad_i * pC = &pPixels[i]; float d = far * (float)pC->m_c[0] + fag * (float)pC->m_c[1] + fab * (float)pC->m_c[2] + faa * (float)pC->m_c[3]; float s = clamp(floorf((d - low) * scale + .5f) * inv_n, 0.0f, 1.0f); float itr = sr + dir * s; float itg = sg + dig * s; float itb = sb + dib * s; float ita = sa + dia * s; float dr = itr - (float)pC->m_c[0]; float dg = itg - (float)pC->m_c[1]; float db = itb - (float)pC->m_c[2]; float da = ita - (float)pC->m_c[3]; total_errf += dr * dr + dg * dg + db * db + da * da; } } total_err = (int64_t)total_errf; return total_err; } static uint32_t estimate_partition( uint32_t mode, const color_quad_i * pPixels, const bc7e_compress_block_params * pComp_params) { const uint32_t total_subsets = g_bc7_num_subsets[mode]; uint32_t total_partitions = minimumu(pComp_params->m_max_partitions_mode[mode], 1U << g_bc7_partition_bits[mode]); if (total_partitions <= 1) return 0; uint64_t best_err = UINT64_MAX; uint32_t best_partition = 0; color_cell_compressor_params params; color_cell_compressor_params_clear(¶ms); params.m_pSelector_weights = (g_bc7_color_index_bitcount[mode] == 2) ? g_bc7_weights2 : g_bc7_weights3; params.m_num_selector_weights = 1 << g_bc7_color_index_bitcount[mode]; memcpy(params.m_weights, pComp_params->m_weights, sizeof(params.m_weights)); if (mode >= 6) { params.m_weights[0] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[0]; params.m_weights[1] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[1]; params.m_weights[2] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[2]; params.m_weights[3] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[3]; } params.m_perceptual = pComp_params->m_perceptual; for ( uint32_t partition = 0; partition < total_partitions; partition++) { const int * pPartition = (total_subsets == 3) ? &g_bc7_partition3[partition * 16] : &g_bc7_partition2[partition * 16]; color_quad_i subset_colors[3][16]; uint32_t subset_total_colors[3]; subset_total_colors[0] = 0; subset_total_colors[1] = 0; subset_total_colors[2] = 0; for ( uint32_t index = 0; index < 16; index++) { const uint32_t p = pPartition[index]; subset_colors[p][subset_total_colors[p]] = pPixels[index]; subset_total_colors[p]++; } uint64_t total_subset_err = 0; for ( uint32_t subset = 0; subset < total_subsets; subset++) { uint64_t err; if (mode == 7) err = color_cell_compression_est_mode7(mode, ¶ms, best_err, subset_total_colors[subset], &subset_colors[subset][0]); else err = color_cell_compression_est(mode, ¶ms, best_err, subset_total_colors[subset], &subset_colors[subset][0]); total_subset_err += err; } // subset if (total_subset_err < best_err) { best_err = total_subset_err; best_partition = partition; if (!best_err) break; } if (total_subsets == 2) { if ((partition == BC7E_2SUBSET_CHECKERBOARD_PARTITION_INDEX) && (best_partition != BC7E_2SUBSET_CHECKERBOARD_PARTITION_INDEX)) break; } } // partition return best_partition; } struct solution { uint32_t m_index; uint64_t m_err; }; static uint32_t estimate_partition_list( uint32_t mode, const color_quad_i * pPixels, const bc7e_compress_block_params * pComp_params, solution * pSolutions, int32_t max_solutions) { const int32_t orig_max_solutions = max_solutions; const uint32_t total_subsets = g_bc7_num_subsets[mode]; uint32_t total_partitions = minimumu(pComp_params->m_max_partitions_mode[mode], 1U << g_bc7_partition_bits[mode]); if (total_partitions <= 1) { pSolutions[0].m_index = 0; pSolutions[0].m_err = 0; return 1; } else if (max_solutions >= (int)total_partitions) { for ( int i = 0; i < (int)total_partitions; i++) { pSolutions[i].m_index = i; pSolutions[i].m_err = i; } return total_partitions; } const int32_t HIGH_FREQUENCY_SORTED_PARTITION_THRESHOLD = 4; if (total_subsets == 2) { if (max_solutions < HIGH_FREQUENCY_SORTED_PARTITION_THRESHOLD) max_solutions = HIGH_FREQUENCY_SORTED_PARTITION_THRESHOLD; } color_cell_compressor_params params; color_cell_compressor_params_clear(¶ms); params.m_pSelector_weights = (g_bc7_color_index_bitcount[mode] == 2) ? g_bc7_weights2 : g_bc7_weights3; params.m_num_selector_weights = 1 << g_bc7_color_index_bitcount[mode]; memcpy(params.m_weights, pComp_params->m_weights, sizeof(params.m_weights)); if (mode >= 6) { params.m_weights[0] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[0]; params.m_weights[1] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[1]; params.m_weights[2] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[2]; params.m_weights[3] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[3]; } params.m_perceptual = pComp_params->m_perceptual; int32_t num_solutions = 0; for ( uint32_t partition = 0; partition < total_partitions; partition++) { const int * pPartition = (total_subsets == 3) ? &g_bc7_partition3[partition * 16] : &g_bc7_partition2[partition * 16]; color_quad_i subset_colors[3][16]; uint32_t subset_total_colors[3]; subset_total_colors[0] = 0; subset_total_colors[1] = 0; subset_total_colors[2] = 0; for ( uint32_t index = 0; index < 16; index++) { const uint32_t p = pPartition[index]; subset_colors[p][subset_total_colors[p]] = pPixels[index]; subset_total_colors[p]++; } uint64_t total_subset_err = 0; for ( uint32_t subset = 0; subset < total_subsets; subset++) { uint64_t err; if (mode == 7) err = color_cell_compression_est_mode7(mode, ¶ms, UINT64_MAX, subset_total_colors[subset], &subset_colors[subset][0]); else err = color_cell_compression_est(mode, ¶ms, UINT64_MAX, subset_total_colors[subset], &subset_colors[subset][0]); total_subset_err += err; } // subset int32_t i; for (i = 0; i < num_solutions; i++) { if (total_subset_err < pSolutions[i].m_err) break; } if (i < num_solutions) { int32_t solutions_to_move = (max_solutions - 1) - i; int32_t num_elements_at_i = num_solutions - i; if (solutions_to_move > num_elements_at_i) solutions_to_move = num_elements_at_i; assert(((i + 1) + solutions_to_move) <= max_solutions); assert((i + solutions_to_move) <= num_solutions); for (int32_t j = solutions_to_move - 1; j >= 0; --j) { pSolutions[i + j + 1] = pSolutions[i + j]; } } if (num_solutions < max_solutions) num_solutions++; if (i < num_solutions) { pSolutions[i].m_err = total_subset_err; pSolutions[i].m_index = partition; } #if BC7E_NON_DETERMINISTIC if ((total_subsets == 2) && (partition == BC7E_2SUBSET_CHECKERBOARD_PARTITION_INDEX)) { if (all(i >= HIGH_FREQUENCY_SORTED_PARTITION_THRESHOLD)) break; } #endif } // partition #if 0 for ( int i = 0; i < num_solutions; i++) { assert(pSolutions[i].m_index < total_partitions); } for ( int i = 0; i < (num_solutions - 1); i++) { assert(pSolutions[i].m_err <= pSolutions[i + 1].m_err); } #endif return min(num_solutions, orig_max_solutions); } static inline void set_block_bits(uint8_t *pBytes, uint32_t val, uint32_t num_bits, uint32_t * pCur_ofs) { assert(num_bits < 32); uint32_t limit = 1U << num_bits; assert(val < limit); NOTE_UNUSED(limit); // only referenced by the assert above (compiled out under NDEBUG) while (num_bits) { const uint32_t n = minimumu(8 - (*pCur_ofs & 7), num_bits); pBytes[*pCur_ofs >> 3] |= (uint8_t)(val << (*pCur_ofs & 7)); val >>= n; num_bits -= n; *pCur_ofs += n; } assert(*pCur_ofs <= 128); } struct bc7_optimization_results { uint32_t m_mode; uint32_t m_partition; int m_selectors[16]; int m_alpha_selectors[16]; color_quad_i m_low[3]; color_quad_i m_high[3]; uint32_t m_pbits[3][2]; uint32_t m_rotation; uint32_t m_index_selector; // True if the WINNING encoding used the one-color optimal-endpoint LUT on any // subset (OR'd across the winning mode's subsets). Set at each winner-update. bool m_used_lut; }; static void encode_bc7_block(void *pBlock, const bc7_optimization_results * pResults) { const uint32_t best_mode = pResults->m_mode; const uint32_t total_subsets = g_bc7_num_subsets[best_mode]; const uint32_t total_partitions = 1 << g_bc7_partition_bits[best_mode]; const int *pPartition; if (total_subsets == 1) pPartition = &g_bc7_partition1[0]; else if (total_subsets == 2) pPartition = &g_bc7_partition2[pResults->m_partition * 16]; else pPartition = &g_bc7_partition3[pResults->m_partition * 16]; int color_selectors[16]; for ( int i = 0; i < 16; i++) color_selectors[i] = pResults->m_selectors[i]; int alpha_selectors[16]; for ( int i = 0; i < 16; i++) alpha_selectors[i] = pResults->m_alpha_selectors[i]; color_quad_i low[3], high[3]; low[0] = pResults->m_low[0]; low[1] = pResults->m_low[1]; low[2] = pResults->m_low[2]; high[0] = pResults->m_high[0]; high[1] = pResults->m_high[1]; high[2] = pResults->m_high[2]; uint32_t pbits[3][2]; for ( int i = 0; i < 3; i++) { pbits[i][0] = pResults->m_pbits[i][0]; pbits[i][1] = pResults->m_pbits[i][1]; } int anchor[3]; anchor[0] = -1; anchor[1] = -1; anchor[2] = -1; for ( uint32_t k = 0; k < total_subsets; k++) { uint32_t anchor_index = 0; if (k) { if ((total_subsets == 3) && (k == 1)) { anchor_index = g_bc7_table_anchor_index_third_subset_1[pResults->m_partition]; } else if ((total_subsets == 3) && (k == 2)) { anchor_index = g_bc7_table_anchor_index_third_subset_2[pResults->m_partition]; } else { anchor_index = g_bc7_table_anchor_index_second_subset[pResults->m_partition]; } } anchor[k] = anchor_index; const uint32_t color_index_bits = get_bc7_color_index_size(best_mode, pResults->m_index_selector); const uint32_t num_color_indices = 1 << color_index_bits; if (color_selectors[anchor_index] & (num_color_indices >> 1)) { for ( uint32_t i = 0; i < 16; i++) { if ((uint32_t)pPartition[i] == k) color_selectors[i] = (num_color_indices - 1) - color_selectors[i]; } if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) { for ( uint32_t q = 0; q < 3; q++) { int t = low[k].m_c[q]; low[k].m_c[q] = high[k].m_c[q]; high[k].m_c[q] = t; } } else { color_quad_i tmp = low[k]; low[k] = high[k]; high[k] = tmp; } if (!g_bc7_mode_has_shared_p_bits[best_mode]) { uint32_t t = pbits[k][0]; pbits[k][0] = pbits[k][1]; pbits[k][1] = t; } } if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) { const uint32_t alpha_index_bits = get_bc7_alpha_index_size(best_mode, pResults->m_index_selector); const uint32_t num_alpha_indices = 1 << alpha_index_bits; if (alpha_selectors[anchor_index] & (num_alpha_indices >> 1)) { for ( uint32_t i = 0; i < 16; i++) { if ((uint32_t)pPartition[i] == k) alpha_selectors[i] = (num_alpha_indices - 1) - alpha_selectors[i]; } int t = low[k].m_c[3]; low[k].m_c[3] = high[k].m_c[3]; high[k].m_c[3] = t; } } } uint8_t *pBlock_bytes = (uint8_t *)(pBlock); memset(pBlock_bytes, 0, BC7E_BLOCK_SIZE); uint32_t cur_bit_ofs = 0; set_block_bits(pBlock_bytes, 1 << best_mode, best_mode + 1, &cur_bit_ofs); if ((best_mode == 4) || (best_mode == 5)) set_block_bits(pBlock_bytes, pResults->m_rotation, 2, &cur_bit_ofs); if (best_mode == 4) set_block_bits(pBlock_bytes, pResults->m_index_selector, 1, &cur_bit_ofs); if (total_partitions > 1) set_block_bits(pBlock_bytes, pResults->m_partition, (total_partitions == 64) ? 6 : 4, &cur_bit_ofs); const uint32_t total_comps = (best_mode >= 4) ? 4 : 3; for ( uint32_t comp = 0; comp < total_comps; comp++) { for ( uint32_t subset = 0; subset < total_subsets; subset++) { set_block_bits(pBlock_bytes, low[subset].m_c[comp], (comp == 3) ? g_bc7_alpha_precision_table[best_mode] : g_bc7_color_precision_table[best_mode], &cur_bit_ofs); set_block_bits(pBlock_bytes, high[subset].m_c[comp], (comp == 3) ? g_bc7_alpha_precision_table[best_mode] : g_bc7_color_precision_table[best_mode], &cur_bit_ofs); } } if (g_bc7_mode_has_p_bits[best_mode]) { for ( uint32_t subset = 0; subset < total_subsets; subset++) { set_block_bits(pBlock_bytes, pbits[subset][0], 1, &cur_bit_ofs); if (!g_bc7_mode_has_shared_p_bits[best_mode]) set_block_bits(pBlock_bytes, pbits[subset][1], 1, &cur_bit_ofs); } } for ( uint32_t y = 0; y < 4; y++) { for ( uint32_t x = 0; x < 4; x++) { int idx = x + y * 4; uint32_t n = pResults->m_index_selector ? get_bc7_alpha_index_size(best_mode, pResults->m_index_selector) : get_bc7_color_index_size(best_mode, pResults->m_index_selector); if ((idx == anchor[0]) || (idx == anchor[1]) || (idx == anchor[2])) n--; set_block_bits(pBlock_bytes, pResults->m_index_selector ? alpha_selectors[idx] : color_selectors[idx], n, &cur_bit_ofs); } } if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) { for ( uint32_t y = 0; y < 4; y++) { for ( uint32_t x = 0; x < 4; x++) { int idx = x + y * 4; uint32_t n = pResults->m_index_selector ? get_bc7_color_index_size(best_mode, pResults->m_index_selector) : get_bc7_alpha_index_size(best_mode, pResults->m_index_selector); if ((idx == anchor[0]) || (idx == anchor[1]) || (idx == anchor[2])) n--; set_block_bits(pBlock_bytes, pResults->m_index_selector ? color_selectors[idx] : alpha_selectors[idx], n, &cur_bit_ofs); } } } assert(cur_bit_ofs == 128); } static inline void encode_bc7_block_mode6(void *pBlock, bc7_optimization_results * pResults) { color_quad_i low, high; uint32_t pbits[2]; uint32_t invert_selectors = 0; if (pResults->m_selectors[0] & 8) { invert_selectors = 15; low = pResults->m_high[0]; high = pResults->m_low[0]; pbits[0] = pResults->m_pbits[0][1]; pbits[1] = pResults->m_pbits[0][0]; } else { low = pResults->m_low[0]; high = pResults->m_high[0]; pbits[0] = pResults->m_pbits[0][0]; pbits[1] = pResults->m_pbits[0][1]; } uint64_t l = 0, h = 0; l = 1 << 6; l |= (low.m_c[0] << 7); l |= (high.m_c[0] << 14); l |= (low.m_c[1] << 21); l |= ((uint64_t)high.m_c[1] << 28); l |= ((uint64_t)low.m_c[2] << 35); l |= ((uint64_t)high.m_c[2] << 42); l |= ((uint64_t)low.m_c[3] << 49); l |= ((uint64_t)high.m_c[3] << 56); l |= ((uint64_t)pbits[0] << 63); h = pbits[1]; h |= ((invert_selectors ^ pResults->m_selectors[0]) << 1); // TODO: Just invert all these bits in one single operation, not as individual h |= ((invert_selectors ^ pResults->m_selectors[1]) << 4); h |= ((invert_selectors ^ pResults->m_selectors[2]) << 8); h |= ((invert_selectors ^ pResults->m_selectors[3]) << 12); h |= ((invert_selectors ^ pResults->m_selectors[4]) << 16); h |= ((invert_selectors ^ pResults->m_selectors[5]) << 20); h |= ((invert_selectors ^ pResults->m_selectors[6]) << 24); h |= ((invert_selectors ^ pResults->m_selectors[7]) << 28); h |= ((uint64_t)(invert_selectors ^ pResults->m_selectors[8]) << 32); h |= ((uint64_t)(invert_selectors ^ pResults->m_selectors[9]) << 36); h |= ((uint64_t)(invert_selectors ^ pResults->m_selectors[10]) << 40); h |= ((uint64_t)(invert_selectors ^ pResults->m_selectors[11]) << 44); h |= ((uint64_t)(invert_selectors ^ pResults->m_selectors[12]) << 48); h |= ((uint64_t)(invert_selectors ^ pResults->m_selectors[13]) << 52); h |= ((uint64_t)(invert_selectors ^ pResults->m_selectors[14]) << 56); h |= ((uint64_t)(invert_selectors ^ pResults->m_selectors[15]) << 60); ((uint64_t *)(pBlock))[0] = l; ((uint64_t *)(pBlock))[1] = h; } static void handle_alpha_block_mode4(const color_quad_i * pPixels, const bc7e_compress_block_params * pComp_params, color_cell_compressor_params * pParams, uint32_t lo_a, uint32_t hi_a, bc7_optimization_results * pOpt_results4, uint64_t * pMode4_err) { pParams->m_has_alpha = false; pParams->m_comp_bits = 5; pParams->m_has_pbits = false; pParams->m_endpoints_share_pbit = false; pParams->m_perceptual = pComp_params->m_perceptual; for ( uint32_t index_selector = 0; index_selector < 2; index_selector++) { if ((pComp_params->m_mode4_index_mask & (1 << index_selector)) == 0) continue; if (index_selector) { pParams->m_pSelector_weights = g_bc7_weights3; pParams->m_pSelector_weightsx = (const vec4F * )&g_bc7_weights3x[0]; pParams->m_num_selector_weights = 8; } else { pParams->m_pSelector_weights = g_bc7_weights2; pParams->m_pSelector_weightsx = (const vec4F * )&g_bc7_weights2x[0]; pParams->m_num_selector_weights = 4; } color_cell_compressor_results results; int selectors[16]; results.m_pSelectors = selectors; int selectors_temp[16]; results.m_pSelectors_temp = selectors_temp; uint64_t trial_err = color_cell_compression(4, pParams, &results, pComp_params, 16, pPixels, true); assert(trial_err == results.m_best_overall_err); uint32_t la = minimumi((lo_a + 2) >> 2, 63); uint32_t ha = minimumi((hi_a + 2) >> 2, 63); if (la == ha) { if (lo_a != hi_a) { if (ha != 63) ha++; else if (la != 0) la--; } } uint64_t best_alpha_err = UINT64_MAX; uint32_t best_la = 0, best_ha = 0; int best_alpha_selectors[16] = {}; for ( int32_t pass = 0; pass < 2; pass++) { int32_t vals[8]; if (index_selector == 0) { vals[0] = (la << 2) | (la >> 4); vals[7] = (ha << 2) | (ha >> 4); for ( uint32_t i = 1; i < 7; i++) vals[i] = (vals[0] * (64 - g_bc7_weights3[i]) + vals[7] * g_bc7_weights3[i] + 32) >> 6; } else { vals[0] = (la << 2) | (la >> 4); vals[3] = (ha << 2) | (ha >> 4); const int32_t w_s1 = 21, w_s2 = 43; vals[1] = (vals[0] * (64 - w_s1) + vals[3] * w_s1 + 32) >> 6; vals[2] = (vals[0] * (64 - w_s2) + vals[3] * w_s2 + 32) >> 6; } uint64_t trial_alpha_err = 0; int trial_alpha_selectors[16]; for ( uint32_t i = 0; i < 16; i++) { const int32_t a = pPixels[i].m_c[3]; int s = 0; int32_t be = iabs32(a - vals[0]); int e = iabs32(a - vals[1]); if (e < be) { be = e; s = 1; } e = iabs32(a - vals[2]); if (e < be) { be = e; s = 2; } e = iabs32(a - vals[3]); if (e < be) { be = e; s = 3; } if (index_selector == 0) { e = iabs32(a - vals[4]); if (e < be) { be = e; s = 4; } e = iabs32(a - vals[5]); if (e < be) { be = e; s = 5; } e = iabs32(a - vals[6]); if (e < be) { be = e; s = 6; } e = iabs32(a - vals[7]); if (e < be) { be = e; s = 7; } } trial_alpha_err += (be * be) * pParams->m_weights[3]; trial_alpha_selectors[i] = s; } if (trial_alpha_err < best_alpha_err) { best_alpha_err = trial_alpha_err; best_la = la; best_ha = ha; for ( uint32_t i = 0; i < 16; i++) best_alpha_selectors[i] = trial_alpha_selectors[i]; } if (pass == 0) { float xl, xh; compute_least_squares_endpoints_a(16, trial_alpha_selectors, index_selector ? (const vec4F * )&g_bc7_weights2x[0] : (const vec4F * )&g_bc7_weights3x[0], &xl, &xh, pPixels); if (xl > xh) swapf(&xl, &xh); la = clampi((int)floor(xl * (63.0f / 255.0f) + .5f), 0, 63); ha = clampi((int)floor(xh * (63.0f / 255.0f) + .5f), 0, 63); } } // pass if (pComp_params->m_uber_level > 0) { const int D = min((int)pComp_params->m_uber_level, 3); for ( int ld = -D; ld <= D; ld++) { for ( int hd = -D; hd <= D; hd++) { la = clamp((int)best_la + ld, 0, 63); ha = clamp((int)best_ha + hd, 0, 63); int32_t vals[8]; if (index_selector == 0) { vals[0] = (la << 2) | (la >> 4); vals[7] = (ha << 2) | (ha >> 4); for ( uint32_t i = 1; i < 7; i++) vals[i] = (vals[0] * (64 - g_bc7_weights3[i]) + vals[7] * g_bc7_weights3[i] + 32) >> 6; } else { vals[0] = (la << 2) | (la >> 4); vals[3] = (ha << 2) | (ha >> 4); const int32_t w_s1 = 21, w_s2 = 43; vals[1] = (vals[0] * (64 - w_s1) + vals[3] * w_s1 + 32) >> 6; vals[2] = (vals[0] * (64 - w_s2) + vals[3] * w_s2 + 32) >> 6; } uint64_t trial_alpha_err = 0; int trial_alpha_selectors[16]; for ( uint32_t i = 0; i < 16; i++) { const int32_t a = pPixels[i].m_c[3]; int s = 0; int32_t be = iabs32(a - vals[0]); int e = iabs32(a - vals[1]); if (e < be) { be = e; s = 1; } e = iabs32(a - vals[2]); if (e < be) { be = e; s = 2; } e = iabs32(a - vals[3]); if (e < be) { be = e; s = 3; } if (index_selector == 0) { e = iabs32(a - vals[4]); if (e < be) { be = e; s = 4; } e = iabs32(a - vals[5]); if (e < be) { be = e; s = 5; } e = iabs32(a - vals[6]); if (e < be) { be = e; s = 6; } e = iabs32(a - vals[7]); if (e < be) { be = e; s = 7; } } trial_alpha_err += (be * be) * pParams->m_weights[3]; trial_alpha_selectors[i] = s; } if (trial_alpha_err < best_alpha_err) { best_alpha_err = trial_alpha_err; best_la = la; best_ha = ha; for ( uint32_t i = 0; i < 16; i++) best_alpha_selectors[i] = trial_alpha_selectors[i]; } } // hd } // ld } trial_err += best_alpha_err; if (trial_err < *pMode4_err) { *pMode4_err = trial_err; pOpt_results4->m_mode = 4; pOpt_results4->m_index_selector = index_selector; pOpt_results4->m_rotation = 0; pOpt_results4->m_partition = 0; pOpt_results4->m_used_lut = results.m_used_lut; pOpt_results4->m_low[0] = results.m_low_endpoint; pOpt_results4->m_high[0] = results.m_high_endpoint; pOpt_results4->m_low[0].m_c[3] = best_la; pOpt_results4->m_high[0].m_c[3] = best_ha; for ( uint32_t i = 0; i < 16; i++) pOpt_results4->m_selectors[i] = selectors[i]; for ( uint32_t i = 0; i < 16; i++) pOpt_results4->m_alpha_selectors[i] = best_alpha_selectors[i]; } } // index_selector } static void handle_alpha_block_mode5(const color_quad_i * pPixels, const bc7e_compress_block_params * pComp_params, color_cell_compressor_params * pParams, uint32_t lo_a, uint32_t hi_a, bc7_optimization_results * pOpt_results5, uint64_t * pMode5_err) { pParams->m_pSelector_weights = g_bc7_weights2; pParams->m_pSelector_weightsx = (const vec4F * )&g_bc7_weights2x[0]; pParams->m_num_selector_weights = 4; pParams->m_comp_bits = 7; pParams->m_has_alpha = false; pParams->m_has_pbits = false; pParams->m_endpoints_share_pbit = false; pParams->m_perceptual = pComp_params->m_perceptual; color_cell_compressor_results results5; results5.m_pSelectors = pOpt_results5->m_selectors; int selectors_temp[16]; results5.m_pSelectors_temp = selectors_temp; *pMode5_err = color_cell_compression(5, pParams, &results5, pComp_params, 16, pPixels, true); assert(*pMode5_err == results5.m_best_overall_err); pOpt_results5->m_low[0] = results5.m_low_endpoint; pOpt_results5->m_high[0] = results5.m_high_endpoint; cif (lo_a == hi_a) { pOpt_results5->m_low[0].m_c[3] = lo_a; pOpt_results5->m_high[0].m_c[3] = hi_a; for ( uint32_t i = 0; i < 16; i++) pOpt_results5->m_alpha_selectors[i] = 0; } else { uint64_t mode5_alpha_err = UINT64_MAX; for ( uint32_t pass = 0; pass < 2; pass++) { int32_t vals[4]; vals[0] = lo_a; vals[3] = hi_a; const int32_t w_s1 = 21, w_s2 = 43; vals[1] = (vals[0] * (64 - w_s1) + vals[3] * w_s1 + 32) >> 6; vals[2] = (vals[0] * (64 - w_s2) + vals[3] * w_s2 + 32) >> 6; int trial_alpha_selectors[16]; uint64_t trial_alpha_err = 0; for ( uint32_t i = 0; i < 16; i++) { const int32_t a = pPixels[i].m_c[3]; int s = 0; int32_t be = iabs32(a - vals[0]); int e = iabs32(a - vals[1]); if (e < be) { be = e; s = 1; } e = iabs32(a - vals[2]); if (e < be) { be = e; s = 2; } e = iabs32(a - vals[3]); if (e < be) { be = e; s = 3; } trial_alpha_selectors[i] = s; trial_alpha_err += (be * be) * pParams->m_weights[3]; } if (trial_alpha_err < mode5_alpha_err) { mode5_alpha_err = trial_alpha_err; pOpt_results5->m_low[0].m_c[3] = lo_a; pOpt_results5->m_high[0].m_c[3] = hi_a; for ( uint32_t i = 0; i < 16; i++) pOpt_results5->m_alpha_selectors[i] = trial_alpha_selectors[i]; } if (!pass) { float xl, xh; compute_least_squares_endpoints_a(16, trial_alpha_selectors, (const vec4F * )&g_bc7_weights2x[0], &xl, &xh, pPixels); uint32_t new_lo_a = clampi((int)floor(xl + .5f), 0, 255); uint32_t new_hi_a = clampi((int)floor(xh + .5f), 0, 255); if (new_lo_a > new_hi_a) swapu(&new_lo_a, &new_hi_a); if ((new_lo_a == lo_a) && (new_hi_a == hi_a)) break; lo_a = new_lo_a; hi_a = new_hi_a; } } if (pComp_params->m_uber_level > 0) { const int D = min((int)pComp_params->m_uber_level, 3); for ( int ld = -D; ld <= D; ld++) { for ( int hd = -D; hd <= D; hd++) { lo_a = clamp((int)pOpt_results5->m_low[0].m_c[3] + ld, 0, 255); hi_a = clamp((int)pOpt_results5->m_high[0].m_c[3] + hd, 0, 255); int32_t vals[4]; vals[0] = lo_a; vals[3] = hi_a; const int32_t w_s1 = 21, w_s2 = 43; vals[1] = (vals[0] * (64 - w_s1) + vals[3] * w_s1 + 32) >> 6; vals[2] = (vals[0] * (64 - w_s2) + vals[3] * w_s2 + 32) >> 6; int trial_alpha_selectors[16]; uint64_t trial_alpha_err = 0; for ( uint32_t i = 0; i < 16; i++) { const int32_t a = pPixels[i].m_c[3]; int s = 0; int32_t be = iabs32(a - vals[0]); int e = iabs32(a - vals[1]); if (e < be) { be = e; s = 1; } e = iabs32(a - vals[2]); if (e < be) { be = e; s = 2; } e = iabs32(a - vals[3]); if (e < be) { be = e; s = 3; } trial_alpha_selectors[i] = s; trial_alpha_err += (be * be) * pParams->m_weights[3]; } if (trial_alpha_err < mode5_alpha_err) { mode5_alpha_err = trial_alpha_err; pOpt_results5->m_low[0].m_c[3] = lo_a; pOpt_results5->m_high[0].m_c[3] = hi_a; for ( uint32_t i = 0; i < 16; i++) pOpt_results5->m_alpha_selectors[i] = trial_alpha_selectors[i]; } } // hd } // ld } *pMode5_err += mode5_alpha_err; } pOpt_results5->m_mode = 5; pOpt_results5->m_index_selector = 0; pOpt_results5->m_rotation = 0; pOpt_results5->m_partition = 0; pOpt_results5->m_used_lut = results5.m_used_lut; } static void handle_alpha_block(void * pBlock, const color_quad_i * pPixels, const bc7e_compress_block_params * pComp_params, color_cell_compressor_params * pParams, uint32_t lo_a, uint32_t hi_a, int forced_partition = -1, uint64_t* pBest_err = nullptr, bool* pUsed_lut = nullptr) { pParams->m_perceptual = pComp_params->m_perceptual; bc7_optimization_results opt_results = {}; uint64_t best_err = UINT64_MAX; // Mode 4 if (pComp_params->m_alpha_settings.m_use_mode4) { color_cell_compressor_params params4 = *pParams; const int num_rotations = (pComp_params->m_perceptual || (!pComp_params->m_alpha_settings.m_use_mode4_rotation)) ? 1 : 4; for ( uint32_t rotation = 0; rotation < (uint32_t)num_rotations; rotation++) { if ((pComp_params->m_mode4_rotation_mask & (1 << rotation)) == 0) continue; memcpy(params4.m_weights, pParams->m_weights, sizeof(params4.m_weights)); if (rotation) swapu(¶ms4.m_weights[rotation - 1], ¶ms4.m_weights[3]); color_quad_i rot_pixels[16]; const color_quad_i * pTrial_pixels = pPixels; uint32_t trial_lo_a = lo_a, trial_hi_a = hi_a; if (rotation) { trial_lo_a = 255; trial_hi_a = 0; for ( uint32_t i = 0; i < 16; i++) { color_quad_i c = pPixels[i]; swapi(&c.m_c[3], &c.m_c[rotation - 1]); rot_pixels[i] = c; trial_lo_a = minimumu(trial_lo_a, c.m_c[3]); trial_hi_a = maximumu(trial_hi_a, c.m_c[3]); } pTrial_pixels = rot_pixels; } bc7_optimization_results trial_opt_results4; uint64_t trial_mode4_err = best_err; handle_alpha_block_mode4(pTrial_pixels, pComp_params, ¶ms4, trial_lo_a, trial_hi_a, &trial_opt_results4, &trial_mode4_err); if (trial_mode4_err < best_err) { best_err = trial_mode4_err; opt_results.m_mode = 4; opt_results.m_index_selector = trial_opt_results4.m_index_selector; opt_results.m_rotation = rotation; opt_results.m_partition = 0; opt_results.m_used_lut = trial_opt_results4.m_used_lut; opt_results.m_low[0] = trial_opt_results4.m_low[0]; opt_results.m_high[0] = trial_opt_results4.m_high[0]; for ( uint32_t i = 0; i < 16; i++) opt_results.m_selectors[i] = trial_opt_results4.m_selectors[i]; for ( uint32_t i = 0; i < 16; i++) opt_results.m_alpha_selectors[i] = trial_opt_results4.m_alpha_selectors[i]; } } // rotation } // Mode 6 if (pComp_params->m_alpha_settings.m_use_mode6) { color_cell_compressor_params params6 = *pParams; params6.m_weights[0] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[0]; params6.m_weights[1] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[1]; params6.m_weights[2] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[2]; params6.m_weights[3] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[3]; color_cell_compressor_results results6; params6.m_pSelector_weights = g_bc7_weights4; params6.m_pSelector_weightsx = (const vec4F *)&g_bc7_weights4x[0]; params6.m_num_selector_weights = 16; params6.m_comp_bits = 7; params6.m_has_pbits = true; params6.m_endpoints_share_pbit = false; params6.m_has_alpha = true; int selectors[16]; results6.m_pSelectors = selectors; int selectors_temp[16]; results6.m_pSelectors_temp = selectors_temp; uint64_t mode6_err = color_cell_compression(6, ¶ms6, &results6, pComp_params, 16, pPixels, true); assert(mode6_err == results6.m_best_overall_err); if (mode6_err < best_err) { best_err = mode6_err; opt_results.m_mode = 6; opt_results.m_index_selector = 0; opt_results.m_rotation = 0; opt_results.m_partition = 0; opt_results.m_low[0] = results6.m_low_endpoint; opt_results.m_high[0] = results6.m_high_endpoint; opt_results.m_pbits[0][0] = results6.m_pbits[0]; opt_results.m_pbits[0][1] = results6.m_pbits[1]; opt_results.m_used_lut = results6.m_used_lut; for ( int i = 0; i < 16; i++) opt_results.m_selectors[i] = selectors[i]; } } // Mode 5 if (pComp_params->m_alpha_settings.m_use_mode5) { color_cell_compressor_params params5 = *pParams; const int num_rotations = (pComp_params->m_perceptual || (!pComp_params->m_alpha_settings.m_use_mode5_rotation)) ? 1 : 4; for ( uint32_t rotation = 0; rotation < (uint32_t)num_rotations; rotation++) { if ((pComp_params->m_mode5_rotation_mask & (1 << rotation)) == 0) continue; memcpy(params5.m_weights, pParams->m_weights, sizeof(params5.m_weights)); if (rotation) swapu(¶ms5.m_weights[rotation - 1], ¶ms5.m_weights[3]); color_quad_i rot_pixels[16]; const color_quad_i * pTrial_pixels = pPixels; uint32_t trial_lo_a = lo_a, trial_hi_a = hi_a; if (rotation) { trial_lo_a = 255; trial_hi_a = 0; for ( uint32_t i = 0; i < 16; i++) { color_quad_i c = pPixels[i]; swapi(&c.m_c[3], &c.m_c[rotation - 1]); rot_pixels[i] = c; trial_lo_a = minimumu(trial_lo_a, c.m_c[3]); trial_hi_a = maximumu(trial_hi_a, c.m_c[3]); } pTrial_pixels = rot_pixels; } bc7_optimization_results trial_opt_results5; uint64_t trial_mode5_err = 0; handle_alpha_block_mode5(pTrial_pixels, pComp_params, ¶ms5, trial_lo_a, trial_hi_a, &trial_opt_results5, &trial_mode5_err); if (trial_mode5_err < best_err) { best_err = trial_mode5_err; opt_results = trial_opt_results5; opt_results.m_rotation = rotation; } } // rotation } // Mode 7 if (pComp_params->m_alpha_settings.m_use_mode7) { solution solutions[BC7E_MAX_PARTITIONS7]; uint32_t num_solutions; if (forced_partition >= 0) { solutions[0].m_index = forced_partition; num_solutions = 1; } else num_solutions = estimate_partition_list(7, pPixels, pComp_params, solutions, pComp_params->m_alpha_settings.m_max_mode7_partitions_to_try); color_cell_compressor_params params7 = *pParams; params7.m_weights[0] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[0]; params7.m_weights[1] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[1]; params7.m_weights[2] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[2]; params7.m_weights[3] *= pComp_params->m_alpha_settings.m_mode67_error_weight_mul[3]; params7.m_pSelector_weights = g_bc7_weights2; params7.m_pSelector_weightsx = (const vec4F *)&g_bc7_weights2x[0]; params7.m_num_selector_weights = 4; params7.m_comp_bits = 5; params7.m_has_pbits = true; params7.m_endpoints_share_pbit = false; params7.m_has_alpha = true; int selectors_temp[16]; const bool disable_faster_part_selection = false; for ( uint32_t solution_index = 0; solution_index < num_solutions; solution_index++) { const uint32_t trial_partition = solutions[solution_index].m_index; assert(trial_partition < 64); const int *pPartition = &g_bc7_partition2[trial_partition * 16]; color_quad_i subset_colors[2][16]; uint32_t subset_total_colors7[2]; subset_total_colors7[0] = 0; subset_total_colors7[1] = 0; int subset_pixel_index7[2][16]; int subset_selectors7[2][16]; color_cell_compressor_results subset_results7[2]; for ( uint32_t idx = 0; idx < 16; idx++) { const uint32_t p = pPartition[idx]; assert(p < 2); subset_colors[p][subset_total_colors7[p]] = pPixels[idx]; subset_pixel_index7[p][subset_total_colors7[p]] = idx; subset_total_colors7[p]++; } uint64_t trial_err = 0; for ( uint32_t subset = 0; subset < 2; subset++) { color_cell_compressor_results * pResults = &subset_results7[subset]; pResults->m_pSelectors = &subset_selectors7[subset][0]; pResults->m_pSelectors_temp = selectors_temp; uint64_t err = color_cell_compression(7, ¶ms7, pResults, pComp_params, subset_total_colors7[subset], &subset_colors[subset][0], (num_solutions <= 2) || disable_faster_part_selection); assert(err == pResults->m_best_overall_err); trial_err += err; if (trial_err > best_err) break; } // subset if (trial_err < best_err) { best_err = trial_err; opt_results.m_mode = 7; opt_results.m_index_selector = 0; opt_results.m_rotation = 0; opt_results.m_partition = trial_partition; for ( uint32_t subset = 0; subset < 2; subset++) { for ( uint32_t i = 0; i < subset_total_colors7[subset]; i++) { const uint32_t pixel_index = subset_pixel_index7[subset][i]; opt_results.m_selectors[pixel_index] = subset_selectors7[subset][i]; } opt_results.m_low[subset] = subset_results7[subset].m_low_endpoint; opt_results.m_high[subset] = subset_results7[subset].m_high_endpoint; opt_results.m_pbits[subset][0] = subset_results7[subset].m_pbits[0]; opt_results.m_pbits[subset][1] = subset_results7[subset].m_pbits[1]; } opt_results.m_used_lut = subset_results7[0].m_used_lut || subset_results7[1].m_used_lut; } } // solution_index if ((num_solutions > 2) && (opt_results.m_mode == 7) && (!disable_faster_part_selection)) { const uint32_t trial_partition = opt_results.m_partition; assert(trial_partition < 64); const int *pPartition = &g_bc7_partition2[trial_partition * 16]; color_quad_i subset_colors[2][16]; uint32_t subset_total_colors7[2]; subset_total_colors7[0] = 0; subset_total_colors7[1] = 0; int subset_pixel_index7[2][16]; int subset_selectors7[2][16]; color_cell_compressor_results subset_results7[2]; for ( uint32_t idx = 0; idx < 16; idx++) { const uint32_t p = pPartition[idx]; assert(p < 2); subset_colors[p][subset_total_colors7[p]] = pPixels[idx]; subset_pixel_index7[p][subset_total_colors7[p]] = idx; subset_total_colors7[p]++; } uint64_t trial_err = 0; for ( uint32_t subset = 0; subset < 2; subset++) { color_cell_compressor_results * pResults = &subset_results7[subset]; pResults->m_pSelectors = &subset_selectors7[subset][0]; pResults->m_pSelectors_temp = selectors_temp; uint64_t err = color_cell_compression(7, ¶ms7, pResults, pComp_params, subset_total_colors7[subset], &subset_colors[subset][0], true); assert(err == pResults->m_best_overall_err); trial_err += err; if (trial_err > best_err) break; } // subset if (trial_err < best_err) { best_err = trial_err; for ( uint32_t subset = 0; subset < 2; subset++) { for ( uint32_t i = 0; i < subset_total_colors7[subset]; i++) { const uint32_t pixel_index = subset_pixel_index7[subset][i]; opt_results.m_selectors[pixel_index] = subset_selectors7[subset][i]; } opt_results.m_low[subset] = subset_results7[subset].m_low_endpoint; opt_results.m_high[subset] = subset_results7[subset].m_high_endpoint; opt_results.m_pbits[subset][0] = subset_results7[subset].m_pbits[0]; opt_results.m_pbits[subset][1] = subset_results7[subset].m_pbits[1]; } opt_results.m_used_lut = subset_results7[0].m_used_lut || subset_results7[1].m_used_lut; } } } if (pBest_err) *pBest_err = best_err; if (pUsed_lut) *pUsed_lut = opt_results.m_used_lut; encode_bc7_block(pBlock, &opt_results); } static void handle_opaque_block(void * pBlock, const color_quad_i * pPixels, const bc7e_compress_block_params * pComp_params, color_cell_compressor_params * pParams, int forced_partition = -1, uint64_t* pBest_err = nullptr, bool* pUsed_lut = nullptr) { int selectors_temp[16]; bc7_optimization_results opt_results = {}; uint64_t best_err = UINT64_MAX; // Mode 6 if (pComp_params->m_opaque_settings.m_use_mode[6]) { pParams->m_pSelector_weights = g_bc7_weights4; pParams->m_pSelector_weightsx = (const vec4F * )&g_bc7_weights4x[0]; pParams->m_num_selector_weights = 16; pParams->m_comp_bits = 7; pParams->m_has_pbits = true; pParams->m_endpoints_share_pbit = false; pParams->m_perceptual = pComp_params->m_perceptual; color_cell_compressor_results results6; results6.m_pSelectors = opt_results.m_selectors; results6.m_pSelectors_temp = selectors_temp; best_err = color_cell_compression(6, pParams, &results6, pComp_params, 16, pPixels, true); opt_results.m_mode = 6; opt_results.m_index_selector = 0; opt_results.m_rotation = 0; opt_results.m_partition = 0; opt_results.m_used_lut = results6.m_used_lut; opt_results.m_low[0] = results6.m_low_endpoint; opt_results.m_high[0] = results6.m_high_endpoint; opt_results.m_pbits[0][0] = results6.m_pbits[0]; opt_results.m_pbits[0][1] = results6.m_pbits[1]; } solution solutions2[BC7E_MAX_PARTITIONS3]; uint32_t num_solutions2 = 0; if (pComp_params->m_opaque_settings.m_use_mode[1] || pComp_params->m_opaque_settings.m_use_mode[3]) { if (forced_partition >= 0) { solutions2[0].m_index = forced_partition; num_solutions2 = 1; } else if (pComp_params->m_opaque_settings.m_max_mode13_partitions_to_try == 1) { solutions2[0].m_index = estimate_partition(1, pPixels, pComp_params); num_solutions2 = 1; } else { num_solutions2 = estimate_partition_list(1, pPixels, pComp_params, solutions2, pComp_params->m_opaque_settings.m_max_mode13_partitions_to_try); } } const bool disable_faster_part_selection = false; // Mode 1 if (pComp_params->m_opaque_settings.m_use_mode[1]) { pParams->m_pSelector_weights = g_bc7_weights3; pParams->m_pSelector_weightsx = (const vec4F *)&g_bc7_weights3x[0]; pParams->m_num_selector_weights = 8; pParams->m_comp_bits = 6; pParams->m_has_pbits = true; pParams->m_endpoints_share_pbit = true; pParams->m_perceptual = pComp_params->m_perceptual; for ( uint32_t solution_index = 0; solution_index < num_solutions2; solution_index++) { const uint32_t trial_partition = solutions2[solution_index].m_index; assert(trial_partition < 64); const int *pPartition = &g_bc7_partition2[trial_partition * 16]; color_quad_i subset_colors[2][16]; uint32_t subset_total_colors1[2]; subset_total_colors1[0] = 0; subset_total_colors1[1] = 0; int subset_pixel_index1[2][16]; int subset_selectors1[2][16]; color_cell_compressor_results subset_results1[2]; for ( uint32_t idx = 0; idx < 16; idx++) { const uint32_t p = pPartition[idx]; assert(p < 2); subset_colors[p][subset_total_colors1[p]] = pPixels[idx]; subset_pixel_index1[p][subset_total_colors1[p]] = idx; subset_total_colors1[p]++; } uint64_t trial_err = 0; for ( uint32_t subset = 0; subset < 2; subset++) { color_cell_compressor_results * pResults = &subset_results1[subset]; pResults->m_pSelectors = &subset_selectors1[subset][0]; pResults->m_pSelectors_temp = selectors_temp; uint64_t err = color_cell_compression(1, pParams, pResults, pComp_params, subset_total_colors1[subset], &subset_colors[subset][0], (num_solutions2 <= 2) || disable_faster_part_selection); assert(err == pResults->m_best_overall_err); trial_err += err; if (trial_err > best_err) break; } // subset if (trial_err < best_err) { best_err = trial_err; opt_results.m_mode = 1; opt_results.m_index_selector = 0; opt_results.m_rotation = 0; opt_results.m_partition = trial_partition; opt_results.m_used_lut = subset_results1[0].m_used_lut || subset_results1[1].m_used_lut; for ( uint32_t subset = 0; subset < 2; subset++) { for ( uint32_t i = 0; i < subset_total_colors1[subset]; i++) { const uint32_t pixel_index = subset_pixel_index1[subset][i]; opt_results.m_selectors[pixel_index] = subset_selectors1[subset][i]; } opt_results.m_low[subset] = subset_results1[subset].m_low_endpoint; opt_results.m_high[subset] = subset_results1[subset].m_high_endpoint; opt_results.m_pbits[subset][0] = subset_results1[subset].m_pbits[0]; } } } if ((num_solutions2 > 2) && (opt_results.m_mode == 1) && (!disable_faster_part_selection)) { const uint32_t trial_partition = opt_results.m_partition; assert(trial_partition < 64); const int *pPartition = &g_bc7_partition2[trial_partition * 16]; color_quad_i subset_colors[2][16]; uint32_t subset_total_colors1[2]; subset_total_colors1[0] = 0; subset_total_colors1[1] = 0; int subset_pixel_index1[2][16]; int subset_selectors1[2][16]; color_cell_compressor_results subset_results1[2]; for ( uint32_t idx = 0; idx < 16; idx++) { const uint32_t p = pPartition[idx]; assert(p < 2); subset_colors[p][subset_total_colors1[p]] = pPixels[idx]; subset_pixel_index1[p][subset_total_colors1[p]] = idx; subset_total_colors1[p]++; } uint64_t trial_err = 0; for ( uint32_t subset = 0; subset < 2; subset++) { color_cell_compressor_results * pResults = &subset_results1[subset]; pResults->m_pSelectors = &subset_selectors1[subset][0]; pResults->m_pSelectors_temp = selectors_temp; uint64_t err = color_cell_compression(1, pParams, pResults, pComp_params, subset_total_colors1[subset], &subset_colors[subset][0], true); assert(err == pResults->m_best_overall_err); trial_err += err; if (trial_err > best_err) break; } // subset if (trial_err < best_err) { best_err = trial_err; opt_results.m_used_lut = subset_results1[0].m_used_lut || subset_results1[1].m_used_lut; for ( uint32_t subset = 0; subset < 2; subset++) { for ( uint32_t i = 0; i < subset_total_colors1[subset]; i++) { const uint32_t pixel_index = subset_pixel_index1[subset][i]; opt_results.m_selectors[pixel_index] = subset_selectors1[subset][i]; } opt_results.m_low[subset] = subset_results1[subset].m_low_endpoint; opt_results.m_high[subset] = subset_results1[subset].m_high_endpoint; opt_results.m_pbits[subset][0] = subset_results1[subset].m_pbits[0]; } } } } // Mode 0 if (pComp_params->m_opaque_settings.m_use_mode[0]) { solution solutions3[BC7E_MAX_PARTITIONS0]; uint32_t num_solutions3 = 0; if (forced_partition >= 0) { solutions3[0].m_index = forced_partition; num_solutions3 = 1; } else if (pComp_params->m_opaque_settings.m_max_mode0_partitions_to_try == 1) { solutions3[0].m_index = estimate_partition(0, pPixels, pComp_params); num_solutions3 = 1; } else { num_solutions3 = estimate_partition_list(0, pPixels, pComp_params, solutions3, pComp_params->m_opaque_settings.m_max_mode0_partitions_to_try); } pParams->m_pSelector_weights = g_bc7_weights3; pParams->m_pSelector_weightsx = (const vec4F *)&g_bc7_weights3x[0]; pParams->m_num_selector_weights = 8; pParams->m_comp_bits = 4; pParams->m_has_pbits = true; pParams->m_endpoints_share_pbit = false; pParams->m_perceptual = pComp_params->m_perceptual; for ( uint32_t solution_index = 0; solution_index < num_solutions3; solution_index++) { const uint32_t best_partition0 = solutions3[solution_index].m_index; const int *pPartition = &g_bc7_partition3[best_partition0 * 16]; color_quad_i subset_colors[3][16]; uint32_t subset_total_colors0[3]; subset_total_colors0[0] = 0; subset_total_colors0[1] = 0; subset_total_colors0[2] = 0; int subset_pixel_index0[3][16]; for ( uint32_t idx = 0; idx < 16; idx++) { const uint32_t p = pPartition[idx]; subset_colors[p][subset_total_colors0[p]] = pPixels[idx]; subset_pixel_index0[p][subset_total_colors0[p]] = idx; subset_total_colors0[p]++; } color_cell_compressor_results subset_results0[3]; int subset_selectors0[3][16]; uint64_t mode0_err = 0; for ( uint32_t subset = 0; subset < 3; subset++) { color_cell_compressor_results * pResults = &subset_results0[subset]; pResults->m_pSelectors = &subset_selectors0[subset][0]; pResults->m_pSelectors_temp = selectors_temp; uint64_t err = color_cell_compression(0, pParams, pResults, pComp_params, subset_total_colors0[subset], &subset_colors[subset][0], true); assert(err == pResults->m_best_overall_err); mode0_err += err; if (mode0_err > best_err) break; } // subset if (mode0_err < best_err) { best_err = mode0_err; opt_results.m_mode = 0; opt_results.m_index_selector = 0; opt_results.m_rotation = 0; opt_results.m_partition = best_partition0; for ( uint32_t subset = 0; subset < 3; subset++) { for ( uint32_t i = 0; i < subset_total_colors0[subset]; i++) { const uint32_t pixel_index = subset_pixel_index0[subset][i]; opt_results.m_selectors[pixel_index] = subset_selectors0[subset][i]; } opt_results.m_low[subset] = subset_results0[subset].m_low_endpoint; opt_results.m_high[subset] = subset_results0[subset].m_high_endpoint; opt_results.m_pbits[subset][0] = subset_results0[subset].m_pbits[0]; opt_results.m_pbits[subset][1] = subset_results0[subset].m_pbits[1]; } opt_results.m_used_lut = subset_results0[0].m_used_lut || subset_results0[1].m_used_lut || subset_results0[2].m_used_lut; } } } // Mode 3 if (pComp_params->m_opaque_settings.m_use_mode[3]) { pParams->m_pSelector_weights = g_bc7_weights2; pParams->m_pSelector_weightsx = (const vec4F *)&g_bc7_weights2x[0]; pParams->m_num_selector_weights = 4; pParams->m_comp_bits = 7; pParams->m_has_pbits = true; pParams->m_endpoints_share_pbit = false; pParams->m_perceptual = pComp_params->m_perceptual; for ( uint32_t solution_index = 0; solution_index < num_solutions2; solution_index++) { const uint32_t trial_partition = solutions2[solution_index].m_index; assert(trial_partition < 64); const int *pPartition = &g_bc7_partition2[trial_partition * 16]; color_quad_i subset_colors[2][16]; uint32_t subset_total_colors3[2]; subset_total_colors3[0] = 0; subset_total_colors3[1] = 0; int subset_pixel_index3[2][16]; int subset_selectors3[2][16]; color_cell_compressor_results subset_results3[2]; for ( uint32_t idx = 0; idx < 16; idx++) { const uint32_t p = pPartition[idx]; assert(p < 2); subset_colors[p][subset_total_colors3[p]] = pPixels[idx]; subset_pixel_index3[p][subset_total_colors3[p]] = idx; subset_total_colors3[p]++; } uint64_t trial_err = 0; for ( uint32_t subset = 0; subset < 2; subset++) { color_cell_compressor_results * pResults = &subset_results3[subset]; pResults->m_pSelectors = &subset_selectors3[subset][0]; pResults->m_pSelectors_temp = selectors_temp; uint64_t err = color_cell_compression(3, pParams, pResults, pComp_params, subset_total_colors3[subset], &subset_colors[subset][0], (num_solutions2 <= 2) || disable_faster_part_selection); assert(err == pResults->m_best_overall_err); trial_err += err; if (trial_err > best_err) break; } // subset if (trial_err < best_err) { best_err = trial_err; opt_results.m_mode = 3; opt_results.m_index_selector = 0; opt_results.m_rotation = 0; opt_results.m_partition = trial_partition; for ( uint32_t subset = 0; subset < 2; subset++) { for ( uint32_t i = 0; i < subset_total_colors3[subset]; i++) { const uint32_t pixel_index = subset_pixel_index3[subset][i]; opt_results.m_selectors[pixel_index] = subset_selectors3[subset][i]; } opt_results.m_low[subset] = subset_results3[subset].m_low_endpoint; opt_results.m_high[subset] = subset_results3[subset].m_high_endpoint; opt_results.m_pbits[subset][0] = subset_results3[subset].m_pbits[0]; opt_results.m_pbits[subset][1] = subset_results3[subset].m_pbits[1]; } opt_results.m_used_lut = subset_results3[0].m_used_lut || subset_results3[1].m_used_lut; } } // solution_index if ((num_solutions2 > 2) && (opt_results.m_mode == 3) && (!disable_faster_part_selection)) { const uint32_t trial_partition = opt_results.m_partition; assert(trial_partition < 64); const int *pPartition = &g_bc7_partition2[trial_partition * 16]; color_quad_i subset_colors[2][16]; uint32_t subset_total_colors3[2]; subset_total_colors3[0] = 0; subset_total_colors3[1] = 0; int subset_pixel_index3[2][16]; int subset_selectors3[2][16]; color_cell_compressor_results subset_results3[2]; for ( uint32_t idx = 0; idx < 16; idx++) { const uint32_t p = pPartition[idx]; assert(p < 2); subset_colors[p][subset_total_colors3[p]] = pPixels[idx]; subset_pixel_index3[p][subset_total_colors3[p]] = idx; subset_total_colors3[p]++; } uint64_t trial_err = 0; for ( uint32_t subset = 0; subset < 2; subset++) { color_cell_compressor_results * pResults = &subset_results3[subset]; pResults->m_pSelectors = &subset_selectors3[subset][0]; pResults->m_pSelectors_temp = selectors_temp; uint64_t err = color_cell_compression(3, pParams, pResults, pComp_params, subset_total_colors3[subset], &subset_colors[subset][0], true); assert(err == pResults->m_best_overall_err); trial_err += err; if (trial_err > best_err) break; } // subset if (trial_err < best_err) { best_err = trial_err; for ( uint32_t subset = 0; subset < 2; subset++) { for ( uint32_t i = 0; i < subset_total_colors3[subset]; i++) { const uint32_t pixel_index = subset_pixel_index3[subset][i]; opt_results.m_selectors[pixel_index] = subset_selectors3[subset][i]; } opt_results.m_low[subset] = subset_results3[subset].m_low_endpoint; opt_results.m_high[subset] = subset_results3[subset].m_high_endpoint; opt_results.m_pbits[subset][0] = subset_results3[subset].m_pbits[0]; opt_results.m_pbits[subset][1] = subset_results3[subset].m_pbits[1]; } opt_results.m_used_lut = subset_results3[0].m_used_lut || subset_results3[1].m_used_lut; } } } // Mode 5 if ((!pComp_params->m_perceptual) && (pComp_params->m_opaque_settings.m_use_mode[5])) { color_cell_compressor_params params5 = *pParams; for ( uint32_t rotation = 0; rotation < 4; rotation++) { if ((pComp_params->m_mode5_rotation_mask & (1 << rotation)) == 0) continue; memcpy(params5.m_weights, pParams->m_weights, sizeof(params5.m_weights)); if (rotation) swapu(¶ms5.m_weights[rotation - 1], ¶ms5.m_weights[3]); color_quad_i rot_pixels[16]; const color_quad_i * pTrial_pixels = pPixels; uint32_t trial_lo_a = 255, trial_hi_a = 255; if (rotation) { trial_lo_a = 255; trial_hi_a = 0; for ( uint32_t i = 0; i < 16; i++) { color_quad_i c = pPixels[i]; swapi(&c.m_c[3], &c.m_c[rotation - 1]); rot_pixels[i] = c; trial_lo_a = minimumu(trial_lo_a, c.m_c[3]); trial_hi_a = maximumu(trial_hi_a, c.m_c[3]); } pTrial_pixels = rot_pixels; } bc7_optimization_results trial_opt_results5; uint64_t trial_mode5_err = 0; handle_alpha_block_mode5(pTrial_pixels, pComp_params, ¶ms5, trial_lo_a, trial_hi_a, &trial_opt_results5, &trial_mode5_err); if (trial_mode5_err < best_err) { best_err = trial_mode5_err; opt_results = trial_opt_results5; opt_results.m_rotation = rotation; } } // rotation } // Mode 2 if (pComp_params->m_opaque_settings.m_use_mode[2]) { solution solutions3[BC7E_MAX_PARTITIONS2]; uint32_t num_solutions3 = 0; if (forced_partition >= 0) { solutions3[0].m_index = forced_partition; num_solutions3 = 1; } else if (pComp_params->m_opaque_settings.m_max_mode2_partitions_to_try == 1) { solutions3[0].m_index = estimate_partition(2, pPixels, pComp_params); num_solutions3 = 1; } else { num_solutions3 = estimate_partition_list(2, pPixels, pComp_params, solutions3, pComp_params->m_opaque_settings.m_max_mode2_partitions_to_try); } pParams->m_pSelector_weights = g_bc7_weights2; pParams->m_pSelector_weightsx = (const vec4F *)&g_bc7_weights2x[0]; pParams->m_num_selector_weights = 4; pParams->m_comp_bits = 5; pParams->m_has_pbits = false; pParams->m_endpoints_share_pbit = false; pParams->m_perceptual = pComp_params->m_perceptual; for ( uint32_t solution_index = 0; solution_index < num_solutions3; solution_index++) { const int32_t best_partition2 = solutions3[solution_index].m_index; uint32_t subset_total_colors2[3]; subset_total_colors2[0] = 0; subset_total_colors2[1] = 0; subset_total_colors2[2] = 0; int subset_pixel_index2[3][16]; const int *pPartition = &g_bc7_partition3[best_partition2 * 16]; color_quad_i subset_colors[3][16]; for ( uint32_t idx = 0; idx < 16; idx++) { const uint32_t p = pPartition[idx]; subset_colors[p][subset_total_colors2[p]] = pPixels[idx]; subset_pixel_index2[p][subset_total_colors2[p]] = idx; subset_total_colors2[p]++; } int subset_selectors2[3][16]; color_cell_compressor_results subset_results2[3]; uint64_t mode2_err = 0; for ( uint32_t subset = 0; subset < 3; subset++) { color_cell_compressor_results * pResults = &subset_results2[subset]; pResults->m_pSelectors = &subset_selectors2[subset][0]; pResults->m_pSelectors_temp = selectors_temp; uint64_t err = color_cell_compression(2, pParams, pResults, pComp_params, subset_total_colors2[subset], &subset_colors[subset][0], true); assert(err == pResults->m_best_overall_err); mode2_err += err; if (mode2_err > best_err) break; } // subset if (mode2_err < best_err) { best_err = mode2_err; opt_results.m_mode = 2; opt_results.m_index_selector = 0; opt_results.m_rotation = 0; opt_results.m_partition = best_partition2; for ( uint32_t subset = 0; subset < 3; subset++) { for ( uint32_t i = 0; i < subset_total_colors2[subset]; i++) { const uint32_t pixel_index = subset_pixel_index2[subset][i]; opt_results.m_selectors[pixel_index] = subset_selectors2[subset][i]; } opt_results.m_low[subset] = subset_results2[subset].m_low_endpoint; opt_results.m_high[subset] = subset_results2[subset].m_high_endpoint; } opt_results.m_used_lut = subset_results2[0].m_used_lut || subset_results2[1].m_used_lut || subset_results2[2].m_used_lut; } } } // Mode 4 if ((!pComp_params->m_perceptual) && (pComp_params->m_opaque_settings.m_use_mode[4])) { color_cell_compressor_params params4 = *pParams; for ( uint32_t rotation = 0; rotation < 4; rotation++) { if ((pComp_params->m_mode4_rotation_mask & (1 << rotation)) == 0) continue; memcpy(params4.m_weights, pParams->m_weights, sizeof(params4.m_weights)); if (rotation) swapu(¶ms4.m_weights[rotation - 1], ¶ms4.m_weights[3]); color_quad_i rot_pixels[16]; const color_quad_i * pTrial_pixels = pPixels; uint32_t trial_lo_a = 255, trial_hi_a = 255; if (rotation) { trial_lo_a = 255; trial_hi_a = 0; for ( uint32_t i = 0; i < 16; i++) { color_quad_i c = pPixels[i]; swapi(&c.m_c[3], &c.m_c[rotation - 1]); rot_pixels[i] = c; trial_lo_a = minimumu(trial_lo_a, c.m_c[3]); trial_hi_a = maximumu(trial_hi_a, c.m_c[3]); } pTrial_pixels = rot_pixels; } bc7_optimization_results trial_opt_results4; uint64_t trial_mode4_err = best_err; handle_alpha_block_mode4(pTrial_pixels, pComp_params, ¶ms4, trial_lo_a, trial_hi_a, &trial_opt_results4, &trial_mode4_err); if (trial_mode4_err < best_err) { best_err = trial_mode4_err; opt_results.m_mode = 4; opt_results.m_index_selector = trial_opt_results4.m_index_selector; opt_results.m_rotation = rotation; opt_results.m_partition = 0; opt_results.m_used_lut = trial_opt_results4.m_used_lut; opt_results.m_low[0] = trial_opt_results4.m_low[0]; opt_results.m_high[0] = trial_opt_results4.m_high[0]; for ( uint32_t i = 0; i < 16; i++) opt_results.m_selectors[i] = trial_opt_results4.m_selectors[i]; for ( uint32_t i = 0; i < 16; i++) opt_results.m_alpha_selectors[i] = trial_opt_results4.m_alpha_selectors[i]; } } // rotation } if (pBest_err) *pBest_err = best_err; if (pUsed_lut) *pUsed_lut = opt_results.m_used_lut; encode_bc7_block(pBlock, &opt_results); } // all solid color blocks can be 100% perfectly encoded with just mode 5 static void handle_block_solid(void * pBlock, uint32_t cr, uint32_t cg, uint32_t cb, uint32_t ca, bool* pUsed_lut = nullptr) { if (pUsed_lut) *pUsed_lut = true; // solid blocks always use the mode-5 optimal-endpoint LUT uint32_t er = g_bc7_mode_5_optimal_endpoints[cr]; uint32_t eg = g_bc7_mode_5_optimal_endpoints[cg]; uint32_t eb = g_bc7_mode_5_optimal_endpoints[cb]; color_quad_i lp, hp; color_quad_i_set(&lp, er & 0xFF, eg & 0xFF, eb & 0xFF, ca); color_quad_i_set(&hp, er >> 8, eg >> 8, eb >> 8, ca); bc7_optimization_results opt; opt.m_mode = 5; opt.m_low[0] = lp; opt.m_high[0] = hp; opt.m_pbits[0][0] = 0; opt.m_pbits[0][1] = 0; opt.m_index_selector = 0; opt.m_rotation = 0; opt.m_partition = 0; for ( int i = 0; i < 16; ++i) opt.m_selectors[i] = BC7E_MODE_5_OPTIMAL_INDEX; for ( int i = 0; i < 16; ++i) opt.m_alpha_selectors[i] = 0; encode_bc7_block(pBlock, &opt); } static void handle_opaque_block_mode6(void * pBlock, const color_quad_i * pPixels, const bc7e_compress_block_params * pComp_params, color_cell_compressor_params * pParams, bool* pUsed_lut = nullptr) { int selectors_temp[16]; bc7_optimization_results opt_results = {}; uint64_t best_err = UINT64_MAX; // Mode 6 pParams->m_pSelector_weights = g_bc7_weights4; pParams->m_pSelector_weightsx = (const vec4F * )&g_bc7_weights4x[0]; pParams->m_num_selector_weights = 16; pParams->m_comp_bits = 7; pParams->m_has_pbits = true; pParams->m_endpoints_share_pbit = false; pParams->m_perceptual = pComp_params->m_perceptual; color_cell_compressor_results results6; results6.m_pSelectors = opt_results.m_selectors; results6.m_pSelectors_temp = selectors_temp; best_err = color_cell_compression(6, pParams, &results6, pComp_params, 16, pPixels, true); NOTE_UNUSED(best_err); // mode 6 here is unconditional; the returned error isn't compared - silence clang -Wunused-but-set-variable opt_results.m_mode = 6; opt_results.m_index_selector = 0; opt_results.m_rotation = 0; opt_results.m_partition = 0; opt_results.m_low[0] = results6.m_low_endpoint; opt_results.m_high[0] = results6.m_high_endpoint; opt_results.m_pbits[0][0] = results6.m_pbits[0]; opt_results.m_pbits[0][1] = results6.m_pbits[1]; if (pUsed_lut) *pUsed_lut = results6.m_used_lut; encode_bc7_block_mode6(pBlock, &opt_results); } // Compress a single 4x4 RGBA block to ONE specified BC7 mode only. Additive API: it does // not touch bc7e_compress_blocks(). Writes the 16-byte block to pBlock and returns its error. // mode : 0..7 // partition : modes 0,1,2,3,7 -> partition pattern index, or -1 to auto-select the // optimal one (existing estimate_partition* logic). Ignored for 4,5,6. // rotation : modes 4,5 -> dual-plane component rotation [0..3] (0 = none). // index_selector : mode 4 -> 0 or 1 (which index set is the scalar channel). // Forcing an opaque mode (0-3) on a block with alpha drops alpha (those modes force A=255). uint64_t bc7e_compress_block_single_mode(uint64_t* pBlock, const uint32_t* pPixelsRGBA, const bc7e_compress_block_params* pComp_params, uint32_t mode, int partition, uint32_t rotation, uint32_t index_selector) { assert(g_codec_initialized); assert(mode <= 7); // Extract the 16 pixels + alpha range (mirrors bc7e_compress_blocks). const color_quad_u8* pSrcPixels = (const color_quad_u8*)pPixelsRGBA; color_quad_i temp_pixels[16]; int lo_a = 255, hi_a = 0; for (uint32_t i = 0; i < 16; i++) { color_quad_u8 c = pSrcPixels[i]; temp_pixels[i].m_c[0] = c.m_c[0]; temp_pixels[i].m_c[1] = c.m_c[1]; temp_pixels[i].m_c[2] = c.m_c[2]; temp_pixels[i].m_c[3] = c.m_c[3]; lo_a = min(lo_a, (int)c.m_c[3]); hi_a = max(hi_a, (int)c.m_c[3]); } color_cell_compressor_params ccparams; color_cell_compressor_params_clear(&ccparams); memcpy(ccparams.m_weights, pComp_params->m_weights, sizeof(ccparams.m_weights)); // Single-mode params: copy caller's settings, disable every mode, enable only this one. bc7e_compress_block_params p = *pComp_params; for (uint32_t i = 0; i < 7; i++) p.m_opaque_settings.m_use_mode[i] = false; p.m_alpha_settings.m_use_mode4 = false; p.m_alpha_settings.m_use_mode5 = false; p.m_alpha_settings.m_use_mode6 = false; p.m_alpha_settings.m_use_mode7 = false; p.m_mode6_only = false; // Validate/clamp a caller-supplied partition to the mode's valid range; -1 means auto-select. // Partition field width: mode 0 is 4 bits (16 patterns); modes 1,2,3,7 are 6 bits (64 patterns); // modes 4,5,6 have no partition field. int forced_partition = partition; if (forced_partition >= 0) { const int num_partitions = 1 << g_bc7_partition_bits[mode]; // 1 for non-partitioned modes assert((num_partitions > 1) && "partition index supplied for a non-partitioned BC7 mode (4/5/6)"); assert((forced_partition < num_partitions) && "partition index out of range for this BC7 mode (mode 0: 0-15, modes 1/2/3/7: 0-63)"); if (num_partitions <= 1) forced_partition = -1; // mode has no partition field; ignore else if (forced_partition >= num_partitions) forced_partition = num_partitions - 1; } uint64_t best_err = UINT64_MAX; if (mode <= 3) { // Opaque-only modes (alpha dropped / forced to 255). p.m_opaque_settings.m_use_mode[mode] = true; handle_opaque_block(pBlock, temp_pixels, &p, &ccparams, forced_partition, &best_err); } else if ((mode == 4) || (mode == 5)) { // A non-zero rotation is only reachable under the handler's linear-metric gate, so // force linear when one is explicitly requested (keeps the result valid + deterministic). if (rotation != 0) p.m_perceptual = false; if (mode == 4) { p.m_alpha_settings.m_use_mode4 = true; p.m_alpha_settings.m_use_mode4_rotation = true; p.m_mode4_rotation_mask = 1u << (rotation & 3); p.m_mode4_index_mask = 1u << (index_selector & 1); } else { p.m_alpha_settings.m_use_mode5 = true; p.m_alpha_settings.m_use_mode5_rotation = true; p.m_mode5_rotation_mask = 1u << (rotation & 3); } handle_alpha_block(pBlock, temp_pixels, &p, &ccparams, (uint32_t)lo_a, (uint32_t)hi_a, -1, &best_err); } else if (mode == 6) { p.m_alpha_settings.m_use_mode6 = true; handle_alpha_block(pBlock, temp_pixels, &p, &ccparams, (uint32_t)lo_a, (uint32_t)hi_a, -1, &best_err); } else // mode == 7 { p.m_alpha_settings.m_use_mode7 = true; handle_alpha_block(pBlock, temp_pixels, &p, &ccparams, (uint32_t)lo_a, (uint32_t)hi_a, forced_partition, &best_err); } return best_err; } void bc7e_compress_blocks( uint32_t num_blocks, uint64_t * pBlocks, const uint32_t * pPixelsRGBA, const bc7e_compress_block_params * pComp_params, uint8_t * pUsed_lut) { if (!g_codec_initialized) { // Caller has forgotten to initialize the codec, or another thread is still working on that. We can't continue. // What do we do here? assert(0); memset(pBlocks, 0, num_blocks * 16); return; } color_cell_compressor_params params; color_cell_compressor_params_clear(¶ms); memcpy(params.m_weights, pComp_params->m_weights, sizeof(params.m_weights)); assert(pComp_params->m_mode4_rotation_mask != 0); assert(pComp_params->m_mode4_index_mask != 0); assert(pComp_params->m_mode5_rotation_mask != 0); assert(pComp_params->m_uber1_mask != 0); for (int32_t block_index = 0; block_index < (int32_t)num_blocks; block_index++) { const color_quad_u8 * pSrcPixels = &((const color_quad_u8 *)(pPixelsRGBA))[block_index * 16]; color_quad_i temp_pixels[16]; int lo_r = 255, hi_r = 0; int lo_g = 255, hi_g = 0; int lo_b = 255, hi_b = 0; float lo_a = 255, hi_a = 0; for ( uint32_t i = 0; i < 16; i++) { color_quad_u8 c = pSrcPixels[i]; int r = c.m_c[0]; int g = c.m_c[1]; int b = c.m_c[2]; int a = c.m_c[3]; temp_pixels[i].m_c[0] = r; temp_pixels[i].m_c[1] = g; temp_pixels[i].m_c[2] = b; temp_pixels[i].m_c[3] = a; lo_r = min(lo_r, r); hi_r = max(hi_r, r); lo_g = min(lo_g, g); hi_g = max(hi_g, g); lo_b = min(lo_b, b); hi_b = max(hi_b, b); float fa = (float)(a); lo_a = min(lo_a, fa); hi_a = max(hi_a, fa); } bool all_same = lo_r==hi_r && lo_g==hi_g && lo_b==hi_b && lo_a==hi_a; uint64_t * pBlock = &pBlocks[block_index * 2]; bool block_used_lut = false; cif (all_same) handle_block_solid(pBlock, lo_r, lo_g, lo_b, float_to_uint8(lo_a), &block_used_lut); else { const bool has_alpha = (lo_a < 255); // TODO: alpha block mode 6 only cif (has_alpha) handle_alpha_block(pBlock, temp_pixels, pComp_params, ¶ms, (int)lo_a, (int)hi_a, -1, nullptr, &block_used_lut); else { if (pComp_params->m_mode6_only) handle_opaque_block_mode6(pBlock, temp_pixels, pComp_params, ¶ms, &block_used_lut); else handle_opaque_block(pBlock, temp_pixels, pComp_params, ¶ms, -1, nullptr, &block_used_lut); } } if (pUsed_lut) pUsed_lut[block_index] = block_used_lut ? 1 : 0; } } void bc7e_compress_block_params_init(bc7e_compress_block_params * p, bool perceptual) { p->m_max_partitions_mode[0] = BC7E_MAX_PARTITIONS0; p->m_max_partitions_mode[1] = BC7E_MAX_PARTITIONS1; p->m_max_partitions_mode[2] = BC7E_MAX_PARTITIONS2; p->m_max_partitions_mode[3] = BC7E_MAX_PARTITIONS3; p->m_max_partitions_mode[4] = 0; p->m_max_partitions_mode[5] = 0; p->m_max_partitions_mode[6] = 0; p->m_max_partitions_mode[7] = BC7E_MAX_PARTITIONS7; p->m_use_luts = true; // default ON, matching bc7e's original behavior; callers may disable (e.g. XBC7 turns it off below lossless Q) p->m_perceptual = perceptual; if (perceptual) { p->m_weights[0] = 128; p->m_weights[1] = 64; p->m_weights[2] = 16; p->m_weights[3] = 256; } else { p->m_weights[0] = 1; p->m_weights[1] = 1; p->m_weights[2] = 1; p->m_weights[3] = 1; } p->m_pbit_search = false; p->m_mode6_only = false; p->m_refinement_passes = 1; p->m_mode4_rotation_mask = 0xF; p->m_mode4_index_mask = 3; p->m_mode5_rotation_mask = 0xF; p->m_uber1_mask = 7; for ( uint32_t i = 0; i < 7; i++) p->m_opaque_settings.m_use_mode[i] = true; p->m_opaque_settings.m_max_mode13_partitions_to_try = 1; p->m_opaque_settings.m_max_mode0_partitions_to_try = 1; p->m_opaque_settings.m_max_mode2_partitions_to_try = 1; p->m_alpha_settings.m_use_mode4 = true; p->m_alpha_settings.m_use_mode5 = true; p->m_alpha_settings.m_use_mode6 = true; p->m_alpha_settings.m_use_mode7 = true; p->m_alpha_settings.m_use_mode4_rotation = true; p->m_alpha_settings.m_use_mode5_rotation = true; p->m_alpha_settings.m_max_mode7_partitions_to_try = 1; p->m_alpha_settings.m_mode67_error_weight_mul[0] = 1; p->m_alpha_settings.m_mode67_error_weight_mul[1] = 1; p->m_alpha_settings.m_mode67_error_weight_mul[2] = 1; p->m_alpha_settings.m_mode67_error_weight_mul[3] = 1; p->m_uber_level = 0; } void bc7e_compress_block_params_init_slowest(bc7e_compress_block_params * p, bool perceptual) { bc7e_compress_block_params_init(p, perceptual); p->m_opaque_settings.m_max_mode13_partitions_to_try = 4; p->m_opaque_settings.m_max_mode0_partitions_to_try = 4; p->m_opaque_settings.m_max_mode2_partitions_to_try = 4; p->m_alpha_settings.m_max_mode7_partitions_to_try = 4; p->m_pbit_search = true; p->m_uber_level = 4; } void bc7e_compress_block_params_init_veryslow(bc7e_compress_block_params * p, bool perceptual) { bc7e_compress_block_params_init(p, perceptual); p->m_opaque_settings.m_max_mode13_partitions_to_try = 2; p->m_opaque_settings.m_max_mode0_partitions_to_try = 2; p->m_opaque_settings.m_max_mode2_partitions_to_try = 2; p->m_alpha_settings.m_max_mode7_partitions_to_try = 2; p->m_pbit_search = true; p->m_uber_level = 2; } void bc7e_compress_block_params_init_slow(bc7e_compress_block_params * p, bool perceptual) { bc7e_compress_block_params_init(p, perceptual); p->m_alpha_settings.m_max_mode7_partitions_to_try = 2; p->m_pbit_search = true; p->m_uber_level = 0; } void bc7e_compress_block_params_init_basic(bc7e_compress_block_params * p, bool perceptual) { bc7e_compress_block_params_init(p, perceptual); if (perceptual) { p->m_opaque_settings.m_use_mode[0] = false; p->m_opaque_settings.m_use_mode[2] = false; p->m_opaque_settings.m_use_mode[3] = false; p->m_opaque_settings.m_use_mode[4] = false; p->m_opaque_settings.m_use_mode[5] = false; p->m_pbit_search = false; p->m_uber_level = 1; } else { p->m_max_partitions_mode[1] = 32; p->m_max_partitions_mode[2] = 32; p->m_max_partitions_mode[3] = 32; p->m_max_partitions_mode[7] = 32; p->m_opaque_settings.m_use_mode[2] = false; p->m_pbit_search = false; p->m_uber_level = 1; } } void bc7e_compress_block_params_init_fast(bc7e_compress_block_params * p, bool perceptual) { bc7e_compress_block_params_init(p, perceptual); if (perceptual) { p->m_opaque_settings.m_use_mode[0] = false; p->m_opaque_settings.m_use_mode[2] = false; p->m_opaque_settings.m_use_mode[3] = false; p->m_opaque_settings.m_use_mode[4] = false; p->m_opaque_settings.m_use_mode[5] = false; p->m_alpha_settings.m_use_mode5 = false; p->m_opaque_settings.m_max_mode13_partitions_to_try = 1; p->m_pbit_search = false; p->m_uber_level = 0; } else { p->m_opaque_settings.m_use_mode[0] = false; p->m_opaque_settings.m_use_mode[2] = false; p->m_opaque_settings.m_use_mode[4] = false; p->m_opaque_settings.m_use_mode[5] = false; p->m_alpha_settings.m_use_mode5 = false; p->m_opaque_settings.m_max_mode13_partitions_to_try = 2; p->m_pbit_search = false; p->m_uber_level = 0; } } void bc7e_compress_block_params_init_veryfast(bc7e_compress_block_params * p, bool perceptual) { bc7e_compress_block_params_init(p, perceptual); if (perceptual) { p->m_opaque_settings.m_use_mode[0] = false; p->m_opaque_settings.m_use_mode[2] = false; p->m_opaque_settings.m_use_mode[3] = false; p->m_opaque_settings.m_use_mode[4] = false; p->m_opaque_settings.m_use_mode[5] = false; p->m_alpha_settings.m_use_mode5 = false; p->m_pbit_search = false; p->m_uber_level = 0; } else { p->m_opaque_settings.m_use_mode[2] = false; p->m_opaque_settings.m_use_mode[4] = false; p->m_opaque_settings.m_use_mode[5] = false; p->m_alpha_settings.m_use_mode5 = false; p->m_pbit_search = false; p->m_uber_level = 0; } } void bc7e_compress_block_params_init_ultrafast(bc7e_compress_block_params * p, bool perceptual) { bc7e_compress_block_params_init(p, perceptual); p->m_mode6_only = true; p->m_alpha_settings.m_use_mode4 = true; p->m_alpha_settings.m_use_mode5 = true; p->m_alpha_settings.m_use_mode7 = false; p->m_mode4_rotation_mask = 1+4; p->m_mode4_index_mask = 3; p->m_mode5_rotation_mask = 1; p->m_pbit_search = false; p->m_uber_level = 0; } } // namespace bc7e_scalar