Convert VP8LFastLog2 to fixed point

The lossless encoding speed-ups are:
- up to 1% with default parameters
- up to 4% in cruncher mode: -q 100 -m 6

Change-Id: Id92d4bad0b0a2c28c8aa9ff5280eea5717017f30
This commit is contained in:
Vincent Rabaud 2024-06-26 15:21:55 +02:00
parent c4af79d053
commit 0a9f1c19f8
5 changed files with 154 additions and 81 deletions

View File

@ -73,16 +73,29 @@ static WEBP_INLINE int VP8LNearLosslessBits(int near_lossless_quality) {
// Keeping a high threshold for now.
#define APPROX_LOG_WITH_CORRECTION_MAX 65536
#define APPROX_LOG_MAX 4096
// VP8LFastLog2 and VP8LFastSLog2 are used on elements from image histograms.
// The histogram values cannot exceed the maximum number of pixels, which
// is (1 << 14) * (1 << 14). Therefore S * log(S) < (1 << 33).
// No more than 32 bits of precision should be chosen.
// To match the original float implementation, 23 bits of precision are used.
#define LOG_2_PRECISION_BITS 23
#define LOG_2_RECIPROCAL 1.44269504088896338700465094007086
// LOG_2_RECIPROCAL * (1 << LOG_2_PRECISION_BITS)
#define LOG_2_RECIPROCAL_FIXED_DOUBLE 12102203.161561485379934310913085937500
#define LOG_2_RECIPROCAL_FIXED 12102203
#define LOG_LOOKUP_IDX_MAX 256
extern const float kLog2Table[LOG_LOOKUP_IDX_MAX];
extern const uint32_t kLog2Table[LOG_LOOKUP_IDX_MAX];
extern const float kSLog2Table[LOG_LOOKUP_IDX_MAX];
typedef float (*VP8LFastLog2SlowFunc)(uint32_t v);
// TODO(vrabaud) remove this table once VP8LFastSLog2 is switched to fixed
// point.
extern const float kLog2fTable[LOG_LOOKUP_IDX_MAX];
typedef uint32_t (*VP8LFastLog2SlowFunc)(uint32_t v);
typedef float (*VP8LFastSLog2SlowFunc)(uint32_t v);
extern VP8LFastLog2SlowFunc VP8LFastLog2Slow;
extern VP8LFastLog2SlowFunc VP8LFastSLog2Slow;
extern VP8LFastSLog2SlowFunc VP8LFastSLog2Slow;
static WEBP_INLINE float VP8LFastLog2(uint32_t v) {
static WEBP_INLINE uint32_t VP8LFastLog2(uint32_t v) {
return (v < LOG_LOOKUP_IDX_MAX) ? kLog2Table[v] : VP8LFastLog2Slow(v);
}
// Fast calculation of v * log2(v) for integer input.
@ -90,6 +103,14 @@ static WEBP_INLINE float VP8LFastSLog2(uint32_t v) {
return (v < LOG_LOOKUP_IDX_MAX) ? kSLog2Table[v] : VP8LFastSLog2Slow(v);
}
static WEBP_INLINE uint64_t RightShiftRound(uint64_t v, uint32_t shift) {
return (v + (1ull << shift >> 1)) >> shift;
}
static WEBP_INLINE int64_t DivRound(int64_t a, int64_t b) {
return ((a < 0) == (b < 0)) ? ((a + b / 2) / b) : ((a - b / 2) / b);
}
// -----------------------------------------------------------------------------
// PrefixEncode()

View File

@ -25,7 +25,7 @@
#include "src/dsp/yuv.h"
// lookup table for small values of log2(int)
const float kLog2Table[LOG_LOOKUP_IDX_MAX] = {
const float kLog2fTable[LOG_LOOKUP_IDX_MAX] = {
0.0000000000000000f, 0.0000000000000000f,
1.0000000000000000f, 1.5849625007211560f,
2.0000000000000000f, 2.3219280948873621f,
@ -156,6 +156,51 @@ const float kLog2Table[LOG_LOOKUP_IDX_MAX] = {
7.9886846867721654f, 7.9943534368588577f
};
// lookup table for small values of log2(int) * (1 << LOG_2_PRECISION_BITS).
// Obtained in Python with:
// a = [ str(round((1<<23)*math.log2(i))) if i else "0" for i in range(256)]
// print(',\n'.join([' '+','.join(v)
// for v in batched([i.rjust(9) for i in a],7)]))
const uint32_t kLog2Table[LOG_LOOKUP_IDX_MAX] = {
0, 0, 8388608, 13295629, 16777216, 19477745, 21684237,
23549800, 25165824, 26591258, 27866353, 29019816, 30072845, 31041538,
31938408, 32773374, 33554432, 34288123, 34979866, 35634199, 36254961,
36845429, 37408424, 37946388, 38461453, 38955489, 39430146, 39886887,
40327016, 40751698, 41161982, 41558811, 41943040, 42315445, 42676731,
43027545, 43368474, 43700062, 44022807, 44337167, 44643569, 44942404,
45234037, 45518808, 45797032, 46069003, 46334996, 46595268, 46850061,
47099600, 47344097, 47583753, 47818754, 48049279, 48275495, 48497560,
48715624, 48929828, 49140306, 49347187, 49550590, 49750631, 49947419,
50141058, 50331648, 50519283, 50704053, 50886044, 51065339, 51242017,
51416153, 51587818, 51757082, 51924012, 52088670, 52251118, 52411415,
52569616, 52725775, 52879946, 53032177, 53182516, 53331012, 53477707,
53622645, 53765868, 53907416, 54047327, 54185640, 54322389, 54457611,
54591338, 54723604, 54854440, 54983876, 55111943, 55238669, 55364082,
55488208, 55611074, 55732705, 55853126, 55972361, 56090432, 56207362,
56323174, 56437887, 56551524, 56664103, 56775645, 56886168, 56995691,
57104232, 57211808, 57318436, 57424133, 57528914, 57632796, 57735795,
57837923, 57939198, 58039632, 58139239, 58238033, 58336027, 58433234,
58529666, 58625336, 58720256, 58814437, 58907891, 59000628, 59092661,
59183999, 59274652, 59364632, 59453947, 59542609, 59630625, 59718006,
59804761, 59890898, 59976426, 60061354, 60145690, 60229443, 60312620,
60395229, 60477278, 60558775, 60639726, 60720140, 60800023, 60879382,
60958224, 61036555, 61114383, 61191714, 61268554, 61344908, 61420785,
61496188, 61571124, 61645600, 61719620, 61793189, 61866315, 61939001,
62011253, 62083076, 62154476, 62225457, 62296024, 62366182, 62435935,
62505289, 62574248, 62642816, 62710997, 62778797, 62846219, 62913267,
62979946, 63046260, 63112212, 63177807, 63243048, 63307939, 63372484,
63436687, 63500551, 63564080, 63627277, 63690146, 63752690, 63814912,
63876816, 63938405, 63999682, 64060650, 64121313, 64181673, 64241734,
64301498, 64360969, 64420148, 64479040, 64537646, 64595970, 64654014,
64711782, 64769274, 64826495, 64883447, 64940132, 64996553, 65052711,
65108611, 65164253, 65219641, 65274776, 65329662, 65384299, 65438691,
65492840, 65546747, 65600416, 65653847, 65707044, 65760008, 65812741,
65865245, 65917522, 65969575, 66021404, 66073013, 66124403, 66175575,
66226531, 66277275, 66327806, 66378127, 66428240, 66478146, 66527847,
66577345, 66626641, 66675737, 66724635, 66773336, 66821842, 66870154,
66918274, 66966204, 67013944, 67061497
};
const float kSLog2Table[LOG_LOOKUP_IDX_MAX] = {
0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f,
8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f,
@ -356,43 +401,42 @@ static float FastSLog2Slow_C(uint32_t v) {
// log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v
// LOG_2_RECIPROCAL ~ 23/16
correction = (23 * (orig_v & (y - 1))) >> 4;
return v_f * (kLog2Table[v] + log_cnt) + correction;
return v_f * (kLog2fTable[v] + log_cnt) + correction;
} else {
return (float)(LOG_2_RECIPROCAL * v * log((double)v));
}
}
static float FastLog2Slow_C(uint32_t v) {
static uint32_t FastLog2Slow_C(uint32_t v) {
assert(v >= LOG_LOOKUP_IDX_MAX);
if (v < APPROX_LOG_WITH_CORRECTION_MAX) {
const uint32_t orig_v = v;
uint32_t log_2;
#if !defined(WEBP_HAVE_SLOW_CLZ_CTZ)
// use clz if available
const int log_cnt = BitsLog2Floor(v) - 7;
const uint32_t log_cnt = BitsLog2Floor(v) - 7;
const uint32_t y = 1 << log_cnt;
const uint32_t orig_v = v;
double log_2;
v >>= log_cnt;
#else
int log_cnt = 0;
uint32_t log_cnt = 0;
uint32_t y = 1;
const uint32_t orig_v = v;
double log_2;
do {
++log_cnt;
v = v >> 1;
y = y << 1;
} while (v >= LOG_LOOKUP_IDX_MAX);
#endif
log_2 = kLog2Table[v] + log_cnt;
log_2 = kLog2Table[v] + (log_cnt << LOG_2_PRECISION_BITS);
if (orig_v >= APPROX_LOG_MAX) {
// Since the division is still expensive, add this correction factor only
// for large values of 'v'.
const int correction = (23 * (orig_v & (y - 1))) >> 4;
log_2 += (double)correction / orig_v;
const uint64_t correction =
(uint64_t)LOG_2_RECIPROCAL_FIXED * (orig_v & (y - 1));
log_2 += (uint32_t)DivRound(correction, orig_v);
}
return (float)log_2;
return log_2;
} else {
return (float)(LOG_2_RECIPROCAL * log((double)v));
return (uint32_t)(LOG_2_RECIPROCAL_FIXED_DOUBLE * log((double)v) + .5);
}
}
@ -779,7 +823,7 @@ VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms;
VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms;
VP8LFastLog2SlowFunc VP8LFastLog2Slow;
VP8LFastLog2SlowFunc VP8LFastSLog2Slow;
VP8LFastSLog2SlowFunc VP8LFastSLog2Slow;
VP8LCostFunc VP8LExtraCost;
VP8LCostCombinedFunc VP8LExtraCostCombined;

View File

@ -53,18 +53,18 @@ static float FastSLog2Slow_MIPS32(uint32_t v) {
// (v % y) = (v % 2^log_cnt) = v & (2^log_cnt - 1)
correction = (23 * (v & (y - 1))) >> 4;
return v_f * (kLog2Table[temp] + log_cnt) + correction;
return v_f * (kLog2fTable[temp] + log_cnt) + correction;
} else {
return (float)(LOG_2_RECIPROCAL * v * log((double)v));
}
}
static float FastLog2Slow_MIPS32(uint32_t v) {
static uint32_t FastLog2Slow_MIPS32(uint32_t v) {
assert(v >= LOG_LOOKUP_IDX_MAX);
if (v < APPROX_LOG_WITH_CORRECTION_MAX) {
uint32_t log_cnt, y;
const int c24 = 24;
double log_2;
uint32_t log_2;
uint32_t temp;
__asm__ volatile(
@ -78,17 +78,17 @@ static float FastLog2Slow_MIPS32(uint32_t v) {
: [c24]"r"(c24), [v]"r"(v)
);
log_2 = kLog2Table[temp] + log_cnt;
log_2 = kLog2Table[temp] + (log_cnt << LOG_2_PRECISION_BITS);
if (v >= APPROX_LOG_MAX) {
// Since the division is still expensive, add this correction factor only
// for large values of 'v'.
const uint32_t correction = (23 * (v & (y - 1))) >> 4;
log_2 += (double)correction / v;
const uint64_t correction =
(uint64_t)LOG_2_RECIPROCAL_FIXED * (v & (y - 1));
log_2 += (uint32_t)DivRound(correction, v);
}
return (float)log_2;
return log_2;
} else {
return (float)(LOG_2_RECIPROCAL * log((double)v));
return (uint32_t)(LOG_2_RECIPROCAL_FIXED_DOUBLE * log((double)v) + .5);
}
}

View File

@ -15,7 +15,7 @@
//
#include <assert.h>
#include <float.h>
#include <string.h>
#include "src/dsp/lossless_common.h"
#include "src/enc/backward_references_enc.h"
@ -31,15 +31,15 @@ extern void VP8LBackwardRefsCursorAdd(VP8LBackwardRefs* const refs,
const PixOrCopy v);
typedef struct {
float alpha_[VALUES_IN_BYTE];
float red_[VALUES_IN_BYTE];
float blue_[VALUES_IN_BYTE];
float distance_[NUM_DISTANCE_CODES];
float* literal_;
uint32_t alpha_[VALUES_IN_BYTE];
uint32_t red_[VALUES_IN_BYTE];
uint32_t blue_[VALUES_IN_BYTE];
uint32_t distance_[NUM_DISTANCE_CODES];
uint32_t* literal_;
} CostModel;
static void ConvertPopulationCountTableToBitEstimates(
int num_symbols, const uint32_t population_counts[], float output[]) {
int num_symbols, const uint32_t population_counts[], uint32_t output[]) {
uint32_t sum = 0;
int nonzeros = 0;
int i;
@ -52,7 +52,7 @@ static void ConvertPopulationCountTableToBitEstimates(
if (nonzeros <= 1) {
memset(output, 0, num_symbols * sizeof(*output));
} else {
const float logsum = VP8LFastLog2(sum);
const uint32_t logsum = VP8LFastLog2(sum);
for (i = 0; i < num_symbols; ++i) {
output[i] = logsum - VP8LFastLog2(population_counts[i]);
}
@ -93,47 +93,47 @@ static int CostModelBuild(CostModel* const m, int xsize, int cache_bits,
return ok;
}
static WEBP_INLINE float GetLiteralCost(const CostModel* const m, uint32_t v) {
return m->alpha_[v >> 24] +
m->red_[(v >> 16) & 0xff] +
m->literal_[(v >> 8) & 0xff] +
m->blue_[v & 0xff];
static WEBP_INLINE int64_t GetLiteralCost(const CostModel* const m,
uint32_t v) {
return (int64_t)m->alpha_[v >> 24] + m->red_[(v >> 16) & 0xff] +
m->literal_[(v >> 8) & 0xff] + m->blue_[v & 0xff];
}
static WEBP_INLINE float GetCacheCost(const CostModel* const m, uint32_t idx) {
static WEBP_INLINE int64_t GetCacheCost(const CostModel* const m,
uint32_t idx) {
const int literal_idx = VALUES_IN_BYTE + NUM_LENGTH_CODES + idx;
return m->literal_[literal_idx];
return (int64_t)m->literal_[literal_idx];
}
static WEBP_INLINE float GetLengthCost(const CostModel* const m,
uint32_t length) {
static WEBP_INLINE int64_t GetLengthCost(const CostModel* const m,
uint32_t length) {
int code, extra_bits;
VP8LPrefixEncodeBits(length, &code, &extra_bits);
return m->literal_[VALUES_IN_BYTE + code] + extra_bits;
return (int64_t)m->literal_[VALUES_IN_BYTE + code] +
((int64_t)extra_bits << LOG_2_PRECISION_BITS);
}
static WEBP_INLINE float GetDistanceCost(const CostModel* const m,
uint32_t distance) {
static WEBP_INLINE int64_t GetDistanceCost(const CostModel* const m,
uint32_t distance) {
int code, extra_bits;
VP8LPrefixEncodeBits(distance, &code, &extra_bits);
return m->distance_[code] + extra_bits;
return (int64_t)m->distance_[code] +
((int64_t)extra_bits << LOG_2_PRECISION_BITS);
}
static WEBP_INLINE void AddSingleLiteralWithCostModel(
const uint32_t* const argb, VP8LColorCache* const hashers,
const CostModel* const cost_model, int idx, int use_color_cache,
float prev_cost, float* const cost, uint16_t* const dist_array) {
float cost_val = prev_cost;
int64_t prev_cost, int64_t* const cost, uint16_t* const dist_array) {
int64_t cost_val = prev_cost;
const uint32_t color = argb[idx];
const int ix = use_color_cache ? VP8LColorCacheContains(hashers, color) : -1;
if (ix >= 0) {
// use_color_cache is true and hashers contains color
const float mul0 = 0.68f;
cost_val += GetCacheCost(cost_model, ix) * mul0;
cost_val += DivRound(GetCacheCost(cost_model, ix) * 68, 100);
} else {
const float mul1 = 0.82f;
if (use_color_cache) VP8LColorCacheInsert(hashers, color);
cost_val += GetLiteralCost(cost_model, color) * mul1;
cost_val += DivRound(GetLiteralCost(cost_model, color) * 82, 100);
}
if (cost[idx] > cost_val) {
cost[idx] = cost_val;
@ -163,7 +163,7 @@ static WEBP_INLINE void AddSingleLiteralWithCostModel(
// therefore no overlapping intervals.
typedef struct CostInterval CostInterval;
struct CostInterval {
float cost_;
int64_t cost_;
int start_;
int end_;
int index_;
@ -173,7 +173,7 @@ struct CostInterval {
// The GetLengthCost(cost_model, k) are cached in a CostCacheInterval.
typedef struct {
float cost_;
int64_t cost_;
int start_;
int end_; // Exclusive.
} CostCacheInterval;
@ -188,8 +188,9 @@ typedef struct {
int count_; // The number of stored intervals.
CostCacheInterval* cache_intervals_;
size_t cache_intervals_size_;
float cost_cache_[MAX_LENGTH]; // Contains the GetLengthCost(cost_model, k).
float* costs_;
// Contains the GetLengthCost(cost_model, k).
int64_t cost_cache_[MAX_LENGTH];
int64_t* costs_;
uint16_t* dist_array_;
// Most of the time, we only need few intervals -> use a free-list, to avoid
// fragmentation with small allocs in most common cases.
@ -298,7 +299,7 @@ static int CostManagerInit(CostManager* const manager,
cur->end_ = 1;
cur->cost_ = manager->cost_cache_[0];
for (i = 1; i < cost_cache_size; ++i) {
const float cost_val = manager->cost_cache_[i];
const int64_t cost_val = manager->cost_cache_[i];
if (cost_val != cur->cost_) {
++cur;
// Initialize an interval.
@ -311,13 +312,17 @@ static int CostManagerInit(CostManager* const manager,
manager->cache_intervals_size_);
}
manager->costs_ = (float*)WebPSafeMalloc(pix_count, sizeof(*manager->costs_));
manager->costs_ =
(int64_t*)WebPSafeMalloc(pix_count, sizeof(*manager->costs_));
if (manager->costs_ == NULL) {
CostManagerClear(manager);
return 0;
}
// Set the initial costs_ high for every pixel as we will keep the minimum.
for (i = 0; i < pix_count; ++i) manager->costs_[i] = FLT_MAX;
// Set the initial costs_ to INT64_MAX for every pixel as we will keep the
// minimum.
for (i = 0; i < pix_count; ++i) {
manager->costs_[i] = (int64_t)((1ull << 63) - 1);
}
return 1;
}
@ -325,7 +330,7 @@ static int CostManagerInit(CostManager* const manager,
// Given the cost and the position that define an interval, update the cost at
// pixel 'i' if it is smaller than the previously computed value.
static WEBP_INLINE void UpdateCost(CostManager* const manager, int i,
int position, float cost) {
int position, int64_t cost) {
const int k = i - position;
assert(k >= 0 && k < MAX_LENGTH);
@ -339,7 +344,7 @@ static WEBP_INLINE void UpdateCost(CostManager* const manager, int i,
// all the pixels between 'start' and 'end' excluded.
static WEBP_INLINE void UpdateCostPerInterval(CostManager* const manager,
int start, int end, int position,
float cost) {
int64_t cost) {
int i;
for (i = start; i < end; ++i) UpdateCost(manager, i, position, cost);
}
@ -424,7 +429,7 @@ static WEBP_INLINE void PositionOrphanInterval(CostManager* const manager,
// interval_in as a hint. The intervals are sorted by start_ value.
static WEBP_INLINE void InsertInterval(CostManager* const manager,
CostInterval* const interval_in,
float cost, int position, int start,
int64_t cost, int position, int start,
int end) {
CostInterval* interval_new;
@ -463,7 +468,7 @@ static WEBP_INLINE void InsertInterval(CostManager* const manager,
// If handling the interval or one of its subintervals becomes to heavy, its
// contribution is added to the costs right away.
static WEBP_INLINE void PushInterval(CostManager* const manager,
float distance_cost, int position,
int64_t distance_cost, int position,
int len) {
size_t i;
CostInterval* interval = manager->head_;
@ -478,7 +483,7 @@ static WEBP_INLINE void PushInterval(CostManager* const manager,
int j;
for (j = position; j < position + len; ++j) {
const int k = j - position;
float cost_tmp;
int64_t cost_tmp;
assert(k >= 0 && k < MAX_LENGTH);
cost_tmp = distance_cost + manager->cost_cache_[k];
@ -498,7 +503,7 @@ static WEBP_INLINE void PushInterval(CostManager* const manager,
const int end = position + (cost_cache_intervals[i].end_ > len
? len
: cost_cache_intervals[i].end_);
const float cost = distance_cost + cost_cache_intervals[i].cost_;
const int64_t cost = distance_cost + cost_cache_intervals[i].cost_;
for (; interval != NULL && interval->start_ < end;
interval = interval_next) {
@ -576,7 +581,7 @@ static int BackwardReferencesHashChainDistanceOnly(
const int pix_count = xsize * ysize;
const int use_color_cache = (cache_bits > 0);
const size_t literal_array_size =
sizeof(float) * (VP8LHistogramNumCodes(cache_bits));
sizeof(*((CostModel*)NULL)->literal_) * VP8LHistogramNumCodes(cache_bits);
const size_t cost_model_size = sizeof(CostModel) + literal_array_size;
CostModel* const cost_model =
(CostModel*)WebPSafeCalloc(1ULL, cost_model_size);
@ -584,13 +589,13 @@ static int BackwardReferencesHashChainDistanceOnly(
CostManager* cost_manager =
(CostManager*)WebPSafeCalloc(1ULL, sizeof(*cost_manager));
int offset_prev = -1, len_prev = -1;
float offset_cost = -1.f;
int64_t offset_cost = -1;
int first_offset_is_constant = -1; // initialized with 'impossible' value
int reach = 0;
if (cost_model == NULL || cost_manager == NULL) goto Error;
cost_model->literal_ = (float*)(cost_model + 1);
cost_model->literal_ = (uint32_t*)(cost_model + 1);
if (use_color_cache) {
cc_init = VP8LColorCacheInit(&hashers, cache_bits);
if (!cc_init) goto Error;
@ -608,11 +613,12 @@ static int BackwardReferencesHashChainDistanceOnly(
// non-processed locations from this point.
dist_array[0] = 0;
// Add first pixel as literal.
AddSingleLiteralWithCostModel(argb, &hashers, cost_model, 0, use_color_cache,
0.f, cost_manager->costs_, dist_array);
AddSingleLiteralWithCostModel(argb, &hashers, cost_model, /*idx=*/0,
use_color_cache, /*prev_cost=*/0,
cost_manager->costs_, dist_array);
for (i = 1; i < pix_count; ++i) {
const float prev_cost = cost_manager->costs_[i - 1];
const int64_t prev_cost = cost_manager->costs_[i - 1];
int offset, len;
VP8LHashChainFindCopy(hash_chain, i, &offset, &len);

View File

@ -180,14 +180,16 @@ static int AnalyzeEntropy(const uint32_t* argb,
// When including transforms, there is an overhead in bits from
// storing them. This overhead is small but matters for small images.
// For spatial, there are 14 transformations.
entropy[kSpatial] += VP8LSubSampleSize(width, transform_bits) *
VP8LSubSampleSize(height, transform_bits) *
VP8LFastLog2(14);
entropy[kSpatial] += RightShiftRound(
(uint64_t)VP8LSubSampleSize(width, transform_bits) *
VP8LSubSampleSize(height, transform_bits) * VP8LFastLog2(14),
LOG_2_PRECISION_BITS);
// For color transforms: 24 as only 3 channels are considered in a
// ColorTransformElement.
entropy[kSpatialSubGreen] += VP8LSubSampleSize(width, transform_bits) *
VP8LSubSampleSize(height, transform_bits) *
VP8LFastLog2(24);
entropy[kSpatialSubGreen] += RightShiftRound(
(uint64_t)VP8LSubSampleSize(width, transform_bits) *
VP8LSubSampleSize(height, transform_bits) * VP8LFastLog2(24),
LOG_2_PRECISION_BITS);
// For palettes, add the cost of storing the palette.
// We empirically estimate the cost of a compressed entry as 8 bits.
// The palette is differential-coded when compressed hence a much