Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
191f1fb
Fix incorrect channel weight swizzle
solidpixel Nov 12, 2026
dd294c6
Fix weight-range saturation to use correct 65535 endpoint max
solidpixel Nov 12, 2026
a7ddd4a
Fix channel-weighted RGB-scale endpoint fitting
solidpixel Nov 12, 2026
4ca987a
Fix channel weighting of partition quantization error
solidpixel Nov 12, 2026
cb1f45d
Fix HDR endpoint selection for negative major-channel deltas
solidpixel Nov 12, 2026
cdfecbc
Preserve large endpoint error estimates when combining partitions
solidpixel Nov 12, 2026
0633f25
Fix directional channel weighting of weight-error scales
solidpixel Nov 13, 2026
7294137
Fix constant-weight RGB-scale endpoint fitting
solidpixel Nov 13, 2026
2f4d60d
Fix channel-weighted projection of ideal texel weights
solidpixel Nov 13, 2026
0d9b5c6
Remove unreachable fallback path
solidpixel Nov 13, 2026
a5f31d2
Fix signed HDR endpoint delta quantization
solidpixel Nov 13, 2026
e865ae8
Fix constant-weight HDR RGB-offset endpoint fitting
solidpixel Nov 13, 2026
9d20e3b
Fix four-partition search and reduce preset candidate limits
solidpixel Nov 13, 2026
98e036f
Fix quantized HDR luminance submode selection
solidpixel Nov 13, 2026
914d4cb
Preserve descending LDR luminance endpoints
solidpixel Nov 13, 2026
6264696
Allow full-precision blue contraction for reversed RGB endpoints
solidpixel Nov 13, 2026
dca06c8
Document the luminance-alpha delta encoding tradeoff
solidpixel Nov 13, 2026
9bec022
Skip duplicate scoring after unchanged first-pass weights
solidpixel Nov 13, 2026
7650d64
Skip unused endpoint model fits during refinement
solidpixel Nov 13, 2026
f7b85c2
Score updates
solidpixel Nov 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Source/astcenc_averages_and_directions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1345,13 +1345,17 @@ void compute_partition_errors_rgb_batch(
// Reuse the original per-channel texel accumulation and reduction for partition averages
vfloat4 averages[batch_size][BLOCK_MAX_PARTITIONS];
vfloat4 units[batch_size];
vfloat4 channel_weights[batch_size];

for (unsigned int c = 0; c < batch_size; c++)
{
compute_partition_averages_rgb(*candidates[c], blk, averages[c]);
units[c] = unit3();
channel_weights[c] = blk.channel_weight;
}

vfloat safe = pack_quads(units);
vfloat channel_weight = pack_quads(channel_weights);
vfloat zero = vfloat::zero();

// Preserve four running texel-error sums per candidate across partition boundaries
Expand Down Expand Up @@ -1495,7 +1499,7 @@ void compute_partition_errors_rgb_batch(

// Defer adding quantization errors until after the texel sums are reduced
vfloat length = max(max_quads(high) - min_quads(low), 1e-7f);
vfloat error_weights = countv * vfloat(weight_imprecision);
vfloat error_weights = channel_weight * (countv * vfloat(weight_imprecision));

vfloat uncor_vector = bs * length;
uncor_quant[p] = sum_rgb_quads((uncor_vector * uncor_vector) * error_weights);
Expand Down Expand Up @@ -1541,13 +1545,17 @@ void compute_partition_errors_rgba_grayscale_batch(
// Reuse the original per-channel texel accumulation and reduction for partition averages
vfloat4 averages[batch_size][BLOCK_MAX_PARTITIONS];
vfloat4 units[batch_size];
vfloat4 channel_weights[batch_size];

for (unsigned int c = 0; c < batch_size; c++)
{
compute_partition_averages_rgba_grayscale(*candidates[c], blk, averages[c]);
units[c] = unit4();
channel_weights[c] = blk.channel_weight;
}

vfloat safe = pack_quads(units);
vfloat channel_weight = pack_quads(channel_weights);
vfloat zero = vfloat::zero();

// Preserve four running texel-error sums per candidate across partition boundaries
Expand Down Expand Up @@ -1674,7 +1682,7 @@ void compute_partition_errors_rgba_grayscale_batch(

// Defer adding quantization errors until after the texel sums are reduced
vfloat length = max(max_quads(high) - min_quads(low), 1e-7f);
vfloat error_weights = countv * vfloat(weight_imprecision);
vfloat error_weights = channel_weight * (countv * vfloat(weight_imprecision));

vfloat uncor_vector = bs * length;
vfloat samec_vector = samec_bs * length;
Expand Down
116 changes: 95 additions & 21 deletions Source/astcenc_color_quantize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -790,13 +790,6 @@ static void quantize_luminance(
float lum0 = hadd_rgb_s(color0) * (1.0f / 3.0f);
float lum1 = hadd_rgb_s(color1) * (1.0f / 3.0f);

if (lum0 > lum1)
{
float avg = (lum0 + lum1) * 0.5f;
lum0 = avg;
lum1 = avg;
}

output[0] = quant_color(quant_level, astc::flt2int_rtn(lum0), lum0);
output[1] = quant_color(quant_level, astc::flt2int_rtn(lum1), lum1);
}
Expand Down Expand Up @@ -1349,6 +1342,9 @@ static void quantize_hdr_rgb(
16.0f
};

uint8_t delta_output[6];
bool is_delta_valid = false;

// Try modes in decreasing precision order
for (int mode = 7; mode >= 0; mode--)
{
Expand Down Expand Up @@ -1484,10 +1480,14 @@ static void quantize_hdr_rgb(
d0_fval = astc::clamp(d0_fval, -65535.0f, 65535.0f);
d1_fval = astc::clamp(d1_fval, -65535.0f, 65535.0f);

int d0_intval = astc::flt2int_rtn(d0_fval * mode_scale);
int d1_intval = astc::flt2int_rtn(d1_fval * mode_scale);

if (abs(d0_intval) >= d_intcutoff || abs(d1_intval) >= d_intcutoff)
// Round signed deltas to nearest, with halfway cases rounding upward
int d0_intval = static_cast<int>(astc::flt_rd(d0_fval * mode_scale + 0.5f));
int d1_intval = static_cast<int>(astc::flt_rd(d1_fval * mode_scale + 0.5f));

// The negative limit is inclusive; the positive limit is exclusive
if ((d0_intval < -d_intcutoff) || (d0_intval >= d_intcutoff) ||
(d1_intval < -d_intcutoff) || (d1_intval >= d_intcutoff))
{
continue;
}
Expand Down Expand Up @@ -1573,7 +1573,20 @@ static void quantize_hdr_rgb(
output[3] = b1_quantval;
output[4] = d0_quantval;
output[5] = d1_quantval;
return;

// Clamping negative C can retain more precision than the direct encoding
if (c_base >= 0.0f)
{
return;
}

for (unsigned int i = 0; i < 6; i++)
{
delta_output[i] = output[i];
}

is_delta_valid = true;
break;
}

// Fall back to flat 8-bit red/green and 7-bit blue, with accuracy comparable to LDR 4:4:3. This
Expand Down Expand Up @@ -1604,6 +1617,34 @@ static void quantize_hdr_rgb(
quant_level, static_cast<uint8_t>(idx), output[i]);
}

// For negative C, retain the clamped delta encoding if it beats the direct fallback
if (is_delta_valid)
{
bool rgb_hdr;
bool alpha_hdr;
vint4 delta0, delta1, direct0, direct1;
unpack_color_endpoints(ASTCENC_PRF_HDR, FMT_HDR_RGB, delta_output,
rgb_hdr, alpha_hdr, delta0, delta1);
unpack_color_endpoints(ASTCENC_PRF_HDR, FMT_HDR_RGB, output,
rgb_hdr, alpha_hdr, direct0, direct1);

vfloat4 d0 = color0_bak - int_to_float(delta0);
vfloat4 d1 = color1_bak - int_to_float(delta1);
float delta_error = hadd_rgb_s(d0 * d0 + d1 * d1);

d0 = color0_bak - int_to_float(direct0);
d1 = color1_bak - int_to_float(direct1);
float direct_error = hadd_rgb_s(d0 * d0 + d1 * d1);

if (delta_error <= direct_error)
{
for (unsigned int i = 0; i < 6; i++)
{
output[i] = delta_output[i];
}
}
}

return;
}

Expand Down Expand Up @@ -1673,11 +1714,23 @@ static void quantize_hdr_luminance_large_range(
lower_v0 = astc::clamp(lower_v0, 0, 255);
lower_v1 = astc::clamp(lower_v1, 0, 255);

// Determine the distance between the point in code-point space and the input value
// Quantization can reverse the submode error ranking, so score the quantized endpoints
upper_v0 = quant_color(quant_level, upper_v0);
upper_v1 = quant_color(quant_level, upper_v1);
lower_v0 = quant_color(quant_level, lower_v0);
lower_v1 = quant_color(quant_level, lower_v1);

int upper0_dec = upper_v0 << 8;
int upper1_dec = upper_v1 << 8;
int lower0_dec = (lower_v1 << 8) + 128;
int lower1_dec = (lower_v0 << 8) - 128;
int lower0_dec = lower_v0 << 8;
int lower1_dec = lower_v1 << 8;

// Equal quantized values select the upper submode in the decoder
if (lower_v0 > lower_v1)
{
lower0_dec = (lower_v1 << 8) + 128;
lower1_dec = (lower_v0 << 8) - 128;
}

int upper0_diff = upper0_dec - ilum0;
int upper1_diff = upper1_dec - ilum1;
Expand All @@ -1699,9 +1752,8 @@ static void quantize_hdr_luminance_large_range(
v1 = lower_v1;
}

// OK; encode
output[0] = quant_color(quant_level, v0);
output[1] = quant_color(quant_level, v1);
output[0] = static_cast<uint8_t>(v0);
output[1] = static_cast<uint8_t>(v1);
}

/**
Expand Down Expand Up @@ -1967,9 +2019,18 @@ uint8_t pack_color_endpoints(
}
}

if (quant_level < QUANT_256)
{
if (try_quantize_rgb_blue_contract(color0_ldr, color1_ldr, color0_out2, color1_out2, quant_level))
// At full precision, only blue-contract endpoints that need ordering adjustments
bool is_blue_contract_allowed = quant_level < QUANT_256;
if (!is_blue_contract_allowed)
{
vint4 color0i = float_to_int_rtn(color0_ldr);
vint4 color1i = float_to_int_rtn(color1_ldr);
is_blue_contract_allowed = hadd_rgb_s(color0i) > hadd_rgb_s(color1i);
}

if (is_blue_contract_allowed &&
try_quantize_rgb_blue_contract(color0_ldr, color1_ldr, color0_out2, color1_out2, quant_level))
{
vint4 color0_unpack;
vint4 color1_unpack;
Expand Down Expand Up @@ -2041,9 +2102,18 @@ uint8_t pack_color_endpoints(
}
}

if (quant_level < QUANT_256)
{
if (try_quantize_rgba_blue_contract(color0_ldr, color1_ldr, color0_out2, color1_out2, quant_level))
// At full precision, only blue-contract endpoints that need ordering adjustments
bool is_blue_contract_allowed = quant_level < QUANT_256;
if (!is_blue_contract_allowed)
{
vint4 color0i = float_to_int_rtn(color0_ldr);
vint4 color1i = float_to_int_rtn(color1_ldr);
is_blue_contract_allowed = hadd_rgb_s(color0i) > hadd_rgb_s(color1i);
}

if (is_blue_contract_allowed &&
try_quantize_rgba_blue_contract(color0_ldr, color1_ldr, color0_out2, color1_out2, quant_level))
{
vint4 color0_unpack;
vint4 color1_unpack;
Expand Down Expand Up @@ -2124,6 +2194,10 @@ uint8_t pack_color_endpoints(
break;

case FMT_LUMINANCE_ALPHA:
// Preferring delta can be suboptimal, but comparing against direct encoding adds cost for
// negligible image quality gains. QUANT_16 L/A endpoint examples:
// Input: (153,153), (153,170); Direct: (153,153), (153,170); Delta: (153,153), (152,165)
// Input: (153,153), (170,153); Direct: (153,153), (170,153); Delta: (153,153), (165,152)
if (quant_level <= 18)
{
if (try_quantize_luminance_alpha_delta(color0_ldr, color1_ldr, output, quant_level))
Expand Down
36 changes: 26 additions & 10 deletions Source/astcenc_compress_symbolic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,12 +561,11 @@ static float compress_symbolic_block_for_partition_1plane(
dec_weights_ideal + i * BLOCK_MAX_WEIGHTS);
}

// Compute maximum colors for the endpoints and ideal weights, then for each endpoint and ideal
// weight pair, compute the smallest weight that will result in a color value greater than 1
// Find the smallest weight that exceeds the color range; endpoints use the 0-65535 scale
vfloat4 min_ep(10.0f);
for (unsigned int i = 0; i < partition_count; i++)
{
vfloat4 ep = (vfloat4(1.0f) - ei.ep.endpt0[i]) / (ei.ep.endpt1[i] - ei.ep.endpt0[i]);
vfloat4 ep = (vfloat4(65535.0f) - ei.ep.endpt0[i]) / (ei.ep.endpt1[i] - ei.ep.endpt0[i]);

vmask4 use_ep = (ep > vfloat4(0.5f)) & (ep < min_ep);
min_ep = select(min_ep, ep, use_ep);
Expand Down Expand Up @@ -694,7 +693,7 @@ static float compress_symbolic_block_for_partition_1plane(
for (unsigned int l = 0; l < config.tune_refinement_limit; l++)
{
recompute_ideal_colors_1plane(
blk, pi, di, workscb.weights,
blk, pi, di, workscb.weights, partition_format_specifiers[i],
workep, rgbs_colors, rgbo_colors);

// Quantize the chosen color, tracking if worth trying the mod value
Expand Down Expand Up @@ -819,6 +818,12 @@ static float compress_symbolic_block_for_partition_1plane(
}
}

// The unchanged first-pass candidate was already scored before weight refinement
if ((l == 0) && !adjustments)
{
break;
}

// Post-realign test
float errorval = compute_difference(config, bsd, workscb, blk);
if (errorval == -ERROR_CALC_DEFAULT)
Expand Down Expand Up @@ -923,16 +928,15 @@ static float compress_symbolic_block_for_partition_2planes(
dec_weights_ideal + i * BLOCK_MAX_WEIGHTS + WEIGHTS_PLANE2_OFFSET);
}

// Compute maximum colors for the endpoints and ideal weights, then for each endpoint and ideal
// weight pair, compute the smallest weight that will result in a color value greater than 1
// Find the smallest weight that exceeds the color range; endpoints use the 0-65535 scale
vfloat4 min_ep1(10.0f);
vfloat4 min_ep2(10.0f);

vfloat4 ep1 = (vfloat4(1.0f) - ei1.ep.endpt0[0]) / (ei1.ep.endpt1[0] - ei1.ep.endpt0[0]);
vfloat4 ep1 = (vfloat4(65535.0f) - ei1.ep.endpt0[0]) / (ei1.ep.endpt1[0] - ei1.ep.endpt0[0]);
vmask4 use_ep1 = (ep1 > vfloat4(0.5f)) & (ep1 < min_ep1);
min_ep1 = select(min_ep1, ep1, use_ep1);

vfloat4 ep2 = (vfloat4(1.0f) - ei2.ep.endpt0[0]) / (ei2.ep.endpt1[0] - ei2.ep.endpt0[0]);
vfloat4 ep2 = (vfloat4(65535.0f) - ei2.ep.endpt0[0]) / (ei2.ep.endpt1[0] - ei2.ep.endpt0[0]);
vmask4 use_ep2 = (ep2 > vfloat4(0.5f)) & (ep2 < min_ep2);
min_ep2 = select(min_ep2, ep2, use_ep2);

Expand Down Expand Up @@ -1084,6 +1088,7 @@ static float compress_symbolic_block_for_partition_2planes(
recompute_ideal_colors_2planes(
blk, bsd, di,
workscb.weights, workscb.weights + WEIGHTS_PLANE2_OFFSET,
partition_format_specifiers[i][0],
workep, rgbs_color, rgbo_color, plane2_component);

// Quantize the chosen color
Expand Down Expand Up @@ -1154,6 +1159,12 @@ static float compress_symbolic_block_for_partition_2planes(
config.profile, bsd, blk, workscb);
}

// The unchanged first-pass candidate was already scored before weight refinement
if ((l == 0) && !adjustments)
{
break;
}

// Post-realign test
float errorval = compute_symbolic_block_difference_2plane(config, bsd, workscb, blk);
if (errorval == -ERROR_CALC_DEFAULT)
Expand Down Expand Up @@ -1424,6 +1435,7 @@ void compress_block(
0.0f,
ctx.config.tune_2partition_early_out_limit_factor,
ctx.config.tune_3partition_early_out_limit_factor,
// Four-partition searches do not use this threshold
0.0f
};

Expand Down Expand Up @@ -1560,7 +1572,10 @@ void compress_block(
// because the other correlation trial may fit the partitioning much better.
float best_error = best_errorvals_for_pcount[partition_count - 1];
float best_error_scale = exit_thresholds_for_pcount[partition_count - 1] * 1.85f;
if (best_error > (best_error_in_prev * best_error_scale))

// No four-partition pruning threshold is defined; evaluate all requested candidates
if ((partition_count < 4) &&
(best_error > (best_error_in_prev * best_error_scale)))
{
trace_add_data("skip", "tune_partition_early_out_limit_factor");
goto END_OF_TESTS;
Expand All @@ -1576,7 +1591,8 @@ void compress_block(
// If using N partitions doesn't improve much over using N-1 partitions then skip trying N+1
float best_error = best_errorvals_for_pcount[partition_count - 1];
float best_error_scale = exit_thresholds_for_pcount[partition_count - 1];
if (best_error > (best_error_in_prev * best_error_scale))
if ((partition_count < 4) &&
(best_error > (best_error_in_prev * best_error_scale)))
{
trace_add_data("skip", "tune_partition_early_out_limit_factor");
goto END_OF_TESTS;
Expand Down
4 changes: 2 additions & 2 deletions Source/astcenc_find_best_partitioning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ unsigned int find_best_partition_candidates(
for (unsigned int j = 0; j < partition_count; j++)
{
float tpp = static_cast<float>(pi.partition_texel_count[j]);
vfloat4 error_weights(tpp * weight_imprecision_estim);
vfloat4 error_weights = blk.channel_weight * (tpp * weight_imprecision_estim);

vfloat4 uncor_vector = uncor_lines[j].b * line_lengths[j];
vfloat4 samec_vector = samec_lines[j].b * line_lengths[j];
Expand Down Expand Up @@ -837,7 +837,7 @@ unsigned int find_best_partition_candidates(
partition_lines3& pl = plines[j];

float tpp = static_cast<float>(pi.partition_texel_count[j]);
vfloat4 error_weights(tpp * weight_imprecision_estim);
vfloat4 error_weights = blk.channel_weight * (tpp * weight_imprecision_estim);

vfloat4 uncor_vector = pl.uncor_line.b * pl.line_length;
vfloat4 samec_vector = pl.samec_line.b * pl.line_length;
Expand Down
Loading
Loading