diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index 5548e80b51..8a2e9260be 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -852,13 +852,14 @@ Encoder verification contract:
- [x] Empty-transform block skip now compares the complete live rate of the two decoder-identical syntax choices after luma and every coded chroma plane have been selected. Current libaom forces all-intra blocks to non-skip; this encoder retains that behavior for every non-empty block and for equal-cost empty blocks, but emits block skip when its adapted context cost is strictly lower than non-skip plus all empty-transform coefficient costs. Costing and writing share the same above-and-left skip-context calculation, and the coefficient estimator returns after the transform-block-skip symbol without reading coefficient storage. This adds no allocation, copy, or persistent state. A focused adapted-CDF regression proves both outcomes through the production decision helper, the two production all-zero fixtures still prove the default real block path, the exact net11 Release rebuild remains at 1,005 warnings and zero errors, all 8,975 HEIF/AV1 tests pass, and current-main `aomdec` at `a40ed1ea9e4ecc3df58a5bccb76623f2c94ae727` accepts all 29 regenerated payloads.
- [~] Palette entropy coding now mirrors current libaom's adaptive luma-mode, chroma-mode, palette-size, and spatial color-index distributions, together with its truncated-binary uniform code used by palette colors. The complete mutable palette probability graph is created once on first palette search or write, so the current palette-disabled frame path retains zero palette allocations. Three focused regressions cover every legal 2-through-8 color alphabet and every defined mode, size, and color-index context; all 1,928 entropy cases and all 8,978 HEIF/AV1 cases pass direct net11 Release VSTest. The exact Release rebuild remains at 1,005 warnings and zero errors. This checkpoint adds the exact entropy foundation only: palette candidate generation, retained color and index storage, mode decision, map tokenization, and production syntax remain incomplete, and no generated payload changed.
- [~] Luma and chroma palette-color coding now matches current libaom's neighbor-cache flags, sorted delta representation, wrapped V-plane deltas, strict delta-versus-raw V selection, and fixed-point color-rate model at 8, 10, and 12 bits. Encoder costing and emission use only fixed stack spans, including explicitly initialized cache-membership state, and steady-state color costing allocates zero managed bytes. The decoder consumes the same bounded color-syntax primitive after the tile reader derives its neighbor cache, removing duplicated color parsing without changing retained palette ownership. Nine focused syntax, exact palette decode, constrained-allocation, truncation, presentation, and allocation cases pass; all 1,933 entropy cases and all 8,983 HEIF/AV1 cases pass direct net11 Release VSTest. The exact Release rebuild remains at 1,005 warnings and zero errors. Retained encoder palette colors, neighbor caches, color-index maps, candidate generation, and production palette selection remain incomplete, and the compact 8-byte frame mode entries were not enlarged.
-- [~] Palette color-index map coding now shares the exact current-libaom neighbor weights, stable color ordering, five context classes, first-index uniform code, and diagonal wavefront between encoder costing, encoder writing, and decoder parsing. The decoder's stack-allocated context scores are explicitly cleared before accumulation, removing an invalid dependency on uninitialized stack contents. Costing and writing use a closed generic operation while the shared driver owns traversal and context derivation, so the semantic operations remain independent of map layout and tail handling. The path adds no retained state or per-call managed allocation. Twelve focused map, exact palette decode, padding, trailing-bit, and allocation cases pass; all 1,941 entropy cases and all 8,991 HEIF/AV1 cases pass direct net11 Release VSTest. The exact Release rebuild remains at 1,005 warnings and zero errors. Production payloads remain unchanged because palette selection is still disabled; retained colors, neighbor caches, index-map storage, candidate generation, and production palette mode decision remain incomplete.
+- [~] Palette color-index map coding now shares the exact current-libaom neighbor weights, stable color ordering, five context classes, first-index uniform code, and diagonal wavefront between encoder costing, encoder writing, and decoder parsing. The decoder's stack-allocated context scores are explicitly cleared before accumulation, removing an invalid dependency on uninitialized stack contents. Costing and writing use a closed generic operation while the shared driver owns traversal and context derivation, so the semantic operations remain independent of map layout and tail handling. The path adds no retained state or per-call managed allocation. Its allocation regression now runs one complete unmeasured hot-path window before measuring an independent 1,000-call steady-state window, so tiered-runtime transitions cannot make the full parallel suite report a one-time allocation as a recurring operation cost. Twelve focused map, exact palette decode, padding, trailing-bit, and allocation cases pass; all 1,941 entropy cases and all 8,991 HEIF/AV1 cases pass direct net11 Release VSTest. The exact Release rebuild remains at 1,005 warnings and zero errors. Production payloads remain unchanged because palette selection is still disabled; retained colors, neighbor caches, index-map storage, candidate generation, and production palette mode decision remain incomplete.
- [~] Retained encoder palette state and production palette writing now mirror current libaom's 50-byte palette-mode contents, separate luma and shared-chroma sizes, three eight-color planes, above-and-left sorted cache, 64-sample above-cache boundary, mode contexts, palette colors, color-index maps, and syntax order. The current block keeps one inline value in the reusable superblock workspace; only the 4x4-granularity top and left picture edges retain copies for later blocks. For a 3840x2160 tile these edges occupy about 73.4 KiB instead of about 6.2 MiB for a 50-byte palette value attached to every 8x8 mode allocation. Luma and chroma index maps share one lazily allocated 32 KiB owner containing two 128x128 maps, so the palette-disabled production path retains no map owner. The compact final-block workspace falls from about 10.3 KiB to about 8.3 KiB. The writer caps map traversal to the coded plane count, writes maps before transform syntax, and publishes palette edges only after the current block has consumed preceding contexts. Eight focused size, alignment, ownership, cache-boundary, round-trip, map-consumption, and edge-publication cases pass; all 114 palette cases, all 1,942 entropy cases, and all 8,996 HEIF/AV1 cases pass direct foreground net11 Release VSTest. The exact net11 Release rebuild reports 1,050 solution warnings and zero errors; Roslynk reports zero compiler errors and no diagnostics in the touched files. The current-main reference is `a40ed1ea9e4ecc3df58a5bccb76623f2c94ae727`. Production payloads remain unchanged because palette candidate generation is still disabled; that live rate-distortion search is the next checkpoint.
- [~] Luma palette clustering now follows current libaom's one-dimensional search primitive exactly: equal-interval midpoint initialization, first-color tie order, rounded centroid means, deterministic empty-cluster replacement, the 50-iteration limit, and retention of the preceding state when distortion increases. Nearest-color assignment improves on libaom's AVX2 implementation by dispatching Vector512, Vector256, Vector128, then scalar through ImageSharp's shared vector-count helpers. The primitive uses only bounded stack scratch and introduces no allocator rent, managed array, or per-row copy. Three independent tests cover exact centroid convergence, initialization order, 12-bit nearest-color distortion, destination bounds, and every hardware-intrinsic tier. The complete AVIF set passes 8,930 of 8,930 cases and the HEIF set passes 230 of 230 cases through direct foreground net11 Release VSTest. The exact net11 Release rebuild reports 1,050 solution warnings and zero errors, and Roslynk reports zero compiler errors. Candidate enumeration, palette-cache snapping, transform RD selection, and production activation remain in the open luma-palette checkpoint.
- [~] Live luma palette selection now follows current libaom's dominant-color and one-dimensional K-means candidate families, cache-bias threshold, sorted duplicate removal, active-edge map extension, and strict winner tie order. It improves on speed-configured libaom by evaluating both candidate families at every legal 2-through-8 size without early pruning, then exhaustively evaluates every legal transform using the existing SIMD prediction, residual, transform, quantization, and reconstruction operators. Candidate storage remains bounded stack memory; the reusable 32 KiB map owner is allocated only when an eligible block enters palette search. A production tile test proves that full 8x8 and clipped 5x3 blocks at 8 and 12 bits select exact colors and indices, extend the visible edges through coded padding, reconstruct every sample without coefficients, and emit a nonempty tile. The complete 57-case intra-superblock set, 8,931-case AVIF set, and 230-case HEIF set pass direct foreground net11 Release VSTest. The exact Release test-project build reports 1,992 baseline warnings and zero errors; Roslynk reports zero compiler errors and no touched-file analyzer warnings. Production frame activation remains gated until chroma palette mode and its rate accounting are complete.
- [~] Paired chroma palette clustering now preserves current libaom's squared two-component distance, first-centroid tie order, independently rounded U/V means, paired deterministic empty-cluster replacement, preceding-state retention on increased distortion, and 50-iteration limit. Keeping the source planes separate avoids interleave/deinterleave copies and improves on libaom's AVX2 ceiling with Vector512, Vector256, Vector128, then scalar dispatch through ImageSharp's shared vector-count helpers. Three independent tests cover exact paired convergence, midpoint initialization, 12-bit distance and index parity, untouched destination bounds, and every intrinsic tier. The exact Release test-project build reports 1,992 baseline warnings and zero errors; the focused three-case set, complete 8,934-case AVIF set, and complete 230-case HEIF set pass direct foreground net11 Release VSTest. Roslynk reports zero compiler errors and no touched-file analyzer warnings. Candidate integration and production activation remain in the open chroma-palette checkpoint.
- [~] Live paired chroma palette selection now follows current libaom's complete 2-through-8 color-size search, U-plane neighbor-cache snapping, stable U-ordered color pairs, shared U/V index map, implicit DCT-DCT transform, and strict rate-distortion winner replacement. It improves on speed-configured libaom by applying no early header-cost pruning, keeps planar U/V source data separate, and reuses the SIMD-first prediction, residual, transform, quantization, and reconstruction operators without allocator-backed candidate storage. The production tile regression proves both palette-mode probability branches, exact paired colors and indices, coefficient-free reconstruction, and nonempty syntax. The complete 58-case intra-superblock set, 8,935-case AVIF set, and 230-case HEIF set pass direct foreground net11 Release VSTest. The exact Release test-project build reports 1,992 baseline warnings and zero errors; Roslynk reports zero compiler errors and no touched-file analyzer warnings. Production frame activation remains the next checkpoint.
- [~] Production palette activation now matches current libaom's default good-quality screen detector: it scans only complete 16x16 luma blocks, normalizes high-bit-depth samples to eight bits, admits 2-through-4-color blocks, and uses the reference's strict greater-than-ten-percent frame-area threshold. A 256-bit stack bitset and a fifth-color early exit replace libaom's larger per-block histogram without changing the decision, allocation, or source precision. The adaptive sequence flag remains enabled, the frame flag is set before picture-state allocation, and intra-block copy remains disabled. Focused regressions prove strict-threshold equality, high-bit-depth normalization, five-color rejection, emitted frame-header activation, production decode, and generated payload retention. The exact Release test-project build reports 1,992 baseline warnings and zero errors; all 8,935 AVIF cases and all 230 HEIF cases pass direct foreground net11 Release VSTest. Current-main `aomdec` at `a40ed1ea9e4ecc3df58a5bccb76623f2c94ae727` accepts all 30 regenerated production payloads, including the 54-byte palette case. Roslynk reports zero compiler errors and no touched-file analyzer warnings.
+- [~] Intra-block-copy rate accounting now uses the live frame-local flag and displacement-vector distributions without copying or adapting either context during candidate measurement. Displacement-vector costing and writing share one closed symbol operation over the exact current-libaom joint, sign, magnitude-class, class-zero, and integer-offset syntax; mode search applies libaom's 120/128 displacement-rate weight with nearest-integer rounding. Independent fixed costs cover all four joint states, both signs, class zero, and large offset classes before adaptive writes, followed by an encoder/decoder round trip through the same sequence. The exact net11 Release build reports 1,005 baseline warnings and zero errors; all 1,942 entropy cases and all 9,006 AV1 cases pass through direct foreground VSTest, and Roslynk reports zero compiler errors and no diagnostics in the touched files. Reference-vector derivation, legal source search, retained displacement state, production writing, and adaptive frame-flag clearing remain before intra-block copy can be activated.
- [x] The expanded checkpoint exposed a pre-existing transform-block test that asserted uninitialized pooled padding was zero. The test now initializes the complete physical luma plane with a sentinel and proves the block operation leaves both adjacent padding samples unchanged. The exact net11 Release rebuild remains at 1,005 baseline warnings and zero errors, the focused allocator-order set passes 30 of 30 cases, and the complete HEIF/AV1 namespace passes 8,859 of 8,859 direct VSTest cases with zero failures or skips.
- [x] Combined-frame OBU output now counts the byte-aligned frame and tile-group headers, non-final tile-size fields, and owned tile payloads before emitting the OBU size. It retains only the small allocator-owned header scratch and writes each entropy-coded tile span directly from its detached owner, removing the second file-sized allocator rent and complete-payload copy. A 64 KiB regression proves exactly one sub-payload-sized byte rent with a balanced return and verifies the exact streamed tile tail; the existing two-tile round trip proves size-prefix and ordering parity. The focused writer and production-frame set passes 32 of 32 direct net11 VSTest cases, current-main `aomdec` accepts all 29 generated native-format payloads, and the complete HEIF/AV1 namespace passes 8,860 of 8,860 cases with zero failures or skips.
- [x] Finalized fixed-block decisions now set the block-level transform-skip flag only when every retained luma and coded chroma transform has zero EOB, matching current libaom's conjunction of per-plane skip state. The previous always-false flag produced legal but redundant non-skip and zero-coefficient syntax. Monochrome and 4:2:0 regressions prove both branches from actual coefficient state; the focused decision and production-frame set passes 32 of 32 direct net11 VSTest cases. Current-main `aomdec` accepts all 29 regenerated payloads, the recorded decoded-frame MD5s are unchanged, and affected 16x16 constant 8-bit and 10-bit payloads are one byte smaller. The complete HEIF/AV1 namespace passes 8,862 of 8,862 cases with zero failures or skips.
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs
index 29f477dc61..af51136b4b 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs
@@ -29,6 +29,24 @@ internal sealed class Av1MotionVectorContext
///
private const int ClassZeroSize = 1 << ClassZeroBitCount;
+ ///
+ /// Defines one non-mutating cost or mutating write operation over shared motion-vector syntax.
+ ///
+ public interface IMotionVectorSymbolOperation
+ {
+ ///
+ /// Processes one entropy-coded symbol.
+ ///
+ /// The tile range encoder.
+ /// The zero-based symbol.
+ /// The live symbol distribution.
+ /// The symbol cost in 1/512-bit units, or zero when writing.
+ public static abstract int ProcessSymbol(
+ Av1SymbolWriter writer,
+ int symbol,
+ Av1Distribution distribution);
+ }
+
///
/// Gets the distribution selecting which vector components are nonzero.
///
@@ -91,6 +109,26 @@ internal sealed class Av1MotionVectorContext
/// The displacement vector to encode.
/// The spatially derived reference vector.
public void Write(Av1SymbolWriter writer, Av1MotionVector value, Av1MotionVector reference)
+ => _ = this.Process(writer, value, reference);
+
+ ///
+ /// Measures a motion-vector delta against the live distributions without changing them.
+ ///
+ /// The tile range encoder associated with the live context.
+ /// The motion vector to measure.
+ /// The spatially derived reference vector.
+ /// The syntax cost in 1/512-bit units.
+ public int GetCost(Av1SymbolWriter writer, Av1MotionVector value, Av1MotionVector reference)
+ => this.Process(writer, value, reference);
+
+ ///
+ /// Processes one complete motion-vector delta through a closed symbol operation.
+ ///
+ private int Process(
+ Av1SymbolWriter writer,
+ Av1MotionVector value,
+ Av1MotionVector reference)
+ where TOperation : struct, IMotionVectorSymbolOperation
{
int row = value.Row - reference.Row;
int column = value.Column - reference.Column;
@@ -99,18 +137,49 @@ internal sealed class Av1MotionVectorContext
// zero/horizontal/vertical/both joint symbols without a lookup.
int jointType = (row != 0 ? 2 : 0) | (column != 0 ? 1 : 0);
- writer.WriteSymbol(jointType, this.Joint);
+ int rate = TOperation.ProcessSymbol(writer, jointType, this.Joint);
if (row != 0)
{
- this.Vertical.Write(writer, row);
+ rate += this.Vertical.Process(writer, row);
}
if (column != 0)
{
- this.Horizontal.Write(writer, column);
+ rate += this.Horizontal.Process(writer, column);
+ }
+
+ return rate;
+ }
+
+ ///
+ /// Emits motion-vector syntax and reports no estimated rate.
+ ///
+ private readonly struct MotionVectorWriteOperation : IMotionVectorSymbolOperation
+ {
+ ///
+ public static int ProcessSymbol(
+ Av1SymbolWriter writer,
+ int symbol,
+ Av1Distribution distribution)
+ {
+ writer.WriteSymbol(symbol, distribution);
+ return 0;
}
}
+ ///
+ /// Measures motion-vector syntax against the live distributions without changing them.
+ ///
+ private readonly struct MotionVectorCostOperation : IMotionVectorSymbolOperation
+ {
+ ///
+ public static int ProcessSymbol(
+ Av1SymbolWriter writer,
+ int symbol,
+ Av1Distribution distribution)
+ => Av1ProbabilityCost.GetSymbolCost(distribution, symbol);
+ }
+
///
/// Owns the adaptive magnitude distributions for one motion-vector component.
///
@@ -286,6 +355,13 @@ internal sealed class Av1MotionVectorContext
/// The tile range encoder.
/// The nonzero component in one-eighth-sample units.
public void Write(Av1SymbolWriter writer, int value)
+ => _ = this.Process(writer, value);
+
+ ///
+ /// Processes one nonzero signed component through the shared motion-vector symbol operation.
+ ///
+ public int Process(Av1SymbolWriter writer, int value)
+ where TOperation : struct, IMotionVectorSymbolOperation
{
int magnitude = Math.Abs(value);
DebugGuard.IsTrue(magnitude > 0 && (magnitude & 7) == 0, "Displacement-vector components must use whole-sample precision.");
@@ -294,13 +370,13 @@ internal sealed class Av1MotionVectorContext
// minus one selects the doubling range; subtracting three converts the eighth-sample bit index to the class.
int magnitudeClass = magnitude <= (ClassZeroSize << 3) ? 0 : Av1Math.MostSignificantBit((uint)(magnitude - 1)) - 3;
DebugGuard.MustBeLessThan(magnitudeClass, MagnitudeClassCount, nameof(magnitudeClass));
- writer.WriteSymbol(value < 0, this.Sign);
- writer.WriteSymbol(magnitudeClass, this.MagnitudeClass);
+ int rate = TOperation.ProcessSymbol(writer, value < 0 ? 1 : 0, this.Sign);
+ rate += TOperation.ProcessSymbol(writer, magnitudeClass, this.MagnitudeClass);
if (magnitudeClass == 0)
{
- writer.WriteSymbol((magnitude >> 3) - 1, this.ClassZero);
- return;
+ rate += TOperation.ProcessSymbol(writer, (magnitude >> 3) - 1, this.ClassZero);
+ return rate;
}
// Remove the class base and the implicit low-bit value 7 plus the final one before coding the remaining
@@ -312,8 +388,13 @@ internal sealed class Av1MotionVectorContext
{
// The decoder reconstructs offsets least-significant bit first, so each adaptive bit model must be
// updated in the same order during encoding.
- writer.WriteSymbol(((integerOffset >> bit) & 1) != 0, this.OffsetBits[bit]);
+ rate += TOperation.ProcessSymbol(
+ writer,
+ (integerOffset >> bit) & 1,
+ this.OffsetBits[bit]);
}
+
+ return rate;
}
}
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
index b2771a1a06..c2668b0d63 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
@@ -669,6 +669,14 @@ internal class Av1SymbolEncoder : IDisposable
w.WriteSymbol(value, this.tileIntraBlockCopy);
}
+ ///
+ /// Measures the frame-local intra-block-copy flag against the live distribution.
+ ///
+ /// Indicates whether intra-block copy is selected.
+ /// The syntax cost in 1/512-bit units.
+ public int GetUseIntraBlockCopyCost(bool value)
+ => Av1ProbabilityCost.GetSymbolCost(this.tileIntraBlockCopy, value ? 1 : 0);
+
///
/// Writes an integer intra-block-copy displacement vector relative to a spatial reference.
///
@@ -677,6 +685,22 @@ internal class Av1SymbolEncoder : IDisposable
public void WriteDisplacementVector(Av1MotionVector value, Av1MotionVector reference)
=> this.displacementVector.Write(this.writer, value, reference);
+ ///
+ /// Measures an integer intra-block-copy displacement vector against the live distributions.
+ ///
+ /// The displacement vector to measure.
+ /// The spatially derived reference vector.
+ /// The discounted syntax cost in 1/512-bit units.
+ public int GetDisplacementVectorCost(Av1MotionVector value, Av1MotionVector reference)
+ {
+ const int DisplacementVectorCostWeight = 120;
+ const int WeightShift = 7;
+ int rate = this.displacementVector.GetCost(this.writer, value, reference);
+
+ // Displacement syntax uses a 120/128 discount during mode search; adding half the divisor rounds to nearest.
+ return ((rate * DisplacementVectorCostWeight) + (1 << (WeightShift - 1))) >> WeightShift;
+ }
+
///
/// Writes a complete block partition type using the selected partition context.
///
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
index c701172e6b..ff4d10afeb 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
@@ -1400,7 +1400,13 @@ public class Av1EntropyTests
using Av1SymbolEncoder encoder = new(Configuration.Default, 64, BaseQIndex, updateCdf: false);
Buffer2DRegion region = new(map);
- _ = encoder.GetPaletteColorMapCost(4, Av1PlaneType.Y, map.Height, map.Width, region);
+
+ // Run past tiered-compilation thresholds before measuring the established steady-state allocation contract.
+ for (int i = 0; i < 1000; i++)
+ {
+ _ = encoder.GetPaletteColorMapCost(4, Av1PlaneType.Y, map.Height, map.Width, region);
+ }
+
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 1000; i++)
@@ -1760,6 +1766,9 @@ public class Av1EntropyTests
using Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex);
bool[] actuals = new bool[values.Length];
+ Assert.Equal(51, encoder.GetUseIntraBlockCopyCost(false));
+ Assert.Equal(1982, encoder.GetUseIntraBlockCopyCost(true));
+
// Act
foreach (bool value in values)
{
@@ -1786,6 +1795,7 @@ public class Av1EntropyTests
{
Av1MotionVector[] references =
[
+ new(-4096, 0),
new(0, -4096),
new(-1024, 0),
new(256, -256),
@@ -1795,6 +1805,7 @@ public class Av1EntropyTests
Av1MotionVector[] values =
[
+ new(-4096, 0),
new(8, -4096),
new(-1040, 24),
new(256, -336),
@@ -1802,9 +1813,16 @@ public class Av1EntropyTests
new(6144, -6144),
];
+ int[] expectedCosts = [1440, 1661, 5231, 5807, 16955, 31656];
Configuration configuration = Configuration.Default;
using Av1SymbolEncoder encoder = new(configuration, 64, BaseQIndex);
+ // These current-libaom costs cover every joint, both signs, class zero, and large-class offset bits.
+ for (int i = 0; i < values.Length; i++)
+ {
+ Assert.Equal(expectedCosts[i], encoder.GetDisplacementVectorCost(values[i], references[i]));
+ }
+
for (int i = 0; i < values.Length; i++)
{
encoder.WriteDisplacementVector(values[i], references[i]);