Browse Source

Match AV1 symbol rate costs to the range coder minimum

pull/2633/head
James Jackson-South 4 weeks ago
parent
commit
aa2ecf6908
  1. 27
      HEIF_IMPLEMENTATION_PLAN.md
  2. 14
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1ProbabilityCost.cs
  3. 4
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
  4. 16
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs

27
HEIF_IMPLEMENTATION_PLAN.md

@ -246,6 +246,33 @@ Further controller-state findings after `b2aee3036` on 2026-09-05:
Updating CDFs after each selected block is not the same as refreshing every RD cost after that block.
This missing cost-state lifecycle is an architectural deviation. Its contribution to time or quality has
not been measured; no isolated cache or invented effort-dependent refresh policy has been introduced.
- Reference `av1/encoder/rd.c:687-853` initializes the complete cost state and combines control and speed-feature
update frequencies. `av1/av1_cx_iface.c:391-394,1342-1348` defaults controls to superblock updates and disables
MV-cost updates for all-intra configuration. These are configured policies, not a public-effort mapping.
`av1/encoder/encoder_alloc.h:58-88` omits the main MV-cost allocation for all-intra; displacement costs are
initialized only when needed (`rd.c:843-851`). Worker costs are separately owned when their update policy
requires independent state (`av1/encoder/ethread.c:1610-1641`). Managed sizing and lifetime must follow those
usage boundaries rather than adding every table to every frame or candidate.
CDF symbol-cost floor correction, verified after `37541f7f3` on 2026-09-05:
- `Av1ProbabilityCost.cs:47-52` previously passed each CDF interval directly to raw probability conversion.
Native `av1/encoder/cost.c:30-48` first floors symbol mass at `EC_MIN_PROB` (4, `aom_dsp/entcode.h:21`).
Raw conversion has a different domain and still permits 1 (`av1/encoder/cost.h:32-42`). Conflating the two
overcharged sufficiently rare symbols by up to 1,024 rate units, or two bits.
- CDF symbol conversion now applies the existing range-coder minimum before raw conversion. Gathered edge
partition costs (`Av1SymbolEncoder.cs:969-980,1003-1014`) use that same symbol boundary, matching native
`av1/encoder/partition_search.c:3419-3449`. Existing raw-probability behavior and tests remain unchanged.
- The new zero-mass middle-interval regression failed before correction: expected 6,656, actual 7,680
(`symbol-floor-red.trx`). Cases at masses 0 through 4 and 8 exercise both ordinary CDF and gathered-symbol
conversion. They do not replace the missing RD-cost refresh lifecycle or coefficient optimizer.
- Final Release .NET 11 build: zero errors and 1,009 existing warnings; Roslynk: zero compiler errors.
Serialized Visual Studio VSTest passes 2,283/2,283 in 27.0448 seconds (`symbol-floor-final.trx`), covering
entropy, intra-superblock, encoder-frame, and public HEIF encoder tests.
- Optimized libaom decoding was repeated after the final edit for 23 palette, eight partition, and twelve color
sequence streams: 38,973 samples, maximum error 0, and zero samples exceeding one. Per-plane reports remain
in the temporary takeover directory. These are bounded same-bitstream checks, not separate-encoder parity,
a quality improvement claim, or performance acceptance. No benchmark was run.
Palette coded-boundary correction, verified after `b2aee3036` on 2026-09-05:

14
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1ProbabilityCost.cs

@ -48,7 +48,19 @@ internal static class Av1ProbabilityCost
{
int inverseLower = symbol == 0 ? Av1Distribution.ProbabilityTop : (int)distribution[symbol - 1];
int inverseUpper = (int)distribution[symbol];
return GetProbabilityCost(inverseLower - inverseUpper);
return GetSymbolCost(inverseLower - inverseUpper);
}
/// <summary>
/// Gets the fixed-point cost of an entropy-coded symbol with a Q15 probability.
/// </summary>
/// <param name="probability">The Q15 probability numerator.</param>
/// <returns>The rate cost in 1/512-bit units.</returns>
public static int GetSymbolCost(int probability)
{
// The range coder reserves a minimum interval even when CDF adaptation collapses a symbol's mass.
// RD costs use that same floor; the raw probability conversion below retains its separate numerical domain.
return GetProbabilityCost(Math.Max(probability, Av1Distribution.ProbabilityMinimum));
}
/// <summary>

4
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs

@ -973,7 +973,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
blockSize,
context);
return Av1ProbabilityCost.GetProbabilityCost(
return Av1ProbabilityCost.GetSymbolCost(
partitionType == Av1PartitionType.Split
? frequency
: Av1Distribution.ProbabilityTop - frequency);
@ -1007,7 +1007,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
blockSize,
context);
return Av1ProbabilityCost.GetProbabilityCost(
return Av1ProbabilityCost.GetSymbolCost(
partitionType == Av1PartitionType.Split
? frequency
: Av1Distribution.ProbabilityTop - frequency);

16
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs

@ -156,6 +156,22 @@ public class Av1EntropyTests
Assert.Equal(1024, Av1ProbabilityCost.GetSymbolCost(distribution, 2));
}
[Theory]
[InlineData(0, 6656)]
[InlineData(1, 6656)]
[InlineData(2, 6656)]
[InlineData(3, 6656)]
[InlineData(4, 6656)]
[InlineData(8, 6144)]
public void SymbolCostUsesRangeCoderMinimumProbability(uint probability, int expected)
{
// A middle interval can collapse during adaptation. Libaom cost.c floors its mass at EC_MIN_PROB=4,
// giving 13 * 512 rate units at and below that floor, while mass 8 costs 12 * 512 units.
Av1Distribution distribution = new(16384, 16384 + probability);
Assert.Equal(expected, Av1ProbabilityCost.GetSymbolCost(distribution, 1));
Assert.Equal(expected, Av1ProbabilityCost.GetSymbolCost((int)probability));
}
[Theory]
[InlineData(0, 0)]
[InlineData(1, 512)]

Loading…
Cancel
Save