rusty_h264_encoder/mb16.rs
1//! I_16x16 macroblock encoding (DC prediction) — the compressing intra path.
2//!
3//! Index-based loops below drive pixel/block-position arithmetic and read
4//! clearer than iterator adapters for this raster math.
5#![allow(clippy::needless_range_loop)]
6//!
7//! Each macroblock is DC-predicted from already-reconstructed neighbors, the
8//! residual is transformed/quantized (luma DC via the secondary Hadamard), the
9//! coefficients are CAVLC-coded, and the macroblock is reconstructed so the next
10//! one can predict from it. `nnz` grids feed the CAVLC `nC` context exactly as a
11//! conforming decoder derives it.
12
13use crate::cabac::CabacEncoder;
14use crate::config::EncoderConfig;
15use crate::signals::{self, FrameSignals};
16use rusty_h264_common::cavlc::{
17 encode_residual_block, scan_4x4_ac, scan_4x4_dcac, write_cbp_inter, write_cbp_intra,
18};
19use rusty_h264_common::inter::{
20 inter_partitions, mc_chroma, mc_luma, predict_mv, predict_partition_mv, MvNeighbor,
21};
22#[cfg(not(accel))]
23use rusty_h264_common::predict::add_residual_4x4;
24use rusty_h264_common::predict::{
25 add_residual_8x8, chroma8x8_pred, chroma_mode_available, chroma_qp, reconstruct_4x4_into,
26 intra4x4_pred, intra8x8_pred, luma16x16_pred, reconstruct_4x4, I16Mode, CHROMA_4X4_SCAN_XY,
27 LUMA_4X4_SCAN_XY,
28};
29#[cfg(not(accel))]
30use rusty_h264_common::transform::inverse_dct_blocks;
31use rusty_h264_common::transform::{
32 dequantize, forward_core, forward_core_8x8, forward_dct_blocks, forward_quant_chroma_dc,
33 forward_quant_luma_dc, inverse_quant_8x8, inverse_quant_chroma_dc,
34 inverse_quant_luma_dc, quantize, quantize_8x8, satd_4x4_sum,
35};
36use rusty_h264_common::aligned::AlignedBytes;
37use rusty_h264_common::{BitWriter, YuvFrame};
38
39/// A/B switch for the batched full-pel rescue grid (`RFF_ME_BATCH=0` disables).
40///
41/// Read ONCE per process, not per call: this sits inside the motion-search rescue
42/// path, and `std::env::var` allocates a `String` and takes the process-wide
43/// environment lock every time. A runtime switch inside a hot loop is its own
44/// measurable tax — cache it.
45
46
47/// λ-normalised threshold for the partition-split search gate (U2).
48///
49/// The existing `split_gate` is a function of qstep ALONE, so it does not scale with
50/// the rate/distortion trade the search is actually making. Normalising the null arm
51/// by λ — the king feature for any search-skip gate — makes one constant transfer
52/// across content AND the whole QP ladder, and in the SAFE direction: the feature is
53/// small exactly where the 16×16 null arm is already good, so easy content skips more.
54///
55/// Harvested over 36 k gated macroblocks (4 clips): at T = 400 the split search is
56/// skipped on 2.9–22.5% of them while keeping **100.00%** of the achievable cost gain
57/// on every clip; T = 600 skips 11–79% for 93–99% kept. `RFF_SPLIT_T=0` disables.
58pub(crate) static DEFER_SUBPEL: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
59
60pub(crate) static SPLIT_T: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
61
62/// Observe-only HARVEST for the sub-8x8 dispatch fit (Great Gate P3.3 gate —
63/// docs/gate-ledger.md sub8x8-split). One CSV row per P_8x8 quad decision:
64/// the 8x8 arm's J, the best SPLIT arm's J (tracked even when 8x8 wins), the
65/// chosen sub_mb_type, lme (for margin normalization — the null-arm-over-λ
66/// king-feature law), and the MB's variance. `RFF_SUB8_HARVEST=<path>`.
67/// R1 PRE-CHECK instrument (docs/gate-repair-plan.md): the SIGNED RD regret of the
68/// SATD split decision, per macroblock, in lambda units.
69///
70/// The census says RD overturns the SATD split pick on 33.8-81.4% of the macroblocks
71/// where SATD chose to split. A RATE cannot justify refitting the proxy: `prom_av1e004`
72/// was a 3x more accurate cost model that measured DEAD NEUTRAL because its error was
73/// rank-invariant near the argmin. What decides R1 is the MAGNITUDE of the disagreement,
74/// and the existing harvest throws it away -- `split_gain` is recorded only when the
75/// split is KEPT and zeroed on revert.
76///
77/// So record one signed number:
78///
79/// ```text
80/// dj = (j_split - j_flat) / lambda
81///
82/// dj > 0 RD reverted: following SATD would have cost `dj` lambda-units. REGRET.
83/// dj < 0 RD kept it: the split saved `-dj`. GAIN.
84/// ```
85///
86/// If the regret mass sits near zero, SATD's false positives are near-ties, the RD pass
87/// is expensive insurance against nothing, and R1 closes without touching the proxy.
88/// A fat regret tail is the only thing that justifies a refit.
89///
90/// RFF_SUB8_REGRET=<path> zero cost when unset (OnceLock + Option, as sub8_harvest)
91mod sub8_regret {
92 use std::io::Write;
93 use std::sync::{Mutex, OnceLock};
94
95 fn sink() -> &'static Option<Mutex<std::fs::File>> {
96 static S: OnceLock<Option<Mutex<std::fs::File>>> = OnceLock::new();
97 S.get_or_init(|| {
98 std::env::var("RFF_SUB8_REGRET").ok().and_then(|p| {
99 let mut f = std::fs::File::create(p).ok()?;
100 let _ = writeln!(f, "reverted,j_split,j_flat,lambda,dj_lambda,split_quads");
101 Some(Mutex::new(f))
102 })
103 })
104 }
105
106 #[inline]
107 pub fn enabled() -> bool {
108 sink().is_some()
109 }
110
111 /// One macroblock's RD trial outcome. `ja`/`jb` are the split and flat J values.
112 pub fn record(ja: f64, jb: f64, lambda: f64, split_quads: usize) {
113 if let Some(m) = sink() {
114 if let Ok(mut f) = m.lock() {
115 let dj = (ja - jb) / lambda.max(1e-9);
116 let _ = writeln!(
117 f, "{},{:.1},{:.1},{:.4},{:.4},{}",
118 (jb <= ja) as u8, ja, jb, lambda, dj, split_quads
119 );
120 }
121 }
122 }
123}
124
125mod sub8_harvest {
126 use std::io::Write;
127 use std::sync::{Mutex, OnceLock};
128
129 fn sink() -> &'static Option<Mutex<std::fs::File>> {
130 static S: OnceLock<Option<Mutex<std::fs::File>>> = OnceLock::new();
131 S.get_or_init(|| {
132 std::env::var("RFF_SUB8_HARVEST").ok().and_then(|p| {
133 let mut f = std::fs::File::create(p).ok()?;
134 // `j8_lme` and `mbvar` are PRE-SEARCH: both are known before the
135 // 8 extra motion searches this gate would skip. `st`/`jsplit` are
136 // post-search (kept for context), `rd_kept` is the label — did the
137 // macroblock's RD trial ultimately KEEP the split?
138 let _ = writeln!(f, "j8,jsplit,st,lme,mbvar,j8_lme,mvdiv,rd_kept");
139 Some(Mutex::new(f))
140 })
141 })
142 }
143
144 /// One quad's row, buffered until the macroblock's RD trial resolves (the
145 /// label is only known then).
146 pub struct Row {
147 pub j8: i64,
148 pub jsplit: i64,
149 pub st: u8,
150 pub lme: f64,
151 pub mbvar: i64,
152 /// PRE-SEARCH motion divergence: |mv_quad - mv16| in quarter-pel. Known
153 /// after the ONE 8x8 search we always run, before the EIGHT sub-searches
154 /// this gate would skip. Unlike `j8_lme` (difficulty) and `mbvar`
155 /// (texture) — both refuted — this measures the thing splitting actually
156 /// exploits: a motion BOUNDARY inside the quad. A quad moving with its
157 /// parent has nothing to split.
158 pub mvdiv: i32,
159 }
160
161 #[inline]
162 pub fn enabled() -> bool {
163 sink().is_some()
164 }
165
166 /// Flushes a macroblock's buffered quad rows with the RD outcome attached.
167 pub fn flush(rows: &[Row], rd_kept: bool) {
168 if let Some(m) = sink() {
169 if let Ok(mut f) = m.lock() {
170 for r in rows {
171 let jl = r.j8 as f64 / r.lme.max(1e-9);
172 let _ = writeln!(
173 f,
174 "{},{},{},{:.3},{},{:.2},{},{}",
175 r.j8, r.jsplit, r.st, r.lme, r.mbvar, jl, r.mvdiv, rd_kept as u8
176 );
177 }
178 }
179 }
180 }
181}
182
183/// P3 RD-pricing probe #2: price the INTRA-vs-INTER decision in the RD
184/// currency instead of the SATD proxy + fitted `tune_intra_penalty`.
185/// `RFF_INTRA_RD=1`.
186///
187/// Today: `c_intra = best_i16_satd + lme*tune_intra_penalty` vs the inter
188/// arm's SATD cost. Both sides are prediction-error proxies, and the penalty
189/// constant exists precisely to correct the proxy's bias — a fitted patch over
190/// a wrong-sign currency (the same defect the sub-8x8 probe confirmed, where
191/// re-pricing moved the worst clip 4.3 points AND improved the winners). The
192/// evaluator needed here already existed and had ZERO callers
193/// (`trial_intra`): snapshot -> encode into scratch -> real bits + recon SSD
194/// -> restore. "Exported != wired", the same law as the SATD asm kernel that
195/// sat uncalled for months.
196fn intra_rd_on() -> bool {
197 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
198 *ON.get_or_init(|| std::env::var("RFF_INTRA_RD").map(|v| v == "1").unwrap_or(false))
199}
200
201/// P3 RD-pricing probe #3: price the PARTITION SHAPE decision (16x16 vs 16x8
202/// vs 8x16 vs P_8x8) in the RD currency. `RFF_SHAPE_RD=1`.
203///
204/// The third and last SATD-priced DEFAULT-ON site. The shapes are compared on
205/// `best_part`'s SATD+lambda*mvbits costs today; finer shapes always reduce
206/// prediction error, so the same wrong-sign bias that made sub-8x8 a net loser
207/// should bias this decision toward over-splitting too — mildly, since 16x8
208/// has far less freedom to fit noise than 4x4. Probe, do not assume.
209/// Weight applied to chroma SSD inside the inter RD trials. 1.0 = the original
210/// equal-weight sum (byte-identical).
211fn chroma_ssd_weight() -> f64 {
212 static V: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
213 *V.get_or_init(|| {
214 std::env::var("RFF_RD_CHROMA_W")
215 .ok()
216 .and_then(|v| v.parse().ok())
217 .unwrap_or(1.0)
218 })
219}
220
221/// Texture ceiling above which shape-RD is vetoed (see the call site).
222fn shape_rd_tex_max() -> i64 {
223 static V: std::sync::OnceLock<i64> = std::sync::OnceLock::new();
224 *V.get_or_init(|| {
225 std::env::var("RFF_SHAPE_RD_TEXMAX")
226 .ok()
227 .and_then(|v| v.parse().ok())
228 // REFIT 1000 -> 2000 on 2026-08-08. The original 1000 was placed in the
229 // open gap of a 24-clip 4x4-era table, below mobile_cif (1494) because
230 // mobile then LOST +1.99% BD-SSIM with the shape-RD pass on. Re-measured
231 // on the current encoder, mobile now WINS -0.90% (I+P) / -0.45% (I+P+B)
232 // unvetoed — a ~2.9-point sign flip on the same clip and the same knob.
233 // The threshold-transfer law, exactly: a threshold is only valid for the
234 // encoder it was fitted against, and this one went stale.
235 //
236 // NOT removed, because the above-the-line HOLDOUT refuted removal:
237 // maxtex_plaid (median_var 2962) regresses +3.50% unvetoed. Both
238 // above-the-line clips flipped sign since the fit, in OPPOSITE
239 // directions, so `median_var` still ORDERS them — the line was just in
240 // the wrong place. 2000 sits in the open gap between them (1499 .. 2962):
241 // mobile is released, plaid stays guarded, and every other clip in the
242 // corpus is under 1000 and therefore byte-identical either way.
243 //
244 // The fit rests on TWO above-the-line points. Content landing in the new
245 // (1000, 2000] band is unmeasured; re-run bench/gate_refit.py when a
246 // clip appears there.
247 .unwrap_or(2000)
248 })
249}
250
251/// Shape-RD override: `Some(true/false)` from `RFF_SHAPE_RD=1/0`, `None` when unset.
252///
253/// This USED to return `bool` and be consumed as `shape_rd_on() || cfg.tune_shape_rd`
254/// — an OR, which meant `RFF_SHAPE_RD=0` could not turn the gate OFF. shape-RD was
255/// therefore the one shipped gate with NO ESCAPE HATCH: it could not be neutralised at
256/// runtime, could not be Tier-0 tested by `gatecheck` (whose contract is "every gate's
257/// neutral setting still reproduces the un-gated bytes"), and could not be A/B'd against
258/// the `fast`-preset regression it is the leading suspect for. Returning an Option makes
259/// the env an OVERRIDE in both directions.
260fn shape_rd_on() -> Option<bool> {
261 static ON: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
262 *ON.get_or_init(|| std::env::var("RFF_SHAPE_RD").ok().map(|v| v == "1"))
263}
264
265/// `RFF_INTRA_RD_ALL=1` removes the grain gate from the intra RD probe (i.e.
266/// price EVERY macroblock by RD) — the arm the 1.71x-for-nothing measurement
267/// was taken on. Default: gated to grain.
268fn intra_rd_grain_gate() -> bool {
269 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
270 *ON.get_or_init(|| std::env::var("RFF_INTRA_RD_ALL").map(|v| v != "1").unwrap_or(true))
271}
272
273/// ONLINE SPLIT-PAYOFF CENSUS (best_part campaign, centre 2). The per-QUAD skip
274/// gate was pruned with three varied pre-search probes — `j8/lambda`
275/// (difficulty), `mbvar` (texture) and `mvdiv` (motion boundary) all lose wins
276/// at ~the rate they skip searches (ratios 0.87-1.00), because whether a quad
277/// benefits from splitting is a property of its RESIDUAL, which does not exist
278/// until you split. No cheap signal predicts it.
279///
280/// But the same harvest shows the payoff varies 2.4x BY CONTENT — the fraction
281/// of quads sitting in a macroblock whose split survived the RD trial:
282/// harbour 13.7%, bus 27.7%, foreman 32.5%, mobile 33.2%. So the dispatch grain
283/// is the FRAME, not the quad: measure the survival rate online over this
284/// frame's first macroblocks and stop searching splits for the remainder if the
285/// content is not paying. Same shape as the free-skip census that gates RD-skip
286/// and greedy-skip, and me_wide's online payoff learner — within-frame, so it
287/// stays deterministic under GOP-parallel encode.
288///
289/// ⚠ VALUE-WEIGHTED, not a count. The first cut of this census gated on the
290/// PERCENTAGE of macroblocks whose split survived, and on crowd_run that threw
291/// away 78% of a -2.43% BD-SSIM win to buy its speed: a frame where only a tenth
292/// of splits survive can still carry a large win if those few save a lot of
293/// bits. Counting decisions instead of weighting them by what they are worth is
294/// exactly the objective error the suppressor campaign names as cardinal
295/// (unit-weighted net gain, never classification accuracy). The census now
296/// accumulates the RD J the surviving splits actually SAVE, in lambda units,
297/// and requires a mean saving per searched macroblock.
298///
299/// `RFF_SUB8_MINPAY` = required mean J saved per searched MB, in lambda units
300/// (0 disables the census); `RFF_SUB8_LEARN` = MBs observed before it may act.
301fn sub8_pay_cfg() -> (usize, usize) {
302 static C: std::sync::OnceLock<(usize, usize)> = std::sync::OnceLock::new();
303 *C.get_or_init(|| {
304 let p = std::env::var("RFF_SUB8_MINPAY").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
305 let l = std::env::var("RFF_SUB8_LEARN").ok().and_then(|v| v.parse().ok()).unwrap_or(64);
306 (p, l)
307 })
308}
309
310/// `RFF_SUB8_GRAIN=0` disables the sub-8x8 grain veto (bisection anchor).
311fn sub8_grain_veto_on() -> bool {
312 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
313 *ON.get_or_init(|| std::env::var("RFF_SUB8_GRAIN").map(|v| v != "0").unwrap_or(true))
314}
315
316/// P3.3 gate probe: re-price the SPLIT-vs-8x8 decision in the RD currency
317/// (`J = SSD_recon + lambda*bits`) instead of the SATD proxy `best_part`
318/// returns. `RFF_SUB8_RD=1`. See docs/gate-ledger.md sub8x8-split: SATD prices
319/// PREDICTION error, which always falls as partitions get finer, while the
320/// quantizer would have zeroed that detail anyway -- the wrong-sign-proxy law.
321/// This trials both arms through the real transform+quantize+reconstruct and
322/// keeps the one the CODED macroblock actually prefers.
323/// Allow the I_8x8 candidate on INTRA macroblocks inside P/B slices?
324///
325/// MEASURED AND PRUNED 2026-08-08 — kept as a comparator, do not re-explore.
326/// Recovered only 0.03-0.05 BD points on the I+P losers (foreman +0.28 -> +0.23,
327/// harbour +0.12 -> +0.08) and COST grain I+P+B 0.21 (-0.45 -> -0.24). The census
328/// explains why: it touches 0.001-0.039% of bytes, because intra-8x8-in-P is rare.
329///
330/// Hypothesis under test: with inter-8x8 already off, the only 8x8 left in a P frame
331/// is on intra-in-P macroblocks, and every remaining 8x8 BD loss sits in a structure
332/// that contains P frames while all-intra is clean. Intra-in-P blocks are the ones
333/// inter prediction failed on, so they skew toward the fine detail 4x4 handles better.
334/// `RFF_I8_IN_P=0` withholds the candidate there.
335/// How much the I_8x8 candidate must BEAT I_4x4/I_16x16 by, in lambda units, before
336/// it is selected — instead of winning on any margin at all.
337///
338/// MEASURED AND PRUNED 2026-08-08 — margin 0 (any win) is OPTIMAL, do not re-explore.
339/// Sweep over the 21-cell table: margin 0 -> 7 losers, sum -6.72%; margin 8 -> 7
340/// losers, sum -2.30%; margin 24 -> 12 losers, sum +1.25%. It gives back akiyo's win
341/// (-1.90 -> -0.64 -> +0.06) far faster than it recovers foreman (+0.28 -> +0.22 ->
342/// +0.23). The premise was wrong: akiyo's win is an accumulation of MANY MARGINAL
343/// per-MB picks, not a few decisive ones, so a margin kills the win first. A per-MB
344/// RD margin is not a proxy for the clip-level decisiveness of a win.
345///
346/// Motivated by a measured propagation effect: an 8x8-coded I-frame is a worse
347/// PREDICTION SOURCE, and the damage grows monotonically with GOP length. BD of 8x8
348/// vs 4x4 at gop 1 / 10 / 30 / 60: foreman -0.06 / +0.01 / +0.28 / +0.84, harbour
349/// -0.37 / -0.12 / +0.12 / +0.31, and even akiyo's win decays -1.90 -> -1.26. Every
350/// clip moves the same direction, so this is a mechanism and not content noise.
351///
352/// The margin is the dispatch. Clips where 8x8 wins DECISIVELY (akiyo, -1.90% on the
353/// I-frame alone) clear any sane margin and keep their win; clips where it wins by a
354/// hair (foreman, -0.06%) are near-ties that flip on quantisation noise and then cost
355/// far more downstream than they saved. Cost margin as the axis, no new signal needed.
356/// GREAT GATE: withhold the 8x8 transform on SCREEN content for this frame.
357///
358/// The PPS still advertises `transform_8x8_mode_flag`; clearing it per frame simply
359/// means no macroblock sets `transform_size_8x8_flag`, which is legal and keeps the
360/// stream decodable by anything that accepted the sequence.
361///
362/// The win-signature to check when touching this: NATURAL clips must be
363/// BYTE-IDENTICAL with the veto compiled in, because it must abstain on them.
364fn apply_screen_t8_veto(fe: &mut FrameEncoder, sig: &crate::signals::FrameSignals) {
365 if fe.t8_pick && screen_t8_veto_on() && sig.is_screen() {
366 // VALUE, not presence: `transform_8x8` stays true so the flag keeps being
367 // written (the PPS advertises it); `t8_pick` false makes every macroblock
368 // write it as ZERO.
369 fe.t8_pick = false;
370 }
371}
372
373/// `RFF_T8_SCREEN=0` opts out of the screen veto (the comparator arm).
374fn screen_t8_veto_on() -> bool {
375 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
376 *ON.get_or_init(|| std::env::var("RFF_T8_SCREEN").map(|v| v != "0").unwrap_or(true))
377}
378
379fn i8_margin() -> f64 {
380 static M: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
381 *M.get_or_init(|| {
382 std::env::var("RFF_I8_MARGIN").ok().and_then(|v| v.parse().ok()).unwrap_or(0.0)
383 })
384}
385
386fn i8_in_p_on() -> bool {
387 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
388 *ON.get_or_init(|| std::env::var("RFF_I8_IN_P").map(|v| v != "0").unwrap_or(true))
389}
390
391fn sub8_rd_on() -> bool {
392 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
393 *ON.get_or_init(|| std::env::var("RFF_SUB8_RD").map(|v| v == "1").unwrap_or(false))
394}
395
396/// Level-aware bit estimate for a planned inter macroblock: the same
397/// `sum rdoq_rate(|level|)` currency the inter-8x8-vs-4x4 decision already uses,
398/// plus the motion syntax (which does NOT cancel between a split arm and an
399/// 8x8 arm -- they carry a different NUMBER of mvds, and that difference is the
400/// whole point of the comparison).
401fn plan_rate_bits(plan: &InterPlan, sub_types: [u8; 4]) -> f64 {
402 let mut r = 0.0f64;
403 if plan.t8x8 {
404 for b in &plan.q8 {
405 for &l in b.iter() {
406 if l != 0 {
407 r += rdoq_rate((l as i64).abs());
408 }
409 }
410 }
411 } else {
412 for b in &plan.q_blocks {
413 for &l in b.iter() {
414 if l != 0 {
415 r += rdoq_rate((l as i64).abs());
416 }
417 }
418 }
419 }
420 for c in 0..2 {
421 for &l in &plan.c_dc_levels[c] {
422 if l != 0 {
423 r += rdoq_rate((l as i64).abs());
424 }
425 }
426 for b in &plan.c_q[c] {
427 for &l in b.iter() {
428 if l != 0 {
429 r += rdoq_rate((l as i64).abs());
430 }
431 }
432 }
433 }
434 for m in plan.mvds.iter().take(plan.n_mvd) {
435 r += (mvd_bits(m.0) + mvd_bits(m.1)) as f64;
436 }
437 // sub_mb_type bins (1 for 8x8, 2 for 8x4, 3 for 4x8/4x4) + mb_type overhead.
438 for &st in &sub_types {
439 r += if st == 0 { 1.0 } else if st == 1 { 2.0 } else { 3.0 };
440 }
441 r + 16.0
442}
443
444/// P3.3 opt-in: search 8x4/4x8/4x4 sub-partitions inside P_8x8 (CABAC quality
445/// path, single-ref). `RFF_SUB8X8_SPLIT=1` enables; unset/0 = byte-identical.
446fn sub8x8_split_on() -> bool {
447 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
448 *ON.get_or_init(|| std::env::var("RFF_SUB8X8_SPLIT").map(|s| s == "1").unwrap_or(false))
449}
450
451fn split_t() -> f64 {
452 let v = SPLIT_T.load(std::sync::atomic::Ordering::Relaxed);
453 if v != u32::MAX {
454 return v as f64;
455 }
456 let d: u32 = std::env::var("RFF_SPLIT_T").ok().and_then(|s| s.parse().ok()).unwrap_or(0);
457 SPLIT_T.store(d, std::sync::atomic::Ordering::Relaxed);
458 d as f64
459}
460
461/// Observe-only HARVEST for the PARTITION-split gate (U2/U5).
462///
463/// The 16×16 search is the null arm and runs first; the 2-way splits and P_8x8 are
464/// the expensive arm (7 further `best_part` calls, each with its own full-pel search
465/// AND sub-pel refinement). Today they are gated by a fixed `split_gate` formula.
466/// Records the null-arm cost, the best split cost, and which won, so the
467/// skip-rate-vs-gain-kept ceiling can be swept before any threshold is touched.
468mod split_harvest {
469 use std::fs::File;
470 use std::io::Write;
471 use std::sync::{Mutex, OnceLock};
472
473 fn sink() -> &'static Option<Mutex<File>> {
474 static S: OnceLock<Option<Mutex<File>>> = OnceLock::new();
475 S.get_or_init(|| {
476 std::env::var("RFF_SPLIT_HARVEST").ok().and_then(|p| {
477 let mut f = File::create(p).ok()?;
478 let _ = writeln!(f, "c16,best,lambda,gate,won");
479 Some(Mutex::new(f))
480 })
481 })
482 }
483
484 #[inline]
485 pub fn enabled() -> bool {
486 sink().is_some()
487 }
488
489 pub fn record(c16: i64, best: i64, lambda: f64, gate: i64, won: u8) {
490 if let Some(m) = sink() {
491 if let Ok(mut f) = m.lock() {
492 let _ = writeln!(f, "{c16},{best},{lambda:.4},{gate},{won}");
493 }
494 }
495 }
496}
497
498/// Descent C escape hatch: `RFF_HPEL_REF=0` restores the copy-then-SATD half-pel path
499/// (byte-identical to it either way — this exists as a bisection anchor).
500fn hpel_ref_enabled() -> bool {
501 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
502 *E.get_or_init(|| std::env::var("RFF_HPEL_REF").map(|v| v != "0").unwrap_or(true))
503}
504
505/// H-23: smooth (x264-shape) mvd cost table, in quarter-bit units scaled to the
506/// same magnitude as the Exp-Golomb model so λ stays calibrated. `RFF_MVCOST=1`.
507static MV_COST_TAB: std::sync::OnceLock<Vec<u16>> = std::sync::OnceLock::new();
508fn build_mv_cost() -> Vec<u16> {
509 (0..4096u32)
510 .map(|a| {
511 let c = 2.0 * ((a + 1) as f64).log2() + 0.718 + if a != 0 { 1.0 } else { 0.0 };
512 // Round to quarter-bits then express in the caller's integer "bits"
513 // domain by keeping 4× resolution — λ is rescaled to match below.
514 (c * 4.0).round() as u16
515 })
516 .collect()
517}
518/// H-24: 0 = off (Exp-Golomb step, byte-identical), 1 = DISPATCHED per frame by
519/// the `b2_mgain` motion probe, 2 = force-on. The BD sign-flip (bus −1.31 /
520/// football −0.24 vs foreman +0.23 / akiyo +0.11) tracks MOTION: the smooth
521/// curve pays where |mvd| is large enough to leave the first bracket, and only
522/// adds noise where every vector already sits inside it.
523static MV_SMOOTH: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
524pub fn set_mv_smooth(on: bool) {
525 MV_SMOOTH.store(if on { 2 } else { 0 }, core::sync::atomic::Ordering::Relaxed)
526}
527pub fn set_mv_smooth_mode(m: u32) {
528 MV_SMOOTH.store(m.min(3), core::sync::atomic::Ordering::Relaxed)
529}
530/// Dispatch threshold on the per-frame mgain probe (`RFF_MVCOST_T`, default 0.10).
531/// Calibrated on the DEPLOYED probe: bus min-frame 0.185 and football med 0.208
532/// route ON; foreman med 0.164 is the boundary case, akiyo ~0.00 routes OFF.
533/// H-26: the measured TRUE table plus a COHERENCE BIAS on every d≠0 entry —
534/// the cheap scalar form of the MV-field externality H-25 root-caused (a chosen
535/// vector that leaves the predictor degrades the neighbours' medians; truth
536/// per-vector under-prices that shared damage). `RFF_MVCOST_BIAS` in bits,
537/// read once at first use. DEFAULT 1.0 — mode 3 ships as the H-26 BIASED form
538/// (the original doc here said "default 0 = pure truth", contradicting the
539/// `unwrap_or(1.0)` shipped in the same commit; the code carries the intent —
540/// set `RFF_MVCOST_BIAS=0` explicitly for the H-25 pure-truth table).
541static MV_TRUE_BIASED: std::sync::OnceLock<Vec<u16>> = std::sync::OnceLock::new();
542fn build_true_biased() -> Vec<u16> {
543 let bias_q4 = (std::env::var("RFF_MVCOST_BIAS")
544 .ok()
545 .and_then(|v| v.parse::<f64>().ok())
546 .unwrap_or(1.0)
547 * 4.0)
548 .round() as u16;
549 crate::mvd_cost_tab::MVD_TRUE_COST4
550 .iter()
551 .enumerate()
552 .map(|(d, &c)| if d == 0 { c } else { c.saturating_add(bias_q4) })
553 .collect()
554}
555
556fn mv_smooth_t() -> f64 {
557 static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
558 *T.get_or_init(|| std::env::var("RFF_MVCOST_T").ok().and_then(|v| v.parse().ok()).unwrap_or(0.10))
559}
560/// 0 = step model, 1 = smooth (this frame routed on), 2 = smooth (forced),
561/// 3 = the MEASURED true-cost table (H-25) — no dispatch needed if it wins
562/// everywhere, since it is the truth both analytic models approximate.
563/// `frame_smooth` is the per-frame probe decision, carried on the frame state
564/// (like `sadfp`) — a process-global here races under the GOP-parallel encode.
565#[inline]
566fn mv_cost_kind(frame_smooth: bool) -> u32 {
567 match mv_smooth_mode() {
568 0 => 0,
569 // H-26 verdict: smooth/truth/truth+bias shuffle within ±0.2 BD fit-noise
570 // of each other at dispatch (bus prefers truth, football prefers smooth,
571 // none dominates), so the dispatch keeps its ORIGINALLY-GATED smooth
572 // ON-model; the measured/biased tables remain as modes 2/3 for research.
573 1 => frame_smooth as u32,
574 2 => 1, // archaeology: the x264 smooth curve, forced
575 _ => 2, // the biased-truth table, forced
576 }
577}
578#[inline]
579fn mv_smooth_mode() -> u32 {
580 match MV_SMOOTH.load(core::sync::atomic::Ordering::Relaxed) {
581 m @ 0..=3 => m,
582 _ => {
583 static E: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
584 // DEFAULT 1 = DISPATCHED (H-24). Owner's call: mean −0.27% BD is
585 // taken over minimax, accepting a known, bounded +0.16-0.18% on
586 // foreman-class content. `RFF_MVCOST=0` restores the pre-H-23 bytes.
587 *E.get_or_init(|| {
588 std::env::var("RFF_MVCOST").ok().and_then(|v| v.parse().ok()).unwrap_or(1)
589 })
590 }
591 }
592}
593
594/// Challenge-1 A3 escape hatch: `RFF_SATD_AVG=0` restores the materialize-then-SATD
595/// quarter-pel cost path (byte-identical either way — a bisection anchor, like
596/// `RFF_HPEL_REF`).
597/// H-14 R3 escape hatch: `RFF_MECTX=0` restores the per-eval safe dispatch
598/// (byte-identical either way — MeCtx returns exactly the safe path's values).
599#[cfg_attr(not(accel), allow(dead_code))] // caller is accel-gated
600fn mectx_enabled() -> bool {
601 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
602 *E.get_or_init(|| std::env::var("RFF_MECTX").map(|v| v != "0").unwrap_or(true))
603}
604
605fn satd_avg_enabled() -> bool {
606 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
607 *E.get_or_init(|| std::env::var("RFF_SATD_AVG").map(|v| v != "0").unwrap_or(true))
608}
609
610/// Track-B B2 (docs/lets-win-optimize.md): run the FULL-PEL phase of the non-fast
611/// motion search in the SAD domain (`psadbw`-class, ~3-4× cheaper per candidate) and
612/// reprice the winner in SATD before the rescue/sub-pel phases — the cost split
613/// every x264 preset uses (SAD fpel, SATD from subme≥2). ⚠ BITSTREAM-CHANGING (a
614/// different full-pel winner can emerge), so it ships opt-in until the per-clip
615/// 4-QP BD gate clears it. `set_me_sadfp` overrides; unset → `RFF_ME_SADFP` env,
616/// default OFF (off = byte-identical to the pre-B2 encoder).
617/// Modes: 0 = off (byte-identical), 1 = DISPATCHED per frame by the `b2_mgain`
618/// probe (the shipping shape), 2 = force-on everywhere (the truth-table A/B arm).
619static ME_SADFP: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
620pub fn set_me_sadfp(on: bool) {
621 // Harness semantics preserved: `true` = the force-on arm truth tables measure.
622 ME_SADFP.store(if on { 2 } else { 0 }, core::sync::atomic::Ordering::Relaxed)
623}
624pub fn set_me_sadfp_mode(m: u32) {
625 ME_SADFP.store(m.min(2), core::sync::atomic::Ordering::Relaxed)
626}
627fn me_sadfp_mode() -> u32 {
628 match ME_SADFP.load(core::sync::atomic::Ordering::Relaxed) {
629 u32::MAX => {
630 static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
631 // DEFAULT = 1 (dispatched) since the H-3 gate: 16-clip corpus mean
632 // −0.26% BD, wins bus −1.71 / football −1.84 / foreman −0.44 /
633 // shields −0.22, every former loss 0.00; residual tail (soccer +0.09,
634 // harbour +0.06) is BD-fit noise — it responds NON-monotonically to
635 // threshold changes (less B2 made soccer read WORSE, +0.18).
636 // `RFF_ME_SADFP=0` is the escape hatch reproducing the pre-B2 bytes.
637 *INIT.get_or_init(|| {
638 std::env::var("RFF_ME_SADFP").ok().and_then(|v| v.parse().ok()).unwrap_or(1)
639 })
640 }
641 m => m,
642 }
643}
644
645/// B2 dispatch threshold on the per-frame `b2_mgain` probe (`RFF_ME_SADT`).
646/// Calibrated on the DEPLOYED estimator (recon reference, sampled MBs), not the
647/// offline source-frame probe — the recurring R6 law.
648fn me_sadt() -> f64 {
649 static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
650 *T.get_or_init(|| std::env::var("RFF_ME_SADT").ok().and_then(|s| s.parse().ok()).unwrap_or(0.13))
651}
652fn me_sadt_dbg() -> bool {
653 static D: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
654 *D.get_or_init(|| std::env::var_os("RFF_ME_SADT_DBG").is_some())
655}
656
657/// Fixed-centre batched diamond passes on SAD-routed frames (`RFF_ME_FC=0` falls
658/// back to the cascading scalar walk — the bisection anchor). Fixed-centre differs
659/// from the cascade only when 2+ points improve in one pass, so it rides B2's BD
660/// gate; dispatched-OFF frames never take this path and stay byte-identical.
661/// ③ Sub-pel ring FC: fixed-centre argmin passes for the HALF-PEL step, batched
662/// through `satd_16x16_x4p` (two calls cover the 8-ring; candidates resolve to
663/// h/h/v/v and c/c/c/c plane reads from an integer centre). Quarter-step and any
664/// declined pass keep the cascading walk. Bitstream-changing → own gate
665/// (`AB_SPFC`), `RFF_SP_FC=0` anchor.
666static SP_FC: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
667pub fn set_sp_fc(on: bool) {
668 SP_FC.store(on as u32, core::sync::atomic::Ordering::Relaxed)
669}
670fn sp_fc_enabled() -> bool {
671 match SP_FC.load(core::sync::atomic::Ordering::Relaxed) {
672 0 => false,
673 1 => true,
674 _ => {
675 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
676 *E.get_or_init(|| std::env::var("RFF_SP_FC").map(|v| v != "0").unwrap_or(false))
677 }
678 }
679}
680
681static ME_FC: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
682pub fn set_me_fc(on: bool) {
683 ME_FC.store(on as u32, core::sync::atomic::Ordering::Relaxed)
684}
685fn me_fc_enabled() -> bool {
686 match ME_FC.load(core::sync::atomic::Ordering::Relaxed) {
687 0 => false,
688 1 => true,
689 _ => {
690 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
691 *E.get_or_init(|| std::env::var("RFF_ME_FC").map(|v| v != "0").unwrap_or(true))
692 }
693 }
694}
695
696/// H-13 SPLIT DISPATCH — measured and REFUTED as a free dispatch, shipped as an
697/// OPT-IN rung (default 0 = off = byte-identical). The premise "splits buy
698/// ~nothing on near-static frames" is FALSE: at T=0.03 akiyo read +2.45% BD,
699/// akiyo_qcif +2.02%, FourPeople +2.00% for only 1.10-1.15× — partition splits
700/// EARN BD on every measured content class (the third death of the split-gate
701/// idea: U2 T=400, the sum-weighted ceiling, now the mgain axis). foreman/bus
702/// route ON at any sane T (min frame mgain 0.061/0.185) and stay byte-identical.
703/// `RFF_SPLIT_MG` (fraction) / `set_split_mg` (milli): a priced speed rung, not
704/// a free lunch.
705static SPLIT_MG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
706pub fn set_split_mg(milli: u32) {
707 SPLIT_MG.store(milli, core::sync::atomic::Ordering::Relaxed)
708}
709fn split_mg() -> f64 {
710 match SPLIT_MG.load(core::sync::atomic::Ordering::Relaxed) {
711 u32::MAX => {
712 static E: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
713 *E.get_or_init(|| {
714 std::env::var("RFF_SPLIT_MG").ok().and_then(|v| v.parse().ok()).unwrap_or(0.0)
715 })
716 }
717 m => m as f64 / 1000.0,
718 }
719}
720
721/// The flash veto: frames whose zero-MV residual is DC-shift-dominated beyond this
722/// fraction route OFF even at high mgain (`RFF_ME_SADDC`). Calibrated on the
723/// DEPLOYED per-frame values: crew's harmful ON-frames read dc 0.843–0.859 (the
724/// camera flashes) while every good ON-frame on bus/football/foreman reads ≤ 0.478
725/// — a 1.76× natural gap; 0.6 sits mid-gap with margin both ways.
726fn me_sad_dcmax() -> f64 {
727 static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
728 *T.get_or_init(|| std::env::var("RFF_ME_SADDC").ok().and_then(|s| s.parse().ok()).unwrap_or(0.6))
729}
730
731// The B2 dispatch signal `b2_mgain` (mgain + dcfrac) lives in `crate::signals`
732// (Great Gate P1) — read through `FrameSignals::mgain_dc`, whose doc carries the
733// 16-clip truth table and the crew-flash dcfrac rationale.
734
735/// Track-B B3: cap on sub-pel ring ITERATIONS per step (`RFF_SP_MAXIT` /
736/// `set_sp_maxit`). 0 = unlimited (the default — byte-identical to the walk-to-
737/// convergence encoder); N caps each step's walk at N passes, the bounded budget
738/// x264's subme levels have always had. Bitstream-changing when set → BD-gated.
739static SP_MAXIT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
740pub fn set_sp_maxit(n: u32) {
741 SP_MAXIT.store(n, core::sync::atomic::Ordering::Relaxed)
742}
743fn sp_maxit() -> u32 {
744 match SP_MAXIT.load(core::sync::atomic::Ordering::Relaxed) {
745 u32::MAX => {
746 static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
747 *INIT.get_or_init(|| {
748 std::env::var("RFF_SP_MAXIT").ok().and_then(|v| v.parse().ok()).unwrap_or(0)
749 })
750 }
751 n => n,
752 }
753}
754
755/// B2 calibration: λ multiplier for the SAD-domain full-pel phase (`RFF_ME_SADL`,
756/// default 1.0). SATD distortion runs ~2× SAD's scale, so λ tuned for SATD weighs
757/// the rate term ~2× heavier in the SAD domain — 0.5 restores the SATD-era
758/// rate/distortion balance. Read once per process (hoisted per search).
759fn me_sadfp_lambda() -> f64 {
760 static E: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
761 // 0.5 = the calibrated default (SATD ≈ 2× SAD's scale; at 1.0 the rate term
762 // weighs double and foreman flips to a BD loss). Rides with the mode-1 default.
763 *E.get_or_init(|| {
764 std::env::var("RFF_ME_SADL").ok().and_then(|v| v.parse().ok()).unwrap_or(0.5)
765 })
766}
767
768/// Descent D: sub-pel ring census — evals/improvements by (step, ring position) and
769/// by loop ITERATION, so a position or an iteration that never pays is visible rather
770/// than assumed.
771#[cfg(feature = "profile")]
772pub mod spstats {
773 use core::sync::atomic::{AtomicU64, Ordering};
774 /// [step 0=half,1=quarter][position 0..8][0=evals,1=improvements]
775 pub static POS: [AtomicU64; 2 * 8 * 2] = [const { AtomicU64::new(0) }; 32];
776 /// [step][iteration 1..=6 clamped][0=evals,1=improvements]
777 pub static IT: [AtomicU64; 2 * 6 * 2] = [const { AtomicU64::new(0) }; 24];
778 #[inline]
779 pub fn ev(st: usize, pos: usize, it: u32) {
780 POS[(st * 8 + pos.min(7)) * 2].fetch_add(1, Ordering::Relaxed);
781 IT[(st * 6 + (it.max(1) as usize - 1).min(5)) * 2].fetch_add(1, Ordering::Relaxed);
782 }
783 #[inline]
784 pub fn imp(st: usize, pos: usize, it: u32) {
785 POS[(st * 8 + pos.min(7)) * 2 + 1].fetch_add(1, Ordering::Relaxed);
786 IT[(st * 6 + (it.max(1) as usize - 1).min(5)) * 2 + 1].fetch_add(1, Ordering::Relaxed);
787 }
788 /// Sub-pel evaluations that re-price an MV already evaluated in the SAME refinement.
789 pub static REDUNDANT: AtomicU64 = AtomicU64::new(0);
790 #[inline]
791 pub fn redundant() { REDUNDANT.fetch_add(1, Ordering::Relaxed); }
792 pub fn reset() {
793 for c in POS.iter() { c.store(0, Ordering::Relaxed); }
794 for c in IT.iter() { c.store(0, Ordering::Relaxed); }
795 REDUNDANT.store(0, Ordering::Relaxed);
796 }
797 pub fn snapshot() -> (Vec<u64>, Vec<u64>) {
798 (POS.iter().map(|c| c.load(Ordering::Relaxed)).collect(),
799 IT.iter().map(|c| c.load(Ordering::Relaxed)).collect())
800 }
801 pub fn redundant_count() -> u64 { REDUNDANT.load(Ordering::Relaxed) }
802}
803
804/// Descent B: which path does each ME cost evaluation actually take?
805#[cfg(feature = "profile")]
806pub mod satdpath {
807 use core::sync::atomic::{AtomicU64, Ordering};
808 pub static C: [AtomicU64; 3] = [const { AtomicU64::new(0) }; 3];
809 #[inline]
810 pub fn bump(i: usize) { C[i].fetch_add(1, Ordering::Relaxed); }
811 pub fn reset() { for c in C.iter() { c.store(0, Ordering::Relaxed); } }
812 pub fn snapshot() -> Vec<u64> { C.iter().map(|c| c.load(Ordering::Relaxed)).collect() }
813}
814
815/// The coarse-to-fine step ladder. DEFAULT `[16,8,4]` — the 64 and 32 rungs were
816/// REMOVED after the per-rung census showed they are ~39% of full-pel evaluations at a
817/// 0.05-0.84% hit rate, and the 20-clip 4-QP BD curve showed those rare hits are actively
818/// HARMFUL: a coarse jump finds a distant MV with marginally lower SATD, but it costs
819/// more mvd bits AND breaks the spatial coherence of the MV field, degrading every
820/// downstream neighbour's predictor. `lambda*mvbits` prices the first effect and is blind
821/// to the second. Dropping them is mean -0.93% BD-PSNR / -1.09% BD-SSIM with a WORST clip
822/// of +0.00%/+0.00% over 20 clips, and 1.15-1.57x fewer ME cost evaluations.
823///
824/// The 8 rung is load-bearing: `[16,4]` reads marginally better BD but makes football_cif
825/// do 1.55x MORE work, because the step-4 walk then has to crawl the distance the 8 rung
826/// covered in one hop. Reach and stride both matter; only the useless TOP is removed.
827///
828/// Bit i of the mask enables rung i of [64,32,16,8,4]. `RFF_DIA_LADDER=64,32,16,8,4`
829/// restores the pre-change ladder byte-for-byte; `set_dia_mask` overrides at runtime so a
830/// single process can measure several ladders.
831pub const DIA_RUNGS: [i32; 5] = [64, 32, 16, 8, 4];
832/// Rungs walked by default: `[16,8,4]`.
833pub const DIA_DEFAULT: u32 = 0b11100;
834
835/// SUB-PARTITION LADDER (best_part campaign, 2026-08-06). A sub-8x8 partition is
836/// seeded with its PARENT's already-converged MV (`extra = [mv16, mv_quad]`), so
837/// the coarse rungs a 16x16 block needs — to reach motion no predictor found —
838/// are near-pure toll here. Measured on foreman (quality, 30f, `mecost`):
839///
840/// | rung | reach | share of ALL ME evals | hit rate |
841/// |---|---|---|---|
842/// | s0 | 4 px | 30.0% | **0.97%** |
843/// | s1 | 2 px | 31.6% | 2.20% |
844/// | s2 | 1 px | 38.3% | 6.42% |
845///
846/// and the shares are IDENTICAL with the split search on or off — i.e. every
847/// 4x4 walks the same 4-pixel-reach ladder as an unpredicted 16x16. s0 alone is
848/// 863k evaluations to change 8,331 answers. The fine rung still reaches any
849/// distance (it walks to convergence), just in 1-px hops from a seed that is
850/// already right. `RFF_DIA_SUB` overrides (same `a,b,c` rung syntax as
851/// `RFF_DIA_LADDER`); `RFF_DIA_SUB=16,8,4` restores the pre-campaign behaviour.
852pub static DIA_SUB_MASK: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
853
854fn dia_sub_mask() -> u32 {
855 let m = DIA_SUB_MASK.load(core::sync::atomic::Ordering::Relaxed);
856 if m != u32::MAX {
857 return m;
858 }
859 static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
860 *INIT.get_or_init(|| match std::env::var("RFF_DIA_SUB") {
861 Ok(v) => {
862 let want: Vec<i32> = v.split(',').filter_map(|t| t.trim().parse().ok()).collect();
863 let mut m = 0u32;
864 for (i, r) in DIA_RUNGS.iter().enumerate() {
865 if want.contains(r) {
866 m |= 1 << i;
867 }
868 }
869 m
870 }
871 // Default: the FINE rung alone (1 px, walked to convergence). Swept
872 // {[16,8,4], [8,4], [4]} on 6 clips as BD-rate vs no-splits — the short
873 // ladder is not a trade, it WINS on quality too, because coarse rungs on
874 // a 4x4 chase spurious far matches that fit the tiny block while
875 // wrecking the MV field its neighbours predict from (the same mechanism
876 // the diagonal-probe note above records, applied to rung REACH):
877 //
878 // clip full [16,8,4] fine [4] evals
879 // foreman -3.48 / -2.14 -3.59 / -2.21 -44.5%
880 // harbour -0.29 / +0.146 -0.36 / +0.073
881 // mobile -2.22 / -2.38 -2.42 / -2.57
882 // tempete -1.15 / -0.77 -1.25 / -0.89
883 // bus -6.61 / -5.53 -6.63 / -5.49 (tie)
884 // screen -11.98 / -12.40 -11.97 / -12.09 (gives back 0.31 of 12.4)
885 Err(_) => 0b10000,
886 })
887}
888pub static DIA_MASK: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
889pub fn set_dia_mask(m: u32) { DIA_MASK.store(m, core::sync::atomic::Ordering::Relaxed) }
890fn dia_mask() -> u32 {
891 let m = DIA_MASK.load(core::sync::atomic::Ordering::Relaxed);
892 if m != u32::MAX { return m; }
893 static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
894 *INIT.get_or_init(|| match std::env::var("RFF_DIA_LADDER") {
895 Ok(v) => {
896 let want: Vec<i32> = v.split(',').filter_map(|t| t.trim().parse().ok()).collect();
897 let mut m = 0u32;
898 for (i, r) in DIA_RUNGS.iter().enumerate() {
899 if want.contains(r) { m |= 1 << i; }
900 }
901 if m == 0 { DIA_DEFAULT } else { m }
902 }
903 Err(_) => DIA_DEFAULT,
904 })
905}
906
907/// Descent A: per-STEP-SIZE census of the coarse-to-fine diamond. The ladder is
908/// [64,32,16,8,4] quarter-pel (i.e. 16,8,4,2,1 full-pel) and each step walks until it
909/// stops improving. Counts evaluations AND improvements per step so a step that never
910/// pays can be identified rather than assumed.
911#[cfg(feature = "profile")]
912pub mod diastats {
913 use core::sync::atomic::{AtomicU64, Ordering};
914 /// [step_index][0]=evals, [1]=improvements
915 pub static C: [AtomicU64; 12] = [const { AtomicU64::new(0) }; 12];
916 #[inline]
917 pub fn ev(i: usize) { C[i * 2].fetch_add(1, Ordering::Relaxed); }
918 #[inline]
919 pub fn imp(i: usize) { C[i * 2 + 1].fetch_add(1, Ordering::Relaxed); }
920 pub fn reset() { for c in C.iter() { c.store(0, Ordering::Relaxed); } }
921 pub fn snapshot() -> Vec<(u64, u64)> {
922 (0..6).map(|i| (C[i * 2].load(Ordering::Relaxed), C[i * 2 + 1].load(Ordering::Relaxed))).collect()
923 }
924}
925
926/// Sub-pel refinement PATTERN (U1). Bit 0 = 4-point diamond ring instead of the
927/// 8-point square; bit 1 = single pass instead of walking to convergence.
928///
929/// Harvested from 280 k real refinements: ~29 evaluations each, but the LAST
930/// improvement lands at eval ~14–15 — **half of every refinement is spent confirming
931/// an answer already found** — and the first ring alone captures 64–72% of the total
932/// gain. An 8-point ring pays 8 evaluations for that confirmation; a 4-point diamond
933/// (what x264's subme uses) pays 4.
934///
935/// `RFF_SUBPEL_PAT`: 0 = 8-point + iterate (the pre-U1 default), 1 = 4-point +
936/// iterate, 2 = 8-point single pass, 3 = 4-point single pass.
937pub(crate) static SUBPEL_PAT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
938
939/// Learning-window size and ring-1 threshold (percent) for the U1 online dispatcher.
940/// `RFF_SUBPEL_DISPATCH=0` disables it (pure `RFF_SUBPEL_PAT` behaviour).
941pub(crate) static SP_DISPATCH: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
942
943fn sp_dispatch_cfg() -> (u32, i64) {
944 use std::sync::OnceLock;
945 let forced = SP_DISPATCH.load(std::sync::atomic::Ordering::Relaxed);
946 if forced == 0 {
947 return (0, 0);
948 }
949 static C: OnceLock<(u32, i64)> = OnceLock::new();
950 *C.get_or_init(|| {
951 // DEFAULT OFF — measured and refuted (see the U1 entry in
952 // docs/WHYS-speed-gap.md). It only delivers speed where a blanket pattern
953 // change already would (bus 1.47x) while costing BD where it delivers none
954 // (foreman +0.97% for 1.04x, mobile +0.33% for 0.98x), and mixing refinement
955 // quality across frames measured WORSE than a uniform cut (bus +0.81%
956 // dispatched vs +0.30% pat2-always) — the refinement feeds the reference
957 // chain, so per-frame inconsistency propagates.
958 let on = std::env::var("RFF_SUBPEL_DISPATCH").map(|s| s != "0").unwrap_or(false);
959 if !on {
960 return (0, 0);
961 }
962 let k = std::env::var("RFF_SUBPEL_LEARN").ok().and_then(|s| s.parse().ok()).unwrap_or(200);
963 let t = std::env::var("RFF_SUBPEL_T").ok().and_then(|s| s.parse().ok()).unwrap_or(67);
964 (k, t)
965 })
966}
967
968/// Explicit override only; `None` means "use the preset's default".
969fn subpel_pattern_override() -> Option<u32> {
970 let v = SUBPEL_PAT.load(std::sync::atomic::Ordering::Relaxed);
971 if v != u32::MAX {
972 return Some(v);
973 }
974 if let Some(e) = std::env::var("RFF_SUBPEL_PAT").ok().and_then(|s| s.parse::<u32>().ok()) {
975 SUBPEL_PAT.store(e, std::sync::atomic::Ordering::Relaxed);
976 return Some(e);
977 }
978 None
979}
980
981/// Observe-only HARVEST for the sub-pel refinement skip-gate (U1).
982///
983/// `me-subpel` is 141 ms of a 320 ms quality encode — 44% — at 241 candidate
984/// evaluations per macroblock. This tap records, per refinement, the NULL-ARM cost
985/// (the full-pel winner, i.e. what we would keep if we skipped) against the cost the
986/// refinement actually reached, so the skip-rate-vs-gain-kept ceiling can be swept
987/// offline before any gate is written. Writes nothing unless `RFF_SUBPEL_HARVEST`
988/// names a file.
989mod subpel_harvest {
990 use std::fs::File;
991 use std::io::Write;
992 use std::sync::{Mutex, OnceLock};
993
994 fn sink() -> &'static Option<Mutex<File>> {
995 static S: OnceLock<Option<Mutex<File>>> = OnceLock::new();
996 S.get_or_init(|| {
997 std::env::var("RFF_SUBPEL_HARVEST").ok().and_then(|p| {
998 let mut f = File::create(p).ok()?;
999 let _ = writeln!(f, "pre,post,lambda,w,h,evals,to_best,ring1");
1000 Some(Mutex::new(f))
1001 })
1002 })
1003 }
1004
1005 #[inline]
1006 pub fn enabled() -> bool {
1007 sink().is_some()
1008 }
1009
1010 #[allow(clippy::too_many_arguments)]
1011 pub fn record(pre: i64, post: i64, lambda: f64, w: usize, h: usize, evals: u32, to_best: u32, ring1: i64) {
1012 if let Some(m) = sink() {
1013 if let Ok(mut f) = m.lock() {
1014 let _ = writeln!(f, "{pre},{post},{lambda:.4},{w},{h},{evals},{to_best},{ring1}");
1015 }
1016 }
1017 }
1018}
1019
1020/// A/B switch for serving B-direct 4×4 MC from the cached half-pel planes
1021/// (`RFF_BDIRECT_PLANES=0` restores the direct `mc_luma` 6-tap). Byte-identical
1022/// either way; the knob exists so the arm can be measured in one binary.
1023fn bdirect_planes_enabled() -> bool {
1024 use std::sync::OnceLock;
1025 static ON: OnceLock<bool> = OnceLock::new();
1026 *ON.get_or_init(|| std::env::var("RFF_BDIRECT_PLANES").map(|s| s != "0").unwrap_or(true))
1027}
1028
1029fn me_batch_enabled() -> bool {
1030 use std::sync::OnceLock;
1031 static ON: OnceLock<bool> = OnceLock::new();
1032 *ON.get_or_init(|| std::env::var("RFF_ME_BATCH").map(|s| s != "0").unwrap_or(true))
1033}
1034
1035/// A 16-byte-aligned 16×16 luma block — the aligned `op1` openh264's SSE2 SAD/SATD
1036/// kernels require (`movdqa`). Safe to construct (`forbid(unsafe)` holds); the asm
1037/// FFI that consumes it lives in `rusty_h264-accel`. Only used on the `asm` feature.
1038#[cfg(accel)]
1039#[repr(align(16))]
1040struct AlignedMb([u8; 256]);
1041
1042/// A B-slice 16×16 inter-coding spec: the prediction direction and the motion it
1043/// uses. `dir` 1 = `B_L0_16x16`, 2 = `B_L1_16x16`, 3 = `B_Bi_16x16` (spec Table
1044/// 7-14). List-0 is `refs[0]` (nearest past anchor); `l1` is List-1 (nearest
1045/// future anchor). `mv0`/`mv1` are the List-0/List-1 motion vectors (quarter-pel).
1046#[derive(Clone, Copy)]
1047struct BInter<'a> {
1048 dir: u8,
1049 l1: &'a crate::RefFrame,
1050 mv0: (i32, i32),
1051 mv1: (i32, i32),
1052 /// 0 = single 16x16 (use `dir`/`mv0`/`mv1`); 1 = 16x8; 2 = 8x16. When non-zero
1053 /// `parts2` carries `(pred, mv0, mv1)` per partition with pred 1=L0 / 2=L1 / 3=Bi.
1054 mvmode: u8,
1055 parts2: [(u8, (i32, i32), (i32, i32)); 2],
1056}
1057
1058/// 16-byte-aligned 256-`i16` DCT/coefficient buffer — the in-place `movdqa` quant
1059/// kernel (openh264 `WelsQuantFour4x4` heritage) requires aligned coefficients.
1060#[cfg(accel)]
1061#[repr(align(16))]
1062struct AlignedDct([i16; 256]);
1063
1064/// Luma variance of the 16×16 source MB at (mb_x, mb_y) — the content signal for
1065/// the adaptive SAD↔SATD cost dispatch (high variance = detail = SAD misprices).
1066/// `256·variance` scale (the /256 of the mean-square is kept integer); only the
1067/// RELATIVE ordering matters for the per-frame percentile, so the constant drops.
1068// `mb_variance` lives in `crate::signals` (Great Gate P1) — imported above; the
1069// per-MB raster vector is shared through `FrameSignals::mb_vars`.
1070
1071/// Adaptive-Quantization per-MB QP map: flat (low-variance) macroblocks get a FINER
1072/// QP (where blocking/banding is visible), busy ones a COARSER QP (where the eye
1073/// masks error) — moving bits to where they're seen. The shift is `strength ·
1074/// (log2 var − frame mean log2 var)`, so it's relative to THIS frame's texture
1075/// distribution (content-invariant), rounded to an integer QP step and clamped.
1076/// `strength == 0` → uniform base QP (byte-identical: every `mb_qp_delta` is 0).
1077/// `RFF_AQ_GRAIN=0` disables the grain veto below — the bisection anchor that
1078/// reproduces the pre-gate bytes exactly.
1079fn aq_grain_veto_on() -> bool {
1080 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1081 *ON.get_or_init(|| std::env::var("RFF_AQ_GRAIN").map(|s| s != "0").unwrap_or(true))
1082}
1083
1084fn aq_qp_map(sig: &FrameSignals, base_qp: u8, strength: f64) -> Vec<u8> {
1085 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncAq);
1086 const AQ_DQP_MAX: i32 = 4;
1087 let n = sig.n_mbs();
1088 if strength == 0.0 || n == 0 {
1089 return vec![base_qp; n];
1090 }
1091 // GRAIN VETO (Great Gate P2 — docs/gate-ledger.md "aq-grain-veto",
1092 // PROVISIONAL: fitted against one textured-grain exemplar). Grain breaks
1093 // AQ's premise from the side the lv_spread back-off cannot see: noise is
1094 // "busy" everywhere (spread stays LOW-to-mid), but it is not maskable
1095 // texture — coarsening it shifts bits into coding noise (measured
1096 // +29.45% BD-SSIM on grain_akiyo, the corpus's only catastrophic AQ loss).
1097 // Three clauses, each grain-physical, ANDed for precision and abstention:
1098 // median_var < 200 — the residual is NOT explained by texture (protects
1099 // mobile 1346+, city 259+; grain reads ≤ 128);
1100 // grain_floor > 5 — even the best-predicted MBs carry residual;
1101 // mgain < 0.1 — a full-pel search cannot reduce it (not motion).
1102 // "Unexplained temporal residual: not texture, not motion → noise."
1103 // Per-frame firing on the 24-clip corpus: grain 58/58 frames, ONE frame of
1104 // one winner (stockholm 1/58); threshold-insensitive across var<150..250.
1105 // Misses (textured grain, var ≥ 200) fail OPEN to current behaviour.
1106 // Clause order = cost order: median_var and the probes are memoized in the
1107 // signal vector, and short-circuiting keeps the mgain probe off almost
1108 // every non-grain frame.
1109 let grain = aq_grain_veto_on() && sig.grain_signature();
1110 signals::census::bump(signals::census::AQ_GRAIN, grain);
1111 if grain {
1112 return vec![base_qp; n];
1113 }
1114 // Per-MB variance (the bit-cost weight) and its log2 (+1 avoids log2(0) on a flat
1115 // MB → reads as maximally flat → finest QP) — both read from the shared signal
1116 // vector (Great Gate P1: one variance walk per frame, N consumers).
1117 // (`(v+1) as f64` is converted on the fly at both consumers below, in the
1118 // same order the old materialized `var` Vec was read — identical sums,
1119 // one n-sized allocation per frame gone.)
1120 let lvs = sig.log_vars();
1121 let (lv, mean_lv) = (&lvs.0, lvs.1);
1122 // CONTENT-ADAPTIVE STRENGTH: back off where the log-variance SPREAD is high. A
1123 // wide/bimodal spread means synthetic-ish content (flat regions beside detailed
1124 // patterns) where "busy = maskable" FAILS and the patterns are salient — full AQ
1125 // there costs PSNR. Natural content's spread is ~1 (keeps full strength); a
1126 // synthetic pan's is ~6 (heavily reduced). Ramp 1.0→`AQ_SPREAD_MIN` over
1127 // [`AQ_SPREAD_LO`, `AQ_SPREAD_HI`].
1128 const AQ_SPREAD_LO: f64 = 1.5;
1129 const AQ_SPREAD_HI: f64 = 5.0;
1130 const AQ_SPREAD_MIN: f64 = 0.0; // extreme spread (pathological synthetic) → AQ OFF
1131 let std_lv = lvs.2; // the shared lv_spread — same formula, computed once
1132 let factor = (1.0 - (std_lv - AQ_SPREAD_LO) / (AQ_SPREAD_HI - AQ_SPREAD_LO)).clamp(AQ_SPREAD_MIN, 1.0);
1133 let eff_strength = strength * factor;
1134 // Per-MB QP shift (clamped): busy (log-var above mean) coarser, flat finer.
1135 // Site 7's ★★ arm (Round 10): the magic-number round frees this loop of
1136 // its libm `round` call. Ties-EVEN vs libm's ties-AWAY — differs only on
1137 // exact .5, which the corpus hash gate measures.
1138 let poly = crate::fastmath::polytier_on();
1139 let dqp: Vec<i32> = lv
1140 .iter()
1141 .map(|&l| {
1142 let x = eff_strength * (l - mean_lv);
1143 (if poly { crate::fastmath::round_ties_even_fast(x) } else { x.round() }) as i32
1144 })
1145 .map(|d| d.clamp(-AQ_DQP_MAX, AQ_DQP_MAX))
1146 .collect();
1147 // RATE COMPENSATION: AQ nets a rate change (coarsening a busy MB saves more bits
1148 // than fining a flat one adds), so shift the whole frame's QP by `c` to restore
1149 // the un-AQ rate — keeping `qp` meaningful. Bit model `bits_i ∝ var_i·2^(−qp_i/6)`
1150 // (variance as the per-MB cost proxy): `c = 6·log2(Σ var·2^(−dqp/6) / Σ var)`.
1151 let sum_v: f64 = sig.mb_vars().iter().map(|&v| (v + 1) as f64).sum();
1152 // `dqp` is clamped to [-AQ_DQP_MAX, AQ_DQP_MAX], so 2^(-d/6) has only nine
1153 // possible values — but it was being recomputed with a `powf` for every
1154 // macroblock of every frame, and after the per-call table fix it was still
1155 // NINE powf + divides per FRAME. It is a pure function of constants, so a
1156 // `OnceLock` evaluates the same expression once per PROCESS — the
1157 // `build_mv_cost` pattern, bit-identical by construction.
1158 static QSTEP: std::sync::OnceLock<[f64; (2 * AQ_DQP_MAX + 1) as usize]> =
1159 std::sync::OnceLock::new();
1160 let qstep = QSTEP.get_or_init(|| {
1161 std::array::from_fn(|i| 2f64.powf(-((i as i32 - AQ_DQP_MAX) as f64) / 6.0))
1162 });
1163 let sum_vs: f64 = sig
1164 .mb_vars()
1165 .iter()
1166 .map(|&v| (v + 1) as f64)
1167 .zip(&dqp)
1168 .map(|(v, &d)| v * qstep[(d + AQ_DQP_MAX) as usize])
1169 .sum();
1170 let c = (6.0 * (sum_vs / sum_v).log2()).round() as i32;
1171 dqp.iter()
1172 .map(|&d| (base_qp as i32 + c + d).clamp(0, 51) as u8)
1173 .collect()
1174}
1175
1176/// Mean per-sampled-pixel residual after GLOBAL-motion compensation of `sy` from
1177/// `ref_y` (coarse ±12 global ME + ±3 refine, subsampled interior). ~0 on a PURE pan
1178/// (a single MV predicts the whole frame) — precisely the content where the local ME
1179/// diamond never genuinely STALLS (its seed = the median = the pan MV is already
1180/// right), so the `me_wide` rescue can only find SPURIOUS MVs that wreck the B-frame
1181/// spatial-direct predictors. Gates `me_wide` off there — non-uniform content
1182/// (real stalls, where me_wide wins) reads well above 0.
1183/// Per-frame HEAD-ROOM probe for the `me_wide` rescue: on a small subsample of
1184/// blocks, how much does a WIDE full-pel search beat a PREDICTOR-LOCAL one?
1185///
1186/// This measures what the rescue actually buys, before the macroblock loop and
1187/// without committing any vector — unlike the online payoff gate, which scores its
1188/// own SATD cost-cut *after* committing MVs and so only ever separated static
1189/// content. Returns the mean relative SAD improvement, in percent.
1190///
1191/// Calibrated against the 20-clip per-clip BD truth table (docs/WHYS-speed-gap.md
1192/// R5): me_wide earns its 1.4–5.1× on high-head-room content (bus +4.57, blue_sky
1193/// +4.70, football +1.51, park_joy +0.91) and REGRESSES on low-head-room content
1194/// (foreman_qcif −1.08, foreman_cif −0.16, tempete −0.12, mobile −0.03).
1195///
1196/// Deliberately PER-FRAME, not per-clip: cross-frame adaptive state is
1197/// nondeterministic under the GOP-parallel encode path (a lesson already paid for
1198/// by the rescue's own learning window).
1199/// Head-room threshold (percent) for the `me_wide` frame gate. DEFAULT-ON at 16.
1200///
1201/// Calibrated on the DEPLOYED estimator (not the offline probe — they differ) and
1202/// gated on the full 20-clip `video-tests` corpus plus four synthesized boundary
1203/// clips, 4-QP BD-rate on PSNR and SSIM:
1204///
1205/// | | me_wide always-on | gated at 16 |
1206/// |---|---|---|
1207/// | real-corpus mean | +0.62% | +0.547% (88% retained) |
1208/// | **worst clip** | **−1.08%** (foreman_qcif) | **0.00%** |
1209/// | clips paying 1.1–3.6× for ~nothing | 13 | 0 |
1210///
1211/// Wins preserved: blue_sky +4.70, bus +4.37, park_joy +0.94, football +0.64,
1212/// shields +0.20; synthesized fast-pan +6.73, rotation +1.72, zoom +1.11.
1213/// Monotone non-regression — no clip is negative — which is what promotes this from
1214/// a speed trade to a default.
1215///
1216/// `RFF_ME_HR=0` disables the gate and reproduces the pre-gate bytes exactly (the
1217/// escape hatch / bisection anchor). Thresholds 13 and 16 both clear the boundary
1218/// clip (foreman_cif +0.07 / +0.03); 10 does NOT (−0.23) — the threshold is
1219/// calibrated on a narrow boundary pair, so treat it as re-tunable, not settled.
1220fn me_wide_hr_thresh() -> f64 {
1221 use std::sync::OnceLock;
1222 static T: OnceLock<f64> = OnceLock::new();
1223 *T.get_or_init(|| std::env::var("RFF_ME_HR").ok().and_then(|s| s.parse().ok()).unwrap_or(16.0))
1224}
1225
1226/// Cached, because it is read per frame — an `env::var` there is its own tax.
1227fn me_wide_hr_dbg() -> bool {
1228 use std::sync::OnceLock;
1229 static D: OnceLock<bool> = OnceLock::new();
1230 *D.get_or_init(|| std::env::var_os("RFF_ME_HR_DBG").is_some())
1231}
1232
1233// `me_wide_headroom` and `global_mc_residual` live in `crate::signals` (Great
1234// Gate P1) — read through `FrameSignals::headroom` / `FrameSignals::gmc_residual`,
1235// memoized so the CABAC-P driver's two consumers (the lme motion term and the
1236// me_wide coherence gate) share ONE computation per frame.
1237
1238/// Adds the mb-tree per-MB QP offset (TEMPORAL AQ — [`crate::mbtree`]) to the
1239/// spatial-AQ `aq_qp` map in place. An empty `qpo` (mb-tree off) or a length
1240/// mismatch is a no-op → byte-identical. Shared by the CAVLC and CABAC slice paths.
1241fn apply_mbtree_qpo(aq_qp: &mut [u8], qpo: &[i32]) {
1242 if qpo.len() == aq_qp.len() {
1243 for (q, &o) in aq_qp.iter_mut().zip(qpo) {
1244 *q = (*q as i32 + o).clamp(0, 51) as u8;
1245 }
1246 }
1247}
1248
1249/// IMPLICIT bi-prediction weights `(w0, w1)` from POC distances (spec §8.4.2.3.2,
1250/// `weighted_bipred_idc == 2`), IDENTICAL to the decoder's `implicit_weights`. The
1251/// closer anchor gets more weight; an equidistant B (`bframes == 1`) yields 32:32,
1252/// i.e. the plain average. `(32, 32)` fallback for the degenerate/out-of-range cases
1253/// the decoder also averages (no long-term refs here).
1254fn implicit_bi_weights(cur_poc: i32, l0_poc: i32, l1_poc: i32) -> (i32, i32) {
1255 let td = (l1_poc - l0_poc).clamp(-128, 127);
1256 let tb = (cur_poc - l0_poc).clamp(-128, 127);
1257 if td == 0 {
1258 return (32, 32);
1259 }
1260 // `(16384 + |td|/2) / td` for every clamped td, tabulated at compile time —
1261 // the one variable integer divide left in the B coding path (`td == 0` is
1262 // guarded above; its slot is unused). Same quotients: BIT-IDENTICAL.
1263 const TX: [i32; 256] = {
1264 let mut t = [0i32; 256];
1265 let mut i = 0usize;
1266 while i < 256 {
1267 let td = i as i32 - 128;
1268 if td != 0 {
1269 t[i] = (16384 + (if td < 0 { -td } else { td }) / 2) / td;
1270 }
1271 i += 1;
1272 }
1273 t
1274 };
1275 let tx = TX[(td + 128) as usize];
1276 let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
1277 let w1 = dsf >> 2;
1278 if !(-64..=128).contains(&w1) {
1279 return (32, 32);
1280 }
1281 (64 - w1, w1)
1282}
1283
1284/// Bi-prediction blend of two motion-compensated samples `p` (List-0) and `q`
1285/// (List-1) under weights `(w0, w1)` — the decoder's `b_mc` blend. `(32, 32)` is the
1286/// plain `(p+q+1)>>1` average.
1287#[inline(always)]
1288fn bi_blend(p: i32, q: i32, w: (i32, i32)) -> u8 {
1289 ((p * w.0 + q * w.1 + 32) >> 6).clamp(0, 255) as u8
1290}
1291
1292/// Zig-zag scan of a raster i16 4×4 block into scan-order i32 — the fused-path
1293/// twin of `scan_4x4_dcac(&q_blocks[..])`, reading quantized levels straight from
1294/// the hot i16 DCT buffer. Byte-identical: the i16→i32 widening of a quant level
1295/// is exact (levels always fit i16, being the input to the i16 idct kernel).
1296#[cfg(accel)]
1297#[inline]
1298fn scan_4x4_dcac_i16(d: &[i16]) -> [i32; 16] {
1299 [
1300 d[0] as i32, d[1] as i32, d[4] as i32, d[8] as i32, d[5] as i32, d[2] as i32,
1301 d[3] as i32, d[6] as i32, d[9] as i32, d[12] as i32, d[13] as i32, d[10] as i32,
1302 d[7] as i32, d[11] as i32, d[14] as i32, d[15] as i32,
1303 ]
1304}
1305
1306
1307/// Per-frame intra encoder state: reconstructed planes (coded size) and the
1308/// per-4×4-block non-zero-coefficient counts used for CAVLC context.
1309pub struct FrameEncoder {
1310 mb_w: usize,
1311 mb_h: usize,
1312 qp: u8, // the CURRENT macroblock's target QPy (AQ varies it per MB)
1313 qpc: u8, // chroma QP for `qp`
1314 /// Running QPy of the last macroblock that coded an `mb_qp_delta` (spec QPY_PREV).
1315 /// `mb_qp_delta = qp − cur_qp`; a skip / cbp==0 MB codes no delta and inherits it.
1316 cur_qp: u8,
1317 /// Implicit bi-prediction weights `(w0, w1)` for the current B-frame (from its
1318 /// L0/L1 anchor POC distances). `(32, 32)` = plain average (P/I frames, `bframes
1319 /// == 1`); unequal for `bframes > 1`.
1320 bi_w: (i32, i32),
1321 cw: usize, // coded luma width
1322 ccw: usize, // coded chroma width
1323 // 16-byte aligned (the openh264 deblock/MC/intra asm load aligned row chunks).
1324 rec_y: AlignedBytes,
1325 rec_u: AlignedBytes,
1326 rec_v: AlignedBytes,
1327 nnz_y: Vec<u8>, // (mb_w*4) x (mb_h*4)
1328 nnz_c: [Vec<u8>; 2], // each (mb_w*2) x (mb_h*2)
1329 modes_y: Vec<u8>, // intra4x4 mode per 4×4 block (2=DC for I_16x16 blocks)
1330 coded_y: Vec<bool>, // whether each 4×4 block is reconstructed (top-right avail)
1331 mv_y: Vec<(i32, i32)>, // motion vector per 4×4 block (quarter-pel) — List-0
1332 inter_y: Vec<bool>, // whether each 4×4 block is inter-coded
1333 ref_idx_y: Vec<i32>, // reference index per 4×4 block (-1 = intra/uncoded) — List-0
1334 // B-slice List-1 motion field (empty for P/I). B_L1/B_Bi commit here so a later
1335 // partition's List-1 median predictor sees it, mirroring the decoder's
1336 // `mv_neighbors_list(.., 1)` over `mv1`/`ref_idx1`.
1337 mv1_y: Vec<(i32, i32)>,
1338 ref_idx1_y: Vec<i32>,
1339 idz: i64, // intra dead-zone divisor: 2 for all-intra, 3 when frames reference each other
1340 rdoq_strength: f64, // CABAC trellis (RDOQ) strength; 0 = off (hard quantize, CAVLC path)
1341 // Explicit P weighted prediction (x264-parity weightp): per-reference LUMA
1342 // (weight, offset) at denom 6 for THIS slice; empty = off/identity. Set by
1343 // the P slice coders from the header's own table, applied post-MC at every
1344 // P prediction build — the same integer form, in the same place, as the
1345 // decoder's `weight_partition`, or decode(encode(x)) drifts on fades.
1346 wp: Vec<(i32, i32)>,
1347 transform_8x8: bool, // High-profile 8x8 transform enabled (transform_8x8_mode_flag)
1348 sub8x8: bool, // P_8x8 sub-partition motion (four 8x8 MVs per MB)
1349 me_wide: bool, // adaptive wide ME grid search rescue (diamond stalls on flat surfaces)
1350 /// Track-B B2 for THIS frame: SAD-domain full-pel phase. Set at construction
1351 /// (force mode), or per frame by the `b2_mgain` dispatcher (mode 1).
1352 sadfp: bool,
1353 /// H-24 mv-cost SHAPE routing for THIS frame (mv_smooth mode 1), set by the
1354 /// same `b2_mgain` probe. Per-frame state, NOT a global: the GOP-parallel
1355 /// encode runs frames concurrently and a global store races across workers.
1356 mv_smooth: bool,
1357 /// H-13: search partition splits this frame (routed off on near-static frames).
1358 do_splits: bool,
1359 me_wide_var: u64, // per-pixel source variance below which a block is "flat"
1360 me_rescue: i64, // per-pixel residual SATD (on a flat block) that flags a diamond stall
1361 me_wide_coh: f64, // gate me_wide off when the frame's global-MC residual is below this (pure pan)
1362 me_range: i32, // rescue grid half-range in px (16 = ±16; wider reaches FAST motion the diamond misses)
1363 me_fast: bool, // also fire the rescue on HIGH-VARIANCE high-residual blocks (fast-motion stalls, not just flat)
1364 // ONLINE per-frame rescue-payoff gate (adaptive; WITHIN-frame so it stays
1365 // deterministic under the frame-parallel encode). Run the real rescue on the
1366 // first `me_learn` stalls of a frame, count how many the fine grid improves by
1367 // ≥6.25%, and if that fraction is below `me_payoff_pct`% disable the rescue for
1368 // the rest of the frame. This separates genuine diamond stalls (tsrc/zoom fine
1369 // grid improves ~33% of fires) from IRREDUCIBLE residual (rotation/fractal ~5-8%)
1370 // using the ACTUAL neighbour-seeded diamond — the only faithful signal (a cheap
1371 // SAD proxy from (0,0) inverts it: rot reads as highest-payoff). Frame-level, so
1372 // no per-block selection concentrates the B-direct-poisoning spurious MVs.
1373 me_learn: u32,
1374 me_payoff_pct: u32,
1375 /// U1 online sub-pel dispatcher (within-frame, so it stays deterministic under
1376 /// GOP-parallel encode). For the first `SP_LEARN` refinements of a frame we run
1377 /// the full 8-point+iterate pattern and accumulate how much of the total gain the
1378 /// FIRST ring captured; once the window fills, a frame whose gain is concentrated
1379 /// in ring 1 switches to the single-pass pattern for the rest of the frame.
1380 ///
1381 /// Harvested justification: ring-1 captures 63.7% of the gain on foreman (which
1382 /// loses +2.34% BD to a blanket single-pass) against 69.9–71.9% on bus/mobile
1383 /// (which lose only +0.30/+0.74% and gain 1.08–1.31×). The fraction separates the
1384 /// content that can afford the cut from the content that cannot.
1385 sp_single_pass: bool,
1386 /// U5-struct: when set, `motion_search` returns its FULL-PEL winner and skips
1387 /// sub-pel refinement entirely. The partition driver uses this to search all
1388 /// candidate shapes cheaply, pick one, and refine ONLY the winner's sub-blocks.
1389 /// Measured ceiling: 3.4–6.4× less sub-pel work (the losing shapes' refinements
1390 /// are pure waste), i.e. ~1.42× whole-encode at 44% sub-pel share.
1391 sp_defer: std::cell::Cell<bool>,
1392 sp_learn_n: std::cell::Cell<u32>,
1393 sp_ring1: std::cell::Cell<i64>,
1394 sp_total: std::cell::Cell<i64>,
1395 sp_1pass: std::cell::Cell<bool>,
1396 resc_n: std::cell::Cell<u32>, // stalls the fine grid ran on this frame (learning phase)
1397 resc_big: std::cell::Cell<u32>, // of those, how many it improved ≥6.25%
1398 resc_off: std::cell::Cell<bool>, // rescue disabled for the rest of this frame
1399 /// May a macroblock CHOOSE the 8x8 transform this frame?
1400 ///
1401 /// Deliberately separate from `transform_8x8`, which means "the PPS advertises
1402 /// transform_8x8_mode_flag, so the flag must be WRITTEN". Those are not the same
1403 /// thing, and conflating them is a desync: clearing `transform_8x8` per frame
1404 /// suppresses a bit the decoder is still reading. Presence is the PPS's business;
1405 /// value is the per-frame gate's. (Third instance of that confusion in one day —
1406 /// the sub-8x8 and B_Direct flag bugs were the other two.)
1407 t8_pick: bool,
1408 /// Margin, in lambda units, that the I_8x8 candidate must BEAT its rivals by.
1409 /// See `i8_margin()`.
1410 i8_margin: f64,
1411 /// True while planning an INTRA macroblock inside a P/B slice. Those blocks are
1412 /// a different population from intra MBs in an I slice -- they are the ones inter
1413 /// prediction FAILED on (occlusion, fine detail), which is exactly where 4x4
1414 /// adapts better than 8x8. Set per call site, never inferred.
1415 intra_in_p: bool,
1416 // Inter 8x8-transform dispatch: 0 = off (intra 8x8 only), 1 = always-RD.
1417 // There is no mode 2: the enum used to document "2 = content-adaptive" and the
1418 // only test of this field is `!= 0`, so mode 2 was never anything but mode 1 --
1419 // confirmed by a BD table that came back identical in all 21 cells.
1420 inter8x8: u8,
1421 inter8_pen: i64, // extra rate charge (nonzero-equiv) on the inter 8x8 candidate
1422 fast: bool, // Preset::Fast — SATD mode decision (no RDO), 16×16/I_16x16 only
1423 #[cfg_attr(not(accel), allow(dead_code))] // read in accel-gated blocks
1424 skip_accel_check: bool, // A/B knob: whole-MB psadbw gate in the P_Skip free-check
1425 coded_path_v2: bool, // A/B knob: route inter coding through encode_inter_mb_v2
1426 tune_lambda_scale: f64, // tuning knob: scale on the RD λ (1.0 = standard)
1427 tune_intra_penalty: f64,
1428 satd_q: f64, // adaptive: fraction of high-variance MBs routed to SATD cost
1429 subpel_force: bool, // force sub-pel refinement even in the fast preset
1430 me_snap: bool, // snap the diamond centre to integer-pel (see config)
1431 me_subpel_iter: bool, // walk the sub-pel refine to convergence
1432 greedy_skip: bool, // quality preset's SAD-thresholded P_Skip (PredictSadSkip)
1433 greedy_min_free: u32, // online free-skip % gating greedy_skip on this frame
1434 rd_skip: bool, // decide P_Skip by J = SSD + lambda*bits, not exact-zero residual
1435 rd_skip_min_free: u32, // online free-skip % gating rd_skip on this frame
1436 rd_skip_fast_t: f64, // skip-gate on SSD(skip)/lambda; <= 0 prices every candidate
1437 satd_var_thresh: i64, // per-frame variance threshold for the routing (set in a pre-pass)
1438 aq_strength: f64, // adaptive quantization: per-MB QP modulation strength (0 = off)
1439 mb_use_satd: bool, // per-MB: this MB uses the SATD cost this decision
1440 // Per-MB luma nnz prediction cache (openh264 scan8 style): a padded 5×5 grid,
1441 // block (lbx,lby) at (lby+1)*5+(lbx+1); row 0 = top neighbours, col 0 = left.
1442 // Unavailable edges hold the sentinel 0x80, so the nnz predict is branchless.
1443 nnz_l_cache: [u8; 25],
1444 // Same, per chroma plane: a padded 3×3 grid for the 2×2 chroma blocks.
1445 nnz_c_cache: [[u8; 9]; 2],
1446 // openh264 predicted-SAD skip apparatus (per MB, mb_w×mb_h): the P_Skip
1447 // prediction's luma SAD, and whether the MB was actually skipped. The greedy
1448 // skip threshold for an MB is the median of its skip *neighbours'* skip SADs
1449 // (`PredictSadSkip`) — so skip propagates only from already-skip regions
1450 // (seeded by free skips) and self-limits, instead of a fixed bound that drifts.
1451 mb_skip_sad: Vec<u32>,
1452 mb_was_skip: Vec<bool>,
1453}
1454
1455/// A chosen inter coding for a macroblock: `mb_type` and, per partition, the
1456/// reference index and motion vector.
1457type InterChoice = (u8, Vec<(i32, (i32, i32))>);
1458
1459
1460
1461/// EXTERNAL MV SCORING (`RFF_MV_CMP=1`). Holds another encoder's motion field
1462/// (per frame, 4x4-block raster) so our own coder can price ITS vectors against
1463/// ours under REAL coded bits instead of SATD — the only way to tell a bad search
1464/// from a bad cost function.
1465pub static EXT_MV: std::sync::Mutex<Vec<Vec<(i32, i32)>>> = std::sync::Mutex::new(Vec::new());
1466/// [n, our bits, ext bits, our SSD, ext SSD, ext won on J, MVs differing]
1467pub static MVCMP: [std::sync::atomic::AtomicU64; 7] = {
1468 const Z: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1469 [Z; 7]
1470};
1471pub static MVCMP_FRAME: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
1472/// Replace our chosen vector with the external field's, for EVERY macroblock where
1473/// that field used a single 16x16 partition. Transplanting one vector in isolation
1474/// is meaningless — `mvd` is coded against the NEIGHBOURS' vectors, so a lone
1475/// foreign vector prices against the wrong predictor. Only a whole coherent field
1476/// can be compared fairly.
1477fn mv_force_on() -> bool {
1478 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1479 *ON.get_or_init(|| std::env::var("RFF_MV_FORCE").map_or(false, |v| v != "0"))
1480}
1481fn mv_cmp_on() -> bool {
1482 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1483 *ON.get_or_init(|| std::env::var("RFF_MV_CMP").map_or(false, |v| v != "0"))
1484}
1485
1486/// [n, sum our cost, sum oracle cost, blocks the oracle beat us on, cost() evals]
1487pub static ME_PROBE: [std::sync::atomic::AtomicU64; 7] = {
1488 const Z: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1489 [Z; 7]
1490};
1491
1492/// Cached — an `env::var` inside the ME loop inflated it 4x when probed naively.
1493fn me_oracle_on() -> bool {
1494 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1495 *ON.get_or_init(|| std::env::var("RFF_ME_ORACLE").map_or(false, |v| v != "0"))
1496}
1497
1498/// A snapshot of one macroblock's per-block grids and reconstruction region,
1499/// used to roll back a trial encode during RD mode decision.
1500///
1501/// Every field is a `Vec`, so building one from scratch is ten heap allocations.
1502/// The RD skip decision snapshots on EVERY candidate macroblock, which made that
1503/// allocation traffic the decision's dominant cost — hence
1504/// [`save_mb_into`](FrameEncoder::save_mb_into), which refills a reused buffer.
1505#[derive(Default)]
1506struct MbState {
1507 rec_y: Vec<u8>,
1508 rec_u: Vec<u8>,
1509 rec_v: Vec<u8>,
1510 nnz_y: Vec<u8>,
1511 nnz_c: [Vec<u8>; 2],
1512 mv_y: Vec<(i32, i32)>,
1513 inter_y: Vec<bool>,
1514 ref_idx_y: Vec<i32>,
1515 coded_y: Vec<bool>,
1516 modes_y: Vec<u8>,
1517 /// QPY_PREV. `qp_delta()` MUTATES this as a side effect of coding
1518 /// `mb_qp_delta`, so a trial encode advances it; without restoring it the
1519 /// real encode then codes its delta against the wrong predecessor and the
1520 /// decoder's QP diverges from the encoder's — a silent stream corruption,
1521 /// not a quality tweak.
1522 cur_qp: u8,
1523}
1524
1525/// Edge-clamped, coded-size source planes (luma, Cb, Cr).
1526/// Fast-preset pruned I4x4 mode search ({MPM, DC, V, H} instead of all 9 — the
1527/// x264-ultrafast-style candidate set). DEFAULT ON for the fast preset (gated:
1528/// +0.5% size at +0.02 dB on all-intra, +17% all-intra speed); RUSTY_FAST_INTRA=0
1529/// restores the exhaustive 9-mode search (the pre-flip bitstream).
1530fn fast_intra_enabled() -> bool {
1531 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1532 *ON.get_or_init(|| std::env::var("RUSTY_FAST_INTRA").map_or(true, |v| v != "0"))
1533}
1534
1535fn coded_source<'a>(
1536 cfg: &EncoderConfig,
1537 frame: &'a YuvFrame,
1538) -> (
1539 std::borrow::Cow<'a, [u8]>,
1540 std::borrow::Cow<'a, [u8]>,
1541 std::borrow::Cow<'a, [u8]>,
1542) {
1543 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncSource);
1544 let cw = cfg.mb_width() * 16;
1545 let ch = cfg.mb_height() * 16;
1546 // MB-aligned frame (the common case): the clamp is the identity, and the
1547 // drivers only READ the planes — borrow them. This was THREE full-plane
1548 // clones per slice (11.11); Cow keeps the padded path allocation intact.
1549 if frame.width == cw && frame.height == ch {
1550 return (
1551 std::borrow::Cow::Borrowed(&frame.y),
1552 std::borrow::Cow::Borrowed(&frame.u),
1553 std::borrow::Cow::Borrowed(&frame.v),
1554 );
1555 }
1556 let y = clamp_plane(&frame.y, frame.width, frame.height, cw, ch);
1557 let u = clamp_plane(&frame.u, frame.chroma_width(), frame.chroma_height(), cw / 2, ch / 2);
1558 let v = clamp_plane(&frame.v, frame.chroma_width(), frame.chroma_height(), cw / 2, ch / 2);
1559 (
1560 std::borrow::Cow::Owned(y),
1561 std::borrow::Cow::Owned(u),
1562 std::borrow::Cow::Owned(v),
1563 )
1564}
1565
1566/// 11.11: per-THREAD recycled slice scratch — the encoder twin of the decoder
1567/// GridPool's `sc_*` fields (D13's finding, never ported to the encoder until
1568/// now). GOP-parallel workers each hold their own copy, so reuse stays
1569/// deterministic. Every buffer is reset to its fresh-allocation contents
1570/// before use — byte-identical by construction.
1571mod enc_scratch {
1572 use std::cell::RefCell;
1573 thread_local! {
1574 static CS: RefCell<Option<super::CabacState>> = const { RefCell::new(None) };
1575 static QPY: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
1576 static REFID: RefCell<Vec<i32>> = const { RefCell::new(Vec::new()) };
1577 // The b-pyramid ref-B deblock tail needs BOTH lists' index grids alive
1578 // at once, so List-1 gets its own recycled slot.
1579 static REFID1: RefCell<Vec<i32>> = const { RefCell::new(Vec::new()) };
1580 static PAYLOAD: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
1581 static BITS: RefCell<super::BitWriter> = RefCell::new(super::BitWriter::new());
1582 static SNAP_A: RefCell<super::MbState> = RefCell::new(super::MbState::default());
1583 static SNAP_B: RefCell<super::MbState> = RefCell::new(super::MbState::default());
1584 }
1585 pub(super) fn take_cs() -> Option<super::CabacState> {
1586 CS.with(|c| c.borrow_mut().take())
1587 }
1588 pub(super) fn put_cs(v: super::CabacState) {
1589 CS.with(|c| *c.borrow_mut() = Some(v));
1590 }
1591 pub(super) fn take_qpy() -> Vec<u8> {
1592 QPY.with(|c| std::mem::take(&mut *c.borrow_mut()))
1593 }
1594 pub(super) fn put_qpy(v: Vec<u8>) {
1595 QPY.with(|c| *c.borrow_mut() = v);
1596 }
1597 pub(super) fn take_refid() -> Vec<i32> {
1598 REFID.with(|c| std::mem::take(&mut *c.borrow_mut()))
1599 }
1600 pub(super) fn put_refid(v: Vec<i32>) {
1601 REFID.with(|c| *c.borrow_mut() = v);
1602 }
1603 pub(super) fn take_refid1() -> Vec<i32> {
1604 REFID1.with(|c| std::mem::take(&mut *c.borrow_mut()))
1605 }
1606 pub(super) fn put_refid1(v: Vec<i32>) {
1607 REFID1.with(|c| *c.borrow_mut() = v);
1608 }
1609 pub(super) fn take_payload() -> Vec<u8> {
1610 PAYLOAD.with(|c| std::mem::take(&mut *c.borrow_mut()))
1611 }
1612 pub(super) fn put_payload(v: Vec<u8>) {
1613 PAYLOAD.with(|c| *c.borrow_mut() = v);
1614 }
1615 pub(super) fn take_bits() -> super::BitWriter {
1616 BITS.with(|c| std::mem::take(&mut *c.borrow_mut()))
1617 }
1618 pub(super) fn put_bits(v: super::BitWriter) {
1619 BITS.with(|c| *c.borrow_mut() = v);
1620 }
1621 // E11 dig (11.17): `save_mb()` built a DEFAULT MbState — ten empty Vecs
1622 // whose pushes then heap-allocate — per RD TRIAL. Two recycled slots: A for
1623 // the driver sites (their lifetimes are sequential), B for `trial_intra`,
1624 // which nests inside a shape-RD trial that still holds A.
1625 pub(super) fn take_snap_a() -> super::MbState {
1626 SNAP_A.with(|c| std::mem::take(&mut *c.borrow_mut()))
1627 }
1628 pub(super) fn put_snap_a(v: super::MbState) {
1629 SNAP_A.with(|c| *c.borrow_mut() = v);
1630 }
1631 pub(super) fn take_snap_b() -> super::MbState {
1632 SNAP_B.with(|c| std::mem::take(&mut *c.borrow_mut()))
1633 }
1634 pub(super) fn put_snap_b(v: super::MbState) {
1635 SNAP_B.with(|c| *c.borrow_mut() = v);
1636 }
1637}
1638
1639/// Edge-extends `plane` from `w`×`h` to the coded `ow`×`oh`, replicating the last
1640/// row/column — the source form the MB grid needs.
1641///
1642/// Row-wise, because the per-pixel form is O(pixels) of scalar `min`+multiply and is
1643/// the DOMINANT cost of `enc-source-copy`: every frame whose height is not a multiple
1644/// of 16 takes this path, which includes all 1080p content (1080/16 = 67.5 → coded
1645/// height 1088). The stage measured 579 ms over the corpus while the three plane
1646/// clones on the MB-aligned fast path account for only ~135 ms of it.
1647///
1648/// Byte-identical to the per-pixel form (`clamp_plane_per_pixel`, kept as the test
1649/// oracle): `x.min(w-1)` is the identity below `w` and pins to the last column above
1650/// it, so a row is a `copy_from_slice` plus a `fill`; `y.min(h-1)` makes the
1651/// overhanging rows copies of the final row. Both lower to memcpy/memset.
1652fn clamp_plane(plane: &[u8], w: usize, h: usize, ow: usize, oh: usize) -> Vec<u8> {
1653 let mut out = vec![0u8; ow * oh];
1654 for y in 0..oh {
1655 let sy = y.min(h - 1);
1656 let src = &plane[sy * w..sy * w + w];
1657 let dst = &mut out[y * ow..y * ow + ow];
1658 if ow <= w {
1659 dst.copy_from_slice(&src[..ow]);
1660 } else {
1661 dst[..w].copy_from_slice(src);
1662 dst[w..].fill(src[w - 1]);
1663 }
1664 }
1665 out
1666}
1667
1668/// The original per-pixel edge extension — kept as the correctness oracle for
1669/// [`clamp_plane`], per the scalar-twin discipline.
1670#[cfg(test)]
1671fn clamp_plane_per_pixel(plane: &[u8], w: usize, h: usize, ow: usize, oh: usize) -> Vec<u8> {
1672 let mut out = vec![0u8; ow * oh];
1673 for y in 0..oh {
1674 for x in 0..ow {
1675 out[y * ow + x] = plane[y.min(h - 1) * w + x.min(w - 1)];
1676 }
1677 }
1678 out
1679}
1680
1681#[cfg(test)]
1682mod source_tests {
1683 use super::*;
1684
1685 /// The ref_bits prune in `best_part` breaks out of the reference loop the
1686 /// moment `λ·ref_bits(r)` alone reaches the incumbent cost — which is only
1687 /// sound if `ref_bits` never DECREASES with `r`. Pinned here over every
1688 /// active-reference count the config admits.
1689 #[test]
1690 fn ref_bits_is_monotone() {
1691 for n in 1..=16usize {
1692 for r in 1..n {
1693 assert!(
1694 ref_bits(r, n) >= ref_bits(r - 1, n),
1695 "ref_bits({r},{n}) < ref_bits({},{n})",
1696 r - 1
1697 );
1698 }
1699 }
1700 // And the coding facts the model encodes: no ref_idx with one ref,
1701 // one flag bit with two.
1702 assert_eq!(ref_bits(0, 1), 0);
1703 assert_eq!(ref_bits(0, 2), 1);
1704 assert_eq!(ref_bits(1, 2), 1);
1705 }
1706
1707 /// Bit-exact golden for the AQ map (site 7's function): flat top row
1708 /// (zero variance → the log2 shortcut arm), textured rest. Pins the whole
1709 /// pipeline — log_vars through the per-MB round loop and the rate
1710 /// compensation — at two strengths.
1711 #[test]
1712 fn aq_qp_map_golden() {
1713 let (cw, ch) = (160usize, 112usize);
1714 let (mb_w, mb_h) = (cw / 16, ch / 16);
1715 // Texture amplitude grows per MB COLUMN (variance gradient → the dqp
1716 // map varies), with exactly ONE flat MB (top-left) so the zero-variance
1717 // arm runs but the lv SPREAD stays inside AQ's active band — a whole
1718 // flat region reads as "pathological synthetic" and latches AQ off,
1719 // which is precisely the degenerate map the assert below refuses.
1720 let mut sy = vec![64u8; cw * ch];
1721 for j in 0..ch {
1722 for i in 0..cw {
1723 let k = 4 + (i / 16) * 12;
1724 sy[j * cw + i] = 80 + ((i * 7 + j * 13) % k) as u8;
1725 }
1726 }
1727 for j in 0..16 {
1728 for i in 0..16 {
1729 sy[j * cw + i] = 64;
1730 }
1731 }
1732 let mut h = 0xcbf2_9ce4_8422_2325u64;
1733 crate::fastmath::TEST_POLYTIER.with(|c| c.set(Some(false)));
1734 for strength in [1.0f64, 0.5] {
1735 let sig = signals::FrameSignals::new(&sy, cw, mb_w, mb_h, None);
1736 assert!(sig.mb_vars().iter().any(|&v| v == 0), "no zero-variance MB");
1737 let map = aq_qp_map(&sig, 26, strength);
1738 // The gate must prove the tool ran: a veto's early-out would pin
1739 // a constant map, not the arithmetic.
1740 assert!(map.iter().any(|&q| q != 26), "AQ map degenerate at {strength}");
1741 // Round 10 decision-identity: the whole AQ pipeline under the
1742 // POLY arm (poly log2 + ties-even round) must produce the
1743 // IDENTICAL u8 map — the integer decisions have fat margins and
1744 // exact .5 ties are measure-zero, and this asserts it rather
1745 // than argues it.
1746 crate::fastmath::TEST_POLYTIER.with(|c| c.set(Some(true)));
1747 let sigp = signals::FrameSignals::new(&sy, cw, mb_w, mb_h, None);
1748 assert_eq!(aq_qp_map(&sigp, 26, strength), map, "poly arm map differs at {strength}");
1749 crate::fastmath::TEST_POLYTIER.with(|c| c.set(Some(false)));
1750 for &q in &map {
1751 h ^= q as u64;
1752 h = h.wrapping_mul(0x100_0000_01b3);
1753 }
1754 }
1755 crate::fastmath::TEST_POLYTIER.with(|c| c.set(None));
1756 assert_eq!(h, 8641818917227936570, "aq_qp_map golden");
1757 }
1758
1759 #[test]
1760 fn clamp_plane_matches_per_pixel_oracle() {
1761 let mut s: u32 = 0xDEAD_BEEF;
1762 let mut rnd = || {
1763 s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1764 (s >> 24) as u8
1765 };
1766 // Real coded geometries plus adversarial ones: width-only overhang,
1767 // height-only overhang (the 1080p case), both, and neither.
1768 let cases = [
1769 (1920usize, 1080usize, 1920usize, 1088usize), // 1080p luma
1770 (960, 540, 960, 544), // 1080p chroma
1771 (352, 288, 352, 288), // exactly aligned
1772 (100, 100, 112, 112), // both axes overhang
1773 (37, 5, 48, 16), // tiny + ragged
1774 (16, 1, 16, 16), // single source row
1775 (1, 1, 16, 16), // single sample
1776 ];
1777 for (w, h, ow, oh) in cases {
1778 let plane: Vec<u8> = (0..w * h).map(|_| rnd()).collect();
1779 assert_eq!(
1780 clamp_plane(&plane, w, h, ow, oh),
1781 clamp_plane_per_pixel(&plane, w, h, ow, oh),
1782 "clamp mismatch for {w}x{h} -> {ow}x{oh}"
1783 );
1784 }
1785 }
1786}
1787
1788/// The constructor's fourteen env knobs, parsed ONCE per process (E20).
1789/// Fields mirror the exact expressions they replaced; `Option` where a cfg or
1790/// preset fallback applies, final value where the default is cfg-independent.
1791struct CtorEnv {
1792 sub8x8: Option<bool>,
1793 me_wide: Option<bool>,
1794 me_wide_var: u64,
1795 me_rescue: i64,
1796 me_wide_coh: f64,
1797 me_range: i32,
1798 me_fast: bool,
1799 me_learn: u32,
1800 me_payoff_pct: u32,
1801 defer_subpel: bool,
1802 inter8: u8,
1803 inter8_pen: i64,
1804 subpel: Option<bool>,
1805 rdskip_minfree: Option<u32>,
1806}
1807fn ctor_env() -> &'static CtorEnv {
1808 static E: std::sync::OnceLock<CtorEnv> = std::sync::OnceLock::new();
1809 E.get_or_init(|| CtorEnv {
1810 sub8x8: std::env::var("RFF_SUB8X8").ok().map(|s| s == "1"),
1811 me_wide: std::env::var("RFF_ME_WIDE").ok().map(|s| s == "1"),
1812 me_wide_var: std::env::var("RFF_ME_WIDE_VAR").ok().and_then(|s| s.parse().ok()).unwrap_or(800),
1813 me_rescue: std::env::var("RFF_ME_RESCUE").ok().and_then(|s| s.parse().ok()).unwrap_or(3),
1814 me_wide_coh: std::env::var("RFF_ME_COH").ok().and_then(|s| s.parse().ok()).unwrap_or(4.0),
1815 me_range: std::env::var("RFF_ME_RANGE").ok().and_then(|s| s.parse().ok()).unwrap_or(24),
1816 me_fast: std::env::var("RFF_ME_FASTMO").map(|s| s != "0").unwrap_or(true),
1817 me_learn: std::env::var("RFF_ME_LEARN").ok().and_then(|s| s.parse().ok()).unwrap_or(40),
1818 me_payoff_pct: std::env::var("RFF_ME_PAYOFF").ok().and_then(|s| s.parse().ok()).unwrap_or(15),
1819 defer_subpel: std::env::var("RFF_DEFER_SUBPEL").map(|v| v != "0").unwrap_or(false),
1820 inter8: std::env::var("RFF_INTER8").ok().and_then(|s| s.parse().ok()).unwrap_or(0),
1821 inter8_pen: std::env::var("RFF_INTER8_PEN").ok().and_then(|s| s.parse().ok()).unwrap_or(8),
1822 subpel: std::env::var("RFF_SUBPEL").ok().map(|v| v != "1"),
1823 rdskip_minfree: std::env::var("RFF_RDSKIP_MINFREE").ok().and_then(|v| v.parse().ok()),
1824 })
1825}
1826
1827impl FrameEncoder {
1828 fn new(cfg: &EncoderConfig) -> Self {
1829 // E20 (inline-execution.md 11.10): this constructor ran FOURTEEN raw
1830 // `std::env::var` reads - an OS query + String alloc + parse each - PER
1831 // SLICE, while every other knob in the tree caches its env read once.
1832 // All fourteen are process-constant by the same contract; `ctor_env()`
1833 // is their one read.
1834 let ke = ctor_env();
1835 let (mb_w, mb_h) = (cfg.mb_width(), cfg.mb_height());
1836 let (cw, ch) = (mb_w * 16, mb_h * 16);
1837 let (ccw, cch) = (cw / 2, ch / 2);
1838 Self {
1839 mb_w,
1840 mb_h,
1841 qp: cfg.qp,
1842 qpc: chroma_qp(cfg.qp),
1843 cur_qp: cfg.qp,
1844 bi_w: (32, 32),
1845 cw,
1846 ccw,
1847 rec_y: AlignedBytes::zeroed(cw * ch),
1848 rec_u: AlignedBytes::zeroed(ccw * cch),
1849 rec_v: AlignedBytes::zeroed(ccw * cch),
1850 nnz_y: vec![0; (mb_w * 4) * (mb_h * 4)],
1851 nnz_c: [vec![0; (mb_w * 2) * (mb_h * 2)], vec![0; (mb_w * 2) * (mb_h * 2)]],
1852 modes_y: vec![2; (mb_w * 4) * (mb_h * 4)],
1853 coded_y: vec![false; (mb_w * 4) * (mb_h * 4)],
1854 mv_y: vec![(0, 0); (mb_w * 4) * (mb_h * 4)],
1855 inter_y: vec![false; (mb_w * 4) * (mb_h * 4)],
1856 ref_idx_y: vec![-1; (mb_w * 4) * (mb_h * 4)],
1857 mv1_y: vec![(0, 0); (mb_w * 4) * (mb_h * 4)],
1858 ref_idx1_y: vec![-1; (mb_w * 4) * (mb_h * 4)],
1859 // All-intra (no inter references) tolerates the larger dead-zone; in
1860 // an I+P stream the IDR is a reference, so keep the standard offset.
1861 idz: if cfg.gop_size <= 1 { 2 } else { 3 },
1862 rdoq_strength: 0.0, // set >0 only in the CABAC slice coders
1863 wp: Vec::new(),
1864 transform_8x8: cfg.transform_8x8,
1865 // sub8x8 stays OPT-IN: the four P_8x8 sub-MVs feed the B-frames'
1866 // spatial-direct predictor, so on DIVERGENT motion (rotation/zoom/mixed)
1867 // it regresses with B-frames (mixed +0.24%, rot +0.42%, zoom +0.40%) —
1868 // a global effect its local RD gate can't see, and no clean dispatch
1869 // signal separates it yet (unlike me_wide's pure-pan coherence gate).
1870 // DEFAULT-ON for Quality (net real-content win; a 6-channel discovery
1871 // harvest proved no cheap gate beats always-on). Quality-only (Fast never
1872 // runs it). env RFF_SUB8X8 (0/1) > cfg.sub_8x8 (Some) > preset default.
1873 sub8x8: ke.sub8x8
1874 .or(cfg.sub_8x8)
1875 .unwrap_or(cfg.preset == crate::config::Preset::Quality),
1876 // me_wide is DEFAULT-ON for the Quality preset. VALIDATED 2026-07-27 on the
1877 // full 20-clip `video-tests` Derf corpus (4-QP BD-rate, PSNR+SSIM, anchor =
1878 // me_wide ON): **mean +0.62% BD-PSNR / +0.69% BD-SSIM**, i.e. turning it off
1879 // costs that much. Biggest wins blue_sky +4.70, bus +4.57, football +1.51,
1880 // park_joy +0.91; synthesized boundary content (smooth fast-pan / rotation /
1881 // zoom) reaches +2.6..+6.7%. The static clips (akiyo, FourPeople) sit at
1882 // exactly 0.00 at ~1.0x — the online payoff gate correctly disables it there.
1883 //
1884 // ⚠ UNFINISHED DISPATCH — the per-clip BD SIGN-FLIPS (+4.70 blue_sky ..
1885 // -1.08 foreman_qcif), and the cost when it fires is 1.0-5.1x. Worst value:
1886 // soccer_4cif 1.70x for +0.00, park_joy 5.08x for +0.91. `me_range` is NOT
1887 // the separating axis — it is a compromise dial (foreman_qcif loses at EVERY
1888 // range 24/16/8/4 = -1.08/-0.55/-0.50/-0.19 while blue_sky wins at every one
1889 // = +4.70/+3.10/+0.73), so shrinking it just trades the win away. The real
1890 // fix is a content signal that predicts the sign; the truth table for it is
1891 // in docs/WHYS-speed-gap.md.
1892 //
1893 // Quality-only (Fast never runs it). Precedence:
1894 // env RFF_ME_WIDE (0/1, for A/B) > cfg.me_wide (Some) > preset default.
1895 sadfp: me_sadfp_mode() == 2,
1896 mv_smooth: false,
1897 do_splits: true,
1898 me_wide: ke.me_wide
1899 .or(cfg.me_wide)
1900 .unwrap_or(cfg.preset == crate::config::Preset::Quality),
1901 me_wide_var: ke.me_wide_var,
1902 me_rescue: ke.me_rescue,
1903 me_wide_coh: ke.me_wide_coh,
1904 me_range: ke.me_range,
1905 me_fast: ke.me_fast,
1906 me_learn: ke.me_learn,
1907 me_payoff_pct: ke.me_payoff_pct,
1908 // U3: `balanced` runs SINGLE-PASS sub-pel. Measured on the 4-QP corpus,
1909 // a single pass captures 95.5–99.4% of the full refinement's BD benefit
1910 // (foreman −38.14 vs −39.94, mobile −49.38 vs −49.66, akiyo −26.10 vs
1911 // −26.43) for 1.03–1.31× less time — a straight Pareto improvement on the
1912 // preset. `RFF_SUBPEL_PAT=0` restores the full walk-to-convergence.
1913 sp_single_pass: cfg.preset == crate::config::Preset::Balanced,
1914 sp_defer: std::cell::Cell::new({
1915 let a = DEFER_SUBPEL.load(std::sync::atomic::Ordering::Relaxed) != 0
1916 || ke.defer_subpel;
1917 // ONLY the Quality preset runs the multi-shape partition driver. On the
1918 // fast/balanced path there is a single 16×16 candidate, so there is no
1919 // losing shape to skip — deferring there does not save the refinement,
1920 // it DELETES it (measured +91..+145% BD before this guard).
1921 a && cfg.preset == crate::config::Preset::Quality
1922 }),
1923 sp_learn_n: std::cell::Cell::new(0),
1924 sp_ring1: std::cell::Cell::new(0),
1925 sp_total: std::cell::Cell::new(0),
1926 sp_1pass: std::cell::Cell::new(false),
1927 resc_n: std::cell::Cell::new(0),
1928 resc_big: std::cell::Cell::new(0),
1929 resc_off: std::cell::Cell::new(false),
1930 // DEFAULT 0 (intra 8x8 only). Inter 8x8 was the dominant regression in
1931 // the 8x8 default measurement: it turned foreman I+P from +0.28% to
1932 // +1.59% and harbour I+P from +0.12% to +1.62% BD-SSIM, the two largest
1933 // losses in the whole matrix, while its wins were <= 0.44%. `RFF_INTER8=1`
1934 // restores the always-RD arm as the comparator.
1935 t8_pick: cfg.transform_8x8,
1936 i8_margin: i8_margin(),
1937 intra_in_p: false,
1938 inter8x8: ke.inter8,
1939 // ~2 bits per 8x8 luma block (×4) of CAVLC-8x8 overhead the level-aware
1940 // rate still under-charges (no native 8x8 entropy model in CAVLC). Keeps
1941 // the per-MB transform RD from over-picking 8x8 on fine-texture MBs where
1942 // it doesn't compact — content-adaptive: only decisively-favorable MBs win.
1943 inter8_pen: ke.inter8_pen,
1944 // Balanced shares Fast's decision path; only sub-pel differs.
1945 //
1946 // `RFF_SUBPEL=1` forces the sub-pel path ON for a non-Quality preset. This
1947 // was the ONE preset-derived field with no override, which is exactly why
1948 // the fast/quality bisection stalled: the other three (sub8x8, me_wide,
1949 // shape-RD) are all byte-identical when toggled on `fast`, so this field
1950 // carries the whole 29% delta by elimination (docs/WHYS-p-frames.md D5d).
1951 // `== Fast`, NOT `!= Quality`. The old form gave Preset::Balanced
1952 // fast = true, i.e. NO sub-pel -- flatly contradicting Balanced's own doc
1953 // ("Fast's decision path plus sub-pel motion refinement, which Fast
1954 // omits"). Combined with a CLI that never parsed "balanced", the whole
1955 // preset was unreachable, which is why a speed harness once adopted it as
1956 // a deliberate NULL ARM.
1957 //
1958 // GRAIN CAVEAT, measured and NOT yet fixed: sub-pel interpolates, and on
1959 // grain it interpolates NOISE -- Balanced is +13.61% BD-SSIM there against
1960 // Fast (Quality is worse still, +18.79%). A per-frame veto on
1961 // `grain_signature()` was attempted at all three slice coders and did NOT
1962 // take: `fe.fast` is consumed before a per-frame mutation lands, so the
1963 // gate must move to FrameEncoder construction or into the ME itself.
1964 // Removed rather than shipped half-applied. Balanced is OPT-IN, so this is
1965 // a documented trade, not a silent regression.
1966 //
1967 // Sub-pel is the single biggest compression lever in the encoder:
1968 // -30.10 (akiyo) / -44.37 (foreman) / -49.52 (mobile) / -34.15 (harbour)
1969 // BD-SSIM against Fast, for 2.50x Fast's CPU where Quality costs 16.24x.
1970 fast: ke.subpel
1971 .unwrap_or(cfg.preset == crate::config::Preset::Fast || SeqFastPath::get()),
1972 skip_accel_check: cfg.tune_skip_accel_check,
1973 coded_path_v2: cfg.coded_path_v2,
1974 aq_strength: cfg.aq_strength,
1975 tune_lambda_scale: cfg.tune_lambda_scale,
1976 tune_intra_penalty: cfg.tune_intra_penalty,
1977 satd_q: cfg.tune_satd_q,
1978 subpel_force: cfg.tune_subpel || cfg.preset == crate::config::Preset::Balanced,
1979 me_snap: cfg.tune_me_snap,
1980 me_subpel_iter: cfg.tune_me_subpel_iter,
1981 greedy_skip: cfg.tune_greedy_skip,
1982 greedy_min_free: cfg.tune_greedy_skip_min_free.unwrap_or(85),
1983 rd_skip: cfg.tune_rd_skip,
1984 rd_skip_fast_t: cfg.tune_rd_skip_fast_t.unwrap_or(0.0),
1985 // DECOUPLED from the preset by env, because welding it to `cfg.preset` is
1986 // what made the sub-pel veto's acceptance test unpassable (D5i): a knob that
1987 // forces integer-pel could never reproduce `--preset fast`, since Fast also
1988 // moves this threshold 90 -> 60. `RFF_RDSKIP_MINFREE` makes the combination
1989 // expressible so it can be measured instead of inferred.
1990 rd_skip_min_free: ke.rdskip_minfree
1991 .or(cfg.tune_rd_skip_min_free)
1992 .unwrap_or(if cfg.preset == crate::config::Preset::Fast { 60 } else { 90 }),
1993 satd_var_thresh: i64::MAX,
1994 mb_use_satd: false,
1995 nnz_l_cache: [0x80; 25],
1996 nnz_c_cache: [[0x80; 9]; 2],
1997 mb_skip_sad: vec![0; mb_w * mb_h],
1998 mb_was_skip: vec![false; mb_w * mb_h],
1999 }
2000 }
2001
2002 /// openh264 `PredictSadSkip`: the greedy P_Skip threshold = the median of the
2003 /// skip SADs of the *skip* neighbours (left A, top B, top-right C, top-left
2004 /// fallback for C). Non-skip neighbours contribute 0, so with no skip neighbour
2005 /// the threshold is 0 (no greedy skip). This makes the skip self-calibrating —
2006 /// it only spreads where a neighbour already skipped at a comparable SAD.
2007 fn pred_skip_sad(&self, mb_x: usize, mb_y: usize) -> u32 {
2008 let mbw = self.mb_w;
2009 let at = |x: isize, y: isize| -> Option<(bool, u32)> {
2010 if x < 0 || y < 0 || x >= mbw as isize {
2011 return None;
2012 }
2013 let i = y as usize * mbw + x as usize;
2014 Some((self.mb_was_skip[i], self.mb_skip_sad[i]))
2015 };
2016 let a = at(mb_x as isize - 1, mb_y as isize); // left
2017 let b = at(mb_x as isize, mb_y as isize - 1); // top
2018 let c = at(mb_x as isize + 1, mb_y as isize - 1) // top-right
2019 .or_else(|| at(mb_x as isize - 1, mb_y as isize - 1)); // top-left fallback
2020 let sad = |n: Option<(bool, u32)>| n.filter(|&(s, _)| s).map_or(0, |(_, v)| v);
2021 let (sa, sb, sc) = (sad(a), sad(b), sad(c));
2022 // B and C unavailable but A available → A only.
2023 if b.is_none() && c.is_none() && a.is_some() {
2024 return sa;
2025 }
2026 match (
2027 a.is_some_and(|(s, _)| s),
2028 b.is_some_and(|(s, _)| s),
2029 c.is_some_and(|(s, _)| s),
2030 ) {
2031 (true, false, false) => sa,
2032 (false, true, false) => sb,
2033 (false, false, true) => sc,
2034 _ => sb.max(sa.min(sc)).min(sa.max(sc)), // median(sa, sb, sc)
2035 }
2036 }
2037
2038 /// The `mb_qp_delta` for the current macroblock (`qp − cur_qp`) and commits the
2039 /// running QPy — called ONLY where the syntax actually codes a delta (I_16x16
2040 /// always; inter / I_4x4 when `cbp != 0`), so a skip / cbp==0 MB leaves `cur_qp`
2041 /// unchanged and inherits it, exactly as the decoder's `step_qp` does.
2042 fn qp_delta(&mut self) -> i32 {
2043 let d = self.qp as i32 - self.cur_qp as i32;
2044 // Spec §7.4.5: mb_qp_delta shall be in [-26, +25]. Shipped knobs bound
2045 // |d| well inside that (AQ_DQP_MAX + MBTREE_DQP_MAX), but an external
2046 // qpo map could exceed it — an illegal bitstream every strict decoder
2047 // may reject (ours reconstructs it anyway via the §7.4.5 modulo).
2048 debug_assert!((-26..=25).contains(&d), "mb_qp_delta {d} outside [-26,25]");
2049 self.cur_qp = self.qp;
2050 d
2051 }
2052
2053 /// MV-predictor neighbors (left, above, above-right) for the 16×16 partition
2054 /// of macroblock `(mb_x, mb_y)`, read from the per-4×4-block grids.
2055 fn mv_neighbors(&self, mb_x: usize, mb_y: usize) -> [MvNeighbor; 3] {
2056 let w4 = self.mb_w * 4;
2057 let get = |avail: bool, bx: isize, by: isize| {
2058 if avail {
2059 let idx = by as usize * w4 + bx as usize;
2060 MvNeighbor {
2061 available: true,
2062 mv: self.mv_y[idx],
2063 ref_idx: self.ref_idx_y[idx],
2064 }
2065 } else {
2066 MvNeighbor::NONE
2067 }
2068 };
2069 let (bx, by) = (mb_x as isize * 4, mb_y as isize * 4);
2070 let a = get(mb_x > 0, bx - 1, by);
2071 let b = get(mb_y > 0, bx, by - 1);
2072 // C = above-right; if unavailable, fall back to D = above-left.
2073 let c = if mb_y > 0 && mb_x + 1 < self.mb_w {
2074 get(true, bx + 4, by - 1)
2075 } else {
2076 get(mb_x > 0 && mb_y > 0, bx - 1, by - 1)
2077 };
2078 [a, b, c]
2079 }
2080
2081 /// The `P_Skip` motion vector (spec §8.4.1.1). P_Skip always references
2082 /// index 0 (the most recent picture).
2083 fn skip_mv(&self, mb_x: usize, mb_y: usize) -> (i32, i32) {
2084 let [a, b, c] = self.mv_neighbors(mb_x, mb_y);
2085 if !a.available
2086 || !b.available
2087 || (a.ref_idx == 0 && a.mv == (0, 0))
2088 || (b.ref_idx == 0 && b.mv == (0, 0))
2089 {
2090 (0, 0)
2091 } else {
2092 predict_mv(a, b, c, 0)
2093 }
2094 }
2095
2096 /// Records a macroblock's per-4×4-block motion state (`ref` = reference index
2097 /// for inter, ignored for intra where `inter` is false).
2098 fn set_mb_mv(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32), inter: bool, refi: i32) {
2099 let w4 = self.mb_w * 4;
2100 for dy in 0..4 {
2101 for dx in 0..4 {
2102 let idx = (mb_y * 4 + dy) * w4 + (mb_x * 4 + dx);
2103 self.mv_y[idx] = mv;
2104 self.inter_y[idx] = inter;
2105 self.ref_idx_y[idx] = if inter { refi } else { -1 };
2106 }
2107 }
2108 }
2109
2110 /// Block-level MV-predictor neighbors for a partition whose top-left 4×4
2111 /// block is `(pbx, pby)` and which is `pwb` blocks wide. Availability uses
2112 /// the decoded-block grid, so in-macroblock partitions see earlier ones.
2113 fn mv_neighbors_block(&self, pbx: isize, pby: isize, pwb: isize) -> [MvNeighbor; 3] {
2114 let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
2115 let get = |bx: isize, by: isize| -> MvNeighbor {
2116 if bx < 0 || by < 0 || bx >= w4 || by >= h4 || !self.coded_y[(by * w4 + bx) as usize] {
2117 MvNeighbor::NONE
2118 } else {
2119 let idx = (by * w4 + bx) as usize;
2120 MvNeighbor { available: true, mv: self.mv_y[idx], ref_idx: self.ref_idx_y[idx] }
2121 }
2122 };
2123 let a = get(pbx - 1, pby);
2124 let b = get(pbx, pby - 1);
2125 let mut c = get(pbx + pwb, pby - 1);
2126 if !c.available {
2127 c = get(pbx - 1, pby - 1); // D fallback
2128 }
2129 [a, b, c]
2130 }
2131
2132 /// List-aware block MV-predictor neighbors (`list` 0 or 1), for the B-slice
2133 /// per-list `mvd` predictor. Identical geometry to [`Self::mv_neighbors_block`]
2134 /// but reads the List-1 motion grid when `list == 1`, matching the decoder's
2135 /// `mv_neighbors_list`. A neighbor not coded in this list reads `ref_idx = -1`
2136 /// (so `predict_partition_mv` treats it as non-matching, exactly as the decoder).
2137 fn mv_neighbors_block_list(&self, pbx: isize, pby: isize, pwb: isize, list: usize) -> [MvNeighbor; 3] {
2138 let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
2139 let (mvg, refg): (&[(i32, i32)], &[i32]) = if list == 0 {
2140 (&self.mv_y, &self.ref_idx_y)
2141 } else {
2142 (&self.mv1_y, &self.ref_idx1_y)
2143 };
2144 let get = |bx: isize, by: isize| -> MvNeighbor {
2145 if bx < 0 || by < 0 || bx >= w4 || by >= h4 || !self.coded_y[(by * w4 + bx) as usize] {
2146 MvNeighbor::NONE
2147 } else {
2148 let idx = (by * w4 + bx) as usize;
2149 MvNeighbor { available: true, mv: mvg[idx], ref_idx: refg[idx] }
2150 }
2151 };
2152 let a = get(pbx - 1, pby);
2153 let b = get(pbx, pby - 1);
2154 let mut c = get(pbx + pwb, pby - 1);
2155 if !c.available {
2156 c = get(pbx - 1, pby - 1); // D fallback
2157 }
2158 [a, b, c]
2159 }
2160
2161 /// SATD cost of a motion-compensated `rw`×`rh` luma region against the source —
2162 /// THE per-candidate ME cost function (Challenge-1 A2 shape: the per-search
2163 /// invariants arrive as parameters instead of being re-derived per candidate).
2164 /// `hp` is the already-resolved plane cache (`None` ⇔ the fast preset, whose
2165 /// SATD path never reads planes), `hr_on` the hoisted `RFF_HPEL_REF` knob,
2166 /// `src_row` the hoisted source slice base. Dispatch order (interior full-pel →
2167 /// in-place plane read → fused avg+SATD → materialize → `mc_luma` fallback) is
2168 /// the historical `mc_satd` order, so the accepted candidate set — and the
2169 /// bitstream — are byte-identical to it.
2170 #[allow(clippy::too_many_arguments)]
2171 #[inline]
2172 fn mc_satd_hp(
2173 &self,
2174 reference: &crate::RefFrame,
2175 hp: Option<&rusty_h264_common::inter::HpelPlanes>,
2176 hr_on: bool,
2177 // `hr_on && RFF_SATD_AVG` (and accel compiled in) — hoisted per search like
2178 // `hr_on`, so the fused-kernel gate costs zero OnceLock loads per candidate.
2179 // Unused (and always false) on non-accel builds.
2180 sa_on: bool,
2181 src_row: &[u8],
2182 lx: usize,
2183 ly: usize,
2184 rw: usize,
2185 rh: usize,
2186 mv: (i32, i32),
2187 ) -> i64 {
2188 #[cfg(not(accel_x86))]
2189 let _ = sa_on;
2190 #[cfg(feature = "profile")]
2191 let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
2192 let ch = self.mb_h * 16;
2193 let cw = self.cw;
2194 let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
2195 let interior_fullpel = mv.0 & 3 == 0
2196 && mv.1 & 3 == 0
2197 && ix0 >= 0
2198 && iy0 >= 0
2199 && ix0 + rw as isize <= cw as isize
2200 && iy0 + rh as isize <= ch as isize;
2201 #[cfg(feature = "profile")]
2202 {
2203 let fullpel = mv.0 & 3 == 0 && mv.1 & 3 == 0;
2204 satdpath::bump(if interior_fullpel { 0 } else if fullpel { 1 } else { 2 });
2205 }
2206 if interior_fullpel {
2207 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
2208 let (rx0, ry0) = (ix0 as usize, iy0 as usize);
2209 return satd_px(src_row, cw, &reference.y[ry0 * cw + rx0..], cw, rw, rh);
2210 }
2211 if let Some(hp) = hp {
2212 if hr_on {
2213 if let Some((plane, base, stride)) =
2214 rusty_h264_common::inter::hpel_ref(hp, lx, ly, rw, rh, mv.0, mv.1)
2215 {
2216 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
2217 return satd_px(src_row, cw, &plane[base..], stride, rw, rh);
2218 }
2219 }
2220 // A3: QUARTER-pel — fuse the two-plane (a+b+1)>>1 average into the
2221 // SATD kernel itself (no 256-byte materialize + reload, no FFI hop).
2222 // `satd_avg` returns the exact `Σ|H·d|` that `satd_px` computes on
2223 // the materialized average, so the cost value — and the bitstream —
2224 // are byte-identical; on non-AVX2 (or a declined size) it returns
2225 // `None` and the old materialize path below runs unchanged.
2226 #[cfg(accel_x86)]
2227 if sa_on {
2228 if let Some((pa, ba, pb, bb, stride)) =
2229 rusty_h264_common::inter::hpel_qpel_refs(hp, lx, ly, rw, rh, mv.0, mv.1)
2230 {
2231 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
2232 if let Some(v) = rusty_h264_accel::satd_avg(
2233 src_row, cw, &pa[ba..], &pb[bb..], stride, rw, rh,
2234 ) {
2235 return v as i64;
2236 }
2237 }
2238 }
2239 let mut pred = [0u8; 256];
2240 if rusty_h264_common::inter::hpel_block(hp, lx, ly, rw, rh, mv.0, mv.1, &mut pred) {
2241 return satd_px(src_row, cw, &pred, rw, rw, rh);
2242 }
2243 mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
2244 return satd_px(src_row, cw, &pred, rw, rw, rh);
2245 }
2246 let mut pred = [0u8; 256];
2247 mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
2248 satd_px(src_row, cw, &pred, rw, rw, rh)
2249 }
2250
2251 /// Track-B B2.1: the SAD twin of `mc_satd_hp` — the SAME dispatch ladder
2252 /// (interior full-pel → in-place plane read → fused avg → materialize →
2253 /// `mc_luma`), with SAD (`psadbw`-class) distortion. `mc_sad` (the fast
2254 /// preset's function) had NONE of the SATD path's accumulated wins, so the
2255 /// first B2 cut measured 61% MORE `mc_luma` fallbacks; this is the parity fix.
2256 /// Every arm reads the same samples the materializing path would, so the SAD
2257 /// value — and therefore the B2-on bitstream — is unchanged by this function.
2258 #[allow(clippy::too_many_arguments)]
2259 #[inline]
2260 fn mc_sad_hp(
2261 &self,
2262 reference: &crate::RefFrame,
2263 hp: Option<&rusty_h264_common::inter::HpelPlanes>,
2264 hr_on: bool,
2265 src_row: &[u8],
2266 lx: usize,
2267 ly: usize,
2268 rw: usize,
2269 rh: usize,
2270 mv: (i32, i32),
2271 _asrc: Option<&[u8; 256]>,
2272 ) -> i64 {
2273 #[cfg(feature = "profile")]
2274 let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
2275 let ch = self.mb_h * 16;
2276 let cw = self.cw;
2277 let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
2278 let interior_fullpel = mv.0 & 3 == 0
2279 && mv.1 & 3 == 0
2280 && ix0 >= 0
2281 && iy0 >= 0
2282 && ix0 + rw as isize <= cw as isize
2283 && iy0 + rh as isize <= ch as isize;
2284 if interior_fullpel {
2285 let (rx0, ry0) = (ix0 as usize, iy0 as usize);
2286 #[cfg(accel)]
2287 if rw == 16 && rh == 16 {
2288 if let Some(src) = _asrc {
2289 return rusty_h264_accel::sad_16x16(src, 16, &reference.y[ry0 * cw + rx0..], cw)
2290 as i64;
2291 }
2292 }
2293 return sad_strided(src_row, cw, &reference.y[ry0 * cw + rx0..], cw, rw, rh);
2294 }
2295 if let Some(hp) = hp {
2296 if hr_on {
2297 // Single-plane phases (h/v/c half-pel AND edge full-pel via the
2298 // padded `f` plane — the E-3 move, which `mc_sad` never had).
2299 if let Some((plane, base, stride)) =
2300 rusty_h264_common::inter::hpel_ref(hp, lx, ly, rw, rh, mv.0, mv.1)
2301 {
2302 return sad_strided(src_row, cw, &plane[base..], stride, rw, rh);
2303 }
2304 // Quarter-pel: fused (a+b+1)>>1 + SAD, no materialize.
2305 if let Some((pa, ba, pb, bb, stride)) =
2306 rusty_h264_common::inter::hpel_qpel_refs(hp, lx, ly, rw, rh, mv.0, mv.1)
2307 {
2308 return sad_avg_strided(src_row, cw, &pa[ba..], &pb[bb..], stride, rw, rh);
2309 }
2310 }
2311 let mut pred = [0u8; 256];
2312 if rusty_h264_common::inter::hpel_block(hp, lx, ly, rw, rh, mv.0, mv.1, &mut pred) {
2313 return sad_strided(src_row, cw, &pred, rw, rw, rh);
2314 }
2315 mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
2316 return sad_strided(src_row, cw, &pred, rw, rw, rh);
2317 }
2318 let mut pred = [0u8; 256];
2319 mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
2320 sad_strided(src_row, cw, &pred, rw, rw, rh)
2321 }
2322
2323 /// SAD (sum of absolute differences) of a motion-compensated `rw`×`rh` luma
2324 /// region against the source — the **fast** preset's motion-search cost.
2325 ///
2326 /// SAD is far cheaper than SATD (no Hadamard transform), and the inner loop is
2327 /// written as `Σ a.abs_diff(b)` over `u8` slices, the exact pattern LLVM
2328 /// auto-vectorizes to the `psadbw` SAD instruction — the same instruction
2329 /// x264's hand-written assembly uses, but reached without any `unsafe`. (x264's
2330 /// fast presets use SAD for the full-pel search for precisely this reason.)
2331 #[allow(clippy::too_many_arguments)]
2332 fn mc_sad(
2333 &self,
2334 reference: &crate::RefFrame,
2335 sy: &[u8],
2336 lx: usize,
2337 ly: usize,
2338 rw: usize,
2339 rh: usize,
2340 mv: (i32, i32),
2341 // 16-aligned source MB (built once per search) for the asm SAD; `None`
2342 // (and unused) on the scalar build.
2343 _asrc: Option<&[u8; 256]>,
2344 ) -> i64 {
2345 // Descent E depth-6: tag WHO is calling mc_luma. The search's edge fallback and
2346 // reconstruction land in the same `inter-mc` bucket; pricing a recon-side lever
2347 // against the merged total is pricing the wrong population.
2348 #[cfg(feature = "profile")]
2349 let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
2350 let ch = self.mb_h * 16;
2351 let cw = self.cw;
2352 let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
2353 let interior_fullpel = mv.0 & 3 == 0
2354 && mv.1 & 3 == 0
2355 && ix0 >= 0
2356 && iy0 >= 0
2357 && ix0 + rw as isize <= cw as isize
2358 && iy0 + rh as isize <= ch as isize;
2359 // Full-pel interior 16×16: openh264's `psadbw` SAD of the aligned source vs
2360 // the (movdqu) reference block. SAD is exact, so this is byte-identical to the
2361 // scalar path — a pure ME speedup (~2.4× the kernel).
2362 #[cfg(accel)]
2363 if interior_fullpel && rw == 16 && rh == 16 {
2364 if let Some(src) = _asrc {
2365 let (rx0, ry0) = (ix0 as usize, iy0 as usize);
2366 return rusty_h264_accel::sad_16x16(src, 16, &reference.y[ry0 * cw + rx0..], cw)
2367 as i64;
2368 }
2369 }
2370 let mut sad = 0u32;
2371 if interior_fullpel {
2372 // Direct from the reference (a copy at full-pel) — no interpolation.
2373 let (rx0, ry0) = (ix0 as usize, iy0 as usize);
2374 let refy = &reference.y;
2375 for dy in 0..rh {
2376 let s = &sy[(ly + dy) * cw + lx..][..rw];
2377 let r = &refy[(ry0 + dy) * cw + rx0..][..rw];
2378 sad += s.iter().zip(r).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
2379 }
2380 } else {
2381 let mut pred = [0u8; 256];
2382 // Same plane-cache read (and same preset gate) as `mc_satd`.
2383 let from_planes = !self.fast
2384 && rusty_h264_common::inter::hpel_block(
2385 reference.hpel(cw, ch),
2386 lx,
2387 ly,
2388 rw,
2389 rh,
2390 mv.0,
2391 mv.1,
2392 &mut pred,
2393 );
2394 if !from_planes {
2395 mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
2396 }
2397 for dy in 0..rh {
2398 let s = &sy[(ly + dy) * cw + lx..][..rw];
2399 let p = &pred[dy * rw..][..rw];
2400 sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
2401 }
2402 }
2403 sad as i64
2404 }
2405
2406 /// `bi_dist` for an arbitrary rect — the B 16×8 / 8×16 partition search needs
2407 /// the bi-blend distortion of a half, not of the whole macroblock. Same blend
2408 /// and same SAD/SATD choice as the 16×16 form.
2409 #[allow(clippy::too_many_arguments)]
2410 fn bi_dist_rect(
2411 &self,
2412 l0: &crate::RefFrame,
2413 l1: &crate::RefFrame,
2414 sy: &[u8],
2415 lx: usize,
2416 ly: usize,
2417 rw: usize,
2418 rh: usize,
2419 mv0: (i32, i32),
2420 mv1: (i32, i32),
2421 ) -> i64 {
2422 let ch = self.mb_h * 16;
2423 let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
2424 mc_luma(&l0.y, self.cw, ch, lx, ly, rw, rh, mv0.0, mv0.1, &mut a);
2425 mc_luma(&l1.y, self.cw, ch, lx, ly, rw, rh, mv1.0, mv1.1, &mut b);
2426 let n = rw * rh;
2427 let mut avg = [0u8; 256];
2428 for i in 0..n {
2429 avg[i] = bi_blend(a[i] as i32, b[i] as i32, self.bi_w);
2430 }
2431 if self.fast && !self.mb_use_satd {
2432 let mut sad = 0u32;
2433 for dy in 0..rh {
2434 let s = &sy[(ly + dy) * self.cw + lx..][..rw];
2435 let p = &avg[dy * rw..][..rw];
2436 sad += s.iter().zip(p).map(|(&x, &y)| x.abs_diff(y) as u32).sum::<u32>();
2437 }
2438 sad as i64
2439 } else {
2440 satd_px(&sy[ly * self.cw + lx..], self.cw, &avg, rw, rw, rh)
2441 }
2442 }
2443
2444 /// Luma distortion of a `B_Bi` 16×16 prediction: motion-compensate `l0`/`l1`,
2445 /// average `(p+q+1)>>1` (the decoder's `b_mc` blend at `weighted_bipred_idc=0`),
2446 /// and score vs the source with the SAME metric the per-list searches used —
2447 /// SAD on the fast path, SATD when this MB is SATD-routed — so `J_bi` compares
2448 /// directly against `J0`/`J1`.
2449 fn bi_dist(
2450 &self,
2451 l0: &crate::RefFrame,
2452 l1: &crate::RefFrame,
2453 sy: &[u8],
2454 lx: usize,
2455 ly: usize,
2456 mv0: (i32, i32),
2457 mv1: (i32, i32),
2458 ) -> i64 {
2459 // Descent E/F: identify this mc_luma population by call site.
2460 #[cfg(feature = "profile")]
2461 let _site = rusty_h264_common::inter::mcstats::SiteTag::new(4);
2462 let ch = self.mb_h * 16;
2463 let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
2464 mc_luma(&l0.y, self.cw, ch, lx, ly, 16, 16, mv0.0, mv0.1, &mut a);
2465 mc_luma(&l1.y, self.cw, ch, lx, ly, 16, 16, mv1.0, mv1.1, &mut b);
2466 let mut avg = [0u8; 256];
2467 for i in 0..256 {
2468 avg[i] = bi_blend(a[i] as i32, b[i] as i32, self.bi_w);
2469 }
2470 if self.fast && !self.mb_use_satd {
2471 let mut sad = 0u32;
2472 for dy in 0..16 {
2473 let s = &sy[(ly + dy) * self.cw + lx..][..16];
2474 let p = &avg[dy * 16..][..16];
2475 sad += s.iter().zip(p).map(|(&x, &y)| x.abs_diff(y) as u32).sum::<u32>();
2476 }
2477 sad as i64
2478 } else {
2479 satd_px(&sy[ly * self.cw + lx..], self.cw, &avg, 16, 16, 16)
2480 }
2481 }
2482
2483 /// Distortion of a pre-formed 16×16 luma prediction vs the source (SAD on the
2484 /// fast path, SATD when SATD-routed) — the mode-decision cost for `B_Direct`,
2485 /// on the same scale as the per-list search J so they compare directly.
2486 fn pred_dist(&self, sy: &[u8], lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
2487 if self.fast && !self.mb_use_satd {
2488 let mut sad = 0u32;
2489 for dy in 0..16 {
2490 let s = &sy[(ly + dy) * self.cw + lx..][..16];
2491 let p = &pred[dy * 16..][..16];
2492 sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
2493 }
2494 sad as i64
2495 } else {
2496 satd_px(&sy[ly * self.cw + lx..], self.cw, pred, 16, 16, 16)
2497 }
2498 }
2499
2500 /// `colZeroFlag` for absolute 4×4 block `(bx, by)` (spec §8.4.1.2.2): true when
2501 /// the co-located picture `RefPicList1[0]` (`l1`) is short-term (always, here —
2502 /// we use no long-term refs) and its co-located block uses List-0 reference 0
2503 /// with a near-zero (|·| ≤ 1) motion vector. Must match the decoder's `col_zero`.
2504 fn col_zero(&self, l1: &crate::RefFrame, bx: usize, by: usize) -> bool {
2505 if l1.w4 == 0 {
2506 return false;
2507 }
2508 let idx = by * l1.w4 + bx;
2509 if idx >= l1.ref_idx.len() {
2510 return false;
2511 }
2512 // b-pyramid: the co-located picture can be a REFERENCE B, whose blocks
2513 // may be L1-only — `predFlagL0Col == 0` reads List 1 instead (spec
2514 // §8.4.1.2.2). This is the SAME defect the decoder root-caused on real
2515 // x264 pyramid streams; the two derivations must mirror or the
2516 // encoder's spatial-direct picks drift from what the decoder computes.
2517 // P/I co-located pictures have empty `ref_idx1` and never take this arm.
2518 if l1.ref_idx[idx] < 0 {
2519 if let (Some(&r1), Some(&m1)) = (l1.ref_idx1.get(idx), l1.mv1.get(idx)) {
2520 return r1 == 0 && m1.0.abs() <= 1 && m1.1.abs() <= 1;
2521 }
2522 return false;
2523 }
2524 l1.ref_idx[idx] == 0 && l1.mv[idx].0.abs() <= 1 && l1.mv[idx].1.abs() <= 1
2525 }
2526
2527 /// Bi-predictive MC of one small region into `pred_y`/`c_pred` at MB-relative
2528 /// offset `(dx, dy)` — the per-4×4 primitive the spatial-direct derivation uses.
2529 /// Mirrors the decoder's `b_mc` (average `(p+q+1)>>1` for bi, copy for uni).
2530 #[allow(clippy::too_many_arguments)]
2531 fn b_mc_block(
2532 &self,
2533 l0: &crate::RefFrame,
2534 l1: &crate::RefFrame,
2535 mb_x: usize,
2536 mb_y: usize,
2537 dx: usize,
2538 dy: usize,
2539 refi0: i32,
2540 m0: (i32, i32),
2541 refi1: i32,
2542 m1: (i32, i32),
2543 pred_y: &mut [u8; 256],
2544 c_pred: &mut [[u8; 64]; 2],
2545 ) {
2546 // Descent E/F: identify this mc_luma population by call site.
2547 #[cfg(feature = "profile")]
2548 let _site = rusty_h264_common::inter::mcstats::SiteTag::new(4);
2549 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
2550 let (px, py) = (mb_x * 16 + dx, mb_y * 16 + dy);
2551 let (mut a, mut b) = ([0u8; 16], [0u8; 16]);
2552 // 4-wide luma exists ONLY here: P partitions bottom out at 8×8, so B-frame
2553 // spatial-direct is the encoder's only 4×4 MC. With B-frames on it is ~8% of
2554 // all MC calls and HALF of all sub-pel ones — and `luma_h`/`luma_v` dispatch
2555 // to asm only at width 16/8, so 4-wide otherwise runs the scalar 6-tap.
2556 // Serving it from the cached half-pel planes (bit-identical, and they are
2557 // already built for this reference by the motion search) is strictly better
2558 // than adding a 4-wide asm kernel.
2559 let mc4 = |r: &crate::RefFrame, mv: (i32, i32), out: &mut [u8; 16]| {
2560 if !self.fast
2561 && bdirect_planes_enabled()
2562 && rusty_h264_common::inter::hpel_block(
2563 r.hpel(self.cw, ch), px, py, 4, 4, mv.0, mv.1, out,
2564 )
2565 {
2566 return;
2567 }
2568 mc_luma(&r.y, self.cw, ch, px, py, 4, 4, mv.0, mv.1, out);
2569 };
2570 if refi0 >= 0 {
2571 mc4(l0, m0, &mut a);
2572 }
2573 if refi1 >= 0 {
2574 mc4(l1, m1, &mut b);
2575 }
2576 for yy in 0..4 {
2577 for xx in 0..4 {
2578 let i = yy * 4 + xx;
2579 let v = match (refi0 >= 0, refi1 >= 0) {
2580 (true, true) => bi_blend(a[i] as i32, b[i] as i32, self.bi_w),
2581 (true, false) => a[i],
2582 _ => b[i],
2583 };
2584 pred_y[(dy + yy) * 16 + (dx + xx)] = v;
2585 }
2586 }
2587 // Chroma: the co-located 2×2 block at half resolution.
2588 let (cpx, cpy) = (mb_x * 8 + dx / 2, mb_y * 8 + dy / 2);
2589 for c in 0..2 {
2590 let (r0, r1) = if c == 0 { (&l0.u, &l1.u) } else { (&l0.v, &l1.v) };
2591 let (mut ca, mut cb) = ([0u8; 4], [0u8; 4]);
2592 if refi0 >= 0 {
2593 mc_chroma(r0, self.ccw, cch, cpx, cpy, 2, 2, m0.0, m0.1, &mut ca);
2594 }
2595 if refi1 >= 0 {
2596 mc_chroma(r1, self.ccw, cch, cpx, cpy, 2, 2, m1.0, m1.1, &mut cb);
2597 }
2598 for yy in 0..2 {
2599 for xx in 0..2 {
2600 let i = yy * 2 + xx;
2601 let v = match (refi0 >= 0, refi1 >= 0) {
2602 (true, true) => bi_blend(ca[i] as i32, cb[i] as i32, self.bi_w),
2603 (true, false) => ca[i],
2604 _ => cb[i],
2605 };
2606 c_pred[c][(dy / 2 + yy) * 8 + (dx / 2 + xx)] = v;
2607 }
2608 }
2609 }
2610 }
2611
2612 /// Spatial-direct (`direct_spatial_mv_pred_flag == 1`) prediction for a 16×16 B
2613 /// macroblock — the shared basis of `B_Skip` and `B_Direct_16x16`. Returns the
2614 /// prediction and the per-4×4 `(refIdxL0, mvL0, refIdxL1, mvL1)` motion the
2615 /// decoder's `decode_b_direct` derives (so the caller commits identical motion).
2616 fn b_direct(
2617 &self,
2618 l0: &crate::RefFrame,
2619 l1: &crate::RefFrame,
2620 mb_x: usize,
2621 mb_y: usize,
2622 ) -> ([u8; 256], [[u8; 64]; 2], [(i32, (i32, i32), i32, (i32, i32)); 16]) {
2623 let (nbx, nby) = ((mb_x * 4) as isize, (mb_y * 4) as isize);
2624 let n0 = self.mv_neighbors_block_list(nbx, nby, 4, 0);
2625 let n1 = self.mv_neighbors_block_list(nbx, nby, 4, 1);
2626 let min_pos = |a: i32, b: i32| if a < 0 { b } else if b < 0 { a } else { a.min(b) };
2627 let rid = |n: &[MvNeighbor; 3]| min_pos(min_pos(n[0].ref_idx, n[1].ref_idx), n[2].ref_idx);
2628 let (mut refi0, mut refi1) = (rid(&n0), rid(&n1));
2629 let direct_zero = refi0 < 0 && refi1 < 0;
2630 if direct_zero {
2631 refi0 = 0;
2632 refi1 = 0;
2633 }
2634 let mv0 = if refi0 >= 0 && !direct_zero { predict_mv(n0[0], n0[1], n0[2], refi0) } else { (0, 0) };
2635 let mv1 = if refi1 >= 0 && !direct_zero { predict_mv(n1[0], n1[1], n1[2], refi1) } else { (0, 0) };
2636 let mut pred_y = [0u8; 256];
2637 let mut c_pred = [[0u8; 64]; 2];
2638 let mut motion = [(0i32, (0i32, 0i32), 0i32, (0i32, 0i32)); 16];
2639 for sby in 0..4 {
2640 for sbx in 0..4 {
2641 // direct_8x8_inference_flag = 1 (SPS): every 4×4 in an 8×8 takes
2642 // the 8×8's OUTER-CORNER co-located block — (0,0)(3,0)(0,3)(3,3),
2643 // the decoder's `col_block` mapping, spec 8.4.1.2.1.
2644 let (czx, czy) = ((sbx / 2) * 3, (sby / 2) * 3);
2645 let cz = !direct_zero && self.col_zero(l1, mb_x * 4 + czx, mb_y * 4 + czy);
2646 let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
2647 let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
2648 motion[sby * 4 + sbx] = (refi0, m0, refi1, m1);
2649 self.b_mc_block(l0, l1, mb_x, mb_y, sbx * 4, sby * 4, refi0, m0, refi1, m1, &mut pred_y, &mut c_pred);
2650 }
2651 }
2652 (pred_y, c_pred, motion)
2653 }
2654
2655 /// Commits a spatial-direct MB's per-4×4 motion into the List-0/List-1 grids so
2656 /// later MBs' neighbor predictors see it (mirrors the decoder's `b_set_motion`).
2657 fn commit_direct_motion(&mut self, mb_x: usize, mb_y: usize, motion: &[(i32, (i32, i32), i32, (i32, i32)); 16]) {
2658 let w4 = self.mb_w * 4;
2659 for sby in 0..4 {
2660 for sbx in 0..4 {
2661 let (refi0, m0, refi1, m1) = motion[sby * 4 + sbx];
2662 let idx = (mb_y * 4 + sby) * w4 + (mb_x * 4 + sbx);
2663 self.inter_y[idx] = true;
2664 self.coded_y[idx] = true;
2665 self.mv_y[idx] = m0;
2666 self.ref_idx_y[idx] = refi0;
2667 self.mv1_y[idx] = m1;
2668 self.ref_idx1_y[idx] = refi1;
2669 }
2670 }
2671 }
2672
2673 /// Rate-aware motion search for a luma region: full-pel diamond + half/
2674 /// quarter-pel refinement minimizing `J = SATD + λ·bits(mvd)`, where the
2675 /// motion cost is measured against `predictors[0]` (the MV predictor the
2676 /// `mvd` will actually be coded against). The search is seeded from every
2677 /// entry in `predictors` plus `(0,0)`. Returns the best MV and its `J`.
2678 ///
2679 /// The rate term is only a *search heuristic* — whatever MV it picks is still
2680 /// coded as a correct `mvd`, so this never affects decodability.
2681 #[allow(clippy::too_many_arguments)]
2682 /// ME ORACLE PROBE (`RFF_ME_ORACLE=1`): does our search actually FIND the best
2683 /// motion vector available to it? Accumulates our chosen cost against an
2684 /// exhaustive +-24 full-pel search refined by the identical sub-pel pass, so a
2685 /// gap is attributable to the SEARCH, not to the cost function or precision.
2686 /// [n, sum(our cost), sum(oracle cost), blocks where oracle won, cost() evals]
2687 fn motion_search(
2688 &self,
2689 reference: &crate::RefFrame,
2690 sy: &[u8],
2691 lx: usize,
2692 ly: usize,
2693 rw: usize,
2694 rh: usize,
2695 predictors: &[(i32, i32)],
2696 lambda_me: f64,
2697 // Some(mv) => skip the full-pel search entirely and refine THIS vector. The
2698 // starting COST is recomputed here rather than passed in, so the baseline the
2699 // refinement must beat is priced by the same closure as every candidate.
2700 start: Option<(i32, i32)>,
2701 ) -> ((i32, i32), i64) {
2702 // Bit length of `se(d)` (Exp-Golomb), i.e. what an `mvd` component costs.
2703 // Branchless closed form of the old `while n > 1 { n >>= 1; len += 2 }` loop:
2704 // that loop yields `len = 1 + 2·floor(log2(codenum+1))`, and for x ≥ 1
2705 // `floor(log2(x)) == 31 - x.leading_zeros()`. Removes a data-dependent branch
2706 // from the innermost ME cost — bit-identical (verified over the d range).
2707 let mvk = mv_cost_kind(self.mv_smooth);
2708 // Law 5: resolve the cost TABLE once per search. `get_or_init` on an
2709 // initialized OnceLock is still an acquire load + branch, and `mvbits`
2710 // is the innermost ME cost — it ran per CANDIDATE per COMPONENT. The
2711 // empty slice for the step model costs nothing and initializes nothing.
2712 let mvtab: &[u16] = match mvk {
2713 1 => MV_COST_TAB.get_or_init(build_mv_cost),
2714 2 => MV_TRUE_BIASED.get_or_init(build_true_biased),
2715 _ => &[],
2716 };
2717 let mvbits = |d: i32| -> u32 {
2718 // H-23: the ME rate model. `RFF_MVCOST=1` swaps the Exp-Golomb STEP
2719 // function for x264's smooth curve `2·log2(|d|+1) + 0.718 + (d!=0)`.
2720 // The step function is FLAT inside a power-of-two bracket — it prices
2721 // d=4 and d=7 identically, so the search takes the far end of a
2722 // bracket for free, inflating |mvd| (and with it the sign+prefix bits
2723 // the accountant found are ~14% of the payload). λ cannot fix this:
2724 // scaling a flat region leaves it flat. Table is in WHOLE bits to keep
2725 // the caller's integer arithmetic; ×4 internally then rounded, so the
2726 // curve's ordering survives quantization.
2727 match mvk {
2728 1 | 2 => {
2729 let a = d.unsigned_abs().min(4095) as usize;
2730 mvtab[a] as u32
2731 }
2732 _ => {
2733 let codenum = if d > 0 { (2 * d - 1) as u32 } else { (-2 * d) as u32 };
2734 1 + 2 * (31 - (codenum + 1).leading_zeros())
2735 }
2736 }
2737 };
2738 let center = predictors[0];
2739 let probe = me_oracle_on();
2740 // Track-B B2: the full-pel phase (seeds/snap/diamond) prices candidates in
2741 // the SAD domain; the winner is repriced in SATD before rescue/sub-pel.
2742 // Refine-only searches have no full-pel phase, so B2 does not apply there.
2743 // `self.sadfp` is force-mode at construction or the per-frame `b2_mgain`
2744 // dispatcher's routing (mode 1).
2745 let sadfp = !self.fast && start.is_none() && self.sadfp;
2746 // Build the 16-aligned source MB ONCE per search for the asm SAD path (fast
2747 // preset — and B2's SAD full-pel phase — full 16×16). Amortized over every
2748 // candidate's SAD; the reference block stays unaligned (movdqu). Scalar
2749 // build does no copy.
2750 #[cfg(accel)]
2751 let asrc_buf = if (self.fast || sadfp) && rw == 16 && rh == 16 {
2752 let mut a = AlignedMb([0u8; 256]);
2753 for dy in 0..16 {
2754 a.0[dy * 16..dy * 16 + 16].copy_from_slice(&sy[(ly + dy) * self.cw + lx..][..16]);
2755 }
2756 Some(a)
2757 } else {
2758 None
2759 };
2760 #[cfg(accel)]
2761 let asrc: Option<&[u8; 256]> = asrc_buf.as_ref().map(|a| &a.0);
2762 #[cfg(not(accel))]
2763 let asrc: Option<&[u8; 256]> = None;
2764 // Challenge-1 A2: hoist the SATD path's per-search invariants OUT of the
2765 // per-candidate closure. `mc_satd` re-derived, for EVERY candidate: the
2766 // plane-cache OnceLock (an acquire load + branch, twice on the quarter-pel
2767 // arm), the `RFF_HPEL_REF` OnceLock, and the source-row slice base (a bounds
2768 // check). All are constant across the ~20-50 evaluations of one search.
2769 // `mc_satd_hp` is the same dispatch with those values passed in — the same
2770 // arms in the same order, so the accepted candidate set is byte-identical.
2771 let use_sad = self.fast && !self.mb_use_satd;
2772 let cw = self.cw;
2773 // Every non-fast search sub-pel-refines at the end, so the planes are built
2774 // for any reference a search touches — hoisting the get_or_init here does not
2775 // build planes a lazy path would have avoided.
2776 let hp: Option<&rusty_h264_common::inter::HpelPlanes> =
2777 if !self.fast { Some(reference.hpel(cw, self.mb_h * 16)) } else { None };
2778 let hr_on = hpel_ref_enabled();
2779 // A3 gate, hoisted with the rest (`RFF_HPEL_REF=0` restores the FULL pre-C/A3
2780 // copy path, so the fused kernel rides the same master anchor).
2781 let sa_on = cfg!(accel_x86) && hr_on && satd_avg_enabled();
2782 let src_row = &sy[ly * cw + lx..];
2783 // H-14 R3: the MeCtx fast evaluator — ONE geometry validation per search,
2784 // then per-eval integer bounds + direct kernel (collects the measured
2785 // ~23 ns/eval dispatch chain). Values are exactly the safe path's, so a
2786 // candidate served here cannot change the bitstream; out-of-window
2787 // candidates fall back to `mc_satd_hp` (equal values there too).
2788 #[cfg(accel_x86)]
2789 let mectx = if !use_sad && mectx_enabled() {
2790 hp.and_then(|p| {
2791 rusty_h264_accel::MeCtx::new(
2792 src_row, cw, &p.f, &p.h, &p.v, &p.c, p.stride, p.pad, p.pw, p.ph,
2793 lx, ly, rw, rh,
2794 )
2795 })
2796 } else {
2797 None
2798 };
2799 let cost = |mv: (i32, i32)| -> i64 {
2800 let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2801 // The smooth table carries 4× resolution; fold that into λ so the
2802 // rate/distortion balance is unchanged and only the SHAPE differs.
2803 let lam_r = if mvk != 0 { lambda_me * 0.25 } else { lambda_me };
2804 // Fast preset: SAD (psadbw — asm kernel on `--features asm`, else auto-vec)
2805 // — far cheaper than SATD, the single biggest reason x264 fast out-runs us.
2806 let dist = if use_sad {
2807 self.mc_sad(reference, sy, lx, ly, rw, rh, mv, asrc)
2808 } else {
2809 #[cfg(accel_x86)]
2810 {
2811 match mectx.as_ref().and_then(|c| c.eval(mv.0, mv.1)) {
2812 Some(d) => d as i64,
2813 None => {
2814 self.mc_satd_hp(reference, hp, hr_on, sa_on, src_row, lx, ly, rw, rh, mv)
2815 }
2816 }
2817 }
2818 #[cfg(not(accel_x86))]
2819 {
2820 self.mc_satd_hp(reference, hp, hr_on, sa_on, src_row, lx, ly, rw, rh, mv)
2821 }
2822 };
2823 dist + (lam_r * rate as f64) as i64
2824 };
2825 // B2's full-pel-phase cost: SAD distortion, λ scaled to the SAD domain
2826 // (`RFF_ME_SADL`, hoisted). Falls through to `cost` (SATD) whenever B2 is
2827 // off, so every pre-B2 path is untouched.
2828 let lam_fp = lambda_me * if sadfp { me_sadfp_lambda() } else { 1.0 };
2829 let cost_fp = |mv: (i32, i32)| -> i64 {
2830 if !sadfp {
2831 return cost(mv);
2832 }
2833 let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2834 self.mc_sad_hp(reference, hp, hr_on, src_row, lx, ly, rw, rh, mv, asrc)
2835 + (lam_fp * rate as f64) as i64
2836 };
2837 // Seed from (0,0) and each predictor; keep the cheapest.
2838 let refine_only = start.is_some();
2839 let (mut best, mut best_c) = match start {
2840 Some(mv) => (mv, cost(mv)),
2841 None => {
2842 let mut b = (0, 0);
2843 let mut bc = cost_fp(b);
2844 for &p in predictors {
2845 let pc = cost_fp(p);
2846 if pc < bc {
2847 bc = pc;
2848 b = p;
2849 }
2850 }
2851 (b, bc)
2852 }
2853 };
2854 // SNAP THE DIAMOND CENTRE TO INTEGER-PEL. The diamond below steps by whole
2855 // pels, so a fractional centre makes EVERY candidate fractional and forces
2856 // all of them through `mc_luma`'s 6-tap filter — measured at 84-90% of all
2857 // SATD evaluations. Snapping puts the whole full-pel phase on the direct
2858 // (no-interpolation) SATD path. The pre-snap seed is kept and re-compared
2859 // after refinement, so this can only change WHERE we search, never make the
2860 // returned vector worse than the seed we started from.
2861 let (seed_mv, mut seed_c) = (best, best_c);
2862 if !refine_only && self.me_snap && (best.0 & 3 != 0 || best.1 & 3 != 0) {
2863 let snapped = ((best.0 + 2).div_euclid(4) * 4, (best.1 + 2).div_euclid(4) * 4);
2864 best_c = cost_fp(snapped);
2865 best = snapped;
2866 }
2867 // Coarse-to-fine full-pel search: a 4-point diamond walked at each step
2868 // size from 16 px down to 1 px (steps in quarter-pel units: 64,32,…,4).
2869 // The larger initial steps reach fast motion the predictor missed; the
2870 // diamond stays orthogonal (no diagonals) — diagonal probes were found to
2871 // chase equally-good far matches on ambiguous motion, wrecking MV-field
2872 // coherence and the neighbor predictors.
2873 // The fast preset trusts the neighbour MV predictor and refines locally
2874 // (one coarse reach + fine), like x264's `me=dia`; quality sweeps the full
2875 // coarse-to-fine range. Each step's diamond still walks until no
2876 // improvement, so even fast reaches far motion — just in smaller hops.
2877 // Descent A: the coarse rungs are ~76-80% of full-pel evals at a 0.05-1.0% hit
2878 // rate (near-equal eval counts per rung = the walk almost never walks, so each
2879 // rung is a flat ~4-eval toll). RFF_DIA_LADDER selects which rungs to pay for.
2880 let mut ladder = [0i32; 5];
2881 let mut nladder = 0usize;
2882 let steps: &[i32] = if self.fast {
2883 &[16, 4]
2884 } else {
2885 // Shape-aware: sub-8x8 partitions inherit a converged parent MV.
2886 let m = if rw < 8 || rh < 8 { dia_sub_mask() } else { dia_mask() };
2887 for (i, r) in DIA_RUNGS.iter().enumerate() {
2888 if m & (1 << i) != 0 {
2889 ladder[nladder] = *r;
2890 nladder += 1;
2891 }
2892 }
2893 &ladder[..nladder]
2894 };
2895 let _gd = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeDiamond);
2896 // FC: batch a fixed-centre diamond pass through the x4 kernels when every
2897 // candidate is an interior full-pel 16×16 read — one source band covers all
2898 // four candidates. Applies to BOTH cost domains (`sad_16x16_x4` on
2899 // SAD-routed frames, `satd_16x16_x4` otherwise); the fast preset keeps its
2900 // own untouched path. Argmin-of-4 replaces the first-improver cascade —
2901 // measured BD-POSITIVE on the SAD domain (bus −1.71→−2.61) and gated on the
2902 // corpus for the SATD domain the same way. `RFF_ME_FC=0` restores cascade.
2903 #[cfg_attr(not(accel_x86), allow(unused_variables))] // consumed by accel_x86-only blocks
2904 let fc = !self.fast && cfg!(accel_x86) && me_fc_enabled()
2905 && matches!((rw, rh), (16, 16) | (16, 8) | (8, 16) | (8, 8));
2906 #[cfg_attr(not(accel_x86), allow(unused_variables))] // consumed by accel_x86-only blocks
2907 let ch_px = self.mb_h as isize * 16;
2908 for (_si, &step) in steps.iter().enumerate() {
2909 if refine_only {
2910 break;
2911 }
2912 loop {
2913 #[cfg(accel_x86)]
2914 if fc && best.0 & 3 == 0 && best.1 & 3 == 0 {
2915 // All four candidates full-pel; interior iff the ±step box is.
2916 let s = (step >> 2) as isize;
2917 let (bx, by) = (lx as isize + (best.0 >> 2) as isize, ly as isize + (best.1 >> 2) as isize);
2918 if bx - s >= 0 && by - s >= 0 && bx + s + rw as isize <= cw as isize && by + s + rh as isize <= ch_px {
2919 let offs = [
2920 (by * cw as isize + bx + s) as usize,
2921 (by * cw as isize + bx - s) as usize,
2922 ((by + s) * cw as isize + bx) as usize,
2923 ((by - s) * cw as isize + bx) as usize,
2924 ];
2925 // 16-wide shapes go through the batch kernel; 8-wide ones
2926 // measured SLOWER batched than the per-candidate Wels asm
2927 // (H-8 speed gate), so they evaluate individually inside the
2928 // SAME argmin — identical values, identical comparisons,
2929 // identical bitstream.
2930 let batch = if rw != 16 {
2931 None
2932 } else if sadfp {
2933 rusty_h264_accel::sad_x4(src_row, cw, &reference.y, offs, cw, rw, rh)
2934 } else {
2935 rusty_h264_accel::satd_x4(src_row, cw, &reference.y, offs, cw, rw, rh)
2936 };
2937 {
2938 let ring = [(step, 0), (-step, 0), (0, step), (0, -step)];
2939 let (mut bi, mut bc) = (usize::MAX, best_c);
2940 for (i, &(dx, dy)) in ring.iter().enumerate() {
2941 let mv = (best.0 + dx, best.1 + dy);
2942 let cc = match batch {
2943 Some(sads) => {
2944 let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2945 sads[i] as i64 + (lam_fp * rate as f64) as i64
2946 }
2947 None => cost_fp(mv),
2948 };
2949 #[cfg(feature = "profile")]
2950 diastats::ev(_si);
2951 if cc < bc {
2952 bc = cc;
2953 bi = i;
2954 }
2955 }
2956 if bi == usize::MAX {
2957 break;
2958 }
2959 best_c = bc;
2960 best = (best.0 + ring[bi].0, best.1 + ring[bi].1);
2961 #[cfg(feature = "profile")]
2962 diastats::imp(_si);
2963 continue;
2964 }
2965 }
2966 }
2967 let mut improved = false;
2968 for &(dx, dy) in &[(step, 0), (-step, 0), (0, step), (0, -step)] {
2969 let c = (best.0 + dx, best.1 + dy);
2970 let cc = cost_fp(c);
2971 #[cfg(feature = "profile")]
2972 diastats::ev(_si);
2973 if cc < best_c {
2974 best_c = cc;
2975 best = c;
2976 improved = true;
2977 #[cfg(feature = "profile")]
2978 diastats::imp(_si);
2979 }
2980 }
2981 if !improved {
2982 break;
2983 }
2984 }
2985 }
2986 // DIAMOND-STALLED RESCUE (content-adaptive: fires on the FAILURE, not a proxy).
2987 // The gradient-descent diamond stalls at a plateau on FLAT cost surfaces and
2988 // never reaches the far-but-better MV that exists within ±16 (measured: ~+22%
2989 // BD-rate vs x264's simple dia on smooth content). The precise stall signal is
2990 // the CONJUNCTION: a FLAT source block (low variance) whose diamond match STILL
2991 // has a high residual — because on a flat surface the RIGHT MV predicts near-
2992 // perfectly, so a high residual there means the diamond missed it (a stall).
2993 // (Residual alone fires on busy blocks where a high residual is inherent — that
2994 // was 3.3× slower on mand for nothing; variance alone fires on flat-but-well-
2995 // predicted blocks. The AND targets exactly the stalls.) Then a FINE ±16 step-2
2996 // grid reaches the true minimum. Fires on a fraction of blocks → affordable.
2997 // Quality preset only.
2998 drop(_gd);
2999 // B2: the full-pel phase priced in the SAD domain — reprice the winner AND
3000 // the pre-snap seed into the SATD domain the rescue + sub-pel phases (and the
3001 // final seed-vs-refined comparison) trade in. Two SATD evaluations per
3002 // search, against the ~20-50 candidate evaluations the SAD domain cheapened.
3003 if sadfp {
3004 best_c = cost(best);
3005 seed_c = cost(seed_mv);
3006 }
3007 let _gr = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeRescue);
3008 // H-14 R1 brick 1: `me_fast` defaults TRUE, which makes `flat`'s value
3009 // IRRELEVANT to the gate below on every default search — yet the full
3010 // rw×rh sum+sum-of-squares walk (256 pixel loads + muls) ran EAGERLY per
3011 // search. Lazy-evaluate it: same boolean outcome in every case (me_fast
3012 // short-circuits first), the dead variance pass simply never runs.
3013 let flat = |sself: &Self| {
3014 !refine_only && {
3015 let (mut s, mut ss) = (0u64, 0u64);
3016 for dy in 0..rh {
3017 for dx in 0..rw {
3018 let v = sy[(ly + dy) * sself.cw + lx + dx] as u64;
3019 s += v;
3020 ss += v * v;
3021 }
3022 }
3023 // `n = rw * rh` is 256/128/64/32/16 for every H.264 partition --
3024 // always a power of two -- but the compiler cannot prove that of a
3025 // runtime value, so both of these were real `div` instructions.
3026 // A shift is EXACT here; the assert is what keeps it exact, because
3027 // a shift on a non-power-of-2 is silently wrong.
3028 let n = (rw * rh) as u64;
3029 debug_assert!(n.is_power_of_two(), "partition area must be a power of two");
3030 let sh = n.trailing_zeros();
3031 ((ss - ((s * s) >> sh)) >> sh) < sself.me_wide_var
3032 }
3033 };
3034 // The online payoff gate may have disabled the rescue for the rest of this
3035 // frame (irreducible-residual content — rotation/fractal — where the fine grid
3036 // fixes almost nothing; measured 2.25× on rot for a ~0% BD gain). A gated-off
3037 // frame runs exactly the diamond → identical to me_wide-off → never worse.
3038 // FAST-MOTION extension: the flat gate targets smooth-surface stalls, but the
3039 // diamond ALSO stalls on FAST motion (bus/football: an exhaustive ±24 search
3040 // recovers 6-15% BD) — those blocks are high-VARIANCE (detail) so `flat` misses
3041 // them. `me_fast` also fires on any high-residual block; the online payoff gate
3042 // then keeps it only where a wider search actually pays off (fast motion), and
3043 // disables it on irreducible-residual detail — the same self-tuning as flat.
3044 if self.me_wide && !self.fast && (self.me_fast || flat(self)) && !self.resc_off.get() {
3045 // H-14 R1 brick 2: `best` was priced by the SAME `dist + (λ·rate) as
3046 // i64` formula on every path that can reach here (cost, cost_fp after
3047 // the B2 reprice, the FC batch with lam_fp == λ off SAD frames), so
3048 // its distortion is recoverable EXACTLY by subtraction — the extra
3049 // full SATD kernel call per search was pure recompute (the
3050 // codec-eliminate-redundancy "return the already-computed value").
3051 let rate_b = mvbits(best.0 - center.0) + mvbits(best.1 - center.1);
3052 let dist = best_c - (lambda_me * rate_b as f64) as i64;
3053 if dist / (rw * rh).max(1) as i64 > self.me_rescue {
3054 // FINE ±16 step-2 grid + ±1 refine — recover the true minimum the
3055 // diamond missed. Fires only on flat-block stalls, so it is affordable.
3056 // SNAP THE GRID CENTRE TO INTEGER-PEL: the diamond seed can be sub-pel
3057 // (sub-pel neighbour predictors), and since every grid point shares
3058 // cx&3, a sub-pel centre forces the WHOLE ±16 grid through mc_luma
3059 // interpolation — measured 89% of zoom's rescue cost. The rescue only
3060 // needs the right REGION (a far MV the diamond missed); the sub-pel
3061 // refine that follows recovers the fraction. Integer centre → the grid
3062 // hits the fast full-pel SATD path (no interpolation).
3063 let pre_c = best_c;
3064 let (cx, cy) = ((best.0 + 2).div_euclid(4) * 4, (best.1 + 2).div_euclid(4) * 4);
3065 let mut gb = best;
3066 // BATCHED FULL-PEL GRID (accel): now that the grid centre is integer-pel
3067 // (all points interior full-pel), hoist the interior/bounds check out of
3068 // the loop and call the AVX2 SATD directly — skipping mc_satd's per-point
3069 // interior test + satd_px dispatch. BYTE-IDENTICAL to the cost() path
3070 // (same 2·satd_16x16 + rate), so it is default-on (RFF_ME_BATCH=0 to A/B
3071 // it off). ~+7% zoom / +4% tsrc on top of the snap; the SATD kernel itself
3072 // is already AVX2 and its transform can't amortise across the grid, so
3073 // this per-call-overhead trim is the ceiling for an "asm grid kernel".
3074 let cw = self.cw;
3075 let r = self.me_range;
3076 let batched = rw == 16 && rh == 16 && cfg!(accel) && {
3077 let (icdx, icdy) = (cx >> 2, cy >> 2);
3078 lx as i32 + icdx >= r
3079 && lx as i32 + icdx + r + 16 <= cw as i32
3080 && ly as i32 + icdy >= r
3081 && ly as i32 + icdy + r + 16 <= (self.mb_h * 16) as i32
3082 && me_batch_enabled()
3083 };
3084 #[cfg(accel)]
3085 if batched {
3086 let (icdx, icdy) = ((cx >> 2), (cy >> 2));
3087 let src = &sy[ly * cw + lx..];
3088 let mut dy = -r;
3089 while dy <= r {
3090 let rby = (ly as i32 + icdy + dy) as usize;
3091 let mut dx = -r;
3092 while dx <= r {
3093 let rbx = (lx as i32 + icdx + dx) as usize;
3094 let satd =
3095 2 * rusty_h264_accel::satd_16x16(src, cw, &reference.y[rby * cw + rbx..], cw) as i64;
3096 let mv = (cx + dx * 4, cy + dy * 4);
3097 let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
3098 let cc = satd + (lambda_me * rate as f64) as i64;
3099 if cc < best_c {
3100 best_c = cc;
3101 gb = mv;
3102 }
3103 dx += 2;
3104 }
3105 dy += 2;
3106 }
3107 }
3108 if !batched {
3109 let mut dy = -r;
3110 while dy <= r {
3111 let mut dx = -r;
3112 while dx <= r {
3113 let cc = cost((cx + dx * 4, cy + dy * 4));
3114 if cc < best_c {
3115 best_c = cc;
3116 gb = (cx + dx * 4, cy + dy * 4);
3117 }
3118 dx += 2;
3119 }
3120 dy += 2;
3121 }
3122 }
3123 best = gb;
3124 for dy in -1..=1 {
3125 for dx in -1..=1 {
3126 let c = (best.0 + dx * 4, best.1 + dy * 4);
3127 let cc = cost(c);
3128 if cc < best_c {
3129 best_c = cc;
3130 best = c;
3131 }
3132 }
3133 }
3134 // LEARNING PHASE: for the first `me_learn` stalls of the frame, tally
3135 // whether the grid actually paid off (≥6.25% cost cut). Once the window
3136 // fills, if too few paid off the residual is irreducible on this content
3137 // → disable the rescue for the rest of the frame. The window's own MVs
3138 // are committed, but they're a small spatially-clustered set (not
3139 // improvement-selected), so on net-neutral content (rot) they can't
3140 // regress — only frame-level on/off avoids the per-block B-direct
3141 // selection effect.
3142 let n = self.resc_n.get();
3143 if n < self.me_learn {
3144 self.resc_n.set(n + 1);
3145 if best_c * 16 <= pre_c * 15 {
3146 self.resc_big.set(self.resc_big.get() + 1);
3147 }
3148 if n + 1 == self.me_learn
3149 && self.resc_big.get() * 100 < self.me_learn * self.me_payoff_pct
3150 {
3151 self.resc_off.set(true);
3152 }
3153 }
3154 }
3155 }
3156 drop(_gr);
3157 let _gs = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeSubpel);
3158 // Sub-pel refinement uses the 6-tap/bilinear interpolation — the expensive
3159 // per-pixel `mc_luma` path that profiling pinned at ~55% of the entire
3160 // encode. The fast preset skips it (integer-pel only, like x264's fastest
3161 // presets `subme=0`): ~3× faster, trading a little quality on sub-pixel
3162 // motion. The quality preset does the full half-pel + quarter-pel rings.
3163 if probe {
3164 // Exhaustive +-24 full-pel around the same centre, then the SAME sub-pel
3165 // pass, so only the full-pel search strategy differs.
3166 let mut ob = center;
3167 let mut oc = i64::MAX;
3168 for gy in -24i32..=24 {
3169 for gx in -24i32..=24 {
3170 let c = (center.0 + gx * 4, center.1 + gy * 4);
3171 let cc = cost(c);
3172 if cc < oc {
3173 oc = cc;
3174 ob = c;
3175 }
3176 }
3177 }
3178 let fullpel_best = ob;
3179 for &st in &[2i32, 1] {
3180 for &(dx, dy) in &[(st, 0), (-st, 0), (0, st), (0, -st)] {
3181 let c = (ob.0 + dx, ob.1 + dy);
3182 let cc = cost(c);
3183 if cc < oc {
3184 oc = cc;
3185 ob = c;
3186 }
3187 }
3188 }
3189 // EXHAUSTIVE sub-pel: every quarter-pel offset in +-3 around the full-pel
3190 // winner. Our own pass is a single 4-point probe at half then quarter, so
3191 // this is what separates a sub-pel deficiency from a full-pel one.
3192 let mut oc_sp = oc;
3193 for dy in -3i32..=3 {
3194 for dx in -3i32..=3 {
3195 let c = (fullpel_best.0 + dx, fullpel_best.1 + dy);
3196 let cc = cost(c);
3197 if cc < oc_sp {
3198 oc_sp = cc;
3199 }
3200 }
3201 }
3202 // our own sub-pel pass has not run yet; replicate it for a fair compare
3203 let (mut mb_, mut mc_) = (best, best_c);
3204 for &st in &[2i32, 1] {
3205 for &(dx, dy) in &[(st, 0), (-st, 0), (0, st), (0, -st)] {
3206 let c = (mb_.0 + dx, mb_.1 + dy);
3207 let cc = cost(c);
3208 if cc < mc_ {
3209 mc_ = cc;
3210 mb_ = c;
3211 }
3212 }
3213 }
3214 use std::sync::atomic::Ordering::Relaxed;
3215 ME_PROBE[0].fetch_add(1, Relaxed);
3216 ME_PROBE[1].fetch_add(mc_.max(0) as u64, Relaxed);
3217 ME_PROBE[2].fetch_add(oc.max(0) as u64, Relaxed);
3218 ME_PROBE[3].fetch_add((mc_ > oc) as u64, Relaxed);
3219 ME_PROBE[5].fetch_add(oc_sp.max(0) as u64, Relaxed);
3220 ME_PROBE[6].fetch_add((mc_ > oc_sp) as u64, Relaxed);
3221 }
3222 let subpel: &[i32] = if (self.fast && !self.subpel_force) || (self.sp_defer.get() && !refine_only) {
3223 &[]
3224 } else {
3225 &[2, 1]
3226 };
3227 // U1 harvest: the null arm is the full-pel winner we would keep on a skip.
3228 let (hv_pre, mut hv_evals) = (best_c, 0u32);
3229 // `to_best` = eval index of the LAST improvement; `ring1` = the cost after the
3230 // first 8-point half-pel ring. Together they answer "how many of these 29
3231 // evaluations actually matter", which is the ceiling for any cheaper pattern.
3232 let (mut hv_to_best, mut hv_ring1) = (0u32, i64::MIN);
3233 // SHAPE-DISPATCHED SUB-PEL PATTERN (best_part campaign). Same reasoning as
3234 // the sub-partition diamond ladder: a sub-8x8 partition inherits a parent
3235 // MV that has ALREADY been full-pel searched and sub-pel refined, so the
3236 // expensive walk-to-convergence 8-point pattern is confirming an answer it
3237 // was handed. This module's own harvest sized that: ~29 evaluations per
3238 // refinement, last improvement at ~14-15 (half the work confirms), and the
3239 // first ring alone carries 64-72% of the gain. Sub-partitions take pattern
3240 // pattern 2 (8-point ring, SINGLE pass) unless a caller pinned one.
3241 // Swept all four on BD-rate vs no-splits (fine ladder active):
3242 //
3243 // pat foreman SSIM bus SSIM
3244 // 0 8pt+iterate -2.21 -5.49
3245 // 1 4pt+iterate -2.06 -5.49
3246 // 2 8pt+single -2.13 -5.64 <- best on bus, ~full on foreman
3247 // 3 4pt+single -2.03 -5.44
3248 //
3249 // The RING is what carries the gain; the ITERATION is confirmation. Pattern
3250 // 2 drops ~29 evals to ~8 and BEATS the full walk on bus — dropping the
3251 // ring as well (1, 3) is where quality actually goes. `RFF_SUBPEL_SUB`
3252 // overrides (0-3); `=0` restores the full pattern for sub-partitions.
3253 let sub_shape = rw < 8 || rh < 8;
3254 let mut pat = subpel_pattern_override().unwrap_or_else(|| {
3255 if sub_shape {
3256 static P: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
3257 *P.get_or_init(|| {
3258 std::env::var("RFF_SUBPEL_SUB").ok().and_then(|v| v.parse().ok()).unwrap_or(2)
3259 })
3260 } else if self.sp_single_pass {
3261 2
3262 } else {
3263 0
3264 }
3265 });
3266 let (sp_learn, sp_t) = sp_dispatch_cfg();
3267 // Only dispatch when the caller has not pinned a pattern (pat 0 = default).
3268 let sp_dispatching = sp_learn > 0 && pat == 0 && !subpel.is_empty();
3269 if sp_dispatching && self.sp_learn_n.get() >= sp_learn && self.sp_1pass.get() {
3270 pat = 2;
3271 }
3272 // Descent D-2 MEMO. The ring walks around a MOVING centre, so iteration N+1's
3273 // ring necessarily re-contains the previous centre and several previous ring
3274 // points: 27-44% of sub-pel evaluations re-price an MV this refinement already
3275 // priced. `cost()` is PURE in `mv` (rate from mv-centre; distortion from the
3276 // fixed reference/source/block captures), so memoizing is EXACT -- identical
3277 // costs, identical comparisons, identical chosen MV, byte-identical output.
3278 // A miss simply recomputes, so the table's hit rate is a SPEED property only.
3279 //
3280 // 64-entry direct-mapped on the low bits of the MV, tagged with the full MV so
3281 // a collision is a miss rather than a wrong answer. Stack-resident (1 KiB) and
3282 // re-initialized per refinement: measured cheaper than a thread-local + RefCell
3283 // borrow on every evaluation, since ~60% of lookups miss.
3284 const SP_MEMO_N: usize = 64;
3285 #[inline(always)]
3286 fn sp_slot(mv: (i32, i32)) -> usize {
3287 ((mv.0 & 7) as usize) | (((mv.1 & 7) as usize) << 3)
3288 }
3289 let mut memo_mv = [(i32::MIN, i32::MIN); SP_MEMO_N];
3290 let mut memo_c = [0i64; SP_MEMO_N];
3291 if !subpel.is_empty() {
3292 let s0 = sp_slot(best);
3293 memo_mv[s0] = best;
3294 memo_c[s0] = best_c;
3295 }
3296 // Descent D-2 census: the ring walks around a MOVING centre, so iteration N+1's ring
3297 // necessarily re-contains the previous centre and several previous ring points.
3298 // Count how many sub-pel evaluations price an MV this refinement ALREADY priced
3299 // -- redundant recompute is byte-identically removable, unlike dropping work.
3300 #[cfg(feature = "profile")]
3301 let mut seen: Vec<(i32, i32)> = Vec::with_capacity(64);
3302 #[cfg(feature = "profile")]
3303 {
3304 seen.push(best);
3305 }
3306 // Track-B B3: the sub-pel iteration BUDGET. The ring walks until no
3307 // improvement; Descent D's census says iteration 1 carries 55% of evals at
3308 // an 11-13% hit rate, iteration 2 another 35-40% at 1.5-2.5%, and the tail
3309 // past that almost never pays — but under B2's SAD-chosen starts the tail
3310 // GROWS (+27% ns/search), eating the SAD savings. A cap bounds the walk the
3311 // way x264's fixed subme budget does. 0 (default) = unlimited =
3312 // byte-identical; bitstream-changing otherwise → BD-gated, opt-in.
3313 let sp_cap = sp_maxit();
3314 // ③: batched fixed-centre half-pel ring (see `sp_fc_enabled`).
3315 #[cfg_attr(not(accel_x86), allow(unused_variables))] // consumed by accel_x86-only blocks
3316 let sp_fc = sp_fc_enabled() && !self.fast && cfg!(accel_x86)
3317 && matches!((rw, rh), (16, 16) | (16, 8) | (8, 16) | (8, 8));
3318 for &step in subpel {
3319 // Snapping starts this refine from an integer centre instead of the
3320 // seed's own fractional lattice, so a single 8-point pass can leave
3321 // precision behind. Walk it until it stops improving to compensate —
3322 // the snap is what pays for the extra probes.
3323 let ring8 = [
3324 (step, 0), (-step, 0), (0, step), (0, -step),
3325 (step, step), (-step, -step), (step, -step), (-step, step),
3326 ];
3327 let ring4 = [(step, 0), (-step, 0), (0, step), (0, -step)];
3328 let ring: &[(i32, i32)] = if pat & 1 != 0 { &ring4 } else { &ring8 };
3329 let mut _iter = 0u32;
3330 loop {
3331 // ③: from an INTEGER centre at step 2, all 8 ring candidates are
3332 // single-plane reads (h/h/v/v axes, c/c/c/c diagonals) — batch them
3333 // as two x4 kernel calls and take the argmin (first-wins in ring
3334 // order). Any decline (edge, half-pel centre, ring4 pattern) falls
3335 // through to the cascading walk for this pass.
3336 // ③b: the QUARTER step — every ±1 offset makes a component odd, so
3337 // all 8 candidates are two-plane average pairs regardless of the
3338 // centre's phase; two `satd_avg_x4` calls cover the ring.
3339 #[cfg(accel_x86)]
3340 if sp_fc && step == 1 && pat & 1 == 0 {
3341 _iter += 1;
3342 let hp8 = hp.expect("sp_fc implies non-fast, which resolves hp");
3343 let ring8 = [
3344 (1, 0), (-1, 0), (0, 1), (0, -1),
3345 (1, 1), (-1, -1), (1, -1), (-1, 1),
3346 ];
3347 let mut prs: [Option<(&[u8], usize, &[u8], usize, usize)>; 8] = [None; 8];
3348 let mut all = true;
3349 for (i, &(dx, dy)) in ring8.iter().enumerate() {
3350 prs[i] = rusty_h264_common::inter::hpel_qpel_refs(
3351 hp8, lx, ly, rw, rh, best.0 + dx, best.1 + dy,
3352 );
3353 all &= prs[i].is_some();
3354 }
3355 if all {
3356 let stride = prs[0].unwrap().4;
3357 // Batch kernel for 16-wide only (8-wide measured slower
3358 // batched than the per-candidate fused path — H-8 gate);
3359 // either way the SAME argmin over the SAME values.
3360 let pack = |a: usize, b: usize, c2: usize, d: usize| {
3361 if rw != 16 {
3362 return None;
3363 }
3364 let g = |i: usize| {
3365 let (pa, oa, pb, ob, _) = prs[i].unwrap();
3366 (pa, oa, pb, ob)
3367 };
3368 rusty_h264_accel::satd_avg_x4(
3369 src_row, cw, [g(a), g(b), g(c2), g(d)], stride, rw, rh,
3370 )
3371 };
3372 {
3373 let (ax, di) = (pack(0, 1, 2, 3), pack(4, 5, 6, 7));
3374 let (mut bi, mut bc) = (usize::MAX, best_c);
3375 for i in 0..8 {
3376 let (dx, dy) = ring8[i];
3377 let mv = (best.0 + dx, best.1 + dy);
3378 let cc = match (i < 4, &ax, &di) {
3379 (true, Some(ax), _) => {
3380 let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
3381 ax[i] as i64 + (lambda_me * rate as f64) as i64
3382 }
3383 (false, _, Some(di)) => {
3384 let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
3385 di[i - 4] as i64 + (lambda_me * rate as f64) as i64
3386 }
3387 _ => cost(mv),
3388 };
3389 hv_evals += 1;
3390 if cc < bc {
3391 bc = cc;
3392 bi = i;
3393 }
3394 }
3395 if hv_ring1 == i64::MIN {
3396 hv_ring1 = if bi == usize::MAX { best_c } else { bc };
3397 }
3398 if bi == usize::MAX
3399 || !self.me_subpel_iter
3400 || pat & 2 != 0
3401 || (sp_cap != 0 && _iter >= sp_cap)
3402 {
3403 if bi != usize::MAX {
3404 best_c = bc;
3405 best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
3406 hv_to_best = hv_evals;
3407 }
3408 break;
3409 }
3410 best_c = bc;
3411 best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
3412 hv_to_best = hv_evals;
3413 continue;
3414 }
3415 }
3416 _iter -= 1;
3417 }
3418 #[cfg(accel_x86)]
3419 if sp_fc && step == 2 && best.0 & 3 == 0 && best.1 & 3 == 0 && pat & 1 == 0 {
3420 _iter += 1;
3421 let hp8 = hp.expect("sp_fc implies non-fast, which resolves hp");
3422 let ring8 = [
3423 (step, 0), (-step, 0), (0, step), (0, -step),
3424 (step, step), (-step, -step), (step, -step), (-step, step),
3425 ];
3426 let mut refs8: [Option<(&[u8], usize, usize)>; 8] = [None; 8];
3427 let mut all = true;
3428 for (i, &(dx, dy)) in ring8.iter().enumerate() {
3429 refs8[i] = rusty_h264_common::inter::hpel_ref(
3430 hp8, lx, ly, rw, rh, best.0 + dx, best.1 + dy,
3431 );
3432 all &= refs8[i].is_some();
3433 }
3434 if all {
3435 let stride = refs8[0].unwrap().2;
3436 // 16-wide batches; 8-wide evaluates per candidate (H-8 gate)
3437 // — identical values, identical argmin, identical bitstream.
3438 let pack = |a: usize, b: usize, c2: usize, d: usize| {
3439 if rw != 16 {
3440 return None;
3441 }
3442 let g = |i: usize| {
3443 let (p, o, _) = refs8[i].unwrap();
3444 (p, o)
3445 };
3446 rusty_h264_accel::satd_x4p(
3447 src_row, cw, [g(a), g(b), g(c2), g(d)], stride, rw, rh,
3448 )
3449 };
3450 {
3451 let (ax, di) = (pack(0, 1, 2, 3), pack(4, 5, 6, 7));
3452 let (mut bi, mut bc) = (usize::MAX, best_c);
3453 for i in 0..8 {
3454 let (dx, dy) = ring8[i];
3455 let mv = (best.0 + dx, best.1 + dy);
3456 let cc = match (i < 4, &ax, &di) {
3457 (true, Some(ax), _) => {
3458 let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
3459 ax[i] as i64 + (lambda_me * rate as f64) as i64
3460 }
3461 (false, _, Some(di)) => {
3462 let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
3463 di[i - 4] as i64 + (lambda_me * rate as f64) as i64
3464 }
3465 _ => cost(mv),
3466 };
3467 hv_evals += 1;
3468 if cc < bc {
3469 bc = cc;
3470 bi = i;
3471 }
3472 }
3473 if hv_ring1 == i64::MIN {
3474 hv_ring1 = if bi == usize::MAX { best_c } else { bc };
3475 }
3476 if bi == usize::MAX
3477 || !self.me_subpel_iter
3478 || pat & 2 != 0
3479 || (sp_cap != 0 && _iter >= sp_cap)
3480 {
3481 if bi != usize::MAX {
3482 best_c = bc;
3483 best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
3484 hv_to_best = hv_evals;
3485 }
3486 break;
3487 }
3488 best_c = bc;
3489 best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
3490 hv_to_best = hv_evals;
3491 continue;
3492 }
3493 }
3494 _iter -= 1; // declined — the cascade pass below re-counts it
3495 }
3496 let mut improved = false;
3497 _iter += 1;
3498 for (_pi, &(dx, dy)) in ring.iter().enumerate() {
3499 let c = (best.0 + dx, best.1 + dy);
3500 let slot = sp_slot(c);
3501 let cc = if memo_mv[slot] == c {
3502 memo_c[slot]
3503 } else {
3504 let v = cost(c);
3505 memo_mv[slot] = c;
3506 memo_c[slot] = v;
3507 v
3508 };
3509 hv_evals += 1;
3510 // Descent D: which ring POSITION and which ITERATION actually pay?
3511 // Same census that showed the diamond's coarse rungs were noise,
3512 // aimed at the stage that is now 41% of encode.
3513 #[cfg(feature = "profile")]
3514 {
3515 spstats::ev(if step == 2 { 0 } else { 1 }, _pi, _iter);
3516 if seen.contains(&c) {
3517 spstats::redundant();
3518 } else {
3519 seen.push(c);
3520 }
3521 }
3522 if cc < best_c {
3523 best_c = cc;
3524 best = c;
3525 improved = true;
3526 hv_to_best = hv_evals;
3527 #[cfg(feature = "profile")]
3528 spstats::imp(if step == 2 { 0 } else { 1 }, _pi, _iter);
3529 }
3530 }
3531 if hv_ring1 == i64::MIN {
3532 hv_ring1 = best_c;
3533 }
3534 if !improved
3535 || !self.me_subpel_iter
3536 || pat & 2 != 0
3537 || (sp_cap != 0 && _iter >= sp_cap)
3538 {
3539 break;
3540 }
3541 }
3542 }
3543 if sp_dispatching {
3544 let n = self.sp_learn_n.get();
3545 if n < sp_learn {
3546 self.sp_learn_n.set(n + 1);
3547 if hv_ring1 != i64::MIN {
3548 self.sp_ring1.set(self.sp_ring1.get() + (hv_pre - hv_ring1).max(0));
3549 self.sp_total.set(self.sp_total.get() + (hv_pre - best_c).max(0));
3550 }
3551 if n + 1 == sp_learn {
3552 let tot = self.sp_total.get();
3553 // Concentrated in ring 1 -> the later rings are affordable to drop.
3554 self.sp_1pass.set(tot > 0 && self.sp_ring1.get() * 100 >= tot * sp_t);
3555 }
3556 }
3557 }
3558 if !subpel.is_empty() && subpel_harvest::enabled() {
3559 subpel_harvest::record(hv_pre, best_c, lambda_me, rw, rh, hv_evals, hv_to_best, hv_ring1);
3560 }
3561 // The snap moved the search off the seed; if the seed was better after all,
3562 // keep it. This is what makes the snap safe by construction.
3563 if self.me_snap && seed_c < best_c {
3564 best = seed_mv;
3565 best_c = seed_c;
3566 }
3567 (best, best_c)
3568 }
3569
3570 /// Encodes macroblock `(mb_x, mb_y)` as an inter macroblock of the given
3571 /// `mode` (0 = P_L0_16x16, 1 = P_16x8, 2 = P_8x16) with one motion vector
3572 /// per partition: motion-compensate each partition, code the macroblock
3573 /// residual, and reconstruct.
3574 #[allow(clippy::too_many_arguments)]
3575 /// Dispatch to the current coded path (`_v1`) or the isolated fused path
3576 /// (`_v2`), selected by the hidden `coded_path_v2` A/B knob. Both must produce
3577 /// byte-identical bitstreams (gated by the `coded_path_ab` test); the split
3578 /// exists so the two run side-by-side in one binary for honest timing.
3579 #[allow(clippy::too_many_arguments)]
3580 fn encode_inter_mb(
3581 &mut self,
3582 w: &mut BitWriter,
3583 refs: &[crate::RefFrame],
3584 sy: &[u8],
3585 su: &[u8],
3586 sv: &[u8],
3587 mb_x: usize,
3588 mb_y: usize,
3589 mode: u8,
3590 parts: &[(i32, (i32, i32))],
3591 ) {
3592 // RDOQ fork at the DISPATCH: `_v2` is the accel-only coefficient-fused
3593 // path and HARD-quantizes — it has no trellis support in either its
3594 // luma or chroma quant. Under a non-zero `rdoq_strength` (B slices
3595 // ship `cabac_rdoq_b = 32` by default; P dispatches on grain/screen)
3596 // it must yield to `_v1`, or accel and scalar builds emit DIFFERENT
3597 // inter bitstreams. Found as the SECOND divergent site while gating
3598 // the first (the `_v1` accel chroma arm) — the twins lesson: the
3599 // divergence class is "accel quant without an rdoq fork", and it had
3600 // two members.
3601 if self.coded_path_v2 && self.rdoq_strength <= 0.0 {
3602 self.encode_inter_mb_v2(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
3603 } else {
3604 self.encode_inter_mb_v1(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
3605 }
3606 }
3607
3608 /// Isolated, coefficient-fused inter coding path (A/B twin of `_v1`). The
3609 /// quantized luma levels stay in the hot 16-byte-aligned i16 DCT buffer for the
3610 /// whole MB; the i32 form is materialized on demand only for *coded* blocks
3611 /// (CAVLC scan + recon dequant), so uncoded quads never pay the conversion and
3612 /// there is no 256-word i32 `q_blocks` round-trip. Byte-identical to `_v1`
3613 /// (gated by `coded_path_ab`). Accel-only optimization; the scalar build reuses
3614 /// `_v1` unchanged.
3615 #[allow(clippy::too_many_arguments)]
3616 fn encode_inter_mb_v2(
3617 &mut self,
3618 w: &mut BitWriter,
3619 refs: &[crate::RefFrame],
3620 sy: &[u8],
3621 su: &[u8],
3622 sv: &[u8],
3623 mb_x: usize,
3624 mb_y: usize,
3625 mode: u8,
3626 parts: &[(i32, (i32, i32))],
3627 ) {
3628 // Descent E/F: identify this mc_luma population by call site.
3629 #[cfg(feature = "profile")]
3630 let _site = rusty_h264_common::inter::mcstats::SiteTag::new(1);
3631 #[cfg(not(accel))]
3632 {
3633 self.encode_inter_mb_v1(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
3634 }
3635 #[cfg(accel)]
3636 {
3637 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncInterCode);
3638 let (qp, qpc) = (self.qp, self.qpc);
3639 let w4 = self.mb_w * 4;
3640 let (_ch, cch) = (self.mb_h * 16, self.mb_h * 8);
3641
3642 // ---- per-partition motion compensation + MV prediction (== v1) ----
3643 let mut pred_y = [0u8; 256];
3644 let mut c_pred = [[0u8; 64]; 2];
3645 let mut mvds = [(0i32, 0i32); 4];
3646 let mut n_mvd = 0;
3647 let _g_mc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
3648 for (part, &(rx, ry, rw, rh)) in inter_partitions(mode).iter().enumerate() {
3649 let (refi, mv) = parts[part];
3650 let reference = &refs[refi as usize];
3651 let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
3652 let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
3653 let pmv = predict_partition_mv(mode, part, a, b, c, refi);
3654 mvds[n_mvd] = (mv.0 - pmv.0, mv.1 - pmv.1);
3655 n_mvd += 1;
3656 for by in ry / 4..ry / 4 + rh / 4 {
3657 let d = (mb_y * 4 + by) * w4 + mb_x * 4 + rx / 4;
3658 let n4 = rw / 4;
3659 self.mv_y[d..d + n4].fill(mv);
3660 self.inter_y[d..d + n4].fill(true);
3661 self.ref_idx_y[d..d + n4].fill(refi);
3662 self.coded_y[d..d + n4].fill(true);
3663 }
3664 if rw == 16 && rh == 16 {
3665 self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
3666 self.wp_luma(refi, &mut pred_y, 0, 0, 16, 16);
3667 } else {
3668 let mut tmp = [0u8; 256];
3669 self.mc_luma_cached(reference, mb_x * 16 + rx, mb_y * 16 + ry, rw, rh, mv.0, mv.1, &mut tmp);
3670 // H-17: the per-pixel re-stride was the runtime-width copy trap
3671 // (a bounds-checked store per pixel); const-width row copies are
3672 // byte-identical and lower to inline moves.
3673 if rw == 8 {
3674 for dy in 0..rh {
3675 pred_y[(ry + dy) * 16 + rx..][..8].copy_from_slice(&tmp[dy * 8..][..8]);
3676 }
3677 } else {
3678 for dy in 0..rh {
3679 pred_y[(ry + dy) * 16 + rx..][..16].copy_from_slice(&tmp[dy * 16..][..16]);
3680 }
3681 }
3682 self.wp_luma(refi, &mut pred_y, rx, ry, rw, rh);
3683 }
3684 let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
3685 for cc in 0..2 {
3686 let rc = if cc == 0 { &reference.u } else { &reference.v };
3687 if crw == 8 && crh == 8 {
3688 mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut c_pred[cc]);
3689 } else {
3690 let mut tc = [0u8; 64];
3691 mc_chroma(rc, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
3692 // H-17: same const-width row-copy fix as luma.
3693 if crw == 4 {
3694 for dy in 0..crh {
3695 c_pred[cc][(cry + dy) * 8 + crx..][..4].copy_from_slice(&tc[dy * 4..][..4]);
3696 }
3697 } else {
3698 for dy in 0..crh {
3699 c_pred[cc][(cry + dy) * 8 + crx..][..8].copy_from_slice(&tc[dy * 8..][..8]);
3700 }
3701 }
3702 }
3703 }
3704 }
3705
3706 // ---- luma residual + quantization: keep levels in the i16 buffer ----
3707 let mut dctw = AlignedDct([0i16; 256]);
3708 let dct = &mut dctw.0;
3709 let mut cbp_luma = 0u32;
3710 drop(_g_mc);
3711 let _g_tq = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncTq);
3712 let base = mb_y * 16 * self.cw + mb_x * 16;
3713 for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
3714 rusty_h264_accel::dct_four_t4(
3715 &mut dct[qi * 64..qi * 64 + 64],
3716 &sy[base + qy * self.cw + qx..],
3717 self.cw,
3718 &pred_y[qy * 16 + qx..],
3719 16,
3720 );
3721 }
3722 let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
3723 let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
3724 for qi in 0..4 {
3725 rusty_h264_accel::quant_four_4x4(&mut dct[qi * 64..qi * 64 + 64], &ff, mf);
3726 }
3727 // cbp per quad straight from the i16 levels (no i32 q_blocks copy).
3728 for blk in 0..16 {
3729 if dct[blk * 16..blk * 16 + 16].iter().any(|&v| v != 0) {
3730 cbp_luma |= 1 << (blk / 4);
3731 }
3732 }
3733
3734 // ---- chroma residual (identical to v1: c_q stays i32) ----
3735 let mut c_dc_levels = [[0i32; 4]; 2];
3736 let mut c_recon_dc = [[0i32; 4]; 2];
3737 let mut c_q = [[[0i32; 16]; 4]; 2];
3738 let (mut any_ac, mut any_dc) = (false, false);
3739 for c in 0..2 {
3740 let src = if c == 0 { su } else { sv };
3741 let dc2x2 = {
3742 #[repr(align(16))]
3743 struct A([i16; 64]);
3744 let mut cdct = A([0i16; 64]);
3745 rusty_h264_accel::dct_four_t4(
3746 &mut cdct.0,
3747 &src[(mb_y * 8) * self.ccw + mb_x * 8..],
3748 self.ccw,
3749 &c_pred[c],
3750 8,
3751 );
3752 let dc = [cdct.0[0] as i32, cdct.0[16] as i32, cdct.0[32] as i32, cdct.0[48] as i32];
3753 let ffc = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
3754 let mfc = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
3755 rusty_h264_accel::quant_four_4x4(&mut cdct.0, &ffc, mfc);
3756 for i in 0..4 {
3757 let q = &mut c_q[c][i];
3758 q[0] = 0;
3759 for j in 1..16 {
3760 let v = cdct.0[i * 16 + j] as i32;
3761 q[j] = v;
3762 if v != 0 {
3763 any_ac = true;
3764 }
3765 }
3766 }
3767 dc
3768 };
3769 let dl = forward_quant_chroma_dc(&dc2x2, qpc, false);
3770 if dl.iter().any(|&v| v != 0) {
3771 any_dc = true;
3772 }
3773 c_recon_dc[c] = inverse_quant_chroma_dc(&dl, qpc);
3774 c_dc_levels[c] = dl;
3775 }
3776 let cbp_chroma: u32 = if any_ac { 2 } else if any_dc { 1 } else { 0 };
3777 let cbp = cbp_luma | (cbp_chroma << 4);
3778
3779 // ---- emit syntax (== v1) ----
3780 drop(_g_tq);
3781 let _g_syn = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
3782 w.write_ue(mode as u32);
3783 // P_8x8 (mb_type 3): sub_mb_type per 8×8 (spec 7.3.5.2) — v1 parity;
3784 // omitting these desynced every v2 P_8x8 macroblock.
3785 if mode == 3 {
3786 for _ in 0..4 {
3787 w.write_ue(0);
3788 }
3789 }
3790 let num_refs = refs.len();
3791 if num_refs > 1 {
3792 for &(refi, _) in parts {
3793 write_ref_idx(w, refi, num_refs);
3794 }
3795 }
3796 for &(mvdx, mvdy) in &mvds[..n_mvd] {
3797 w.write_se(mvdx);
3798 w.write_se(mvdy);
3799 }
3800 write_cbp_inter(w, cbp);
3801 // transform_size_8x8_flag (v1 parity): v2 codes 4×4-transform residual
3802 // only, but the flag must be PRESENT whenever the stream enables the
3803 // 8×8 transform — the decoder reads it unconditionally for
3804 // cbp_luma != 0. Every shape v2 emits allows it (P_8x8 subs are all
3805 // P_L0_8x8), so presence == (cbp_luma > 0 && transform_8x8).
3806 if cbp_luma > 0 && self.transform_8x8 {
3807 w.write_bit(false);
3808 }
3809 if cbp != 0 {
3810 w.write_se(self.qp_delta()); // mb_qp_delta (AQ per-MB QPy)
3811 }
3812 self.nnz_cache_load(mb_x, mb_y);
3813 drop(_g_syn);
3814
3815 // ---- CAVLC: scan straight from the i16 levels for coded blocks ----
3816 let _g_scan = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Scatter);
3817 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3818 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
3819 let total = if cbp_luma & (1 << (blk / 4)) != 0 {
3820 let nc = self.nc_pred(lbx, lby);
3821 let scan16 = scan_4x4_dcac_i16(&dct[blk * 16..blk * 16 + 16]);
3822 encode_residual_block(w, &scan16, 16, nc) as u8
3823 } else {
3824 0
3825 };
3826 self.nnz_cache_set(lbx, lby, total);
3827 self.nnz_y[by * w4 + bx] = total;
3828 }
3829 if cbp_chroma != 0 {
3830 for c in 0..2 {
3831 encode_residual_block(w, &c_dc_levels[c], 4, -1);
3832 }
3833 }
3834 if cbp_chroma == 2 {
3835 self.chroma_cache_load(mb_x, mb_y);
3836 let w2 = self.mb_w * 2;
3837 for c in 0..2 {
3838 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
3839 let nc = self.chroma_nc_pred(c, bx, by);
3840 let ac = scan_4x4_ac(&c_q[c][by * 2 + bx]);
3841 let total = encode_residual_block(w, &ac, 15, nc) as u8;
3842 self.chroma_nnz_cache_set(c, bx, by, total);
3843 self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
3844 }
3845 }
3846 }
3847 drop(_g_scan);
3848
3849 // ---- reconstruction: dequantize luma straight from the i16 levels ----
3850 let _g_rec = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
3851 #[repr(align(16))]
3852 struct Align16([i16; 64]);
3853 let mut dct_in = Align16([0i16; 64]);
3854 for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
3855 let rec_off = base + qy * self.cw + qx;
3856 if cbp_luma & (1 << qi) == 0 {
3857 for r in 0..8 {
3858 let (dsti, srci) = (rec_off + r * self.cw, (qy + r) * 16 + qx);
3859 self.rec_y[dsti..dsti + 8].copy_from_slice(&pred_y[srci..srci + 8]);
3860 }
3861 continue;
3862 }
3863 for k in 0..4 {
3864 let blk = qi * 4 + k;
3865 let mut lvl = [0i32; 16];
3866 for i in 0..16 {
3867 lvl[i] = dct[blk * 16 + i] as i32;
3868 }
3869 let deq = dequantize(&lvl, qp);
3870 for i in 0..16 {
3871 dct_in.0[k * 16 + i] = deq[i] as i16;
3872 }
3873 }
3874 rusty_h264_accel::idct_four_t4_rec(
3875 &mut self.rec_y[rec_off..],
3876 self.cw,
3877 &pred_y[qy * 16 + qx..],
3878 16,
3879 &dct_in.0,
3880 );
3881 }
3882 // chroma recon (identical to v1)
3883 for c in 0..2 {
3884 let base_c = (mb_y * 8) * self.ccw + mb_x * 8;
3885 let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
3886 if cbp_chroma == 0 {
3887 for r in 0..8 {
3888 let dsti = base_c + r * self.ccw;
3889 plane[dsti..dsti + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
3890 }
3891 } else {
3892 #[repr(align(16))]
3893 struct A([i16; 64]);
3894 let mut d = A([0i16; 64]);
3895 for i in 0..4 {
3896 let deq = dequantize(&c_q[c][i], qpc);
3897 for j in 0..16 {
3898 d.0[i * 16 + j] = deq[j] as i16;
3899 }
3900 d.0[i * 16] = c_recon_dc[c][i] as i16;
3901 }
3902 rusty_h264_accel::idct_four_t4_rec(&mut plane[base_c..], self.ccw, &c_pred[c], 8, &d.0);
3903 }
3904 }
3905 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
3906 self.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 2;
3907 }
3908 }
3909 }
3910
3911 #[allow(clippy::too_many_arguments)]
3912 fn encode_inter_mb_v1(
3913 &mut self,
3914 w: &mut BitWriter,
3915 refs: &[crate::RefFrame],
3916 sy: &[u8],
3917 su: &[u8],
3918 sv: &[u8],
3919 mb_x: usize,
3920 mb_y: usize,
3921 mode: u8,
3922 parts: &[(i32, (i32, i32))],
3923 ) {
3924 self.encode_inter_mb_v1_b(w, refs, sy, su, sv, mb_x, mb_y, mode, parts, None);
3925 }
3926
3927 /// As [`Self::encode_inter_mb_v1`], but `b_mode` selects B-slice framing: the
3928 /// macroblock is coded as `B_L0_16x16` (`mb_type == 1`) instead of the P-slice
3929 /// `mb_type == mode`. Everything else — the single List-0 partition, the median
3930 /// `mvd_l0` predictor, the residual, and the reconstruction — is byte-identical
3931 /// to `P_L0_16x16`, so the caller passes `mode == 0`, `refs == &[L0_anchor]`
3932 /// (length 1 ⇒ no `ref_idx` coded), and `parts == &[(0, mv)]`.
3933 /// Decide + reconstruct one inter macroblock (motion compensation, residual,
3934 /// quantize, reconstruct, commit motion grids) — everything except entropy
3935 /// coding. Returns an [`InterPlan`] coded by either backend, so CAVLC and CABAC
3936 /// share this whole path bit-for-bit (the P/B analogue of [`plan_mb`]).
3937 #[allow(clippy::too_many_arguments)]
3938 fn plan_inter_mb(
3939 &mut self,
3940 refs: &[crate::RefFrame],
3941 sy: &[u8],
3942 su: &[u8],
3943 sv: &[u8],
3944 mb_x: usize,
3945 mb_y: usize,
3946 mode: u8,
3947 parts: &[(i32, (i32, i32))],
3948 bspec: Option<BInter>,
3949 sub_types: [u8; 4],
3950 ) -> InterPlan {
3951 crate::signals::census::work(crate::signals::census::W_MB_PLAN);
3952 // Descent E/F: identify this mc_luma population by call site.
3953 #[cfg(feature = "profile")]
3954 let _site = rusty_h264_common::inter::mcstats::SiteTag::new(1);
3955 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncInterCode);
3956 let (qp, qpc) = (self.qp, self.qpc);
3957 let w4 = self.mb_w * 4;
3958 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
3959
3960 // ---- per-partition motion compensation + MV prediction ----
3961 let mut pred_y = [0u8; 256];
3962 let mut c_pred = [[0u8; 64]; 2];
3963 let mut mvds = [(0i32, 0i32); 16]; // ≤16 sub-partitions; no per-MB Vec alloc
3964 let mut plan_refs = [0i32; 4]; // per-partition ref_idx_l0 (0 for B / 1-ref)
3965 let mut n_mvd = 0;
3966 let _g_mc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
3967 // The SPLIT test must come FIRST. `mvmode > 0` means a 16x8/8x16 partition
3968 // beat every 16x16 mode INCLUDING direct, and when it beat direct the caller
3969 // leaves `dir` at 0 -- so a `dir == 0` test placed ahead of this one claims
3970 // the macroblock, reconstructs B_Direct, and emits nothing into `mvds`, while
3971 // `emit_mb_cabac_b` still writes the split mb_type. The decoder then reads a
3972 // B_Bi_16x8 with two zero mvds where the encoder reconstructed direct motion.
3973 // Measured: 34 macroblocks per 6 frames, -2.4 dB luma, and INVISIBLE to the
3974 // conformance matrix -- both decoders agree with each other, they just
3975 // disagree with the encoder.
3976 if let Some(b) = bspec.filter(|b| b.mvmode > 0) {
3977 // ---- B 16x8 / 8x16: two partitions, each L0 / L1 / Bi ----
3978 // Prediction and commit run PARTITION-major (partition 1 predicts off
3979 // partition 0's committed motion, exactly as the decoder's recon does);
3980 // the mvds are then serialised LIST-major for the emit, which is the
3981 // spec 7.3.5.1 order the decoder parses.
3982 let (rects, _) = b_part_layout(b.mvmode);
3983 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
3984 let mut pm: [[(i32, i32); 2]; 2] = [[(0, 0); 2]; 2]; // [part][list] mvd
3985 for (part, &(rx, ry, rw, rh)) in rects.iter().enumerate() {
3986 let (pred, mv0, mv1) = b.parts2[part];
3987 let (u0, u1) = (pred == 1 || pred == 3, pred == 2 || pred == 3);
3988 let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
3989 if u0 {
3990 let [a, c0, c1] = self.mv_neighbors_block_list(pbx, pby, (rw / 4) as isize, 0);
3991 let p = predict_partition_mv(b.mvmode, part, a, c0, c1, 0);
3992 pm[part][0] = (mv0.0 - p.0, mv0.1 - p.1);
3993 }
3994 if u1 {
3995 let [a, c0, c1] = self.mv_neighbors_block_list(pbx, pby, (rw / 4) as isize, 1);
3996 let p = predict_partition_mv(b.mvmode, part, a, c0, c1, 0);
3997 pm[part][1] = (mv1.0 - p.0, mv1.1 - p.1);
3998 }
3999 // Motion compensation for this rect.
4000 let (lx, ly) = (mb_x * 16 + rx, mb_y * 16 + ry);
4001 let (cx, cy) = (mb_x * 8 + rx / 2, mb_y * 8 + ry / 2);
4002 let (cw2, ch2) = (rw / 2, rh / 2);
4003 let mut ay = [0u8; 256];
4004 let mut by_ = [0u8; 256];
4005 let mut ac = [[0u8; 64]; 2];
4006 let mut bc = [[0u8; 64]; 2];
4007 if u0 {
4008 mc_luma(&refs[0].y, self.cw, ch, lx, ly, rw, rh, mv0.0, mv0.1, &mut ay);
4009 mc_chroma(&refs[0].u, self.ccw, cch, cx, cy, cw2, ch2, mv0.0, mv0.1, &mut ac[0]);
4010 mc_chroma(&refs[0].v, self.ccw, cch, cx, cy, cw2, ch2, mv0.0, mv0.1, &mut ac[1]);
4011 }
4012 if u1 {
4013 mc_luma(&b.l1.y, self.cw, ch, lx, ly, rw, rh, mv1.0, mv1.1, &mut by_);
4014 mc_chroma(&b.l1.u, self.ccw, cch, cx, cy, cw2, ch2, mv1.0, mv1.1, &mut bc[0]);
4015 mc_chroma(&b.l1.v, self.ccw, cch, cx, cy, cw2, ch2, mv1.0, mv1.1, &mut bc[1]);
4016 }
4017 for r in 0..rh {
4018 for c in 0..rw {
4019 let d = (ry + r) * 16 + rx + c;
4020 let sidx = r * rw + c;
4021 pred_y[d] = match (u0, u1) {
4022 (true, true) => bi_blend(ay[sidx] as i32, by_[sidx] as i32, self.bi_w),
4023 (true, false) => ay[sidx],
4024 _ => by_[sidx],
4025 };
4026 }
4027 }
4028 for cc in 0..2 {
4029 for r in 0..ch2 {
4030 for c in 0..cw2 {
4031 let d = (ry / 2 + r) * 8 + rx / 2 + c;
4032 let sidx = r * cw2 + c;
4033 c_pred[cc][d] = match (u0, u1) {
4034 (true, true) => bi_blend(ac[cc][sidx] as i32, bc[cc][sidx] as i32, self.bi_w),
4035 (true, false) => ac[cc][sidx],
4036 _ => bc[cc][sidx],
4037 };
4038 }
4039 }
4040 }
4041 // Commit this partition before the next one predicts.
4042 let (cmv0, cr0) = (if u0 { mv0 } else { (0, 0) }, if u0 { 0 } else { -1 });
4043 let (cmv1, cr1) = (if u1 { mv1 } else { (0, 0) }, if u1 { 0 } else { -1 });
4044 for by2 in ry / 4..(ry + rh) / 4 {
4045 let d = (mb_y * 4 + by2) * w4 + mb_x * 4 + rx / 4;
4046 let n4 = rw / 4;
4047 self.inter_y[d..d + n4].fill(true);
4048 self.coded_y[d..d + n4].fill(true);
4049 self.mv_y[d..d + n4].fill(cmv0);
4050 self.ref_idx_y[d..d + n4].fill(cr0);
4051 self.mv1_y[d..d + n4].fill(cmv1);
4052 self.ref_idx1_y[d..d + n4].fill(cr1);
4053 }
4054 }
4055 // Serialise LIST-major: all L0 mvds, then all L1.
4056 for list in 0..2 {
4057 for part in 0..2 {
4058 let pred = b.parts2[part].0;
4059 let used = if list == 0 { pred == 1 || pred == 3 } else { pred == 2 || pred == 3 };
4060 if used {
4061 mvds[n_mvd] = pm[part][list];
4062 n_mvd += 1;
4063 }
4064 }
4065 }
4066 } else if let Some(b) = bspec.filter(|b| b.dir == 0) {
4067 // ---- B_Direct_16x16 (mb_type 0): spatial-direct prediction, no mvd ----
4068 let (dp, dc, motion) = self.b_direct(&refs[0], b.l1, mb_x, mb_y);
4069 pred_y = dp;
4070 c_pred = dc;
4071 self.commit_direct_motion(mb_x, mb_y, &motion);
4072 } else if let Some(b) = bspec {
4073 // ---- B 16×16 prediction: List-0 / List-1 / Bi ----
4074 let use0 = b.dir == 1 || b.dir == 3;
4075 let use1 = b.dir == 2 || b.dir == 3;
4076 let (lx, ly) = (mb_x * 16, mb_y * 16);
4077 let (cx, cy) = (mb_x * 8, mb_y * 8);
4078 let (pbx, pby) = ((mb_x * 4) as isize, (mb_y * 4) as isize);
4079 // Per-list `mvd` against the median predictor over that list's neighbors.
4080 if use0 {
4081 let [a, c0, c1] = self.mv_neighbors_block_list(pbx, pby, 4, 0);
4082 let p = predict_partition_mv(0, 0, a, c0, c1, 0);
4083 mvds[n_mvd] = (b.mv0.0 - p.0, b.mv0.1 - p.1);
4084 n_mvd += 1;
4085 }
4086 if use1 {
4087 let [a, c0, c1] = self.mv_neighbors_block_list(pbx, pby, 4, 1);
4088 let p = predict_partition_mv(0, 0, a, c0, c1, 0);
4089 mvds[n_mvd] = (b.mv1.0 - p.0, b.mv1.1 - p.1);
4090 n_mvd += 1;
4091 }
4092 // Motion compensation. L0/L1 write straight into pred; Bi averages
4093 // (p+q+1)>>1 — the decoder's `b_mc` blend with weighted_bipred_idc=0.
4094 let mut a_y = [0u8; 256];
4095 let mut b_y = [0u8; 256];
4096 let mut a_c = [[0u8; 64]; 2];
4097 let mut b_c = [[0u8; 64]; 2];
4098 if use0 {
4099 mc_luma(&refs[0].y, self.cw, ch, lx, ly, 16, 16, b.mv0.0, b.mv0.1, &mut a_y);
4100 mc_chroma(&refs[0].u, self.ccw, cch, cx, cy, 8, 8, b.mv0.0, b.mv0.1, &mut a_c[0]);
4101 mc_chroma(&refs[0].v, self.ccw, cch, cx, cy, 8, 8, b.mv0.0, b.mv0.1, &mut a_c[1]);
4102 }
4103 if use1 {
4104 mc_luma(&b.l1.y, self.cw, ch, lx, ly, 16, 16, b.mv1.0, b.mv1.1, &mut b_y);
4105 mc_chroma(&b.l1.u, self.ccw, cch, cx, cy, 8, 8, b.mv1.0, b.mv1.1, &mut b_c[0]);
4106 mc_chroma(&b.l1.v, self.ccw, cch, cx, cy, 8, 8, b.mv1.0, b.mv1.1, &mut b_c[1]);
4107 }
4108 match (use0, use1) {
4109 (true, true) => {
4110 for i in 0..256 {
4111 pred_y[i] = bi_blend(a_y[i] as i32, b_y[i] as i32, self.bi_w);
4112 }
4113 for c in 0..2 {
4114 for i in 0..64 {
4115 c_pred[c][i] = bi_blend(a_c[c][i] as i32, b_c[c][i] as i32, self.bi_w);
4116 }
4117 }
4118 }
4119 (true, false) => {
4120 pred_y = a_y;
4121 c_pred = a_c;
4122 }
4123 _ => {
4124 pred_y = b_y;
4125 c_pred = b_c;
4126 }
4127 }
4128 // Commit per-list motion so later MBs' per-list predictors see it.
4129 // Row fills (E1, 11.16): every value is CONSTANT across the MB.
4130 let (cmv0, cr0) = (if use0 { b.mv0 } else { (0, 0) }, if use0 { 0 } else { -1 });
4131 let (cmv1, cr1) = (if use1 { b.mv1 } else { (0, 0) }, if use1 { 0 } else { -1 });
4132 for by in 0..4 {
4133 let d = (mb_y * 4 + by) * w4 + mb_x * 4;
4134 self.inter_y[d..d + 4].fill(true);
4135 self.coded_y[d..d + 4].fill(true);
4136 self.mv_y[d..d + 4].fill(cmv0);
4137 self.ref_idx_y[d..d + 4].fill(cr0);
4138 self.mv1_y[d..d + 4].fill(cmv1);
4139 self.ref_idx1_y[d..d + 4].fill(cr1);
4140 }
4141 } else if mode == 3 && sub_types != [0u8; 4] {
4142 // ---- P_8x8 with sub-partitions (Great Gate P3.3) ------------------
4143 // Mirrors the decoder's `decode_p8x8` EXACTLY: per sub-partition in
4144 // decode order, median-predict from the COMMITTED grid (plain
4145 // `predict_mv` -- the 16x8/8x16 directional rules do not apply to
4146 // sub-partitions), derive the mvd, commit, then motion-compensate.
4147 // `parts` is FLAT in decode order; `ref_idx` is per 8x8 (spec: its
4148 // sub-partitions share it).
4149 let mut k = 0usize;
4150 for p8 in 0..4usize {
4151 let (b8x, b8y) = ((p8 % 2) * 8, (p8 / 2) * 8);
4152 plan_refs[p8] = parts[k].0;
4153 for &(srx, sry, srw, srh) in sub_mb_partitions_p(sub_types[p8]) {
4154 let (refi, mv) = parts[k];
4155 debug_assert_eq!(refi, plan_refs[p8], "ref_idx_l0 is per 8x8");
4156 k += 1;
4157 let (px, py) = (b8x + srx, b8y + sry);
4158 let (pbx, pby) =
4159 ((mb_x * 4 + px / 4) as isize, (mb_y * 4 + py / 4) as isize);
4160 let [a, b, c] = self.mv_neighbors_block(pbx, pby, (srw / 4) as isize);
4161 let pmv = predict_mv(a, b, c, refi);
4162 mvds[n_mvd] = (mv.0 - pmv.0, mv.1 - pmv.1);
4163 n_mvd += 1;
4164 // Commit BEFORE the next sub-partition predicts (chaining --
4165 // exactly the decoder's order).
4166 for by in py / 4..(py + srh) / 4 {
4167 for bx in px / 4..(px + srw) / 4 {
4168 let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
4169 self.mv_y[idx] = mv;
4170 self.inter_y[idx] = true;
4171 self.ref_idx_y[idx] = refi;
4172 self.coded_y[idx] = true;
4173 }
4174 }
4175 // Luma + chroma MC into the sub-region (parametric kernels --
4176 // 4-wide takes the scalar fall-through; the 4-wide kernel
4177 // brick is the recorded default-on precondition).
4178 let reference = &refs[refi as usize];
4179 let mut tmp = [0u8; 256];
4180 mc_luma(&reference.y, self.cw, ch, mb_x * 16 + px, mb_y * 16 + py, srw, srh, mv.0, mv.1, &mut tmp);
4181 for dy in 0..srh {
4182 pred_y[(py + dy) * 16 + px..][..srw].copy_from_slice(&tmp[dy * srw..][..srw]);
4183 }
4184 self.wp_luma(refi, &mut pred_y, px, py, srw, srh);
4185 let (crx, cry, crw, crh) = (px / 2, py / 2, srw / 2, srh / 2);
4186 for cc in 0..2 {
4187 let rc = if cc == 0 { &reference.u } else { &reference.v };
4188 let mut tc = [0u8; 64];
4189 mc_chroma(rc, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
4190 for dy in 0..crh {
4191 c_pred[cc][(cry + dy) * 8 + crx..][..crw].copy_from_slice(&tc[dy * crw..][..crw]);
4192 }
4193 }
4194 }
4195 }
4196 } else {
4197 for (part, &(rx, ry, rw, rh)) in inter_partitions(mode).iter().enumerate() {
4198 let (refi, mv) = parts[part];
4199 plan_refs[part] = refi; // per-partition ref_idx_l0 → carried to the CABAC emit
4200 let reference = &refs[refi as usize];
4201 let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
4202 let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
4203 let pmv = predict_partition_mv(mode, part, a, b, c, refi);
4204 mvds[n_mvd] = (mv.0 - pmv.0, mv.1 - pmv.1);
4205 n_mvd += 1;
4206 // Commit this partition's motion so later partitions can predict from it.
4207 for by in ry / 4..ry / 4 + rh / 4 {
4208 for bx in rx / 4..rx / 4 + rw / 4 {
4209 let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
4210 self.mv_y[idx] = mv;
4211 self.inter_y[idx] = true;
4212 self.ref_idx_y[idx] = refi;
4213 self.coded_y[idx] = true;
4214 }
4215 }
4216 // Luma MC into the partition's sub-region. A full-MB (16×16) partition is
4217 // the whole `pred_y`, so MC straight into it — no scratch + repack copy.
4218 if rw == 16 && rh == 16 {
4219 self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
4220 } else {
4221 let mut tmp = [0u8; 256];
4222 self.mc_luma_cached(reference, mb_x * 16 + rx, mb_y * 16 + ry, rw, rh, mv.0, mv.1, &mut tmp);
4223 // H-17: const-width row copies (see the v2 twin).
4224 if rw == 8 {
4225 for dy in 0..rh {
4226 pred_y[(ry + dy) * 16 + rx..][..8].copy_from_slice(&tmp[dy * 8..][..8]);
4227 }
4228 } else {
4229 for dy in 0..rh {
4230 pred_y[(ry + dy) * 16 + rx..][..16].copy_from_slice(&tmp[dy * 16..][..16]);
4231 }
4232 }
4233 }
4234 self.wp_luma(refi, &mut pred_y, rx, ry, rw, rh);
4235 // Chroma MC (half-resolution region); 8×8 = the whole plane prediction.
4236 let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
4237 for cc in 0..2 {
4238 let rc = if cc == 0 { &reference.u } else { &reference.v };
4239 if crw == 8 && crh == 8 {
4240 mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut c_pred[cc]);
4241 } else {
4242 let mut tc = [0u8; 64];
4243 mc_chroma(rc, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
4244 // H-17: const-width row copies (see the v2 twin).
4245 if crw == 4 {
4246 for dy in 0..crh {
4247 c_pred[cc][(cry + dy) * 8 + crx..][..4].copy_from_slice(&tc[dy * 4..][..4]);
4248 }
4249 } else {
4250 for dy in 0..crh {
4251 c_pred[cc][(cry + dy) * 8 + crx..][..8].copy_from_slice(&tc[dy * 8..][..8]);
4252 }
4253 }
4254 }
4255 }
4256 }
4257 } // end P per-partition formation (else of the B branch)
4258
4259 // ---- luma residual + quantization ----
4260 let mut q_blocks = [[0i32; 16]; 16]; // raster, levels
4261 let mut cbp_luma = 0u32;
4262 // Inter 8x8-transform candidate (High profile, scalar path). Filled by the
4263 // per-MB 4x4-vs-8x8 RD below; false/zero means the 4x4 residual is used.
4264 #[allow(unused_mut)]
4265 let mut t8x8 = false;
4266 #[allow(unused_mut)]
4267 let mut q8 = [[0i32; 64]; 4];
4268 drop(_g_mc);
4269 let _g_tq = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncTq);
4270 #[cfg(accel)]
4271 {
4272 // openh264 `WelsDctFourT4_sse2` (fused residual+DCT) → i16, then
4273 // `WelsQuantFour4x4_sse2` in place — the whole DCT→quant chain stays in i16,
4274 // no i32 round-trip. Quant is openh264's structure carrying OUR deadzone
4275 // (`quant_dz_ff` + `QUANT_MF_OH`), so levels are bit-identical to `quantize`.
4276 let mut dctw = AlignedDct([0i16; 256]);
4277 let dct = &mut dctw.0;
4278 let base = mb_y * 16 * self.cw + mb_x * 16;
4279 for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
4280 rusty_h264_accel::dct_four_t4(
4281 &mut dct[qi * 64..qi * 64 + 64],
4282 &sy[base + qy * self.cw + qx..],
4283 self.cw,
4284 &pred_y[qy * 16 + qx..],
4285 16,
4286 );
4287 }
4288 if self.rdoq_strength > 0.0 {
4289 // Trellis for inter (Great Gate P2 — mirrors the I16 site): scalar
4290 // RDOQ from the asm DCT output instead of the asm hard quantizer.
4291 // Inter codes DC in the 4×4 (first=0) and uses the /6 deadzone —
4292 // exactly the scalar arm's `rdoq(&coeffs, qp, 6, strength, 0)`.
4293 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
4294 let coeffs: [i32; 16] = std::array::from_fn(|i| dct[blk * 16 + i] as i32);
4295 let q = rdoq(&coeffs, qp, 6, self.rdoq_strength, 0);
4296 if q.iter().any(|&v| v != 0) {
4297 cbp_luma |= 1 << (blk / 4);
4298 }
4299 q_blocks[lby * 4 + lbx] = q;
4300 }
4301 } else {
4302 let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
4303 let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
4304 for qi in 0..4 {
4305 rusty_h264_accel::quant_four_4x4(&mut dct[qi * 64..qi * 64 + 64], &ff, mf);
4306 }
4307 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
4308 let mut nz = false;
4309 for i in 0..16 {
4310 let v = dct[blk * 16 + i] as i32;
4311 q_blocks[lby * 4 + lbx][i] = v;
4312 nz |= v != 0;
4313 }
4314 if nz {
4315 cbp_luma |= 1 << (blk / 4);
4316 }
4317 }
4318 } // end hard-quantize arm (rdoq_strength == 0)
4319 }
4320 #[cfg(not(accel))]
4321 {
4322 // Scalar/`wide`: gather all 16 residual blocks, batched forward-DCT, quantize.
4323 let mut res_blocks = [[0i32; 16]; 16]; // raster
4324 for lby in 0..4 {
4325 for lbx in 0..4 {
4326 let b = &mut res_blocks[lby * 4 + lbx];
4327 for dy in 0..4 {
4328 let row = &sy[(mb_y * 16 + lby * 4 + dy) * self.cw + mb_x * 16 + lbx * 4..][..4];
4329 for dx in 0..4 {
4330 b[dy * 4 + dx] = row[dx] as i32
4331 - pred_y[(lby * 4 + dy) * 16 + (lbx * 4 + dx)] as i32;
4332 }
4333 }
4334 }
4335 }
4336 let mut coeffs = [[0i32; 16]; 16];
4337 forward_dct_blocks(&res_blocks, &mut coeffs);
4338 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
4339 let q = rdoq(&coeffs[lby * 4 + lbx], qp, 6, self.rdoq_strength, 0);
4340 if q.iter().any(|&v| v != 0) {
4341 cbp_luma |= 1 << (blk / 4);
4342 }
4343 q_blocks[lby * 4 + lbx] = q;
4344 }
4345 }
4346
4347 // Per-MB transform-size RD (runs in scalar AND accel builds — q_blocks +
4348 // cbp_luma are filled by whichever quant path ran; the 8x8 candidate + its
4349 // recon are pure Rust). One 8x8 DCT per 8x8 block vs four 4x4s.
4350 // Content-adaptive by construction — the winner is chosen per MB.
4351 //
4352 // `sub_types == [0;4]` IS the spec's `noSubMbPartSizeLessThan8x8Flag`
4353 // (7.3.5): transform_size_8x8_flag is FORBIDDEN when any sub-partition is
4354 // smaller than 8x8. The comment this replaces asserted "every inter
4355 // partition here is >= 8x8", which was true when it was written and stopped
4356 // being true when sub-8x8 shipped -- `sub_types` is a parameter of this very
4357 // function. A stale invariant in a comment is not a guard.
4358 {
4359 // `allow_t8` is the spec's condition for transform_size_8x8_flag being
4360 // PERMITTED at all, evaluated here rather than at emit time: if the plan
4361 // picked 8x8 for a macroblock that may not signal it, the flag would be
4362 // suppressed while our reconstruction still used the 8x8 transform --
4363 // encoder/decoder drift, which our own conformance gate cannot see.
4364 // * `sub_types == [0;4]` is noSubMbPartSizeLessThan8x8Flag.
4365 // * B_Direct_16x16 is permitted since direct_8x8_inference_flag = 1
4366 // in our SPS (its direct partitions count as 8×8).
4367 let allow_t8 = sub_types == [0u8; 4];
4368 if self.t8_pick && self.inter8x8 != 0 && allow_t8 {
4369 let lambda =
4370 0.85 * self.tune_lambda_scale * crate::fastmath::lambda_qp(qp);
4371 let mut ssd4 = 0i64;
4372 let mut rate4 = 0f64;
4373 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
4374 let mut predb = [0i32; 16];
4375 for dy in 0..4 {
4376 for dx in 0..4 {
4377 predb[dy * 4 + dx] =
4378 pred_y[(lby * 4 + dy) * 16 + (lbx * 4 + dx)] as i32;
4379 }
4380 }
4381 let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
4382 let s = reconstruct_4x4(&deq, &predb);
4383 for dy in 0..4 {
4384 for dx in 0..4 {
4385 let sx = mb_x * 16 + lbx * 4 + dx;
4386 let syy = mb_y * 16 + lby * 4 + dy;
4387 let d = s[dy * 4 + dx] as i64 - sy[syy * self.cw + sx] as i64;
4388 ssd4 += d * d;
4389 }
4390 }
4391 for &l in &q_blocks[lby * 4 + lbx] {
4392 if l != 0 {
4393 rate4 += rdoq_rate((l as i64).abs());
4394 }
4395 }
4396 }
4397 let (q8c, cbp8, rate8, _rec8, ssd8) =
4398 plan_inter8_luma(sy, self.cw, mb_x, mb_y, &pred_y, qp);
4399 // Both candidates priced with the SAME level-aware rate (Σ rdoq_rate);
4400 // `inter8_pen` is an optional extra bias (default 0) on the 8x8 flag.
4401 let j4 = ssd4 as f64 + lambda * (rate4 + 16.0);
4402 let j8 = ssd8 as f64 + lambda * (rate8 + 16.0 + self.inter8_pen as f64);
4403 if cbp8 > 0 && j8 < j4 {
4404 t8x8 = true;
4405 cbp_luma = cbp8;
4406 q8 = q8c;
4407 }
4408 }
4409 }
4410
4411 // ---- chroma residual (prediction already built per partition) ----
4412 let mut c_dc_levels = [[0i32; 4]; 2];
4413 let mut c_recon_dc = [[0i32; 4]; 2];
4414 let mut c_q = [[[0i32; 16]; 4]; 2];
4415 let (mut any_ac, mut any_dc) = (false, false);
4416 for c in 0..2 {
4417 let src = if c == 0 { su } else { sv };
4418 // Fast path: one dct_four_t4 covers the whole 8x8 chroma region (all 4
4419 // blocks, residual+DCT fused straight from the planes); block b's pre-quant
4420 // DC is dct[b*16] (quad z-scan == 2x2 raster); quant_four_4x4 with our
4421 // FF/MF is bit-identical to scalar `quantize`. Same pairing the P_Skip
4422 // free-check proved byte-identical over the corpus.
4423 #[cfg(accel)]
4424 let (mut dc2x2, applied) = if self.rdoq_strength > 0.0 {
4425 // RDOQ fork — the SAME fork every other accel quant site
4426 // carries (inter luma, I16 AC, intra chroma AC). The fused
4427 // dct+quant kernel HARD-quantizes, so under a non-zero trellis
4428 // strength it must yield to the scalar twin below. Without
4429 // this, accel and scalar builds emitted DIFFERENT bitstreams
4430 // for inter chroma AC the day `cabac_rdoq_b` shipped
4431 // default-on — the one path where the accel/scalar
4432 // byte-identity invariant silently did not hold.
4433 ([0i32; 4], false)
4434 } else {
4435 #[repr(align(16))]
4436 struct A([i16; 64]);
4437 let mut dct = A([0i16; 64]);
4438 rusty_h264_accel::dct_four_t4(
4439 &mut dct.0,
4440 &src[(mb_y * 8) * self.ccw + mb_x * 8..],
4441 self.ccw,
4442 &c_pred[c],
4443 8,
4444 );
4445 let dc = [
4446 dct.0[0] as i32,
4447 dct.0[16] as i32,
4448 dct.0[32] as i32,
4449 dct.0[48] as i32,
4450 ];
4451 let ffc = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
4452 let mfc = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
4453 rusty_h264_accel::quant_four_4x4(&mut dct.0, &ffc, mfc);
4454 for i in 0..4 {
4455 let q = &mut c_q[c][i];
4456 q[0] = 0;
4457 for j in 1..16 {
4458 let v = dct.0[i * 16 + j] as i32;
4459 q[j] = v;
4460 if v != 0 {
4461 any_ac = true;
4462 }
4463 }
4464 }
4465 (dc, true)
4466 };
4467 #[cfg(not(accel))]
4468 let (mut dc2x2, applied) = ([0i32; 4], false);
4469 if !applied {
4470 // Scalar/`wide` twin: gather, batch forward DCT, quantize per block.
4471 let mut res_blocks = [[0i32; 16]; 4];
4472 for by in 0..2 {
4473 for bx in 0..2 {
4474 let b = &mut res_blocks[by * 2 + bx];
4475 for dy in 0..4 {
4476 let row = &src[(mb_y * 8 + by * 4 + dy) * self.ccw + mb_x * 8 + bx * 4..][..4];
4477 for dx in 0..4 {
4478 b[dy * 4 + dx] = row[dx] as i32
4479 - c_pred[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
4480 }
4481 }
4482 }
4483 }
4484 let mut coeffs = [[0i32; 16]; 4];
4485 forward_dct_blocks(&res_blocks, &mut coeffs);
4486 for i in 0..4 {
4487 dc2x2[i] = coeffs[i][0];
4488 let mut q = rdoq(&coeffs[i], qpc, 6, self.rdoq_strength, 1);
4489 q[0] = 0;
4490 if q[1..].iter().any(|&v| v != 0) {
4491 any_ac = true;
4492 }
4493 c_q[c][i] = q;
4494 }
4495 }
4496 let dl = forward_quant_chroma_dc(&dc2x2, qpc, false);
4497 if dl.iter().any(|&v| v != 0) {
4498 any_dc = true;
4499 }
4500 c_recon_dc[c] = inverse_quant_chroma_dc(&dl, qpc);
4501 c_dc_levels[c] = dl;
4502 }
4503 let cbp_chroma: u32 = if any_ac { 2 } else if any_dc { 1 } else { 0 };
4504 let cbp = cbp_luma | (cbp_chroma << 4);
4505
4506 drop(_g_tq);
4507 let _g_rec = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
4508 // ---- reconstruction (luma) ----
4509 #[cfg(accel)]
4510 if t8x8 {
4511 // 8x8-transform recon is pure Rust (no asm 8x8 kernels yet); inverse of
4512 // the decoder's t8x8 inter path. Same code as the scalar branch below.
4513 let weight = [16i32; 64];
4514 for b8 in 0..4usize {
4515 let (b8x, b8y) = (b8 % 2, b8 / 2);
4516 let res_r = inverse_quant_8x8(&q8[b8], qp, &weight);
4517 let predb: [i32; 64] = std::array::from_fn(|i| {
4518 pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
4519 });
4520 let recon = add_residual_8x8(&res_r, &predb);
4521 for dy in 0..8 {
4522 let d = (mb_y * 16 + b8y * 8 + dy) * self.cw + mb_x * 16 + b8x * 8;
4523 self.rec_y[d..d + 8].copy_from_slice(&recon[dy * 8..dy * 8 + 8]);
4524 }
4525 }
4526 } else {
4527 // Dequantize all 16 blocks into the 4-quadrant int16 layout (16-byte
4528 // aligned — the kernel uses movdqa coeff loads), then inverse-DCT + add
4529 // prediction + clip per quadrant via openh264. The inverse butterfly +
4530 // (x+32)>>6 is bit-identical to reconstruct_4x4 (verified in accel).
4531 // An 8x8 quad whose cbp bit is clear has ZERO residual: reconstruction
4532 // IS the prediction (the decoder's own uncoded-region fast path) — a row
4533 // copy replaces dequant + convert + idct for that quad. Byte-identical:
4534 // idct of an all-zero block adds (0+32)>>6 = 0 to pred, clip is identity.
4535 #[repr(align(16))]
4536 struct Align16([i16; 64]);
4537 let mut dct_in = Align16([0i16; 64]);
4538 let base = mb_y * 16 * self.cw + mb_x * 16;
4539 for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
4540 let rec_off = base + qy * self.cw + qx;
4541 if cbp_luma & (1 << qi) == 0 {
4542 for r in 0..8 {
4543 let (dsti, srci) = (rec_off + r * self.cw, (qy + r) * 16 + qx);
4544 self.rec_y[dsti..dsti + 8].copy_from_slice(&pred_y[srci..srci + 8]);
4545 }
4546 continue;
4547 }
4548 for k in 0..4 {
4549 let blk = qi * 4 + k;
4550 let (lbx, lby) = LUMA_4X4_SCAN_XY[blk];
4551 let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
4552 for i in 0..16 {
4553 dct_in.0[k * 16 + i] = deq[i] as i16;
4554 }
4555 }
4556 rusty_h264_accel::idct_four_t4_rec(
4557 &mut self.rec_y[rec_off..],
4558 self.cw,
4559 &pred_y[qy * 16 + qx..],
4560 16,
4561 &dct_in.0,
4562 );
4563 }
4564 }
4565 #[cfg(not(accel))]
4566 if t8x8 {
4567 // 8x8-transform reconstruction (inverse of the decoder's t8x8 inter path).
4568 let weight = [16i32; 64];
4569 for b8 in 0..4usize {
4570 let (b8x, b8y) = (b8 % 2, b8 / 2);
4571 let res_r = inverse_quant_8x8(&q8[b8], qp, &weight);
4572 let predb: [i32; 64] = std::array::from_fn(|i| {
4573 pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
4574 });
4575 let recon = add_residual_8x8(&res_r, &predb);
4576 for dy in 0..8 {
4577 let d = (mb_y * 16 + b8y * 8 + dy) * self.cw + mb_x * 16 + b8x * 8;
4578 self.rec_y[d..d + 8].copy_from_slice(&recon[dy * 8..dy * 8 + 8]);
4579 }
4580 }
4581 } else {
4582 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
4583 let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
4584 // Fused recon straight from the u8 prediction (E1 r2; same
4585 // transplant as plan_i4x4's) — no i32 predb, no temp, no store.
4586 reconstruct_4x4_into(
4587 &deq,
4588 &pred_y,
4589 (lby * 4) * 16 + lbx * 4,
4590 16,
4591 &mut self.rec_y,
4592 (mb_y * 16 + lby * 4) * self.cw + mb_x * 16 + lbx * 4,
4593 self.cw,
4594 );
4595 }
4596 }
4597 for c in 0..2 {
4598 // Fast path: dequantize into the quad i16 layout (raster == the kernel's
4599 // z-order for a 2x2) with the Hadamard DC injected, then ONE
4600 // idct+add-pred+clip kernel writes the 8x8 straight into the plane —
4601 // bit-identical to the scalar tail below (verified kernel pairing).
4602 #[cfg(accel)]
4603 {
4604 let base = (mb_y * 8) * self.ccw + mb_x * 8;
4605 let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
4606 if cbp_chroma == 0 {
4607 // No chroma residual at all: recon = prediction (row copies).
4608 for r in 0..8 {
4609 let dsti = base + r * self.ccw;
4610 plane[dsti..dsti + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
4611 }
4612 } else {
4613 #[repr(align(16))]
4614 struct A([i16; 64]);
4615 let mut d = A([0i16; 64]);
4616 for i in 0..4 {
4617 let deq = dequantize(&c_q[c][i], qpc);
4618 for j in 0..16 {
4619 d.0[i * 16 + j] = deq[j] as i16;
4620 }
4621 d.0[i * 16] = c_recon_dc[c][i] as i16;
4622 }
4623 rusty_h264_accel::idct_four_t4_rec(&mut plane[base..], self.ccw, &c_pred[c], 8, &d.0);
4624 }
4625 }
4626 #[cfg(not(accel))]
4627 {
4628 // Dequantize the 4 blocks (raster, DC overridden by the 2×2-Hadamard
4629 // recon), then batch the inverse DCT and share the add+clip tail.
4630 let mut deq_blocks = [[0i32; 16]; 4];
4631 for i in 0..4 {
4632 deq_blocks[i] = dequantize(&c_q[c][i], qpc);
4633 deq_blocks[i][0] = c_recon_dc[c][i];
4634 }
4635 let mut res = [[0i32; 16]; 4];
4636 inverse_dct_blocks(&deq_blocks, &mut res);
4637 let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
4638 for by in 0..2 {
4639 for bx in 0..2 {
4640 let mut predb = [0i32; 16];
4641 for dy in 0..4 {
4642 for dx in 0..4 {
4643 predb[dy * 4 + dx] = c_pred[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
4644 }
4645 }
4646 let s = add_residual_4x4(&res[by * 2 + bx], &predb);
4647 store(plane, self.ccw, mb_x * 8 + bx * 4, mb_y * 8 + by * 4, &s);
4648 }
4649 }
4650 }
4651 }
4652 // MV grid + coded flags were set per partition; mark modes as DC.
4653 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
4654 self.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 2;
4655 }
4656 InterPlan { mvds, plan_refs, n_mvd, cbp, q_blocks, c_dc_levels, c_q, t8x8, q8, sub_types }
4657 }
4658
4659 /// Code one planned inter macroblock as CAVLC (the original `encode_inter_mb_v1_b`
4660 /// tail). `plan_inter_mb` already committed the reconstruction + motion grids.
4661 #[allow(clippy::too_many_arguments)]
4662 fn encode_inter_mb_v1_b(
4663 &mut self,
4664 w: &mut BitWriter,
4665 refs: &[crate::RefFrame],
4666 sy: &[u8],
4667 su: &[u8],
4668 sv: &[u8],
4669 mb_x: usize,
4670 mb_y: usize,
4671 mode: u8,
4672 parts: &[(i32, (i32, i32))],
4673 bspec: Option<BInter>,
4674 ) {
4675 let plan = self.plan_inter_mb(refs, sy, su, sv, mb_x, mb_y, mode, parts, bspec, [0u8; 4]);
4676 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
4677 self.emit_inter_cavlc(w, refs.len(), mb_x, mb_y, mode, parts, bspec, &plan);
4678 }
4679
4680 /// CAVLC entropy coding for a planned inter macroblock.
4681 #[allow(clippy::too_many_arguments)]
4682 fn emit_inter_cavlc(
4683 &mut self,
4684 w: &mut BitWriter,
4685 num_refs: usize,
4686 mb_x: usize,
4687 mb_y: usize,
4688 mode: u8,
4689 parts: &[(i32, (i32, i32))],
4690 bspec: Option<BInter>,
4691 plan: &InterPlan,
4692 ) {
4693 let w4 = self.mb_w * 4;
4694 let (cbp, cbp_luma, cbp_chroma) = (plan.cbp, plan.cbp & 15, plan.cbp >> 4);
4695 // Same rule as the CABAC path (and as `plan_inter_mb`'s `allow_t8`):
4696 // with direct_8x8_inference_flag = 1 every shape we emit (incl.
4697 // B_Direct_16x16) may carry transform_size_8x8_flag.
4698 let allow8 = true;
4699 let _ = &bspec; // shape still consumed below for mb_type
4700 // mb_pred order (spec 7.3.5.1): mb_type, then all ref_idx_l0, then all mvd_l0.
4701 // B-slice mb_type = the B direction 1/2/3; P-slice uses `mode`. ref_idx coded
4702 // only when >1 reference is active.
4703 w.write_ue(bspec.map_or(mode as u32, |b| b.dir as u32)); // inter mb_type
4704 // P_8x8 (mb_type 3): sub_mb_type per 8×8 (spec 7.3.5.2, before ref_idx/mvd).
4705 // 0 = P_L0_8x8 (one MV) — the only shape emitted for now.
4706 if mode == 3 {
4707 for _ in 0..4 {
4708 w.write_ue(0);
4709 }
4710 }
4711 if num_refs > 1 {
4712 for &(refi, _) in parts {
4713 write_ref_idx(w, refi, num_refs);
4714 }
4715 }
4716 for &(mvdx, mvdy) in &plan.mvds[..plan.n_mvd] {
4717 w.write_se(mvdx);
4718 w.write_se(mvdy);
4719 }
4720 write_cbp_inter(w, cbp);
4721 // transform_size_8x8_flag: after cbp, before mb_qp_delta, present only when
4722 // luma has coefficients, the 8x8 transform is enabled, and the macroblock is
4723 // ALLOWED to signal it. Omitting `allow8` wrote the flag on B_Direct_16x16
4724 // macroblocks, which is exactly why CAVLC B + 8x8 produced an invalid slice
4725 // ("mb_type N in B slice too large") -- the defect the old blanket refusal
4726 // in `lib.rs` was masking rather than the missing feature it claimed.
4727 if cbp_luma > 0 && self.transform_8x8 && allow8 {
4728 w.write_bit(plan.t8x8);
4729 }
4730 if cbp != 0 {
4731 w.write_se(self.qp_delta()); // mb_qp_delta (AQ per-MB QPy)
4732 }
4733 self.nnz_cache_load(mb_x, mb_y);
4734 if plan.t8x8 {
4735 // 8x8 residual: four interleaved 4x4 CAVLC sub-blocks per 8x8 block
4736 // (coeff k of sub s -> 8x8 scan position 4k+s), the inverse of the
4737 // decoder's t8x8 inter luma read. nnz set per 4x4 sub-block.
4738 for b8 in 0..4usize {
4739 let (b8x, b8y) = (b8 % 2, b8 / 2);
4740 let scan8 = scan_8x8_fwd(&plan.q8[b8]);
4741 for sub in 0..4usize {
4742 let (cx, cy) = (b8x * 2 + sub % 2, b8y * 2 + sub / 2);
4743 let (bx, by) = (mb_x * 4 + cx, mb_y * 4 + cy);
4744 let total = if cbp_luma & (1 << b8) != 0 {
4745 let nc = self.nc_pred(cx, cy);
4746 let blk: [i32; 16] = std::array::from_fn(|k| scan8[4 * k + sub]);
4747 encode_residual_block(w, &blk, 16, nc) as u8
4748 } else {
4749 0
4750 };
4751 self.nnz_cache_set(cx, cy, total);
4752 self.nnz_y[by * w4 + bx] = total;
4753 }
4754 }
4755 } else {
4756 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
4757 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
4758 let total = if cbp_luma & (1 << (blk / 4)) != 0 {
4759 let nc = self.nc_pred(lbx, lby);
4760 let scan16 = scan_4x4_dcac(&plan.q_blocks[lby * 4 + lbx]);
4761 encode_residual_block(w, &scan16, 16, nc) as u8
4762 } else {
4763 0
4764 };
4765 self.nnz_cache_set(lbx, lby, total);
4766 self.nnz_y[by * w4 + bx] = total;
4767 }
4768 }
4769 if cbp_chroma != 0 {
4770 for c in 0..2 {
4771 encode_residual_block(w, &plan.c_dc_levels[c], 4, -1);
4772 }
4773 }
4774 if cbp_chroma == 2 {
4775 self.chroma_cache_load(mb_x, mb_y);
4776 let w2 = self.mb_w * 2;
4777 for c in 0..2 {
4778 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4779 let nc = self.chroma_nc_pred(c, bx, by);
4780 let ac = scan_4x4_ac(&plan.c_q[c][by * 2 + bx]);
4781 let total = encode_residual_block(w, &ac, 15, nc) as u8;
4782 self.chroma_nnz_cache_set(c, bx, by, total);
4783 self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
4784 }
4785 }
4786 }
4787 }
4788
4789 /// Descent F: reconstruction / skip-check MC through the cached half-pel planes
4790 /// instead of the per-pixel 6-tap. `hpel_block` is proven bit-identical to `mc_luma`
4791 /// (`hpel_block_matches_mc_luma_exactly`) and the `f` plane is the padded,
4792 /// edge-replicated reference, so both paths are BYTE-IDENTICAL; anything outside the
4793 /// padded plane still falls back to `mc_luma`.
4794 ///
4795 /// Census that motivated it: with the search's edge fallback fixed, `mc_luma` is
4796 /// 3.8-5.2% of encode and splits recon ~56-67% / skip-check ~24-35%, the latter at a
4797 /// content-independent one call per macroblock.
4798 #[inline]
4799 fn mc_luma_cached(
4800 &self,
4801 reference: &crate::RefFrame,
4802 x0: usize,
4803 y0: usize,
4804 bw: usize,
4805 bh: usize,
4806 mvx: i32,
4807 mvy: i32,
4808 out: &mut [u8],
4809 ) {
4810 let ch = self.mb_h * 16;
4811 let cw = self.cw;
4812 if !self.fast {
4813 let p = reference.hpel(cw, ch);
4814 if rusty_h264_common::inter::hpel_block(p, x0, y0, bw, bh, mvx, mvy, out) {
4815 return;
4816 }
4817 if let Some((plane, base, stride)) =
4818 rusty_h264_common::inter::hpel_ref(p, x0, y0, bw, bh, mvx, mvy)
4819 {
4820 for r in 0..bh {
4821 out[r * bw..r * bw + bw].copy_from_slice(&plane[base + r * stride..][..bw]);
4822 }
4823 return;
4824 }
4825 }
4826 mc_luma(&reference.y, cw, ch, x0, y0, bw, bh, mvx, mvy, out);
4827 }
4828
4829 /// Motion-compensates the `P_Skip` prediction (luma + both chroma) from
4830 /// reference 0 at the skip MV.
4831 /// Luma half of the P_Skip prediction. Split out so the fast path can test the
4832 /// luma residual first and only motion-compensate chroma when luma is free —
4833 /// for the majority of (non-free) macroblocks the chroma MC is never needed.
4834 /// Applies this slice's explicit LUMA weight for reference `r` to a region
4835 /// of a 16-stride prediction buffer — the encoder-side twin of the
4836 /// decoder's `weight_partition` luma arm, same integer form (denom 6:
4837 /// `((p·w + 32) >> 6) + o`, clamped). Identity/off skips in O(1).
4838 #[inline]
4839 fn wp_luma(&self, r: i32, buf: &mut [u8; 256], x0: usize, y0: usize, rw: usize, rh: usize) {
4840 let Some(&(lw, lo)) = self.wp.get(r.max(0) as usize) else { return };
4841 if lw == 64 && lo == 0 {
4842 return;
4843 }
4844 for dy in 0..rh {
4845 for p in buf[(y0 + dy) * 16 + x0..][..rw].iter_mut() {
4846 *p = (((*p as i32 * lw + 32) >> 6) + lo).clamp(0, 255) as u8;
4847 }
4848 }
4849 }
4850
4851 fn skip_predict_luma(
4852 &self,
4853 refs: &[crate::RefFrame],
4854 mb_x: usize,
4855 mb_y: usize,
4856 mv: (i32, i32),
4857 ) -> [u8; 256] {
4858 // Descent E/F: identify this mc_luma population by call site.
4859 #[cfg(feature = "profile")]
4860 let _site = rusty_h264_common::inter::mcstats::SiteTag::new(3);
4861 let reference = &refs[0]; // P_Skip always references index 0
4862 let mut pred_y = [0u8; 256];
4863 self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
4864 // P_Skip references index 0 — weighted like any other P prediction
4865 // (the decoder weights its P_Skip recon too).
4866 self.wp_luma(0, &mut pred_y, 0, 0, 16, 16);
4867 pred_y
4868 }
4869
4870 /// Chroma half of the P_Skip prediction (see [`Self::skip_predict_luma`]).
4871 fn skip_predict_chroma(
4872 &self,
4873 refs: &[crate::RefFrame],
4874 mb_x: usize,
4875 mb_y: usize,
4876 mv: (i32, i32),
4877 ) -> [[u8; 64]; 2] {
4878 let reference = &refs[0];
4879 let cch = self.mb_h * 8;
4880 let mut pred_c = [[0u8; 64]; 2];
4881 for c in 0..2 {
4882 let rc = if c == 0 { &reference.u } else { &reference.v };
4883 mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut pred_c[c]);
4884 }
4885 pred_c
4886 }
4887
4888 /// Whether the luma half of the P_Skip prediction has an all-zero quantized
4889 /// residual. Tested first and independently so the caller can defer the chroma
4890 /// MC + test for the common case where luma already disqualifies the skip (a
4891 /// "free", exact P_Skip costs no bits and is strictly beneficial).
4892 fn skip_luma_is_free(&self, sy: &[u8], mb_x: usize, mb_y: usize, pred_y: &[u8; 256]) -> bool {
4893 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFree);
4894 let qp = self.qp;
4895 // Fast path (deployment): the SAME asm kernels the coding path uses —
4896 // `dct_four_t4` computes the 4x4 DCTs of (src - pred) for an 8x8 quad
4897 // STRAIGHT FROM THE PLANES (no scalar gather), `quant_four_4x4` quantizes
4898 // with the identical FF/MF math as scalar `quantize` (bit-identical), and
4899 // "free" = all 64 levels zero, which is order-independent. Per-quad early
4900 // exit. The knob interleaves this against the scalar twin for A/B.
4901 #[cfg(accel)]
4902 if self.skip_accel_check {
4903 #[repr(align(16))]
4904 struct Align16([i16; 64]);
4905 let mut dct = Align16([0i16; 64]);
4906 let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
4907 let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
4908 for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
4909 rusty_h264_accel::dct_four_t4(
4910 &mut dct.0,
4911 &sy[(mb_y * 16 + qy) * self.cw + mb_x * 16 + qx..],
4912 self.cw,
4913 &pred_y[qy * 16 + qx..],
4914 16,
4915 );
4916 rusty_h264_accel::quant_four_4x4(&mut dct.0, &ff, mf);
4917 if dct.0.iter().any(|&v| v != 0) {
4918 return false;
4919 }
4920 }
4921 return true;
4922 }
4923 // Exact quantize-to-zero bounds (mirrors `quantize`: level != 0 iff
4924 // (|c| + ff[p])·mf_oh[p] >= 2^16). With |C_ij| <= 4·SAD (max |H| entry = 2)
4925 // and C_DC = Σres, most blocks are decided by one SAD/sum pass — the full
4926 // scalar DCT+quant proof only runs for the rare undecided middle band.
4927 // BIT-EXACT: both shortcuts are sufficient conditions of the exact check.
4928 let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
4929 // NINE integer divides per skip test became a table lookup.
4930 // `(65536 + mf - 1) / mf` is `ceil(65536 / mf)`, a pure function of
4931 // `mf = QUANT_MF_OH[qp][p]` -- 52 x 8 values, so the table is EXACT.
4932 // `t_dc` also re-divided the `p == 0` case the loop had just computed.
4933 let ceil8 = &rusty_h264_common::transform::CEIL_65536_MF[qp as usize];
4934 let mut t_min = i32::MAX;
4935 for p in 0..8 {
4936 t_min = t_min.min(ceil8[p] as i32 - ff[p] as i32);
4937 }
4938 let t_dc = ceil8[0] as i32 - ff[0] as i32;
4939 // Whole-MB gate: SAD(any 4x4) <= SAD(MB), so 4*SAD_MB < T_min proves all 16
4940 // blocks quantize to zero from ONE (psadbw) SAD. On skip-heavy content most
4941 // free MBs are exact/near-exact copies (SAD_MB ~ 0) - they skip the whole
4942 // per-block walk. Not-free MBs pay one extra SAD (~2% of their check).
4943 for by in 0..4 {
4944 for bx in 0..4 {
4945 let mut res = [0i32; 16];
4946 let (mut sad, mut dc) = (0i32, 0i32);
4947 for dy in 0..4 {
4948 let row = &sy[(mb_y * 16 + by * 4 + dy) * self.cw + mb_x * 16 + bx * 4..][..4];
4949 for dx in 0..4 {
4950 let d = row[dx] as i32
4951 - pred_y[(by * 4 + dy) * 16 + (bx * 4 + dx)] as i32;
4952 res[dy * 4 + dx] = d;
4953 sad += d.abs();
4954 dc += d;
4955 }
4956 }
4957 if 4 * sad < t_min {
4958 continue; // every |C| <= 4·SAD < T_min → all levels zero
4959 }
4960 if dc.abs() >= t_dc {
4961 return false; // DC level provably nonzero
4962 }
4963 if quantize(&forward_core(&res), qp, 6).iter().any(|&v| v != 0) {
4964 return false;
4965 }
4966 }
4967 }
4968 true
4969 }
4970
4971 /// Chroma half of [`Self::skip_is_free`].
4972 fn skip_chroma_is_free(
4973 &self,
4974 su: &[u8],
4975 sv: &[u8],
4976 mb_x: usize,
4977 mb_y: usize,
4978 pred_c: &[[u8; 64]; 2],
4979 ) -> bool {
4980 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFree);
4981 let qpc = self.qpc;
4982 // Fast path: one dct_four_t4 covers the whole 8x8 chroma plane region (all 4
4983 // blocks, residual+DCT fused, no scalar gather). Block order is the quad's
4984 // z-scan == raster for 2x2, so block b's DC (pre-quant) sits at dct[b*16] —
4985 // exactly the dc2x2 the Hadamard check needs. quant_four_4x4 with our FF/MF
4986 // is bit-identical to scalar `quantize`; AC-free = positions 1..16 all zero.
4987 #[cfg(accel)]
4988 if self.skip_accel_check {
4989 #[repr(align(16))]
4990 struct Align16C([i16; 64]);
4991 let mut dct = Align16C([0i16; 64]);
4992 let ff = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
4993 let mf = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
4994 for c in 0..2 {
4995 let src = if c == 0 { su } else { sv };
4996 rusty_h264_accel::dct_four_t4(
4997 &mut dct.0,
4998 &src[(mb_y * 8) * self.ccw + mb_x * 8..],
4999 self.ccw,
5000 &pred_c[c],
5001 8,
5002 );
5003 let dc2x2 = [
5004 dct.0[0] as i32,
5005 dct.0[16] as i32,
5006 dct.0[32] as i32,
5007 dct.0[48] as i32,
5008 ];
5009 rusty_h264_accel::quant_four_4x4(&mut dct.0, &ff, mf);
5010 for b in 0..4 {
5011 if dct.0[b * 16 + 1..b * 16 + 16].iter().any(|&v| v != 0) {
5012 return false;
5013 }
5014 }
5015 if forward_quant_chroma_dc(&dc2x2, qpc, false).iter().any(|&v| v != 0) {
5016 return false;
5017 }
5018 }
5019 return true;
5020 }
5021 for c in 0..2 {
5022 let src = if c == 0 { su } else { sv };
5023 let mut dc2x2 = [0i32; 4];
5024 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
5025 let mut res = [0i32; 16];
5026 // Row slices (11.15): the LUMA twin got this shape in the
5027 // transcendentals round (23 bc -> 2); the chroma twin was missed.
5028 for dy in 0..4 {
5029 let row = &src[(mb_y * 8 + by * 4 + dy) * self.ccw + mb_x * 8 + bx * 4..][..4];
5030 for dx in 0..4 {
5031 res[dy * 4 + dx] = row[dx] as i32
5032 - pred_c[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
5033 }
5034 }
5035 let coeffs = forward_core(&res);
5036 dc2x2[by * 2 + bx] = coeffs[0];
5037 if quantize(&coeffs, qpc, 6)[1..].iter().any(|&v| v != 0) {
5038 return false;
5039 }
5040 }
5041 if forward_quant_chroma_dc(&dc2x2, qpc, false).iter().any(|&v| v != 0) {
5042 return false;
5043 }
5044 }
5045 true
5046 }
5047
5048 /// SSD between the source and a macroblock prediction (luma + chroma).
5049 #[allow(clippy::too_many_arguments)]
5050 fn pred_ssd(
5051 &self,
5052 sy: &[u8],
5053 su: &[u8],
5054 sv: &[u8],
5055 mb_x: usize,
5056 mb_y: usize,
5057 pred_y: &[u8; 256],
5058 pred_c: &[[u8; 64]; 2],
5059 ) -> i64 {
5060 let mut ssd = 0i64;
5061 for dy in 0..16 {
5062 let i = (mb_y * 16 + dy) * self.cw + mb_x * 16;
5063 for (a, b) in sy[i..i + 16].iter().zip(&pred_y[dy * 16..dy * 16 + 16]) {
5064 let d = *a as i64 - *b as i64;
5065 ssd += d * d;
5066 }
5067 }
5068 for c in 0..2 {
5069 let src = if c == 0 { su } else { sv };
5070 for dy in 0..8 {
5071 let i = (mb_y * 8 + dy) * self.ccw + mb_x * 8;
5072 for (a, b) in src[i..i + 8].iter().zip(&pred_c[c][dy * 8..dy * 8 + 8]) {
5073 let d = *a as i64 - *b as i64;
5074 ssd += d * d;
5075 }
5076 }
5077 }
5078 ssd
5079 }
5080
5081 /// SSD between the *reconstructed* macroblock and the source.
5082 fn mb_ssd(&self, sy: &[u8], su: &[u8], sv: &[u8], mb_x: usize, mb_y: usize) -> i64 {
5083 let mut ssd = 0i64;
5084 // TWO bounds checks per pixel (source and recon) became two per ROW.
5085 for dy in 0..16 {
5086 let i = (mb_y * 16 + dy) * self.cw + mb_x * 16;
5087 for (a, b) in sy[i..i + 16].iter().zip(&self.rec_y[i..i + 16]) {
5088 let d = *a as i64 - *b as i64;
5089 ssd += d * d;
5090 }
5091 }
5092 // CHROMA WEIGHT. Chroma SSD is summed at 1:1 with luma here, but every
5093 // metric this decision is graded on is LUMA (`ssim_y`, Y-PSNR), and a
5094 // 4:2:0 macroblock carries half as many chroma samples as luma, so an
5095 // equal-weight sum lets chroma steer a decision the grader cannot see.
5096 // Worst on the most chroma-rich content in the corpus. Default 1.0 =
5097 // byte-identical; the sweep lives in the gate ledger.
5098 let cw_ = chroma_ssd_weight();
5099 if cw_ != 0.0 {
5100 let mut cssd = 0i64;
5101 for c in 0..2 {
5102 let (src, rec) = if c == 0 { (su, &self.rec_u) } else { (sv, &self.rec_v) };
5103 for dy in 0..8 {
5104 let i = (mb_y * 8 + dy) * self.ccw + mb_x * 8;
5105 for (a, b) in src[i..i + 8].iter().zip(&rec[i..i + 8]) {
5106 let d = *a as i64 - *b as i64;
5107 cssd += d * d;
5108 }
5109 }
5110 }
5111 ssd += if cw_ == 1.0 { cssd } else { (cssd as f64 * cw_) as i64 };
5112 }
5113 ssd
5114 }
5115
5116 /// Reconstructs a `P_Skip` macroblock (reconstruction *is* the prediction —
5117 /// no residual coded) and records its motion state.
5118 /// Writes a macroblock's prediction into the recon planes verbatim, with NO
5119 /// motion-state side effects (the B path commits motion via
5120 /// `commit_direct_motion`). B_Skip used to skip this entirely — legal while
5121 /// B recon had no reader, but intra-in-B predicts from these planes, and a
5122 /// stale neighbour is encoder-recon drift the two-decoder gate cannot see
5123 /// (it shows up as a quality drop at unchanged bytes).
5124 fn commit_pred_pixels(&mut self, mb_x: usize, mb_y: usize, pred_y: &[u8; 256], pred_c: &[[u8; 64]; 2]) {
5125 let base = mb_y * 16 * self.cw + mb_x * 16;
5126 for r in 0..16 {
5127 let d = base + r * self.cw;
5128 self.rec_y[d..d + 16].copy_from_slice(&pred_y[r * 16..r * 16 + 16]);
5129 }
5130 let cbase = mb_y * 8 * self.ccw + mb_x * 8;
5131 for c in 0..2 {
5132 let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
5133 for r in 0..8 {
5134 let d = cbase + r * self.ccw;
5135 plane[d..d + 8].copy_from_slice(&pred_c[c][r * 8..r * 8 + 8]);
5136 }
5137 }
5138 }
5139
5140 fn commit_skip(
5141 &mut self,
5142 mb_x: usize,
5143 mb_y: usize,
5144 mv: (i32, i32),
5145 pred_y: &[u8; 256],
5146 pred_c: &[[u8; 64]; 2],
5147 ) {
5148 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
5149 // Skip recon = the prediction verbatim: straight row copies (byte-identical
5150 // to the old per-4x4 gather + store scatter, ~5x fewer ops).
5151 let base = mb_y * 16 * self.cw + mb_x * 16;
5152 for r in 0..16 {
5153 let d = base + r * self.cw;
5154 self.rec_y[d..d + 16].copy_from_slice(&pred_y[r * 16..r * 16 + 16]);
5155 }
5156 let cbase = mb_y * 8 * self.ccw + mb_x * 8;
5157 for c in 0..2 {
5158 let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
5159 for r in 0..8 {
5160 let d = cbase + r * self.ccw;
5161 plane[d..d + 8].copy_from_slice(&pred_c[c][r * 8..r * 8 + 8]);
5162 }
5163 }
5164 self.set_mb_mv(mb_x, mb_y, mv, true, 0);
5165 let w4 = self.mb_w * 4;
5166 for row in 0..4 {
5167 let st = (mb_y * 4 + row) * w4 + mb_x * 4;
5168 self.modes_y[st..st + 4].fill(2);
5169 self.coded_y[st..st + 4].fill(true);
5170 }
5171 }
5172
5173 /// Trial-encodes an inter macroblock to measure its rate-distortion cost
5174 /// `(SSD, bits)` without committing: snapshot the macroblock's grid + recon
5175 /// region, run the real `encode_inter_mb` into a scratch writer, read the
5176 /// bit count and reconstruction SSD, then restore. Neighbor CAVLC context is
5177 /// read (not mutated), so the bit count is accurate.
5178 #[allow(clippy::too_many_arguments)]
5179 fn trial_inter(
5180 &mut self,
5181 refs: &[crate::RefFrame],
5182 sy: &[u8],
5183 su: &[u8],
5184 sv: &[u8],
5185 mb_x: usize,
5186 mb_y: usize,
5187 mode: u8,
5188 parts: &[(i32, (i32, i32))],
5189 ) -> (i64, usize) {
5190 let snap = self.save_mb(mb_x, mb_y);
5191 let mut scratch = BitWriter::new();
5192 self.encode_inter_mb(&mut scratch, refs, sy, su, sv, mb_x, mb_y, mode, parts);
5193 let bits = scratch.bit_len();
5194 let ssd = self.mb_ssd(sy, su, sv, mb_x, mb_y);
5195 self.load_mb(mb_x, mb_y, &snap);
5196 (ssd, bits)
5197 }
5198
5199 /// Trial-encodes the macroblock as **intra** (`encode_mb` runs its own
5200 /// I_16x16-vs-I_4x4 decision), measuring `(SSD, bits)` without committing —
5201 /// the intra candidate for the RD mode decision.
5202 /// Trial-encodes THIS macroblock as coded (`plan_mb` picks intra vs the
5203 /// already-chosen inter) and returns `(recon SSD, real bits)`, restoring
5204 /// every grid it touched. The RD currency for a mode decision — see
5205 /// `intra_rd_on`.
5206 fn trial_intra(
5207 &mut self,
5208 sy: &[u8],
5209 su: &[u8],
5210 sv: &[u8],
5211 mb_x: usize,
5212 mb_y: usize,
5213 is_p: bool,
5214 ) -> (i64, usize) {
5215 let mut snap = enc_scratch::take_snap_b();
5216 self.save_mb_into(mb_x, mb_y, &mut snap);
5217 // E2 r2: a fresh BitWriter (heap Vec) was built PER RD TRIAL; recycle
5218 // this thread's — `clear()` keeps the grown allocation.
5219 let mut scratch = enc_scratch::take_bits();
5220 scratch.clear();
5221 encode_mb(self, &mut scratch, mb_x, mb_y, sy, su, sv, is_p);
5222 let bits = scratch.bit_len();
5223 enc_scratch::put_bits(scratch);
5224 let ssd = self.mb_ssd(sy, su, sv, mb_x, mb_y);
5225 self.load_mb(mb_x, mb_y, &snap);
5226 enc_scratch::put_snap_b(snap);
5227 (ssd, bits)
5228 }
5229
5230 /// Best `(ref_idx, mv, cost)` for one partition by `SATD + λ·bits`, searched
5231 /// across every reference (`cost` is that SATD-domain rate-distortion cost).
5232 /// `extra` seeds the search with already-found MVs (e.g. the 16×16 result when
5233 /// refining a sub-partition).
5234 #[allow(clippy::too_many_arguments)]
5235 #[inline]
5236 fn best_part(
5237 &self,
5238 refs: &[crate::RefFrame],
5239 sy: &[u8],
5240 nb: &[MvNeighbor; 3],
5241 num_refs: usize,
5242 rx: usize,
5243 ry: usize,
5244 rw: usize,
5245 rh: usize,
5246 extra: &[(i32, i32)],
5247 lme: f64,
5248 ) -> (i32, (i32, i32), i64) {
5249 crate::signals::census::work(crate::signals::census::W_BEST_PART);
5250 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMe);
5251 let [a, b, c] = *nb;
5252 let (mut br, mut bmv, mut bc) = (0i32, (0, 0), i64::MAX);
5253 for r in 0..num_refs {
5254 // EXACT ref_bits prune (multiref campaign): reference `r`'s total
5255 // cost is `motion_cost + λ·ref_bits(r)` with `motion_cost >= 0`
5256 // (SATD plus a nonnegative mvd rate), so it is bounded below by
5257 // the λ·ref_bits term alone. Once the incumbent `bc` is at or
5258 // under that bound, reference `r` cannot win the strict `<` — and
5259 // `ref_bits` is NONDECREASING in `r` (Exp-Golomb length; pinned by
5260 // `ref_bits_is_monotone`), so neither can any later reference:
5261 // `break`, byte-identically. Fires exactly where multi-ref costs
5262 // the most for the least — near-perfect ref-0 matches on cheap
5263 // partitions at high λ.
5264 let rb = (lme * ref_bits(r, num_refs) as f64) as i64;
5265 if rb >= bc {
5266 break;
5267 }
5268 crate::signals::census::work(crate::signals::census::W_REF_SEARCH);
5269 // STACK seeds. This was `vec![..]` + `extend_from_slice` — a heap
5270 // allocation on EVERY search, and the sub-8x8 split arm took the call
5271 // count from 50k to 208k per clip, so it is ~208k allocations whose
5272 // payload is at most three MVs. `extra` is [] / [mv16] / [mv16, mv]
5273 // at every call site; the assert pins that rather than trusting it.
5274 debug_assert!(extra.len() <= 3, "seed budget");
5275 let mut sbuf = [(0i32, 0i32); 4];
5276 sbuf[0] = predict_mv(a, b, c, r as i32);
5277 let n = 1 + extra.len().min(3);
5278 sbuf[1..n].copy_from_slice(&extra[..n - 1]);
5279 let seeds = &sbuf[..n];
5280 let (mv, cost) = self.motion_search(&refs[r], sy, rx, ry, rw, rh, &seeds, lme, None);
5281 let cost = cost + rb;
5282 if cost < bc {
5283 bc = cost;
5284 br = r as i32;
5285 bmv = mv;
5286 }
5287 }
5288 (br, bmv, bc)
5289 }
5290
5291 /// Sub-pel-refines ONE already-chosen partition, reusing `motion_search`'s cost
5292 /// closure via its `start` hook so the rate term and predictor centre are exactly
5293 /// the ones the full search used. Companion to `best_part` under `sp_defer`.
5294 #[allow(clippy::too_many_arguments)]
5295 fn refine_part(
5296 &self,
5297 refs: &[crate::RefFrame],
5298 sy: &[u8],
5299 nb: &[MvNeighbor; 3],
5300 num_refs: usize,
5301 rx: usize,
5302 ry: usize,
5303 rw: usize,
5304 rh: usize,
5305 lme: f64,
5306 r: i32,
5307 mv: (i32, i32),
5308 ) -> ((i32, i32), i64) {
5309 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMe);
5310 let [a, b, c] = *nb;
5311 let rb = (lme * ref_bits(r as usize, num_refs) as f64) as i64;
5312 let seeds = [predict_mv(a, b, c, r)];
5313 let (m, cc) = self.motion_search(&refs[r as usize], sy, rx, ry, rw, rh, &seeds, lme, Some(mv));
5314 (m, cc + rb)
5315 }
5316
5317 /// Cheapest `I_16x16` prediction's SAD over the four whole-block modes, using
5318 /// the already-reconstructed top/left neighbours — the intra candidate's cost
5319 /// in the fast (SAD) mode decision, without the full `I_4x4` search.
5320 fn best_i16_sad(&self, sy: &[u8], mb_x: usize, mb_y: usize) -> i64 {
5321 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCost);
5322 let (lx, ly) = (mb_x * 16, mb_y * 16);
5323 let (avail_top, avail_left) = (mb_y > 0, mb_x > 0);
5324 let mut top = [0u8; 16];
5325 let mut left = [0u8; 16];
5326 if avail_top {
5327 let r = (ly - 1) * self.cw + lx;
5328 top.copy_from_slice(&self.rec_y[r..r + 16]);
5329 }
5330 if avail_left {
5331 // E2 r2 EXPERIMENT: a bounded column slice — `i * cw` is provably
5332 // inside `15*cw + 1` for i < 16, so LLVM can retire the per-element
5333 // checks the strided gather carried. (Kept only if the bc count
5334 // dropped; see 11.12a.)
5335 let col = &self.rec_y[ly * self.cw + lx - 1..(ly + 15) * self.cw + lx];
5336 for i in 0..16 {
5337 left[i] = col[i * self.cw];
5338 }
5339 }
5340 let corner = if avail_top && avail_left {
5341 self.rec_y[(ly - 1) * self.cw + lx - 1]
5342 } else {
5343 0
5344 };
5345 let mut best = i64::MAX;
5346 for mode in [I16Mode::Dc, I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
5347 if !mode.available(avail_top, avail_left) {
5348 continue;
5349 }
5350 let pred = i16_pred(self, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
5351 best = best.min(sad_16x16(sy, self.cw, lx, ly, &pred));
5352 }
5353 best
5354 }
5355
5356 /// SATD sibling of [`Self::best_i16_sad`] — the intra candidate's cost in the
5357 /// quality preset's SATD mode decision (openh264's `WelsMdI16x16`).
5358 fn best_i16_satd(&self, sy: &[u8], mb_x: usize, mb_y: usize) -> i64 {
5359 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCost);
5360 let (lx, ly) = (mb_x * 16, mb_y * 16);
5361 let (avail_top, avail_left) = (mb_y > 0, mb_x > 0);
5362 let mut top = [0u8; 16];
5363 let mut left = [0u8; 16];
5364 if avail_top {
5365 let r = (ly - 1) * self.cw + lx;
5366 top.copy_from_slice(&self.rec_y[r..r + 16]);
5367 }
5368 if avail_left {
5369 // E2 r2 EXPERIMENT: a bounded column slice — `i * cw` is provably
5370 // inside `15*cw + 1` for i < 16, so LLVM can retire the per-element
5371 // checks the strided gather carried. (Kept only if the bc count
5372 // dropped; see 11.12a.)
5373 let col = &self.rec_y[ly * self.cw + lx - 1..(ly + 15) * self.cw + lx];
5374 for i in 0..16 {
5375 left[i] = col[i * self.cw];
5376 }
5377 }
5378 let corner = if avail_top && avail_left {
5379 self.rec_y[(ly - 1) * self.cw + lx - 1]
5380 } else {
5381 0
5382 };
5383 let mut best = i64::MAX;
5384 for mode in [I16Mode::Dc, I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
5385 if !mode.available(avail_top, avail_left) {
5386 continue;
5387 }
5388 let pred = i16_pred(self, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
5389 best = best.min(satd_16x16(sy, self.cw, lx, ly, &pred));
5390 }
5391 best
5392 }
5393
5394 /// Snapshots the per-block grids and reconstruction for one macroblock, so a
5395 /// trial encode can be rolled back.
5396 fn save_mb(&self, mb_x: usize, mb_y: usize) -> MbState {
5397 let mut d = MbState::default();
5398 self.save_mb_into(mb_x, mb_y, &mut d);
5399 d
5400 }
5401
5402 /// [`save_mb`](Self::save_mb) into an existing buffer, reusing its allocations.
5403 /// The per-macroblock region is a fixed size, so after the first call every
5404 /// `Vec` already has the capacity it needs and refilling is a pure copy.
5405 fn save_mb_into(&self, mb_x: usize, mb_y: usize, d: &mut MbState) {
5406 let w4 = self.mb_w * 4;
5407 let w2 = self.mb_w * 2;
5408 // REFUTED, do not retry: the `load_mb` row-slice shape does NOT mirror onto
5409 // this function. Rewriting these as `$o.clear()` + `extend_from_slice` per
5410 // row does retire all 15 bounds checks, but `Vec::extend_from_slice` carries
5411 // a capacity check and a `memcpy` call of its own, and the function grew
5412 // 1030 -> 1771 instructions (+72%) to buy them. A write into a
5413 // pre-sized destination (`load_mb`) and an append into a growable one are
5414 // not the same shape; only the former is a clean win.
5415 macro_rules! reg4 {
5416 ($v:expr, $o:expr) => {{
5417 $o.clear();
5418 for dy in 0..4 {
5419 for dx in 0..4 {
5420 $o.push($v[(mb_y * 4 + dy) * w4 + mb_x * 4 + dx]);
5421 }
5422 }
5423 }};
5424 }
5425 macro_rules! regn {
5426 ($v:expr, $o:expr, $n:expr, $ox:expr, $oy:expr, $stride:expr) => {{
5427 $o.clear();
5428 for dy in 0..$n {
5429 for dx in 0..$n {
5430 $o.push($v[($oy + dy) * $stride + $ox + dx]);
5431 }
5432 }
5433 }};
5434 }
5435 regn!(self.rec_y, d.rec_y, 16, mb_x * 16, mb_y * 16, self.cw);
5436 regn!(self.rec_u, d.rec_u, 8, mb_x * 8, mb_y * 8, self.ccw);
5437 regn!(self.rec_v, d.rec_v, 8, mb_x * 8, mb_y * 8, self.ccw);
5438 reg4!(self.nnz_y, d.nnz_y);
5439 regn!(self.nnz_c[0], d.nnz_c[0], 2, mb_x * 2, mb_y * 2, w2);
5440 regn!(self.nnz_c[1], d.nnz_c[1], 2, mb_x * 2, mb_y * 2, w2);
5441 reg4!(self.mv_y, d.mv_y);
5442 reg4!(self.inter_y, d.inter_y);
5443 reg4!(self.ref_idx_y, d.ref_idx_y);
5444 reg4!(self.coded_y, d.coded_y);
5445 reg4!(self.modes_y, d.modes_y);
5446 d.cur_qp = self.cur_qp;
5447 }
5448
5449 /// Restores a macroblock's grids + reconstruction from a [`save_mb`] snapshot.
5450 fn load_mb(&mut self, mb_x: usize, mb_y: usize, s: &MbState) {
5451 let w4 = self.mb_w * 4;
5452 let w2 = self.mb_w * 2;
5453 // One bounds check PER ROW instead of per element. `$n` is a literal at
5454 // every call site, so both slices have a compile-time extent and LLVM
5455 // folds the length equality `copy_from_slice` would otherwise check --
5456 // this is the shape that WORKS, as distinct from the runtime-extent row
5457 // slice the decoder campaign measured and REFUTED.
5458 macro_rules! put4 {
5459 ($v:expr, $src:expr) => {
5460 for dy in 0..4 {
5461 let d = (mb_y * 4 + dy) * w4 + mb_x * 4;
5462 $v[d..d + 4].copy_from_slice(&$src[dy * 4..dy * 4 + 4]);
5463 }
5464 };
5465 }
5466 macro_rules! putn {
5467 ($v:expr, $src:expr, $n:expr, $ox:expr, $oy:expr, $stride:expr) => {
5468 for dy in 0..$n {
5469 let d = ($oy + dy) * $stride + $ox;
5470 $v[d..d + $n].copy_from_slice(&$src[dy * $n..dy * $n + $n]);
5471 }
5472 };
5473 }
5474 putn!(self.rec_y, s.rec_y, 16, mb_x * 16, mb_y * 16, self.cw);
5475 putn!(self.rec_u, s.rec_u, 8, mb_x * 8, mb_y * 8, self.ccw);
5476 putn!(self.rec_v, s.rec_v, 8, mb_x * 8, mb_y * 8, self.ccw);
5477 put4!(self.nnz_y, s.nnz_y);
5478 putn!(self.nnz_c[0], s.nnz_c[0], 2, mb_x * 2, mb_y * 2, w2);
5479 putn!(self.nnz_c[1], s.nnz_c[1], 2, mb_x * 2, mb_y * 2, w2);
5480 put4!(self.mv_y, s.mv_y);
5481 put4!(self.inter_y, s.inter_y);
5482 put4!(self.ref_idx_y, s.ref_idx_y);
5483 put4!(self.coded_y, s.coded_y);
5484 put4!(self.modes_y, s.modes_y);
5485 self.cur_qp = s.cur_qp;
5486 }
5487
5488 /// Loads the per-MB luma nnz prediction cache (openh264 `scan8` style): the top
5489 /// row from the macroblock above and the left column from the macroblock to the
5490 /// left (both already in `nnz_y`), with `0x80` at the picture edges. After this,
5491 /// neighbour nnz reads are branchless cache indexing — no bounds-checked `Option`.
5492 fn nnz_cache_load(&mut self, mb_x: usize, mb_y: usize) {
5493 let w4 = self.mb_w * 4;
5494 for lbx in 0..4 {
5495 self.nnz_l_cache[1 + lbx] = if mb_y == 0 {
5496 0x80
5497 } else {
5498 self.nnz_y[(mb_y * 4 - 1) * w4 + (mb_x * 4 + lbx)]
5499 };
5500 }
5501 for lby in 0..4 {
5502 self.nnz_l_cache[(lby + 1) * 5] = if mb_x == 0 {
5503 0x80
5504 } else {
5505 self.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 - 1)]
5506 };
5507 }
5508 }
5509
5510 /// Branchless nnz prediction (`nC`) for luma block `(lbx,lby)` from the cache —
5511 /// the `0x80` sentinel + `& 0x7f` mask collapse the four availability cases
5512 /// (matches the scalar nnz predict). Call after the block's left/top are cached.
5513 #[inline]
5514 fn nc_pred(&self, lbx: usize, lby: usize) -> i32 {
5515 let left = self.nnz_l_cache[(lby + 1) * 5 + lbx] as i32; // (lbx-1)+1
5516 let top = self.nnz_l_cache[lby * 5 + (lbx + 1)] as i32; // (lby-1)+1
5517 let r = left + top;
5518 if r < 0x80 {
5519 (r + 1) >> 1
5520 } else {
5521 r & 0x7f
5522 }
5523 }
5524
5525 /// Records a luma block's nnz into the per-MB cache (for later neighbour reads).
5526 #[inline]
5527 fn nnz_cache_set(&mut self, lbx: usize, lby: usize, total: u8) {
5528 self.nnz_l_cache[(lby + 1) * 5 + (lbx + 1)] = total;
5529 }
5530
5531 /// Loads the per-MB chroma nnz prediction cache (both planes) from the chroma
5532 /// blocks above/left, `0x80` at the picture edges — the chroma analogue of
5533 /// [`Self::nnz_cache_load`] (2×2 blocks → padded 3×3 grid).
5534 fn chroma_cache_load(&mut self, mb_x: usize, mb_y: usize) {
5535 let w2 = self.mb_w * 2;
5536 for c in 0..2 {
5537 for bx in 0..2 {
5538 self.nnz_c_cache[c][1 + bx] = if mb_y == 0 {
5539 0x80
5540 } else {
5541 self.nnz_c[c][(mb_y * 2 - 1) * w2 + (mb_x * 2 + bx)]
5542 };
5543 }
5544 for by in 0..2 {
5545 self.nnz_c_cache[c][(by + 1) * 3] = if mb_x == 0 {
5546 0x80
5547 } else {
5548 self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 - 1)]
5549 };
5550 }
5551 }
5552 }
5553
5554 /// Branchless chroma nnz prediction (`nC`) for plane `c`, block `(bx,by)`.
5555 #[inline]
5556 fn chroma_nc_pred(&self, c: usize, bx: usize, by: usize) -> i32 {
5557 let left = self.nnz_c_cache[c][(by + 1) * 3 + bx] as i32;
5558 let top = self.nnz_c_cache[c][by * 3 + (bx + 1)] as i32;
5559 let r = left + top;
5560 if r < 0x80 {
5561 (r + 1) >> 1
5562 } else {
5563 r & 0x7f
5564 }
5565 }
5566
5567 /// Records a chroma block's nnz into the per-MB cache.
5568 #[inline]
5569 fn chroma_nnz_cache_set(&mut self, c: usize, bx: usize, by: usize, total: u8) {
5570 self.nnz_c_cache[c][(by + 1) * 3 + (bx + 1)] = total;
5571 }
5572}
5573
5574/// Encodes a slice's macroblocks then RBSP trailing bits, returning the
5575/// **deblocked** reconstruction to serve as the next frame's reference.
5576///
5577/// `is_p` selects P-slice framing (`mb_skip_run` prefix + intra `mb_type` +5
5578/// offset). In phase 4a every macroblock is still coded intra; motion-compensated
5579/// macroblocks arrive in 4b (using `reference`).
5580/// Boundary strengths for one macroblock, derived from the encoder's own grids
5581/// the moment it finishes coding.
5582///
5583/// `ref_idx_y` holds raw indices (-1 for intra) rather than the deblocker's
5584/// `NO_REF` sentinel; safe because reference identity is only compared between
5585/// two INTER blocks, which always carry a valid index.
5586// NOT inlined: this sits at three exits of the hottest loop in the encoder, and
5587// inlining it there costs more in I-cache and register pressure on the
5588// surrounding code than the call saves (measured: the loop grew ~2x the
5589// derivation's own cost).
5590#[inline(never)]
5591fn derive_mb_bs_from(
5592 fe: &FrameEncoder,
5593 mb_x: usize,
5594 mb_y: usize,
5595 kind: rusty_h264_common::deblock::MbKind,
5596) -> rusty_h264_common::deblock::MbBs {
5597 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncBs);
5598 let view = rusty_h264_common::deblock::BlockInfo {
5599 inter: &fe.inter_y,
5600 nnz: &fe.nnz_y,
5601 mv: &fe.mv_y,
5602 ref_id: &fe.ref_idx_y,
5603 mv1: &[],
5604 ref_id1: &[],
5605 w4: fe.mb_w * 4,
5606 t8x8: &[],
5607 poc0: &[],
5608 poc1: &[],
5609 bs: &[], kind: &[],
5610 };
5611 rusty_h264_common::deblock::derive_mb_kind(&view, mb_x, mb_y, kind)
5612}
5613
5614pub(crate) fn encode_slice_data(
5615 w: &mut BitWriter,
5616 cfg: &EncoderConfig,
5617 frame: &YuvFrame,
5618 qp: u8,
5619 is_p: bool,
5620 refs: &[crate::RefFrame],
5621 qpo: &[i32],
5622 aq_probe: Option<&YuvFrame>,
5623 wp: &[(i32, i32)],
5624) -> crate::RefFrame {
5625 let _g_prep = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncPrep);
5626 let mut fe = FrameEncoder::new(cfg);
5627 fe.wp = wp.to_vec();
5628 let precomp = rusty_h264_common::deblock::precomputed_bs_enabled();
5629 let mut bs_grid =
5630 vec![rusty_h264_common::deblock::MbBs::UNSET; if precomp { fe.mb_w * fe.mb_h } else { 0 }];
5631 fe.qp = qp;
5632 fe.qpc = chroma_qp(qp);
5633 fe.cur_qp = qp;
5634 if cfg.cabac_dz_div > 0 {
5635 fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
5636 } // QPY_PREV starts at the slice QP so the first mb_qp_delta is 0
5637 let (sy, su, sv) = coded_source(cfg, frame);
5638 // Great Gate P1: ONE lazy signal vector per frame; every gate below reads
5639 // through it, so no probe runs twice and unused signals cost nothing.
5640 // On an IDR (no refs) the previous SOURCE frame stands in as the temporal
5641 // reference — the AQ grain veto needs it (docs/gate-ledger.md aq-grain-veto);
5642 // every ME gate below still keys on `refs`, not on the signal vector.
5643 let probe_y: Option<Vec<u8>> =
5644 if refs.is_empty() { aq_probe.map(|f| coded_source(cfg, f).0.into_owned()) } else { None };
5645 let sig = FrameSignals::new(
5646 &sy,
5647 fe.cw,
5648 fe.mb_w,
5649 fe.mb_h,
5650 refs.first().map(|r| &r.y[..]).or(probe_y.as_deref()),
5651 );
5652 apply_screen_t8_veto(&mut fe, &sig);
5653 let lambda = 0.85 * fe.tune_lambda_scale * crate::fastmath::lambda_qp(qp);
5654 let num_refs = refs.len();
5655 // me_wide CONTENT GATE: on a pure PAN the global-MC residual ≈ 0, so the diamond's
5656 // seed (median = pan MV) is already right and the wide rescue only over-fits
5657 // (spurious MVs that hurt the B-frames' spatial-direct — the panc regression).
5658 // Gate it off there; non-uniform content (real stalls) reads well above 0.
5659 if is_p && fe.me_wide && !refs.is_empty() && sig.gmc_residual() < fe.me_wide_coh {
5660 fe.me_wide = false;
5661 }
5662 // me_wide HEAD-ROOM GATE (the dispatcher the truth table asked for). The rescue
5663 // only pays where a wide search actually beats a predictor-local one; measure
5664 // that directly per frame and route the frame. `RFF_ME_HR` sets the threshold
5665 // (percent); 0 disables the gate and restores the always-on behaviour.
5666 // Skip the probe entirely when the gate is disabled: it must not tax the
5667 // default path (`RFF_ME_HR=0`), which stays byte-identical to pre-gate output.
5668 if fe.me_wide && !refs.is_empty() && (me_wide_hr_thresh() > 0.0 || me_wide_hr_dbg()) {
5669 let hr = sig.headroom();
5670 if me_wide_hr_dbg() {
5671 eprintln!("ME_HR qp{qp} headroom={hr:.2}");
5672 }
5673 if me_wide_hr_thresh() > 0.0 && hr < me_wide_hr_thresh() {
5674 fe.me_wide = false;
5675 }
5676 }
5677 // Track-B B2 DISPATCH (WHYS H-2): SAD full-pel wins where a plain full-pel
5678 // translational search actually improves on zero motion (`b2_mgain`) and loses
5679 // on flash/fine-detail content. Probe per frame, route the frame — per-frame,
5680 // not cross-frame, so it stays deterministic under GOP-parallel encode.
5681 if me_sadfp_mode() == 1 && !fe.fast && !refs.is_empty() {
5682 let (mg, dc) = sig.mgain_dc();
5683 if me_sadt_dbg() {
5684 eprintln!("B2_MG qp{qp} mgain={mg:.3} dcfrac={dc:.3}");
5685 }
5686 fe.sadfp = mg >= me_sadt() && dc <= me_sad_dcmax();
5687 // H-24: the mv-cost SHAPE rides the same probe (its BD sign-flip tracks
5688 // motion for the same physical reason B2's does).
5689 if mv_smooth_mode() == 1 {
5690 // dcfrac veto mirrors B2's: crew-class FLASH frames satisfy the mgain
5691 // test but SAD/mvd statistics mislead there (H-13/H-26).
5692 fe.mv_smooth = mg >= mv_smooth_t() && dc <= me_sad_dcmax();
5693 }
5694 // H-13: near-static frames skip the split searches entirely.
5695 let smg = split_mg();
5696 if smg > 0.0 {
5697 fe.do_splits = mg >= smg;
5698 }
5699 }
5700 // Content-adaptive cost-function dispatch (codec-content-adaptive-dispatch): the
5701 // fast preset prices modes by cheap SAD, which is rate-blind on detailed MBs;
5702 // route the top `satd_q` fraction of highest-VARIANCE MBs to the rate-faithful
5703 // SATD cost. A per-frame PERCENTILE threshold makes the routed fraction — hence
5704 // the speed/quality split — content-invariant (same q → same fraction on any
5705 // clip). `satd_q == 0` leaves the threshold at MAX (pure SAD, byte-identical).
5706 if is_p && fe.satd_q > 0.0 {
5707 fe.satd_var_thresh = sig.var_percentile_thresh(fe.satd_q);
5708 }
5709 // Adaptive Quantization: per-MB target QPy from content (finer on flat MBs,
5710 // coarser on busy ones). `mb_qpy` records each MB's ACTUAL QPy (a skip / cbp==0
5711 // MB inherits `cur_qp`), for the deblock filter. `strength 0` → uniform → the
5712 // mb_qp_delta stays 0, byte-identical.
5713 let mut aq_qp = aq_qp_map(&sig, qp, fe.aq_strength);
5714 apply_mbtree_qpo(&mut aq_qp, qpo); // mb-tree temporal AQ (empty = byte-identical)
5715 signals::harvest(
5716 &sig,
5717 if is_p { 'P' } else { 'I' },
5718 qp,
5719 &signals::GateDecisions {
5720 me_wide: fe.me_wide,
5721 sadfp: fe.sadfp,
5722 mv_smooth: fe.mv_smooth,
5723 do_splits: fe.do_splits,
5724 lme_scale: 1.0,
5725 satd_thresh: fe.satd_var_thresh,
5726 },
5727 );
5728 let mut mb_qpy = enc_scratch::take_qpy();
5729 mb_qpy.clear();
5730 mb_qpy.resize(fe.mb_w * fe.mb_h, qp);
5731 let mut skip_run = 0u32;
5732 // ---- adaptive RD-skip gate -------------------------------------------
5733 // RD P_Skip is a large win on temporally redundant content and a large LOSS
5734 // on detailed content (SSIM: akiyo -13.1%, FourPeople -5.6% vs in_to_tree
5735 // +34.0%, stockholm +95.7%). The separating signal is the content's own
5736 // FREE-skip rate — how much of it is already exactly redundant — and the gap
5737 // is wide (winners >=58.7%, losers <=6.4%). Measure it ONLINE over the first
5738 // slice of the frame and enable RD skip for the remainder only if it clears
5739 // the bar. Within-frame, so it stays deterministic under GOP-parallel encode.
5740 if is_p && mv_cmp_on() {
5741 MVCMP_FRAME.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
5742 }
5743 // Reused across every RD-skip candidate — see `MbState`.
5744 let mut rdskip_snap = MbState::default();
5745 let mut rdskip_free = 0usize;
5746 let mut rdskip_seen = 0usize;
5747 let mut rdskip_on = false;
5748 let mut greedy_on = fe.greedy_min_free == 0; // 0 = ungated (historic behaviour)
5749 let rdskip_learn = (fe.mb_w * fe.mb_h / 8).max(64);
5750 let rdskip_min_free = fe.rd_skip_min_free as usize;
5751
5752 drop(_g_prep);
5753 let _g_loop = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMbLoop);
5754 for mb_y in 0..fe.mb_h {
5755 for mb_x in 0..fe.mb_w {
5756 let mb_idx = mb_y * fe.mb_w + mb_x;
5757 let mb_qp = aq_qp[mb_idx];
5758 fe.qp = mb_qp;
5759 fe.qpc = chroma_qp(mb_qp);
5760 // P_Skip: motion-compensate from the most-recent reference; accept if free.
5761 // Chosen inter coding: (mb_type, per-partition (ref_idx, mv)).
5762 let mut inter: Option<InterChoice> = None;
5763 // Bits of an inter macroblock already encoded by the skip decision
5764 // below. When present the emit path splices them instead of encoding
5765 // the same macroblock a second time.
5766 let mut coded: Option<BitWriter> = None;
5767 if is_p {
5768 if num_refs > 0 {
5769 // P_Skip prediction (reference 0). A free skip (zero residual) is
5770 // taken immediately; the quality preset also takes a greedy P_Skip
5771 // when its SAD is below the neighbour-predicted bound (below).
5772 let _g_skip = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncSkip);
5773 let _g_smc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Neighbors);
5774 rdskip_seen += 1;
5775 if rdskip_seen >= rdskip_learn {
5776 rdskip_on = rdskip_free * 100 >= rdskip_seen * rdskip_min_free;
5777 greedy_on = fe.greedy_min_free == 0
5778 || rdskip_free * 100 >= rdskip_seen * fe.greedy_min_free as usize;
5779 }
5780 let mv_skip = fe.skip_mv(mb_x, mb_y);
5781 let skip_y = fe.skip_predict_luma(refs, mb_x, mb_y, mv_skip);
5782 drop(_g_smc);
5783 let luma_free = fe.skip_luma_is_free(&sy, mb_x, mb_y, &skip_y);
5784 // Chroma MC only when it can matter: luma already free (so the
5785 // skip might be taken) or the quality path needs it below.
5786 let skip_c = if luma_free || !fe.fast {
5787 fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip)
5788 } else {
5789 [[0u8; 64]; 2]
5790 };
5791 let is_free =
5792 luma_free && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &skip_c);
5793 // Skip-prediction luma SAD (the quality preset's predicted-SAD apparatus).
5794 let skip_sad = if fe.fast {
5795 0
5796 } else {
5797 let (lx, ly) = (mb_x * 16, mb_y * 16);
5798 let mut s = 0u32;
5799 for dy in 0..16 {
5800 let src = &sy[(ly + dy) * fe.cw + lx..][..16];
5801 let p = &skip_y[dy * 16..][..16];
5802 s += src.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
5803 }
5804 s
5805 };
5806 if is_free {
5807 fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
5808 if !fe.fast {
5809 fe.mb_was_skip[mb_idx] = true;
5810 fe.mb_skip_sad[mb_idx] = skip_sad;
5811 }
5812 mb_qpy[mb_idx] = fe.cur_qp; // skip inherits QPy
5813 rdskip_free += 1;
5814 if precomp {
5815 bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, rusty_h264_common::deblock::MbKind::Skip);
5816 }
5817 skip_run += 1;
5818 continue;
5819 }
5820 drop(_g_skip);
5821 let (lx, ly) = (mb_x * 16, mb_y * 16);
5822 let nb = {
5823 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMvPred);
5824 fe.mv_neighbors_block(mb_x as isize * 4, mb_y as isize * 4, 4)
5825 };
5826 let lme = lambda.sqrt();
5827
5828 if fe.fast {
5829 // Fast preset: pick the cheapest *prediction* by SATD (no
5830 // trial-encoding), then always code its residual — P_16x16 vs
5831 // I_16x16 only, no sub-partitions. Crucially it does NOT make a
5832 // SATD skip-vs-code decision: P_Skip is taken only for a truly
5833 // free (zero-residual) macroblock, handled above. Pricing skip
5834 // by SATD would drop residual the QP wants coded and tank PSNR;
5835 // like x264's fast presets, fast trades *efficiency* (more bits)
5836 // for speed, not quality. The faster ME is what makes it fast.
5837 // Adaptive dispatch: high-variance MBs price by SATD (both
5838 // inter — via `mb_use_satd` in `best_part` — and intra), the
5839 // rest by cheap SAD. Set the per-MB flag before best_part.
5840 // Dedup (E15 W9): memoized twin, already built when satd_q > 0.
5841 fe.mb_use_satd = fe.satd_q > 0.0
5842 && sig.mb_vars()[mb_y * fe.mb_w + mb_x] >= fe.satd_var_thresh;
5843 let (r16, mv16, cost_inter) =
5844 fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
5845 let cost_intra = if fe.mb_use_satd {
5846 fe.best_i16_satd(&sy, mb_x, mb_y)
5847 } else {
5848 fe.best_i16_sad(&sy, mb_x, mb_y)
5849 } + (lme * fe.tune_intra_penalty) as i64;
5850 inter = if cost_intra < cost_inter {
5851 None // intra wins → encode_mb below
5852 } else {
5853 Some((0, vec![(r16, mv16)]))
5854 };
5855 } else {
5856 // Quality preset: openh264's mode-decision model — SATD + λ·mvbits
5857 // cost ESTIMATE (no per-candidate trial-encode); modes are ranked
5858 // by that cost and only the winner is encoded (once) below. This
5859 // removes ~the 93%-of-quality re-encode cost.
5860
5861 // Greedy P_Skip (openh264 `PredictSadSkip`): take the skip when its
5862 // luma SAD is below the neighbour-predicted skip SAD. The threshold
5863 // is what skip neighbours achieved, so the skip propagates from the
5864 // free skips and self-limits — no fixed bound, no inter-chain drift.
5865 if fe.greedy_skip && greedy_on && skip_sad < fe.pred_skip_sad(mb_x, mb_y) {
5866 fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
5867 fe.mb_was_skip[mb_idx] = true;
5868 fe.mb_skip_sad[mb_idx] = skip_sad;
5869 mb_qpy[mb_idx] = fe.cur_qp; // skip inherits QPy
5870 if precomp {
5871 bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, rusty_h264_common::deblock::MbKind::Skip);
5872 }
5873 skip_run += 1;
5874 continue;
5875 }
5876
5877 // 16×16 baseline (SATD + λ·bits, with sub-pel refinement).
5878 let (r16, mv16, c16) =
5879 fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
5880 let mut best_c = c16;
5881 let mut pick: Option<InterChoice> = Some((0, vec![(r16, mv16)]));
5882
5883 // Sub-partitions, ranked by SATD, gated on a heavy 16×16 (a likely
5884 // motion boundary — the 4 sub-pel searches are the expensive part).
5885 const QSTEP16: [i64; 6] = [10, 11, 13, 14, 16, 18];
5886 let qstep16 = QSTEP16[(fe.qp % 6) as usize] << (fe.qp / 6);
5887 let split_gate = ((30 * (qstep16 + 160)) >> 3) * 2;
5888 let split_t = split_t();
5889 if fe.do_splits && c16 > split_gate && (split_t <= 0.0 || (c16 as f64) >= split_t * lme) {
5890 let (rt, mvt, ct) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 8, &[mv16], lme);
5891 let (rb, mvb, cb) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly + 8, 16, 8, &[mv16], lme);
5892 let (rl, mvl, cl) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 8, 16, &[mv16], lme);
5893 let (rr, mvr, cr) = fe.best_part(refs, &sy, &nb, num_refs, lx + 8, ly, 8, 16, &[mv16], lme);
5894 if ct + cb < best_c {
5895 best_c = ct + cb;
5896 pick = Some((1u8, vec![(rt, mvt), (rb, mvb)]));
5897 }
5898 if cl + cr < best_c {
5899 best_c = cl + cr;
5900 pick = Some((2u8, vec![(rl, mvl), (rr, mvr)]));
5901 }
5902
5903 // P_8x8: four independent 8×8 sub-partitions (finer motion
5904 // granularity — the win on complex/boundary motion). Each 8×8
5905 // seeded by the 16×16 MV; the exact chained MVD is computed in
5906 // plan_inter_mb. Same heavy-16×16 gate as the 2-way splits.
5907 if fe.sub8x8 {
5908 let mut c8 = (lme * 4.0) as i64; // ~4 sub_mb_type bits
5909 let mut p8 = Vec::with_capacity(4);
5910 for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
5911 let (r, mv, c) = fe.best_part(
5912 refs, &sy, &nb, num_refs, lx + qx, ly + qy, 8, 8, &[mv16], lme,
5913 );
5914 c8 += c;
5915 p8.push((r, mv));
5916 }
5917 if c8 < best_c {
5918 best_c = c8;
5919 pick = Some((3u8, p8));
5920 }
5921 }
5922 }
5923
5924 // U5-struct: everything above searched FULL-PEL only when
5925 // `sp_defer` is set. Now that a shape has won, refine just its
5926 // sub-blocks — the losing shapes' refinements were the waste
5927 // (measured 3.4–6.4× more refinement than necessary).
5928 if fe.sp_defer.get() {
5929 if let Some((mode, parts)) = pick.as_mut() {
5930 let regions: &[(usize, usize, usize, usize)] = match mode {
5931 1 => &[(0, 0, 16, 8), (0, 8, 16, 8)],
5932 2 => &[(0, 0, 8, 16), (8, 0, 8, 16)],
5933 3 => &[(0, 0, 8, 8), (8, 0, 8, 8), (0, 8, 8, 8), (8, 8, 8, 8)],
5934 _ => &[(0, 0, 16, 16)],
5935 };
5936 let mut tot = if *mode == 3 { (lme * 4.0) as i64 } else { 0 };
5937 for (i, &(qx, qy, pw, ph)) in regions.iter().enumerate() {
5938 let (r, mv) = parts[i];
5939 let (m2, c2) = fe.refine_part(
5940 refs, &sy, &nb, num_refs, lx + qx, ly + qy, pw, ph, lme, r, mv,
5941 );
5942 parts[i] = (r, m2);
5943 tot += c2;
5944 }
5945 best_c = tot;
5946 }
5947 }
5948 if split_harvest::enabled() {
5949 let won = match pick.as_ref().map(|p| p.0) {
5950 Some(0) | None => 0u8,
5951 Some(m) => m,
5952 };
5953 split_harvest::record(c16, best_c, lme, split_gate, won);
5954 }
5955 // Intra is ALWAYS a candidate (textured / occluded content):
5956 // I_16x16 SATD + λ·mode bits.
5957 let c_intra = fe.best_i16_satd(&sy, mb_x, mb_y)
5958 + (lme * fe.tune_intra_penalty) as i64;
5959 inter = if c_intra < best_c { None } else { pick };
5960 fe.mb_was_skip[mb_idx] = false;
5961 fe.mb_skip_sad[mb_idx] = skip_sad;
5962 }
5963
5964 // ---- RD P_Skip ----------------------------------------
5965 // The default criterion skips only when the residual quantizes
5966 // to EXACTLY zero. That matches x264 at both extremes (akiyo
5967 // 72.5% vs 73.6%, mobile 1.0% vs 1.4%) but falls 17-23 points
5968 // short in the middle (foreman 6.4% vs 23.6%), because x264
5969 // also skips macroblocks whose residual is small-but-nonzero.
5970 // Decide it properly: trial-encode the chosen mode for real
5971 // bits + reconstruction SSD, and compare J = SSD + lambda*R
5972 // against the skip. Raw-SAD versions of this comparison fail
5973 // badly (coding REPAIRS the residual, skipping keeps it), so
5974 // the distortion term has to come from the reconstruction.
5975 if fe.rd_skip && rdskip_on && inter.is_some() {
5976 let skip_cp = fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip);
5977 // A P_Skip carries no residual, so its RECONSTRUCTION *is*
5978 // its prediction — the skip SSD needs no state mutation at
5979 // all. The commit / mb_ssd / restore round trip this
5980 // replaces cost a full macroblock save+restore on every
5981 // candidate, including the ones that go on to code.
5982 let ssd_s = fe.pred_ssd(&sy, &su, &sv, mb_x, mb_y, &skip_y, &skip_cp);
5983 debug_assert_eq!(ssd_s, {
5984 let snap = fe.save_mb(mb_x, mb_y);
5985 fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_cp);
5986 let v = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y);
5987 fe.load_mb(mb_x, mb_y, &snap);
5988 v
5989 }, "skip prediction SSD must equal the committed-skip reconstruction SSD");
5990 // A skip inside a run costs ~1 bit of mb_skip_run.
5991 let j_skip = ssd_s as f64 + lambda;
5992 // Search-skip gate: when the null arm is this cheap it
5993 // almost always wins, so take it without pricing the coded
5994 // arm at all. This is where the decision's remaining cost
5995 // lives — the coded arm is encoded and then discarded on
5996 // 55-80% of candidates.
5997 let take_skip = if fe.rd_skip_fast_t > 0.0
5998 && (ssd_s as f64) <= lambda * fe.rd_skip_fast_t
5999 {
6000 true
6001 } else {
6002 // Otherwise encode ONCE, into scratch, and KEEP the state.
6003 // If the skip loses, those are the real bits and they splice
6004 // straight into the slice. The previous shape trial-encoded,
6005 // threw the result away, and then encoded again — paying
6006 // twice on the path that actually codes.
6007 fe.save_mb_into(mb_x, mb_y, &mut rdskip_snap);
6008 // E11 dig (11.17): pooled — was a fresh Vec per coded-arm trial.
6009 let mut scratch = enc_scratch::take_bits();
6010 scratch.clear();
6011 {
6012 let (m, p) = inter.as_ref().unwrap();
6013 fe.encode_inter_mb(
6014 &mut scratch, refs, &sy, &su, &sv, mb_x, mb_y, *m, p,
6015 );
6016 }
6017 let bits_c = scratch.bit_len();
6018 let ssd_c = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y);
6019 let won = j_skip <= ssd_c as f64 + lambda * bits_c as f64;
6020 if won {
6021 fe.load_mb(mb_x, mb_y, &rdskip_snap); // undo it; take the skip
6022 enc_scratch::put_bits(scratch); // recycle the unused coded arm
6023 true
6024 } else {
6025 coded = Some(scratch); // keep it — no second encode
6026 false
6027 }
6028 };
6029 if take_skip {
6030 fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_cp);
6031 if !fe.fast {
6032 fe.mb_was_skip[mb_idx] = true;
6033 fe.mb_skip_sad[mb_idx] = skip_sad;
6034 }
6035 mb_qpy[mb_idx] = fe.cur_qp;
6036 if precomp {
6037 bs_grid[mb_idx] = derive_mb_bs_from(
6038 &fe, mb_x, mb_y,
6039 rusty_h264_common::deblock::MbKind::Skip,
6040 );
6041 }
6042 skip_run += 1;
6043 continue;
6044 }
6045 }
6046 }
6047 w.write_ue(skip_run); // run of skipped macroblocks before this one
6048 skip_run = 0;
6049 }
6050 if mv_force_on() && is_p && inter.is_some() {
6051 let fi = MVCMP_FRAME.load(std::sync::atomic::Ordering::Relaxed);
6052 let ext = EXT_MV.lock().unwrap();
6053 if let Some(field) = ext.get(fi) {
6054 let w4 = fe.mb_w * 4;
6055 let b0 = (mb_y * 4) * w4 + mb_x * 4;
6056 // uniform 16x16 only: a sub-partitioned macroblock has no single
6057 // vector to transplant, so leave those to our own decision
6058 let uniform = (0..4).all(|r| {
6059 (0..4).all(|c| field.get(b0 + r * w4 + c) == field.get(b0))
6060 });
6061 if uniform {
6062 if let Some(&emv) = field.get(b0) {
6063 inter = Some((0, vec![(0, emv)]));
6064 MVCMP[6].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6065 }
6066 }
6067 }
6068 }
6069 if mv_cmp_on() && is_p {
6070 if let Some((mode, parts)) = inter.as_ref() {
6071 let fi = MVCMP_FRAME.load(std::sync::atomic::Ordering::Relaxed);
6072 let ext = EXT_MV.lock().unwrap();
6073 if let Some(field) = ext.get(fi) {
6074 let bidx = (mb_y * 4) * (fe.mb_w * 4) + mb_x * 4;
6075 if let Some(&emv) = field.get(bidx) {
6076 let (mode, parts) = (*mode, parts.clone());
6077 drop(ext);
6078 // Both priced through the SAME pipeline: MC, transform,
6079 // quantize, CAVLC. Real bits, real reconstruction SSD.
6080 let (so, bo) =
6081 fe.trial_inter(refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts);
6082 let (se, be) = fe.trial_inter(
6083 refs, &sy, &su, &sv, mb_x, mb_y, 0, &[(0, emv)],
6084 );
6085 let jo = so as f64 + lambda * bo as f64;
6086 let je = se as f64 + lambda * be as f64;
6087 use std::sync::atomic::Ordering::Relaxed;
6088 MVCMP[0].fetch_add(1, Relaxed);
6089 MVCMP[1].fetch_add(bo as u64, Relaxed);
6090 MVCMP[2].fetch_add(be as u64, Relaxed);
6091 MVCMP[3].fetch_add(so.max(0) as u64, Relaxed);
6092 MVCMP[4].fetch_add(se.max(0) as u64, Relaxed);
6093 MVCMP[5].fetch_add((je < jo) as u64, Relaxed);
6094 MVCMP[6].fetch_add((parts[0].1 != emv) as u64, Relaxed);
6095 }
6096 }
6097 }
6098 }
6099 // Capture the kind before `inter` is consumed: the deblocking
6100 // strengths of an intra macroblock are pure constants.
6101 let mb_kind = match &inter {
6102 // A single partition covers the whole macroblock with one
6103 // (ref, mv), which collapses the internal derivation to nnz.
6104 Some((_, parts)) if parts.len() == 1 => {
6105 rusty_h264_common::deblock::MbKind::InterUniform
6106 }
6107 Some(_) => rusty_h264_common::deblock::MbKind::Inter,
6108 None => rusty_h264_common::deblock::MbKind::Intra,
6109 };
6110 match inter {
6111 Some((mode, parts)) => match coded {
6112 // Encoded already, during the skip decision — splice the bits in
6113 // rather than encoding this macroblock for a second time.
6114 Some(sc) => {
6115 w.append(&sc);
6116 enc_scratch::put_bits(sc);
6117 }
6118 None => {
6119 fe.encode_inter_mb(w, refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts)
6120 }
6121 },
6122 None => encode_mb(&mut fe, w, mb_x, mb_y, &sy, &su, &sv, is_p),
6123 }
6124 mb_qpy[mb_idx] = fe.cur_qp; // ACTUAL QPy (updated iff an mb_qp_delta was coded)
6125 if precomp {
6126 bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, mb_kind);
6127 }
6128 }
6129 }
6130 debug_assert!(
6131 !precomp || bs_grid.iter().all(|b| *b != rusty_h264_common::deblock::MbBs::UNSET),
6132 "a macroblock loop exit failed to store its boundary strengths"
6133 );
6134 if is_p && skip_run > 0 {
6135 w.write_ue(skip_run); // trailing skipped macroblocks
6136 }
6137 w.rbsp_trailing_bits();
6138
6139 // Deblock the reconstruction; the result is the inter reference. Baseline: the
6140 // intra mask is `!inter_y` (passed directly, no alloc); no B (List-1 empty); no
6141 // 8×8 transform (t8x8 empty). ref_id is each block's List-0 ref index.
6142 drop(_g_loop);
6143 let _g_fin = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFinal);
6144 // No NO_REF-mapping collect: it ran over every 4x4 block every frame (~1.9 MB
6145 // of allocation + map at 1080p) to produce a grid that is only ever read for
6146 // INTER-vs-INTER comparisons, where the encoder's raw indices are already
6147 // equivalent. Intra blocks short-circuit before reference identity is touched.
6148 let info = rusty_h264_common::deblock::BlockInfo {
6149 inter: &fe.inter_y,
6150 nnz: &fe.nnz_y,
6151 mv: &fe.mv_y,
6152 ref_id: &fe.ref_idx_y,
6153 mv1: &[],
6154 ref_id1: &[],
6155 w4: fe.mb_w * 4,
6156 t8x8: &[],
6157 bs: &bs_grid,
6158 poc0: &[],
6159 poc1: &[],
6160 kind: &[],
6161 };
6162 // Per-MB actual QPy (AQ varies it; `mb_qp_delta`-driven). With `aq_strength 0`
6163 // this is uniform, reproducing the old scalar-QP filtering exactly.
6164 drop(_g_fin);
6165 rusty_h264_common::deblock::filter_frame(
6166 &mut fe.rec_y,
6167 &mut fe.rec_u,
6168 &mut fe.rec_v,
6169 fe.mb_w,
6170 fe.mb_h,
6171 &mb_qpy,
6172 0, // chroma_qp_index_offset — the encoder emits 0
6173 0, // slice_alpha_c0_offset — the encoder always signals zero offsets
6174 0, // slice_beta_offset
6175 &info,
6176 );
6177 enc_scratch::put_qpy(mb_qpy);
6178 let w4 = fe.mb_w * 4;
6179 crate::RefFrame {
6180 y: fe.rec_y,
6181 u: fe.rec_u,
6182 v: fe.rec_v,
6183 poc: 0, // set by the caller (it knows the display order)
6184 frame_num: 0, // set by the caller
6185 // List-0 motion field, for a later B-frame's spatial-direct colZeroFlag.
6186 mv: fe.mv_y,
6187 ref_idx: fe.ref_idx_y,
6188 mv1: Vec::new(),
6189 ref_idx1: Vec::new(),
6190 w4,
6191 // Filtered lazily on first sub-pel search use (see `RefFrame::hpel`).
6192 hpel: std::sync::OnceLock::new(),
6193 }
6194}
6195
6196/// Codes a B-slice's macroblock layer. B-frames are **non-reference**, so the
6197/// reconstruction is computed (the CAVLC nnz predictor needs it) but discarded.
6198///
6199/// This brick: every MB is coded `B_L0_16x16` (`mb_type == 1`) — a real
6200/// motion-compensated prediction from `l0` (the nearest PAST anchor, List-0 index
6201/// 0) plus a coded residual. Because every MB is List-0-only with `ref_idx`
6202/// inferred 0, the per-4×4 List-0 motion field and its median `mvd` predictor are
6203/// byte-identical to the P-slice `P_L0_16x16` path — so this reuses
6204/// [`FrameEncoder::encode_inter_mb_v1_b`] verbatim, differing from P only in the
6205/// `mb_type` value. `l1` (nearest future anchor) is unused until `B_Bi` lands.
6206#[allow(clippy::too_many_arguments)]
6207#[allow(clippy::too_many_arguments)]
6208pub(crate) fn encode_slice_data_b(
6209 w: &mut BitWriter,
6210 cfg: &EncoderConfig,
6211 frame: &YuvFrame,
6212 qp: u8,
6213 poc: i32,
6214 l0: &crate::RefFrame,
6215 l1: &crate::RefFrame,
6216 _qpo: &[i32],
6217) {
6218 let mut fe = FrameEncoder::new(cfg);
6219 fe.qp = qp;
6220 fe.qpc = chroma_qp(qp);
6221 fe.cur_qp = qp;
6222 if cfg.cabac_dz_div > 0 {
6223 fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
6224 } // QPY_PREV starts at the slice QP so the first mb_qp_delta is 0
6225 // Implicit bi-prediction weights from the anchor POC distances (matches the
6226 // decoder). Equidistant B (bframes==1) → 32:32 (plain average); unequal → weighted.
6227 fe.bi_w = implicit_bi_weights(poc, l0.poc, l1.poc);
6228 let (sy, su, sv) = coded_source(cfg, frame);
6229 // Great Gate P1: the shared per-frame signal vector (List-0 anchor as ref).
6230 let sig = FrameSignals::new(&sy, fe.cw, fe.mb_w, fe.mb_h, Some(&l0.y[..]));
6231 apply_screen_t8_veto(&mut fe, &sig);
6232 let lambda = 0.85 * fe.tune_lambda_scale * crate::fastmath::lambda_qp(qp);
6233 let lme = lambda.sqrt();
6234 let refs = std::slice::from_ref(l0); // List-0 = [nearest past anchor]
6235 // Same content-adaptive SAD→SATD dispatch as the P path (codec-content-adaptive-
6236 // dispatch): the top `satd_q` fraction of highest-variance MBs price by SATD.
6237 if fe.satd_q > 0.0 {
6238 fe.satd_var_thresh = sig.var_percentile_thresh(fe.satd_q);
6239 }
6240 signals::harvest(
6241 &sig,
6242 'b',
6243 qp,
6244 &signals::GateDecisions { satd_thresh: fe.satd_var_thresh, ..Default::default() },
6245 );
6246 let mut skip_run = 0u32; // run of consecutive B_Skip MBs pending a coded MB
6247 for mb_y in 0..fe.mb_h {
6248 for mb_x in 0..fe.mb_w {
6249 let (lx, ly) = (mb_x * 16, mb_y * 16);
6250 let (pbx, pby) = (mb_x as isize * 4, mb_y as isize * 4);
6251 // Dedup (E15 W9): memoized twin, already built when satd_q > 0.
6252 fe.mb_use_satd =
6253 fe.satd_q > 0.0 && sig.mb_vars()[mb_y * fe.mb_w + mb_x] >= fe.satd_var_thresh;
6254 // Per-list median MV predictors — the search-rate center AND the actual
6255 // `mvd` predictor (identical to the decoder's `predict_partition_mv`).
6256 let n0 = fe.mv_neighbors_block_list(pbx, pby, 4, 0);
6257 let n1 = fe.mv_neighbors_block_list(pbx, pby, 4, 1);
6258 let pmv0 = predict_partition_mv(0, 0, n0[0], n0[1], n0[2], 0);
6259 let pmv1 = predict_partition_mv(0, 0, n1[0], n1[1], n1[2], 0);
6260 // Independent List-0 / List-1 motion searches (their J already includes
6261 // the mvd rate against the matching predictor, so J0/J1 compare directly).
6262 // Spatial-direct prediction (basis of B_Skip and B_Direct_16x16).
6263 let (dp, dc, dmotion) = fe.b_direct(l0, l1, mb_x, mb_y);
6264 // B_Skip: take the direct prediction with NO coded residual (~1 bit in
6265 // the mb_skip_run) only when it is truly FREE — its residual quantizes to
6266 // zero at the B QP, so skipping loses nothing. (A looser SATD-threshold
6267 // skip was measured strictly WORSE: on B's derived prediction the SATD
6268 // proxy over-values the skip, dropping residual the quantizer wanted —
6269 // the same proxy-vs-quantization gap seen on sub-pel. So skip only when
6270 // provably free; the rest goes through the L0/L1/Bi/Direct RD decision.)
6271 if fe.skip_luma_is_free(&sy, mb_x, mb_y, &dp)
6272 && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &dc)
6273 {
6274 fe.commit_direct_motion(mb_x, mb_y, &dmotion);
6275 fe.commit_pred_pixels(mb_x, mb_y, &dp, &dc);
6276 skip_run += 1;
6277 continue;
6278 }
6279 let d_direct = fe.pred_dist(&sy, lx, ly, &dp);
6280 let (mv0, j0) = fe.motion_search(l0, &sy, lx, ly, 16, 16, &[pmv0], lme, None);
6281 let (mv1, j1) = fe.motion_search(l1, &sy, lx, ly, 16, 16, &[pmv1], lme, None);
6282 // Bi: average the two winners' predictions; rate = both mvds.
6283 let d_bi = fe.bi_dist(l0, l1, &sy, lx, ly, mv0, mv1);
6284 let r_bi = mvd_bits(mv0.0 - pmv0.0) + mvd_bits(mv0.1 - pmv0.1)
6285 + mvd_bits(mv1.0 - pmv1.0) + mvd_bits(mv1.1 - pmv1.1);
6286 let j_bi = d_bi + (lme * r_bi as f64) as i64;
6287 // B_Direct (mb_type 0): spatial-direct prediction, NO coded MV — so its
6288 // J (d_direct, computed above) carries zero mvd rate and it wins wherever
6289 // the derived motion predicts as well as an explicit vector.
6290 // Pick the cheapest of {0=Direct, 1=L0, 2=L1, 3=Bi}; Direct wins ties.
6291 let (mut dir, mut best) = (0u8, d_direct);
6292 if j0 < best { dir = 1; best = j0; }
6293 if j1 < best { dir = 2; best = j1; }
6294 if j_bi < best { dir = 3; best = j_bi; }
6295 let _ = best; // CAVLC B has no partition search to price against it
6296 w.write_ue(skip_run); // run of B_Skips preceding this coded MB
6297 skip_run = 0;
6298 let bspec = BInter { dir, l1, mv0, mv1, mvmode: 0, parts2: [(0, (0, 0), (0, 0)); 2] };
6299 fe.encode_inter_mb_v1_b(w, refs, &sy, &su, &sv, mb_x, mb_y, 0, &[], Some(bspec));
6300 }
6301 }
6302 if skip_run > 0 {
6303 w.write_ue(skip_run); // trailing B_Skip run
6304 }
6305 w.rbsp_trailing_bits();
6306}
6307
6308/// `se(d)` Exp-Golomb bit length — the `mvd`-component rate for the B mode
6309/// decision. Same closed form as `motion_search`'s private `mvbits` (kept separate
6310/// so the P search's heuristic — and thus P output — is untouched).
6311#[inline(always)]
6312fn mvd_bits(d: i32) -> u32 {
6313 let codenum = if d > 0 { (2 * d - 1) as u32 } else { (-2 * d) as u32 };
6314 1 + 2 * (31 - (codenum + 1).leading_zeros())
6315}
6316
6317/// Reads a 4×4 residual block (source minus a raster prediction block).
6318/// Writes `ref_idx_l0` (spec: `te(v)` when two references are active — a single
6319/// flag — else `ue(v)`). Only called when more than one reference is active.
6320fn write_ref_idx(w: &mut BitWriter, refi: i32, num_refs: usize) {
6321 if num_refs == 2 {
6322 w.write_bit(refi == 0); // te(v): value = !bit
6323 } else {
6324 w.write_ue(refi as u32);
6325 }
6326}
6327
6328/// Approximate bit cost of coding `ref_idx = r` with `num_refs` active, for the
6329/// motion-estimation rate term. Zero with a single reference (no `ref_idx` coded).
6330fn ref_bits(r: usize, num_refs: usize) -> u32 {
6331 if num_refs <= 1 {
6332 0
6333 } else if num_refs == 2 {
6334 1
6335 } else {
6336 let mut n = r as u32 + 1;
6337 let mut len = 1;
6338 while n > 1 {
6339 n >>= 1;
6340 len += 2;
6341 }
6342 len
6343 }
6344}
6345
6346fn residual(src: &[u8], stride: usize, x0: usize, y0: usize, pred: &[i32; 16]) -> [i32; 16] {
6347 let mut r = [0i32; 16];
6348 for dy in 0..4 {
6349 let row = &src[(y0 + dy) * stride + x0..][..4];
6350 for dx in 0..4 {
6351 r[dy * 4 + dx] = row[dx] as i32 - pred[dy * 4 + dx];
6352 }
6353 }
6354 r
6355}
6356
6357/// Writes reconstructed samples back into a plane.
6358fn store(plane: &mut [u8], stride: usize, x0: usize, y0: usize, s: &[u8; 16]) {
6359 for dy in 0..4 {
6360 let d = (y0 + dy) * stride + x0;
6361 plane[d..d + 4].copy_from_slice(&s[dy * 4..dy * 4 + 4]);
6362 }
6363}
6364
6365/// Extracts the 4×4 raster prediction block at `(bx, by)` from a 16×16 (256-sample)
6366/// luma prediction.
6367#[cfg(not(accel))]
6368fn pred_block(pred: &[u8; 256], bx: usize, by: usize) -> [i32; 16] {
6369 let mut p = [0i32; 16];
6370 for dy in 0..4 {
6371 let row = &pred[(by * 4 + dy) * 16 + bx * 4..][..4];
6372 for dx in 0..4 {
6373 p[dy * 4 + dx] = row[dx] as i32;
6374 }
6375 }
6376 p
6377}
6378
6379/// Sum of absolute transformed differences over a 16×16 luma macroblock — the
6380/// mode-decision cost (correlates with coded bits better than plain SAD).
6381/// SATD of a `w`×`h` luma block: `src` (stride `ss`) vs `pred` (stride `ps`).
6382///
6383/// With the `accel` cfg and a supported size this is `2 · rusty_h264_accel::satd_*`
6384/// (the portable SIMD kernels that replaced openh264's asm, same contract), which
6385/// is **byte-identical** to the scalar `Σ|H·d|` Hadamard: the kernel returns
6386/// `(Σ+1)>>1`, and `Σ` is always even (every 4×4 Hadamard coefficient shares the block
6387/// sum's parity, so 16 of them sum even), so `×2` recovers `Σ` exactly — proven over
6388/// 20 k random blocks at 4×4/8×8/16×16 in `tests/satd_asm_compare.rs`. Without accel (or
6389/// for an unsupported size) it falls back to the scalar Hadamard — the original path.
6390#[inline]
6391pub(crate) fn satd_px(src: &[u8], ss: usize, pred: &[u8], ps: usize, w: usize, h: usize) -> i64 {
6392 #[cfg(accel)]
6393 {
6394 let asm = match (w, h) {
6395 (16, 16) => Some(rusty_h264_accel::satd_16x16(src, ss, pred, ps)),
6396 (16, 8) => Some(rusty_h264_accel::satd_16x8(src, ss, pred, ps)),
6397 (8, 16) => Some(rusty_h264_accel::satd_8x16(src, ss, pred, ps)),
6398 (8, 8) => Some(rusty_h264_accel::satd_8x8(src, ss, pred, ps)),
6399 (4, 4) => Some(rusty_h264_accel::satd_4x4(src, ss, pred, ps)),
6400 // CENSUS #8, closed by COMPOSITION rather than new intrinsics. The
6401 // sub-8x8 split arm made 8x4 and 4x8 hot, and openh264 ships no
6402 // kernel for either — but SATD here is DEFINED as the sum of 4x4
6403 // Hadamards (see the scalar arm below), so both are exactly two
6404 // `satd_4x4` calls. Each wrapper returns (Σ+1)>>1 and every 4x4 Σ is
6405 // even, so summing the halves and doubling once is bit-identical to
6406 // the scalar path — verified by hash, not argued.
6407 (8, 4) => Some(
6408 rusty_h264_accel::satd_4x4(src, ss, pred, ps)
6409 + rusty_h264_accel::satd_4x4(&src[4..], ss, &pred[4..], ps),
6410 ),
6411 (4, 8) => Some(
6412 rusty_h264_accel::satd_4x4(src, ss, pred, ps)
6413 + rusty_h264_accel::satd_4x4(&src[4 * ss..], ss, &pred[4 * ps..], ps),
6414 ),
6415 _ => None,
6416 };
6417 if let Some(v) = asm {
6418 return 2 * v as i64;
6419 }
6420 }
6421 // Scalar Hadamard (also the no-asm path): Σ over the 4×4 sub-blocks.
6422 let (nbx, nby) = (w / 4, h / 4);
6423 let mut blocks = [[0i32; 16]; 16];
6424 let mut bi = 0;
6425 for by in 0..nby {
6426 for bx in 0..nbx {
6427 let blk = &mut blocks[bi];
6428 for dy in 0..4 {
6429 for dx in 0..4 {
6430 blk[dy * 4 + dx] =
6431 src[(by * 4 + dy) * ss + bx * 4 + dx] as i32 - pred[(by * 4 + dy) * ps + bx * 4 + dx] as i32;
6432 }
6433 }
6434 bi += 1;
6435 }
6436 }
6437 satd_4x4_sum(&blocks[..nbx * nby])
6438}
6439
6440/// SAD of a `w`×`h` block: `src` (stride `ss`) vs a strided region `r` (stride `rs`)
6441/// — the openh264 `psadbw` kernels for the shapes that ship them (they take strides,
6442/// so in-place plane reads need NO materialize), scalar `Σ abs_diff` rows otherwise
6443/// (LLVM lowers the idiom to `psadbw` for contiguous rows).
6444#[inline]
6445fn sad_strided(src: &[u8], ss: usize, r: &[u8], rs: usize, w: usize, h: usize) -> i64 {
6446 #[cfg(accel)]
6447 {
6448 match (w, h) {
6449 (16, 16) => return rusty_h264_accel::sad_16x16(src, ss, r, rs) as i64,
6450 (16, 8) => return rusty_h264_accel::sad_16x8(src, ss, r, rs) as i64,
6451 (8, 16) => return rusty_h264_accel::sad_8x16(src, ss, r, rs) as i64,
6452 _ => {}
6453 }
6454 }
6455 let mut sad = 0u32;
6456 for dy in 0..h {
6457 let a = &src[dy * ss..][..w];
6458 let b = &r[dy * rs..][..w];
6459 sad += a.iter().zip(b).map(|(&x, &y)| x.abs_diff(y) as u32).sum::<u32>();
6460 }
6461 sad as i64
6462}
6463
6464/// Fused `SAD(src, (a+b+1)>>1)` — the quarter-pel SAD without materializing the
6465/// average (the B2 sibling of the A3 `satd_avg` kernel, scalar because the avg+SAD
6466/// idiom auto-vectorizes and quarter-phase SAD evals are seed-frequency only).
6467#[inline]
6468fn sad_avg_strided(src: &[u8], ss: usize, a: &[u8], b: &[u8], rs: usize, w: usize, h: usize) -> i64 {
6469 let mut sad = 0u32;
6470 for dy in 0..h {
6471 let s = &src[dy * ss..][..w];
6472 let pa = &a[dy * rs..][..w];
6473 let pb = &b[dy * rs..][..w];
6474 for i in 0..w {
6475 let p = ((pa[i] as u16 + pb[i] as u16 + 1) >> 1) as u8;
6476 sad += s[i].abs_diff(p) as u32;
6477 }
6478 }
6479 sad as i64
6480}
6481
6482fn satd_16x16(src: &[u8], stride: usize, lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
6483 satd_px(&src[ly * stride + lx..], stride, pred, 16, 16, 16)
6484}
6485
6486/// SAD over a 16×16 luma macroblock against a prediction — the fast preset's
6487/// intra cost, kept in the same (SAD) domain as its inter cost. `Σ a.abs_diff(b)`
6488/// over `u8` slices auto-vectorizes to `psadbw`.
6489fn sad_16x16(src: &[u8], stride: usize, lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
6490 let mut sad = 0u32;
6491 for dy in 0..16 {
6492 let s = &src[(ly + dy) * stride + lx..][..16];
6493 let p = &pred[dy * 16..][..16];
6494 sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
6495 }
6496 sad as i64
6497}
6498
6499/// SATD over an 8×8 chroma block (four 4×4 sub-blocks) against a prediction.
6500fn satd_8x8(src: &[u8], stride: usize, x0: usize, y0: usize, pred: &[u8; 64]) -> i64 {
6501 satd_px(&src[y0 * stride + x0..], stride, pred, 8, 8, 8)
6502}
6503
6504/// SATD of one 4×4 luma block against a prediction.
6505fn satd_4x4(src: &[u8], stride: usize, px: usize, py: usize, pred: &[u8; 16]) -> i64 {
6506 satd_px(&src[py * stride + px..], stride, pred, 4, 4, 4)
6507}
6508
6509/// Whether an `Intra_4x4` mode is usable given top/left neighbor availability.
6510fn i4_mode_available(mode: u8, top: bool, left: bool) -> bool {
6511 match mode {
6512 0 | 3 | 7 => top, // vertical, diag-down-left, vertical-left
6513 1 | 8 => left, // horizontal, horizontal-up
6514 2 => true, // DC
6515 _ => top && left, // diag-down-right, vertical-right, horizontal-down
6516 }
6517}
6518
6519/// Result of planning an I_4x4 macroblock (luma). Reconstruction has already
6520/// been written into the frame's `rec_y` and `coded_y` by [`plan_i4x4`].
6521struct I4Plan {
6522 modes: [u8; 16], // per-block intra4x4 mode, raster [lby*4+lbx]
6523 q: [[i32; 16]; 16], // per-block quantized coefficients (full, raster)
6524 cbp_luma: u32, // 4-bit coded-block-pattern (one bit per 8×8 region)
6525 nonzero: i64, // total non-zero coefficients (rate proxy)
6526}
6527
6528/// A fully-decided intra macroblock: the mode decision, the quantized coefficients,
6529/// and the committed reconstruction. Produced by [`plan_mb`] (which reuses the
6530/// entire mode-decision + transform + reconstruct path), then consumed by an
6531/// entropy backend — `emit_mb_cavlc` or `emit_mb_cabac` — so the two coders share
6532/// every non-entropy decision bit-for-bit (the bringup-encoder reuse guarantee).
6533struct MbPlan {
6534 use_i4: bool,
6535 // I_16x16 (when !use_i4): prediction mode, whether any AC is coded (cbp_luma=15),
6536 // luma DC levels (block order), per-4×4 quantized AC (raster).
6537 i16_mode: I16Mode,
6538 i16_cbp15: bool,
6539 i16_dc_levels: [i32; 16],
6540 i16_q: [[i32; 16]; 16],
6541 // I_4x4 (when use_i4 && i8 is None): the sub-plan, already reconstructed.
6542 i4: Option<I4Plan>,
6543 // I_8x8 (High profile; when use_i4 && i8 is Some): the sub-plan, already
6544 // reconstructed. use_i4 means "I_NxN"; i8 present disambiguates 8x8 from 4x4.
6545 i8: Option<I8Plan>,
6546 // Chroma (shared by both luma types).
6547 chroma_mode: u8,
6548 cbp_chroma: u32,
6549 c_dc_levels: [[i32; 4]; 2],
6550 c_q_blocks: [[[i32; 16]; 4]; 2],
6551}
6552
6553/// A fully-decided inter macroblock: the per-partition motion residuals, coded
6554/// block pattern, and quantized residual, with the reconstruction + motion grids
6555/// already committed. Produced by [`FrameEncoder::plan_inter_mb`] (which reuses the
6556/// whole MC + residual + reconstruct path), then coded by `emit_inter_cavlc` or
6557/// `emit_inter_cabac` — so the two entropy backends share every non-entropy
6558/// decision bit-for-bit (the P/B analogue of [`MbPlan`]).
6559struct InterPlan {
6560 // Per-partition mvd (P: mvd_l0; B: mvd_l0 then mvd_l1). 16 slots: P_8x8 with
6561 // 4x4 sub-partitions carries up to 16 (Great Gate P3.3); every other mode <= 4.
6562 mvds: [(i32, i32); 16],
6563 plan_refs: [i32; 4], // per-partition ref_idx_l0 (multi-ref P; 0 for B / single-ref)
6564 /// P_8x8 only: `sub_mb_type` per 8x8 quad ([0;4] = all 8x8 = the pre-P3.3
6565 /// shape, byte-identical emission). Ignored by every other mode.
6566 sub_types: [u8; 4],
6567 n_mvd: usize,
6568 cbp: u32,
6569 q_blocks: [[i32; 16]; 16], // luma quantized levels (raster) — used when !t8x8
6570 c_dc_levels: [[i32; 4]; 2],
6571 c_q: [[[i32; 16]; 4]; 2],
6572 t8x8: bool, // transform_size_8x8_flag (High profile, 8x8 luma residual)
6573 q8: [[i32; 64]; 4], // per-8x8-block quantized levels (raster) — used when t8x8
6574}
6575
6576/// Gathers the 4×4 luma intra neighbors at pixel `(px, py)` from `rec_y`.
6577fn gather_i4(
6578 fe: &FrameEncoder,
6579 px: usize,
6580 py: usize,
6581 avail_top: bool,
6582 avail_left: bool,
6583 bx: usize,
6584 by: usize,
6585) -> ([u8; 8], [u8; 4], u8) {
6586 let (cw, w4) = (fe.cw, fe.mb_w * 4);
6587 let mut top = [0u8; 8];
6588 let mut left = [0u8; 4];
6589 let mut corner = 0;
6590 if avail_top {
6591 let r = (py - 1) * cw + px;
6592 top[..4].copy_from_slice(&fe.rec_y[r..r + 4]);
6593 let tr_avail = bx + 1 < w4 && fe.coded_y[(by - 1) * w4 + (bx + 1)];
6594 if tr_avail {
6595 top[4..8].copy_from_slice(&fe.rec_y[r + 4..r + 8]);
6596 } else {
6597 let t3 = top[3];
6598 top[4..8].fill(t3);
6599 }
6600 }
6601 if avail_left {
6602 let col = &fe.rec_y[py * cw + px - 1..(py + 3) * cw + px];
6603 for i in 0..4 {
6604 left[i] = col[i * cw];
6605 }
6606 }
6607 if avail_top && avail_left {
6608 corner = fe.rec_y[(py - 1) * cw + px - 1];
6609 }
6610 (top, left, corner)
6611}
6612
6613/// Plans an I_4x4 macroblock: picks a mode per 4×4 block (lowest-SATD available
6614/// mode), quantizes, and reconstructs serially into `rec_y` so each block can
6615/// predict from the previous one.
6616/// Neighbour 4x4 block intra mode for the MPM candidate: in-MB blocks read the
6617/// in-progress local `modes`; blocks in earlier MBs read `fe.modes_y`. (bx, by)
6618/// are the current block's absolute 4x4 grid coords.
6619#[inline]
6620fn modes_at(fe: &FrameEncoder, modes: &[u8; 16], lbx: usize, lby: usize, dx: isize, dy: isize, bx: usize, by: usize) -> u8 {
6621 let (nx, ny) = (lbx as isize + dx, lby as isize + dy);
6622 if (0..4).contains(&nx) && (0..4).contains(&ny) {
6623 modes[ny as usize * 4 + nx as usize]
6624 } else {
6625 let w4 = fe.mb_w * 4;
6626 let gx = (bx as isize + dx) as usize;
6627 let gy = (by as isize + dy) as usize;
6628 fe.modes_y[gy * w4 + gx]
6629 }
6630}
6631
6632fn plan_i4x4(fe: &mut FrameEncoder, sy: &[u8], mb_x: usize, mb_y: usize, qp: u8) -> I4Plan {
6633 let w4 = fe.mb_w * 4;
6634 let mut modes = [2u8; 16];
6635 let mut q = [[0i32; 16]; 16];
6636 let mut cbp_luma = 0u32;
6637 let mut nonzero = 0i64;
6638
6639 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
6640 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
6641 let (px, py) = (bx * 4, by * 4);
6642 let avail_top = by > 0;
6643 let avail_left = bx > 0;
6644 let (top, left, corner) = gather_i4(fe, px, py, avail_top, avail_left, bx, by);
6645
6646 // Pick the lowest-SATD available mode. RUSTY_FAST_INTRA prunes the
6647 // candidate set to {MPM, DC, V, H} (x264-ultrafast-style); the H.264
6648 // predicted mode (min of left/top block modes, DC on the edge) keeps the
6649 // 1-bit prev_intra4x4_pred_mode signalling cheap for the common winner.
6650 let mut best_m = 2u8;
6651 let mut best_cost = i64::MAX;
6652 // E2 W16: keep the winner's prediction from the search loop; it was
6653 // re-predicted below after the decision.
6654 let mut best_pred = [0u8; 16];
6655 if fe.fast && fast_intra_enabled() {
6656 let lm = if bx > 0 { modes_at(fe, &modes, lbx, lby, -1, 0, bx, by) } else { 2 };
6657 let tm = if by > 0 { modes_at(fe, &modes, lbx, lby, 0, -1, bx, by) } else { 2 };
6658 let mpm = lm.min(tm);
6659 let mut cands = [mpm, 2u8, 0, 1];
6660 for i in 1..4 {
6661 for j in 0..i {
6662 if cands[i] == cands[j] {
6663 cands[i] = 255;
6664 }
6665 }
6666 }
6667 for &m in cands.iter() {
6668 if m == 255 || !i4_mode_available(m, avail_top, avail_left) {
6669 continue;
6670 }
6671 let pred = intra4x4_pred(m, avail_top, avail_left, &top, &left, corner);
6672 let cost = satd_4x4(sy, fe.cw, px, py, &pred);
6673 if cost < best_cost {
6674 best_cost = cost;
6675 best_m = m;
6676 best_pred = pred;
6677 }
6678 }
6679 } else {
6680 for m in 0..9u8 {
6681 if !i4_mode_available(m, avail_top, avail_left) {
6682 continue;
6683 }
6684 let pred = intra4x4_pred(m, avail_top, avail_left, &top, &left, corner);
6685 let cost = satd_4x4(sy, fe.cw, px, py, &pred);
6686 if cost < best_cost {
6687 best_cost = cost;
6688 best_m = m;
6689 best_pred = pred;
6690 }
6691 }
6692 }
6693
6694 // Quantize + reconstruct with the chosen mode (the search's own winner).
6695 let pred = best_pred;
6696 let mut predb = [0i32; 16];
6697 for i in 0..16 {
6698 predb[i] = pred[i] as i32;
6699 }
6700 let res = residual(sy, fe.cw, px, py, &predb);
6701 let qb = rdoq(&forward_core(&res), qp, fe.idz, fe.rdoq_strength, 0); // full 16 incl DC
6702 // E2 W17: the decoder's fused recon (11.6a win 1 executed) — no [u8;16]
6703 // temp, no separate `store` walk; abl/prof parity is inside the callee.
6704 reconstruct_4x4_into(&dequantize(&qb, qp), &pred, 0, 4, &mut fe.rec_y, py * fe.cw + px, fe.cw);
6705 fe.coded_y[by * w4 + bx] = true;
6706
6707 let nz = qb.iter().filter(|&&v| v != 0).count();
6708 if nz > 0 {
6709 cbp_luma |= 1 << ((lby / 2) * 2 + (lbx / 2));
6710 }
6711 nonzero += nz as i64;
6712 modes[lby * 4 + lbx] = best_m;
6713 q[lby * 4 + lbx] = qb;
6714 }
6715 I4Plan {
6716 modes,
6717 q,
6718 cbp_luma,
6719 nonzero,
6720 }
6721}
6722
6723/// A planned I_8x8 macroblock (High profile): one intra8x8 mode + one 8x8 DCT per
6724/// 8x8 block. Reconstructed serially into `rec_y` (each block predicts from the
6725/// previous), and `modes_y` written per block so later blocks' MPM sees earlier.
6726struct I8Plan {
6727 modes: [u8; 4], // per-8x8-block intra8x8 mode (raster b8 0..3)
6728 q: [[i32; 64]; 4], // per-8x8-block quantized levels (raster)
6729 cbp_luma: u32, // 4-bit coded-block-pattern (one bit per 8x8 block)
6730 nonzero: i64, // rate proxy
6731}
6732
6733/// Forward zig-zag scan of a raster 8x8 block: `scan[i] = raster[ZIGZAG_8X8[i]]`
6734/// (the inverse of the decoder's `un_scan_8x8`).
6735const ZIGZAG_8X8: [usize; 64] = [
6736 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
6737 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,
6738 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
6739];
6740
6741#[inline]
6742fn scan_8x8_fwd(raster: &[i32; 64]) -> [i32; 64] {
6743 std::array::from_fn(|i| raster[ZIGZAG_8X8[i]])
6744}
6745
6746/// Gather the 8x8 intra reference samples (top[16] incl top-right, left[8], corner)
6747/// from `rec_y` — the encoder counterpart of the decoder's `gather_i8`.
6748fn gather_i8_enc(
6749 fe: &FrameEncoder,
6750 px: usize,
6751 py: usize,
6752 avail_top: bool,
6753 avail_left: bool,
6754 bx: usize,
6755 by: usize,
6756) -> ([u8; 16], [u8; 8], u8, bool) {
6757 let (cw, w4) = (fe.cw, fe.mb_w * 4);
6758 let mut top = [0u8; 16];
6759 let mut left = [0u8; 8];
6760 let mut corner = 0;
6761 if avail_top {
6762 let r = (py - 1) * cw + px;
6763 top[..8].copy_from_slice(&fe.rec_y[r..r + 8]);
6764 let tr_avail = bx + 2 < w4 && fe.coded_y[(by - 1) * w4 + (bx + 2)];
6765 if tr_avail {
6766 top[8..16].copy_from_slice(&fe.rec_y[r + 8..r + 16]);
6767 } else {
6768 let t7 = top[7];
6769 top[8..16].fill(t7);
6770 }
6771 }
6772 if avail_left {
6773 let col = &fe.rec_y[py * cw + px - 1..(py + 7) * cw + px];
6774 for i in 0..8 {
6775 left[i] = col[i * cw];
6776 }
6777 }
6778 let avail_corner = avail_top && avail_left;
6779 if avail_corner {
6780 corner = fe.rec_y[(py - 1) * cw + px - 1];
6781 }
6782 (top, left, corner, avail_corner)
6783}
6784
6785/// Plans an I_8x8 macroblock: per 8x8 block, picks the lowest-SATD intra8x8 mode,
6786/// 8x8-forward-transforms + quantizes, and reconstructs serially into `rec_y`.
6787fn plan_i8x8(fe: &mut FrameEncoder, sy: &[u8], mb_x: usize, mb_y: usize, qp: u8) -> I8Plan {
6788 let w4 = fe.mb_w * 4;
6789 let mut modes = [2u8; 4];
6790 let mut q = [[0i32; 64]; 4];
6791 let mut cbp_luma = 0u32;
6792 let mut nonzero = 0i64;
6793 let weight = [16i32; 64];
6794
6795 for b8 in 0..4usize {
6796 let (b8x, b8y) = (b8 % 2, b8 / 2);
6797 let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
6798 let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2); // top-left 4x4 cell
6799 let avail_top = b8y > 0 || mb_y > 0;
6800 let avail_left = b8x > 0 || mb_x > 0;
6801 let (top, left, corner, avail_corner) =
6802 gather_i8_enc(fe, px, py, avail_top, avail_left, bx, by);
6803
6804 // Mode decision: lowest-SATD available intra8x8 mode (same 9 modes / avail
6805 // rules as intra4x4). The MPM (predict_i4_mode on the top-left 4x4) keeps the
6806 // 1-bit prev-mode signalling cheap; a small penalty biases toward it.
6807 let predicted = predict_i4_mode(fe, bx, by);
6808 let mut best_m = 2u8;
6809 let mut best_cost = i64::MAX;
6810 // E2 W18: keep the winner's prediction; it was re-predicted below.
6811 let mut best_pred = [0u8; 64];
6812 for m in 0..9u8 {
6813 if !i4_mode_available(m, avail_top, avail_left) {
6814 continue;
6815 }
6816 let pred = intra8x8_pred(m, avail_top, avail_left, avail_corner, &top, &left, corner);
6817 let mut cost = satd_8x8(sy, fe.cw, px, py, &pred);
6818 if m != predicted {
6819 cost += 4 * fe.qp as i64; // ~mode-signal penalty (rem vs prev flag)
6820 }
6821 if cost < best_cost {
6822 best_cost = cost;
6823 best_m = m;
6824 best_pred = pred;
6825 }
6826 }
6827 modes[b8] = best_m;
6828
6829 // Forward 8x8 transform + quantize + reconstruct (shared decoder primitives).
6830 let pred = best_pred;
6831 let mut res = [0i32; 64];
6832 // E2 W19: row slices (the proven plan_inter8_luma shape).
6833 for dy in 0..8 {
6834 let row = &sy[(py + dy) * fe.cw + px..][..8];
6835 for dx in 0..8 {
6836 res[dy * 8 + dx] = row[dx] as i32 - pred[dy * 8 + dx] as i32;
6837 }
6838 }
6839 let levels = quantize_8x8(&forward_core_8x8(&res), qp, &weight, fe.idz);
6840 let nz = levels.iter().filter(|&&v| v != 0).count();
6841 if nz > 0 {
6842 cbp_luma |= 1 << b8;
6843 }
6844 nonzero += nz as i64;
6845 q[b8] = levels;
6846
6847 let res_r = inverse_quant_8x8(&levels, qp, &weight);
6848 let predb: [i32; 64] = std::array::from_fn(|i| pred[i] as i32);
6849 let recon = add_residual_8x8(&res_r, &predb);
6850 for dy in 0..8 {
6851 let d = (py + dy) * fe.cw + px;
6852 fe.rec_y[d..d + 8].copy_from_slice(&recon[dy * 8..dy * 8 + 8]);
6853 }
6854 // Publish the mode into all four 4x4 cells + mark coded — so the next 8x8
6855 // block's MPM (and later MBs' neighbours) see it, exactly as the decoder does.
6856 for sry in 0..2 {
6857 let d = (by + sry) * w4 + bx;
6858 fe.modes_y[d..d + 2].fill(best_m);
6859 fe.coded_y[d..d + 2].fill(true);
6860 }
6861 }
6862 I8Plan {
6863 modes,
6864 q,
6865 cbp_luma,
6866 nonzero,
6867 }
6868}
6869
6870/// Inter 8×8-transform luma candidate. Forward-8×8 + quantize + reconstruct each of
6871/// the four 8×8 blocks of the motion-compensated residual `(source − pred_y)`, the
6872/// pure inverse of the decoder's t8x8 inter luma path (`inv_quant8` ∘ `un_scan_8x8`
6873/// ∘ `add_residual_8x8`). Returns the quantized levels, `cbp_luma`, a LEVEL-AWARE rate
6874/// estimate (Σ `rdoq_rate(|level|)` — charges the 8×8's fewer-but-larger coeffs at
6875/// their true bit cost, not a blind count), the 256-sample reconstruction, and its
6876/// SSD vs source. Inter deadzone `dz_div = 6`; scaling list flat (16).
6877#[allow(clippy::too_many_arguments)]
6878fn plan_inter8_luma(
6879 sy: &[u8],
6880 cw: usize,
6881 mb_x: usize,
6882 mb_y: usize,
6883 pred_y: &[u8; 256],
6884 qp: u8,
6885) -> ([[i32; 64]; 4], u32, f64, [u8; 256], i64) {
6886 let weight = [16i32; 64];
6887 let mut q8 = [[0i32; 64]; 4];
6888 let mut cbp = 0u32;
6889 let mut rate = 0f64;
6890 let mut rec = [0u8; 256];
6891 let mut ssd = 0i64;
6892 for b8 in 0..4usize {
6893 let (b8x, b8y) = (b8 % 2, b8 / 2);
6894 let mut res = [0i32; 64];
6895 for dy in 0..8 {
6896 let row = &sy[(mb_y * 16 + b8y * 8 + dy) * cw + mb_x * 16 + b8x * 8..][..8];
6897 for dx in 0..8 {
6898 let p = pred_y[(b8y * 8 + dy) * 16 + (b8x * 8 + dx)] as i32;
6899 res[dy * 8 + dx] = row[dx] as i32 - p;
6900 }
6901 }
6902 let levels = quantize_8x8(&forward_core_8x8(&res), qp, &weight, 6);
6903 let mut nz = false;
6904 for &l in &levels {
6905 if l != 0 {
6906 nz = true;
6907 rate += rdoq_rate((l as i64).abs());
6908 }
6909 }
6910 if nz {
6911 cbp |= 1 << b8;
6912 }
6913 q8[b8] = levels;
6914
6915 let res_r = inverse_quant_8x8(&levels, qp, &weight);
6916 let predb: [i32; 64] =
6917 std::array::from_fn(|i| pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32);
6918 let recon = add_residual_8x8(&res_r, &predb);
6919 for dy in 0..8 {
6920 let row = &sy[(mb_y * 16 + b8y * 8 + dy) * cw + mb_x * 16 + b8x * 8..][..8];
6921 for dx in 0..8 {
6922 let ri = (b8y * 8 + dy) * 16 + (b8x * 8 + dx);
6923 rec[ri] = recon[dy * 8 + dx];
6924 let d = recon[dy * 8 + dx] as i64 - row[dx] as i64;
6925 ssd += d * d;
6926 }
6927 }
6928 }
6929 (q8, cbp, rate, rec, ssd)
6930}
6931
6932/// 16×16 luma intra prediction. For interior MBs (both neighbors available) this
6933/// dispatches to accel's `i16x16_luma_pred` (bit-identical to the spec
6934/// predictor); edge MBs (partial availability → C-only DC variants) use the scalar
6935/// path. The scalar `top`/`left`/`corner` are gathered by the caller regardless.
6936#[inline]
6937fn i16_pred(
6938 fe: &FrameEncoder,
6939 mode: I16Mode,
6940 avail_top: bool,
6941 avail_left: bool,
6942 top: &[u8; 16],
6943 left: &[u8; 16],
6944 corner: u8,
6945 lx: usize,
6946 ly: usize,
6947) -> [u8; 256] {
6948 #[cfg(accel)]
6949 if avail_top && avail_left {
6950 let mode_n = match mode {
6951 I16Mode::Vertical => 0,
6952 I16Mode::Horizontal => 1,
6953 I16Mode::Dc => 2,
6954 I16Mode::Plane => 3,
6955 };
6956 let mut p = AlignedMb([0; 256]);
6957 rusty_h264_accel::i16x16_luma_pred(mode_n, &mut p.0, &fe.rec_y[..], ly * fe.cw + lx, fe.cw);
6958 return p.0;
6959 }
6960 let _ = (fe, lx, ly);
6961 luma16x16_pred(mode, avail_top, avail_left, top, left, corner)
6962}
6963
6964/// 8×8 chroma intra prediction. Interior MBs use accel's `chroma8x8_pred`
6965/// for the V/Plane modes (bit-identical); DC/Horizontal (C-only in openh264) and edge MBs
6966/// use the scalar path.
6967#[inline]
6968#[allow(clippy::too_many_arguments)]
6969fn chroma_pred(
6970 fe: &FrameEncoder,
6971 mode: u8,
6972 avail_top: bool,
6973 avail_left: bool,
6974 c: usize,
6975 top: &[u8; 8],
6976 left: &[u8; 8],
6977 corner: u8,
6978 cx: usize,
6979 cy: usize,
6980) -> [u8; 64] {
6981 #[cfg(accel)]
6982 if avail_top && avail_left && (mode == 2 || mode == 3) {
6983 let plane = if c == 0 { &fe.rec_u } else { &fe.rec_v };
6984 // E2 W20: a 64-byte aligned buffer — the 256-byte AlignedMb zeroed 192
6985 // dead bytes and then paid a second 64-byte copy-out.
6986 #[repr(align(16))]
6987 struct A64([u8; 64]);
6988 let mut p = A64([0; 64]);
6989 rusty_h264_accel::chroma8x8_pred(mode, &mut p.0, &plane[..], cy * fe.ccw + cx, fe.ccw);
6990 return p.0;
6991 }
6992 let _ = (fe, c, cx, cy);
6993 chroma8x8_pred(mode, avail_top, avail_left, top, left, corner)
6994}
6995
6996/// Predicted `Intra_4x4` mode for the block at absolute coords `(bx, by)` —
6997/// `min` of the left/top neighbor modes, or DC if either is unavailable.
6998fn predict_i4_mode(fe: &FrameEncoder, bx: usize, by: usize) -> u8 {
6999 if bx == 0 || by == 0 {
7000 return 2;
7001 }
7002 let w4 = fe.mb_w * 4;
7003 fe.modes_y[by * w4 + (bx - 1)].min(fe.modes_y[(by - 1) * w4 + bx])
7004}
7005
7006#[allow(clippy::too_many_arguments)]
7007/// Zig-zag scan: block (raster 4×4) index at scan position i.
7008const RDOQ_ZZ: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
7009
7010/// Approximate CABAC bit cost of coding one residual coefficient at magnitude
7011/// `level`: significant_coeff_flag (~1) + coeff_abs_level_minus1 bins (gt1 + UEG0)
7012/// + sign (~1); `level == 0` is significant_coeff_flag = 0 (~1). A coarse model —
7013/// the transform-norm/bin-to-bit scaling is absorbed into the calibrated strength.
7014#[inline]
7015fn rdoq_rate(level: i64) -> f64 {
7016 // The closed form (level 0 → 1, 1 → 3, k ≥ 2 → 3 + min(k−1, 13), saturating
7017 // at 16 from level 14 up) tabulated: two branches + a min + an int→f64
7018 // convert become one clamped load. Same 16 values, BIT-IDENTICAL.
7019 const LUT: [f64; 16] = [
7020 1.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 16.0,
7021 ];
7022 LUT[(level.min(15)) as usize]
7023}
7024
7025/// `65536 / QUANT_MF_OH[qp][k]` for every (qp, k) — [`rdoq`]'s dequant steps.
7026/// The reciprocals are pure functions of two const tables, so they are computed
7027/// ONCE per process instead of eight `divsd` per trellis call (once per 4×4
7028/// residual block). IEEE division is deterministic: same operands, same bits.
7029fn rdoq_qstep(qp: u8) -> &'static [f64; 8] {
7030 static T: std::sync::OnceLock<[[f64; 8]; 52]> = std::sync::OnceLock::new();
7031 &T.get_or_init(|| {
7032 std::array::from_fn(|q| {
7033 std::array::from_fn(|k| 65536.0 / rusty_h264_common::transform::QUANT_MF_OH[q][k] as f64)
7034 })
7035 })[qp as usize]
7036}
7037
7038/// Rate-distortion optimized quantization (CABAC trellis, RDOQ) for one 4×4 residual
7039/// block. Refines the hard-decision levels toward min over {|q|, |q|-1} of
7040/// `SSD_coef + λ·R_cabac` per coefficient (coefficient-domain distortion
7041/// `(|coeff| - level·deq_step)²`; `λ = strength·2^((qp-12)/3)`). `strength == 0`
7042/// returns the hard quantization unchanged (the CAVLC path). `first` = 1 skips the
7043/// DC (AC-only categories: I_16x16 AC, chroma AC), else 0.
7044fn rdoq(coeffs: &[i32; 16], qp: u8, dz_div: i64, strength: f64, first: usize) -> [i32; 16] {
7045 let mut q = quantize(coeffs, qp, dz_div);
7046 if strength <= 0.0 {
7047 return q;
7048 }
7049 let lambda = strength * crate::fastmath::lambda_qp(qp);
7050 // Distortion is measured in the QUANTIZER-INPUT (forward-transform) domain, where
7051 // level L reconstructs to L·qstep, qstep = 2^16 / MF (the inverse of the forward
7052 // quant scale). The transform norm (forward↔pixel) folds into `strength`.
7053 const POS: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7];
7054 // Eight reciprocals per call became a process-lifetime table ([`rdoq_qstep`]):
7055 // the hoist-to-call-scope round left 8 `divsd` per trellis invocation; both
7056 // inputs are const tables, so the divides now happen once per process.
7057 let qstep = rdoq_qstep(qp);
7058 let dist = |p: usize, level: i64| -> f64 {
7059 let e = coeffs[p].unsigned_abs() as f64 - level as f64 * qstep[POS[p]];
7060 e * e
7061 };
7062 // Pass 1: per-coefficient level lowering (|q| → |q|-1) minimizing D + λ·R.
7063 for i in first..16 {
7064 let p = RDOQ_ZZ[i];
7065 let m = q[p].unsigned_abs() as i64;
7066 if m == 0 {
7067 continue;
7068 }
7069 let j_keep = dist(p, m) + lambda * rdoq_rate(m);
7070 let j_down = dist(p, m - 1) + lambda * rdoq_rate(m - 1);
7071 if j_down < j_keep {
7072 let nl = (m - 1) as i32;
7073 q[p] = if q[p] < 0 { -nl } else { nl };
7074 }
7075 }
7076 // Pass 2: last-significant-position trimming. Zeroing the trailing significant
7077 // coefficient frees its own bits AND the last_significant flag + every sig=0 flag
7078 // between it and the previous significant coefficient (positions past the new last
7079 // aren't coded at all) — the dominant RDOQ gain on sparse (inter) residuals.
7080 loop {
7081 let Some(li) = (first..16).rev().find(|&i| q[RDOQ_ZZ[i]] != 0) else {
7082 break;
7083 };
7084 let p = RDOQ_ZZ[li];
7085 let m = q[p].unsigned_abs() as i64;
7086 let prev = (first..li).rev().find(|&i| q[RDOQ_ZZ[i]] != 0);
7087 let base = prev.map_or(first, |j| j + 1);
7088 let bits = rdoq_rate(m) + 1.0 + (li - base) as f64; // coeff + last-flag + freed sig=0
7089 let d_add = dist(p, 0) - dist(p, m);
7090 if d_add < lambda * bits {
7091 q[p] = 0;
7092 } else {
7093 break;
7094 }
7095 }
7096 q
7097}
7098
7099/// Decide one intra macroblock (I_16x16 vs I_4x4, prediction modes, chroma),
7100/// forward-transform + quantize, and commit the reconstruction + neighbour mode
7101/// state — everything except entropy coding. The returned [`MbPlan`] is coded by
7102/// either entropy backend, so CAVLC and CABAC share this whole path bit-for-bit.
7103fn plan_mb(
7104 fe: &mut FrameEncoder,
7105 mb_x: usize,
7106 mb_y: usize,
7107 sy: &[u8],
7108 su: &[u8],
7109 sv: &[u8],
7110) -> MbPlan {
7111 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCode);
7112 let qp = fe.qp;
7113 let qpc = fe.qpc;
7114 // Lagrangian λ for rate-distortion decisions (standard H.264 form).
7115 let lambda = 0.85 * fe.tune_lambda_scale * crate::fastmath::lambda_qp(qp);
7116
7117 // ---------------- luma ----------------
7118 let (lx, ly) = (mb_x * 16, mb_y * 16);
7119 let avail_top = mb_y > 0;
7120 let avail_left = mb_x > 0;
7121 let mut top = [0u8; 16];
7122 let mut left = [0u8; 16];
7123 if avail_top {
7124 let r = (ly - 1) * fe.cw + lx;
7125 top.copy_from_slice(&fe.rec_y[r..r + 16]);
7126 }
7127 if avail_left {
7128 let col = &fe.rec_y[ly * fe.cw + lx - 1..(ly + 15) * fe.cw + lx];
7129 for i in 0..16 {
7130 left[i] = col[i * fe.cw];
7131 }
7132 }
7133 let corner = if avail_top && avail_left {
7134 fe.rec_y[(ly - 1) * fe.cw + lx - 1]
7135 } else {
7136 0
7137 };
7138
7139 let w4 = fe.mb_w * 4;
7140
7141 // ============ I_16x16 plan (reconstruct into a local buffer) ============
7142 let mut i16_mode = I16Mode::Dc;
7143 let mut best_pred = i16_pred(fe, I16Mode::Dc, avail_top, avail_left, &top, &left, corner, lx, ly);
7144 let mut best_cost = satd_16x16(sy, fe.cw, lx, ly, &best_pred);
7145 for mode in [I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
7146 if !mode.available(avail_top, avail_left) {
7147 continue;
7148 }
7149 let pred = i16_pred(fe, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
7150 let cost = satd_16x16(sy, fe.cw, lx, ly, &pred);
7151 if cost < best_cost {
7152 best_cost = cost;
7153 i16_mode = mode;
7154 best_pred = pred;
7155 }
7156 }
7157 // I_16x16 blocks are independent (one fixed whole-MB prediction), so batch the
7158 // forward DCT (`forward_dct_blocks` → SIMD), bit-identical to `forward_core`.
7159 let mut dc4x4 = [0i32; 16];
7160 let mut i16_q = [[0i32; 16]; 16];
7161 // Fast path: forward DCT of (src - pred) straight from the planes per 8x8 quad,
7162 // quantize with the identical FF/MF math (deadzone = fe.idz), recon via the
7163 // bit-identical idct+add+clip kernel — the same pairing encode_inter_mb and the
7164 // P_Skip free-check already use, byte-identical to the scalar twin below.
7165 #[cfg(accel)]
7166 let (i16_dc_levels, _i16_recon_dc, recon16) = {
7167 #[repr(align(16))]
7168 struct A([i16; 256]);
7169 let mut dct = A([0i16; 256]);
7170 let base = ly * fe.cw + lx;
7171 for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
7172 rusty_h264_accel::dct_four_t4(
7173 &mut dct.0[qi * 64..qi * 64 + 64],
7174 &sy[base + qy * fe.cw + qx..],
7175 fe.cw,
7176 &best_pred[qy * 16 + qx..],
7177 16,
7178 );
7179 }
7180 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
7181 dc4x4[lby * 4 + lbx] = dct.0[blk * 16] as i32;
7182 }
7183 if fe.rdoq_strength > 0.0 {
7184 // Trellis (all-intra only): scalar RDOQ from the asm DCT output instead of
7185 // the asm hard quantizer. dct.0 keeps the raw DCT here; the recon loop below
7186 // overwrites it with the dequantized RDOQ levels.
7187 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
7188 let coeffs: [i32; 16] = std::array::from_fn(|i| dct.0[blk * 16 + i] as i32);
7189 let mut q = rdoq(&coeffs, qp, fe.idz, fe.rdoq_strength, 1);
7190 q[0] = 0;
7191 i16_q[lby * 4 + lbx] = q;
7192 }
7193 } else {
7194 let ff = rusty_h264_common::transform::quant_dz_ff(qp, fe.idz);
7195 let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
7196 for qi in 0..4 {
7197 rusty_h264_accel::quant_four_4x4(&mut dct.0[qi * 64..qi * 64 + 64], &ff, mf);
7198 }
7199 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
7200 let q = &mut i16_q[lby * 4 + lbx];
7201 q[0] = 0;
7202 for i in 1..16 {
7203 q[i] = dct.0[blk * 16 + i] as i32;
7204 }
7205 }
7206 }
7207 let i16_dc_levels = forward_quant_luma_dc(&dc4x4, qp, true);
7208 let i16_recon_dc = inverse_quant_luma_dc(&i16_dc_levels, qp);
7209 // Recon: dequantize (DC injected from the Hadamard) back into quad layout,
7210 // then idct+add-pred+clip into the trial buffer.
7211 let mut recon16 = [0u8; 256];
7212 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
7213 let mut deq = dequantize(&i16_q[lby * 4 + lbx], qp);
7214 deq[0] = i16_recon_dc[lby * 4 + lbx];
7215 for i in 0..16 {
7216 dct.0[blk * 16 + i] = deq[i] as i16;
7217 }
7218 }
7219 for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
7220 rusty_h264_accel::idct_four_t4_rec(
7221 &mut recon16[qy * 16 + qx..],
7222 16,
7223 &best_pred[qy * 16 + qx..],
7224 16,
7225 &dct.0[qi * 64..qi * 64 + 64],
7226 );
7227 }
7228 (i16_dc_levels, i16_recon_dc, recon16)
7229 };
7230 #[cfg(not(accel))]
7231 let (i16_dc_levels, _i16_recon_dc, recon16) = {
7232 let mut res_blocks = [[0i32; 16]; 16];
7233 for by in 0..4 {
7234 for bx in 0..4 {
7235 let predb = pred_block(&best_pred, bx, by);
7236 res_blocks[by * 4 + bx] = residual(sy, fe.cw, lx + bx * 4, ly + by * 4, &predb);
7237 }
7238 }
7239 let mut coeffs = [[0i32; 16]; 16];
7240 forward_dct_blocks(&res_blocks, &mut coeffs);
7241 for i in 0..16 {
7242 dc4x4[i] = coeffs[i][0];
7243 let mut q = rdoq(&coeffs[i], qp, fe.idz, fe.rdoq_strength, 1);
7244 q[0] = 0;
7245 i16_q[i] = q;
7246 }
7247 let i16_dc_levels = forward_quant_luma_dc(&dc4x4, qp, true);
7248 let i16_recon_dc = inverse_quant_luma_dc(&i16_dc_levels, qp);
7249 let mut recon16 = [0u8; 256];
7250 let mut deq_blocks = [[0i32; 16]; 16];
7251 for i in 0..16 {
7252 deq_blocks[i] = dequantize(&i16_q[i], qp);
7253 deq_blocks[i][0] = i16_recon_dc[i];
7254 }
7255 let mut idct = [[0i32; 16]; 16];
7256 inverse_dct_blocks(&deq_blocks, &mut idct);
7257 for by in 0..4 {
7258 for bx in 0..4 {
7259 let s = add_residual_4x4(&idct[by * 4 + bx], &pred_block(&best_pred, bx, by));
7260 for dy in 0..4 {
7261 for dx in 0..4 {
7262 recon16[(by * 4 + dy) * 16 + (bx * 4 + dx)] = s[dy * 4 + dx];
7263 }
7264 }
7265 }
7266 }
7267 (i16_dc_levels, i16_recon_dc, recon16)
7268 };
7269 let i16_cbp15 = i16_q.iter().any(|b| b[1..].iter().any(|&c| c != 0));
7270 let i16_dc_nz = i16_dc_levels.iter().filter(|&&v| v != 0).count() as i64;
7271 let i16_ac_nz: i64 = i16_q
7272 .iter()
7273 .map(|b| b[1..].iter().filter(|&&v| v != 0).count() as i64)
7274 .sum();
7275 // I_16x16 AC is all-or-nothing: any AC ⇒ all 16 blocks pay a coeff_token.
7276 let i16_rate = i16_dc_nz + i16_ac_nz + if i16_cbp15 { 16 } else { 0 };
7277 // Reconstruction distortion (SSD) for the rate-distortion decision.
7278 let mut ssd16 = 0i64;
7279 for dy in 0..16 {
7280 let sr = (ly + dy) * fe.cw + lx;
7281 for (r, sv2) in recon16[dy * 16..dy * 16 + 16].iter().zip(&sy[sr..sr + 16]) {
7282 let d = *r as i64 - *sv2 as i64;
7283 ssd16 += d * d;
7284 }
7285 }
7286
7287 // ============ chroma (shared by both luma types; commit immediately) ============
7288 let (cx, cy) = (mb_x * 8, mb_y * 8);
7289 // Gather both components' neighbors, then pick a chroma mode by combined SATD.
7290 let mut ntop = [[0u8; 8]; 2];
7291 let mut nleft = [[0u8; 8]; 2];
7292 let mut ncorner = [0u8; 2];
7293 for c in 0..2 {
7294 let rec_c = if c == 0 { &fe.rec_u } else { &fe.rec_v };
7295 if avail_top {
7296 let r = (cy - 1) * fe.ccw + cx;
7297 ntop[c].copy_from_slice(&rec_c[r..r + 8]);
7298 }
7299 if avail_left {
7300 let col = &rec_c[cy * fe.ccw + cx - 1..(cy + 7) * fe.ccw + cx];
7301 for i in 0..8 {
7302 nleft[c][i] = col[i * fe.ccw];
7303 }
7304 }
7305 if avail_top && avail_left {
7306 ncorner[c] = rec_c[(cy - 1) * fe.ccw + cx - 1];
7307 }
7308 }
7309 let mut chroma_mode = 0u8;
7310 let mut best_c_cost = i64::MAX;
7311 // E2 W11 (inline-execution.md 11.12): the WINNING mode's two prediction
7312 // planes were re-predicted after the search; keep them from the loop.
7313 let mut best_pred8 = [[0u8; 64]; 2];
7314 for m in 0..4u8 {
7315 if !chroma_mode_available(m, avail_top, avail_left) {
7316 continue;
7317 }
7318 let mut cost = 0i64;
7319 let mut cur = [[0u8; 64]; 2];
7320 for c in 0..2 {
7321 let src = if c == 0 { su } else { sv };
7322 cur[c] = chroma_pred(fe, m, avail_top, avail_left, c, &ntop[c], &nleft[c], ncorner[c], cx, cy);
7323 cost += satd_8x8(src, fe.ccw, cx, cy, &cur[c]);
7324 }
7325 if cost < best_c_cost {
7326 best_c_cost = cost;
7327 chroma_mode = m;
7328 best_pred8 = cur;
7329 }
7330 }
7331
7332 let mut c_dc_levels = [[0i32; 4]; 2];
7333 let mut c_q_blocks = [[[0i32; 16]; 4]; 2];
7334 let mut any_chroma_ac = false;
7335 let mut any_chroma_dc = false;
7336 for c in 0..2 {
7337 let src = if c == 0 { su } else { sv };
7338 let pred8 = best_pred8[c]; // the search's winner, not a re-prediction
7339 #[cfg_attr(accel, allow(unused_variables))] // non-accel scalar path only
7340 let pblk = |bx: usize, by: usize| -> [i32; 16] {
7341 let mut predb = [0i32; 16];
7342 for dy in 0..4 {
7343 for dx in 0..4 {
7344 predb[dy * 4 + dx] = pred8[(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
7345 }
7346 }
7347 predb
7348 };
7349 // Fast path: forward DCT of (src - pred8) straight from the planes, quantize
7350 // with identical FF/MF (idz deadzone), recon via one idct+add+clip kernel —
7351 // bit-identical to the scalar twin below (proven kernel pairings).
7352 let mut dc2x2 = [0i32; 4];
7353 let mut qbs = [[0i32; 16]; 4];
7354 #[cfg(accel)]
7355 let recon_dc = {
7356 #[repr(align(16))]
7357 struct A([i16; 64]);
7358 let mut d = A([0i16; 64]);
7359 rusty_h264_accel::dct_four_t4(&mut d.0, &src[cy * fe.ccw + cx..], fe.ccw, &pred8, 8);
7360 for i in 0..4 {
7361 dc2x2[i] = d.0[i * 16] as i32;
7362 }
7363 if fe.rdoq_strength > 0.0 {
7364 // Trellis (all-intra only): scalar RDOQ from the asm chroma DCT.
7365 for i in 0..4 {
7366 let coeffs: [i32; 16] = std::array::from_fn(|j| d.0[i * 16 + j] as i32);
7367 let mut q = rdoq(&coeffs, qpc, fe.idz, fe.rdoq_strength, 1);
7368 q[0] = 0;
7369 if q[1..].iter().any(|&v| v != 0) {
7370 any_chroma_ac = true;
7371 }
7372 qbs[i] = q;
7373 }
7374 } else {
7375 let ff = rusty_h264_common::transform::quant_dz_ff(qpc, fe.idz);
7376 let mf = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
7377 rusty_h264_accel::quant_four_4x4(&mut d.0, &ff, mf);
7378 for i in 0..4 {
7379 let q = &mut qbs[i];
7380 q[0] = 0;
7381 for j in 1..16 {
7382 let v = d.0[i * 16 + j] as i32;
7383 q[j] = v;
7384 if v != 0 {
7385 any_chroma_ac = true;
7386 }
7387 }
7388 }
7389 }
7390 let dl = forward_quant_chroma_dc(&dc2x2, qpc, true);
7391 if dl.iter().any(|&v| v != 0) {
7392 any_chroma_dc = true;
7393 }
7394 let recon_dc = inverse_quant_chroma_dc(&dl, qpc);
7395 for i in 0..4 {
7396 let deq = dequantize(&qbs[i], qpc);
7397 for j in 0..16 {
7398 d.0[i * 16 + j] = deq[j] as i16;
7399 }
7400 d.0[i * 16] = recon_dc[i] as i16;
7401 }
7402 let plane = if c == 0 { &mut fe.rec_u } else { &mut fe.rec_v };
7403 rusty_h264_accel::idct_four_t4_rec(&mut plane[cy * fe.ccw + cx..], fe.ccw, &pred8, 8, &d.0);
7404 c_dc_levels[c] = dl;
7405 recon_dc
7406 };
7407 #[cfg(not(accel))]
7408 let recon_dc = {
7409 let mut res_blocks = [[0i32; 16]; 4];
7410 for by in 0..2 {
7411 for bx in 0..2 {
7412 res_blocks[by * 2 + bx] =
7413 residual(src, fe.ccw, cx + bx * 4, cy + by * 4, &pblk(bx, by));
7414 }
7415 }
7416 let mut coeffs = [[0i32; 16]; 4];
7417 forward_dct_blocks(&res_blocks, &mut coeffs);
7418 for i in 0..4 {
7419 dc2x2[i] = coeffs[i][0];
7420 let mut q = rdoq(&coeffs[i], qpc, fe.idz, fe.rdoq_strength, 1);
7421 q[0] = 0;
7422 qbs[i] = q;
7423 if q[1..].iter().any(|&v| v != 0) {
7424 any_chroma_ac = true;
7425 }
7426 }
7427 let dl = forward_quant_chroma_dc(&dc2x2, qpc, true);
7428 if dl.iter().any(|&v| v != 0) {
7429 any_chroma_dc = true;
7430 }
7431 let recon_dc = inverse_quant_chroma_dc(&dl, qpc);
7432 let mut deq_blocks = [[0i32; 16]; 4];
7433 for i in 0..4 {
7434 deq_blocks[i] = dequantize(&qbs[i], qpc);
7435 deq_blocks[i][0] = recon_dc[i];
7436 }
7437 let mut idct = [[0i32; 16]; 4];
7438 inverse_dct_blocks(&deq_blocks, &mut idct);
7439 let plane = if c == 0 { &mut fe.rec_u } else { &mut fe.rec_v };
7440 for by in 0..2 {
7441 for bx in 0..2 {
7442 let s = add_residual_4x4(&idct[by * 2 + bx], &pblk(bx, by));
7443 store(plane, fe.ccw, cx + bx * 4, cy + by * 4, &s);
7444 }
7445 }
7446 c_dc_levels[c] = dl;
7447 recon_dc
7448 };
7449 let _ = recon_dc;
7450 c_q_blocks[c] = qbs;
7451 }
7452 let cbp_chroma: u32 = if any_chroma_ac {
7453 2
7454 } else if any_chroma_dc {
7455 1
7456 } else {
7457 0
7458 };
7459
7460 // ============ I_NxN plan + RD: I_16x16 vs I_4x4 vs (High profile) I_8x8 ============
7461 // I_4x4 and I_8x8 both reconstruct serially into rec_y, but each block predicts
7462 // only from NEIGHBOURS + earlier blocks it fills itself — never the stale MB
7463 // content — so running I_8x8 after I_4x4 needs no restore. J = SSD + λ·R picks the
7464 // per-MB transform (the content-adaptive win: 8x8 on smooth, 4x4 on detail).
7465 let base = ly * fe.cw + lx;
7466 let i4 = if i16_rate > 2 {
7467 Some(plan_i4x4(fe, sy, mb_x, mb_y, qp))
7468 } else {
7469 None
7470 };
7471 let (j4, i4_recon) = match &i4 {
7472 Some(p) => {
7473 let mut ssd = 0i64;
7474 let mut rec = [0u8; 256];
7475 for dy in 0..16 {
7476 let sr = base + dy * fe.cw;
7477 let row = &fe.rec_y[sr..sr + 16];
7478 rec[dy * 16..dy * 16 + 16].copy_from_slice(row);
7479 for (a, b) in row.iter().zip(&sy[sr..sr + 16]) {
7480 let d = *a as i64 - *b as i64;
7481 ssd += d * d;
7482 }
7483 }
7484 (ssd as f64 + lambda * (p.nonzero + 16) as f64, Some(rec))
7485 }
7486 None => (f64::INFINITY, None),
7487 };
7488 // STRUCTURAL gate (candidate): offer the I_8x8 candidate at all only when the
7489 // macroblock's slice type says it is worth offering. See `i8_in_p_on`.
7490 let i8 = if fe.t8_pick && (!fe.intra_in_p || i8_in_p_on()) {
7491 Some(plan_i8x8(fe, sy, mb_x, mb_y, qp))
7492 } else {
7493 None
7494 };
7495 let j8 = match &i8 {
7496 Some(p) => {
7497 let mut ssd = 0i64;
7498 for dy in 0..16 {
7499 let sr = base + dy * fe.cw;
7500 for (a, b) in fe.rec_y[sr..sr + 16].iter().zip(&sy[sr..sr + 16]) {
7501 let d = *a as i64 - *b as i64;
7502 ssd += d * d;
7503 }
7504 }
7505 ssd as f64 + lambda * (p.nonzero + 16) as f64
7506 }
7507 None => f64::INFINITY,
7508 };
7509 let j16 = ssd16 as f64 + lambda * i16_rate as f64;
7510
7511 // ============ commit the RD winner's reconstruction + neighbour modes ============
7512 // The 8x8 candidate must clear its rivals BY A MARGIN (default 0 = any win, the
7513 // pre-margin behaviour). `i8_margin` is in lambda units so it is QP-invariant.
7514 let m8 = fe.i8_margin * lambda;
7515 let (use_i4, i4, i8) = if i8.is_some() && j8 + m8 <= j4 && j8 + m8 <= j16 {
7516 // I_8x8: plan_i8x8 already committed rec_y AND modes_y (per 8x8 block).
7517 (true, None, i8)
7518 } else if i4.is_some() && j4 < j16 {
7519 // I_4x4: restore its reconstruction (I_8x8 may have overwritten rec_y), publish modes.
7520 let rec = i4_recon.unwrap();
7521 for dy in 0..16 {
7522 let d = base + dy * fe.cw;
7523 fe.rec_y[d..d + 16].copy_from_slice(&rec[dy * 16..dy * 16 + 16]);
7524 }
7525 let modes = i4.as_ref().unwrap().modes;
7526 for lby in 0..4 {
7527 let d = (mb_y * 4 + lby) * w4 + mb_x * 4;
7528 fe.modes_y[d..d + 4].copy_from_slice(&modes[lby * 4..lby * 4 + 4]);
7529 }
7530 (true, i4, None)
7531 } else {
7532 // I_16x16: commit its reconstruction, mark modes DC.
7533 for dy in 0..16 {
7534 let d = (ly + dy) * fe.cw + lx;
7535 fe.rec_y[d..d + 16].copy_from_slice(&recon16[dy * 16..dy * 16 + 16]);
7536 }
7537 for lby in 0..4 {
7538 let d = (mb_y * 4 + lby) * w4 + mb_x * 4;
7539 fe.modes_y[d..d + 4].fill(2);
7540 }
7541 (false, None, None)
7542 };
7543 // Mark all luma blocks coded for the next macroblock's top-right availability.
7544 for lby in 0..4 {
7545 let d = (mb_y * 4 + lby) * w4 + mb_x * 4;
7546 fe.coded_y[d..d + 4].fill(true);
7547 }
7548
7549 MbPlan {
7550 use_i4,
7551 i16_mode,
7552 i16_cbp15,
7553 i16_dc_levels,
7554 i16_q,
7555 i4,
7556 i8,
7557 chroma_mode,
7558 cbp_chroma,
7559 c_dc_levels,
7560 c_q_blocks,
7561 }
7562}
7563
7564/// Emit one planned intra macroblock as CAVLC (the original `encode_mb` tail). Reads
7565/// only the decided values from `plan`; `plan_mb` already committed recon + modes.
7566fn encode_mb(
7567 fe: &mut FrameEncoder,
7568 w: &mut BitWriter,
7569 mb_x: usize,
7570 mb_y: usize,
7571 sy: &[u8],
7572 su: &[u8],
7573 sv: &[u8],
7574 is_p: bool,
7575) {
7576 fe.intra_in_p = is_p;
7577 let plan = plan_mb(fe, mb_x, mb_y, sy, su, sv);
7578 // In a P-slice, intra macroblock types are offset by 5 (0..4 are inter).
7579 let mb_type_offset = if is_p { 5 } else { 0 };
7580 let w4 = fe.mb_w * 4;
7581 let cbp_chroma = plan.cbp_chroma;
7582
7583 // ============ emit luma ============
7584 if let Some(i8) = plan.i8.as_ref().filter(|_| plan.use_i4) {
7585 // ---- I_8x8 (High profile): mb_type = I_NxN, transform_size_8x8_flag = 1, then
7586 // one intra8x8 mode per 8x8 block, cbp, mb_qp_delta, and the 8x8 residual as
7587 // four interleaved 4x4 CAVLC sub-blocks (coeff k of sub s -> 8x8 scan 4k+s). ----
7588 let cbp = i8.cbp_luma | (cbp_chroma << 4);
7589 w.write_ue(mb_type_offset); // mb_type = I_NxN
7590 w.write_bit(true); // transform_size_8x8_flag = 1
7591 for b8 in 0..4usize {
7592 let (bx, by) = (mb_x * 4 + (b8 % 2) * 2, mb_y * 4 + (b8 / 2) * 2);
7593 let predicted = predict_i4_mode(fe, bx, by);
7594 let actual = i8.modes[b8];
7595 if actual == predicted {
7596 w.write_bit(true);
7597 } else {
7598 w.write_bit(false);
7599 let rem = if actual < predicted { actual } else { actual - 1 };
7600 w.write_bits(rem as u32, 3);
7601 }
7602 }
7603 w.write_ue(plan.chroma_mode as u32); // intra_chroma_pred_mode
7604 write_cbp_intra(w, cbp);
7605 if cbp != 0 {
7606 w.write_se(fe.qp_delta());
7607 }
7608 fe.nnz_cache_load(mb_x, mb_y);
7609 for b8 in 0..4usize {
7610 let (b8x, b8y) = (b8 % 2, b8 / 2);
7611 let scan8 = scan_8x8_fwd(&i8.q[b8]);
7612 for sub in 0..4usize {
7613 let (cx, cy) = (b8x * 2 + sub % 2, b8y * 2 + sub / 2);
7614 let (bx, by) = (mb_x * 4 + cx, mb_y * 4 + cy);
7615 let total = if i8.cbp_luma & (1 << b8) != 0 {
7616 let nc = fe.nc_pred(cx, cy);
7617 let blk: [i32; 16] = std::array::from_fn(|k| scan8[4 * k + sub]);
7618 encode_residual_block(w, &blk, 16, nc) as u8
7619 } else {
7620 0
7621 };
7622 fe.nnz_cache_set(cx, cy, total);
7623 fe.nnz_y[by * w4 + bx] = total;
7624 }
7625 }
7626 } else if plan.use_i4 {
7627 let i4 = plan.i4.as_ref().unwrap();
7628 let cbp = i4.cbp_luma | (cbp_chroma << 4);
7629 w.write_ue(mb_type_offset); // mb_type = I_4x4 (+5 in P-slices)
7630 if fe.transform_8x8 {
7631 w.write_bit(false); // transform_size_8x8_flag = 0 (this I_NxN is 4x4)
7632 }
7633 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
7634 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7635 let predicted = predict_i4_mode(fe, bx, by);
7636 let actual = i4.modes[lby * 4 + lbx];
7637 if actual == predicted {
7638 w.write_bit(true);
7639 } else {
7640 w.write_bit(false);
7641 let rem = if actual < predicted { actual } else { actual - 1 };
7642 w.write_bits(rem as u32, 3);
7643 }
7644 }
7645 w.write_ue(plan.chroma_mode as u32); // intra_chroma_pred_mode
7646 write_cbp_intra(w, cbp);
7647 if cbp != 0 {
7648 w.write_se(fe.qp_delta()); // mb_qp_delta (AQ per-MB QPy)
7649 }
7650 fe.nnz_cache_load(mb_x, mb_y);
7651 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
7652 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7653 let total = if i4.cbp_luma & (1 << (blk / 4)) != 0 {
7654 let nc = fe.nc_pred(lbx, lby);
7655 let scan16 = scan_4x4_dcac(&i4.q[lby * 4 + lbx]);
7656 encode_residual_block(w, &scan16, 16, nc) as u8
7657 } else {
7658 0
7659 };
7660 fe.nnz_cache_set(lbx, lby, total);
7661 fe.nnz_y[by * w4 + bx] = total;
7662 }
7663 } else {
7664 let mb_type = 1 + plan.i16_mode as u32 + 4 * cbp_chroma + if plan.i16_cbp15 { 12 } else { 0 };
7665 w.write_ue(mb_type + mb_type_offset);
7666 w.write_ue(plan.chroma_mode as u32); // intra_chroma_pred_mode
7667 w.write_se(fe.qp_delta()); // mb_qp_delta (I_16x16 always codes it; AQ per-MB QPy)
7668 fe.nnz_cache_load(mb_x, mb_y);
7669 let nc_dc = fe.nc_pred(0, 0);
7670 let dc_scan = scan_4x4_dcac(&plan.i16_dc_levels);
7671 encode_residual_block(w, &dc_scan, 16, nc_dc);
7672 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
7673 fe.nnz_cache_set(lbx, lby, 0);
7674 fe.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 0;
7675 }
7676 if plan.i16_cbp15 {
7677 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
7678 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7679 let nc = fe.nc_pred(lbx, lby);
7680 let ac = scan_4x4_ac(&plan.i16_q[lby * 4 + lbx]);
7681 let total = encode_residual_block(w, &ac, 15, nc) as u8;
7682 fe.nnz_cache_set(lbx, lby, total);
7683 fe.nnz_y[by * w4 + bx] = total;
7684 }
7685 }
7686 }
7687
7688 // ============ emit chroma residual (shared) ============
7689 if cbp_chroma != 0 {
7690 for c in 0..2 {
7691 encode_residual_block(w, &plan.c_dc_levels[c], 4, -1);
7692 }
7693 }
7694 if cbp_chroma == 2 {
7695 fe.chroma_cache_load(mb_x, mb_y);
7696 let w2 = fe.mb_w * 2;
7697 for c in 0..2 {
7698 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
7699 let nc = fe.chroma_nc_pred(c, bx, by);
7700 let ac = scan_4x4_ac(&plan.c_q_blocks[c][by * 2 + bx]);
7701 let total = encode_residual_block(w, &ac, 15, nc) as u8;
7702 fe.chroma_nnz_cache_set(c, bx, by, total);
7703 fe.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
7704 }
7705 }
7706 }
7707}
7708
7709// ============================================================================
7710// CABAC I-slice entropy coding — the exact forward inverse of the decoder's
7711// `decode_slice_data_cabac` I-slice path (rusty_h264-decoder mb16.rs). Every
7712// binarization + context-selection here mirrors a `parse_*_cabac` there; the
7713// neighbour state (nzc cache, cbf_dc, cat, cmode, mb_cbp, last_delta_qp) is
7714// reconstructed identically so the contexts evolve bit-for-bit. Reuses `plan_mb`
7715// for the entire mode-decision/transform/recon (shared with CAVLC).
7716// ============================================================================
7717
7718// Res-property tables: the ONE copy shared with the decoder via `cabac_tables`
7719// (they were byte-copies here; a single divergent entry desyncs the coders).
7720use rusty_h264_common::cabac_tables::{
7721 CACHE30 as CB_CACHE30, G_SCAN4 as CB_G_SCAN4, NZC_CACHE as CB_NZC_CACHE,
7722 RES_CBF as CB_RES_CBF, RES_MAP as CB_RES_MAP, RES_MAXC2 as CB_RES_MAXC2,
7723 RES_MAXPOS as CB_RES_MAXPOS, RES_ONE as CB_RES_ONE,
7724};
7725const CB_RP_I16_DC: usize = 1;
7726const CB_RP_I16_AC: usize = 2;
7727const CB_RP_LUMA_4X4: usize = 3;
7728const CB_RP_CHROMA_DC: usize = 7;
7729const CB_RP_CHROMA_AC: usize = 9;
7730/// Luma 8x8 (ctxBlockCat 5). Mirrors the decoder's `RP_LUMA_8X8`.
7731const CB_RP_LUMA_8X8: usize = 6;
7732
7733/// Inverse of `cabac_unary(ctx, off)`: bin0 at `ctx`; for value >= 1, `value-1` ones
7734/// then a terminating 0, all at `ctx+off`.
7735fn cb_unary(cab: &mut CabacEncoder, ctx: usize, off: usize, value: u32) {
7736 if value == 0 {
7737 cab.encode_decision(ctx, 0);
7738 return;
7739 }
7740 cab.encode_decision(ctx, 1);
7741 for _ in 0..value - 1 {
7742 cab.encode_decision(ctx + off, 1);
7743 }
7744 cab.encode_decision(ctx + off, 0);
7745}
7746
7747/// Exp-Golomb order-`k` in bypass — inverse of `cabac_exp_bypass(k)`.
7748fn cb_exp_bypass(cab: &mut CabacEncoder, mut k: i32, mut n: u32) {
7749 while n >= (1 << k) {
7750 cab.encode_bypass(1);
7751 n -= 1 << k;
7752 k += 1;
7753 }
7754 cab.encode_bypass(0);
7755 // The suffix tail is exactly `encode_bypass_bits` (MSB-first, k bits) — the
7756 // API CABAC-4 reserved and then reimplemented inline here (H9). Same call
7757 // sequence, byte-identical.
7758 cab.encode_bypass_bits(n, k as u32);
7759}
7760
7761/// UEG0 coeff-level suffix — inverse of `cabac_ueg_level(ctx)` (TU prefix <=13 at
7762/// `ctx`, then an EG0 bypass suffix).
7763fn cb_ueg_level(cab: &mut CabacEncoder, ctx: usize, value: u32) {
7764 if value == 0 {
7765 cab.encode_decision(ctx, 0);
7766 return;
7767 }
7768 let ones = value.min(13);
7769 for _ in 0..ones {
7770 cab.encode_decision(ctx, 1);
7771 }
7772 if value < 13 {
7773 cab.encode_decision(ctx, 0);
7774 } else {
7775 cb_exp_bypass(cab, 0, value - 13);
7776 }
7777}
7778
7779/// `mb_qp_delta` — inverse of `parse_mb_qp_delta_cabac` (ctxIdxOffset 60).
7780pub fn cb_mb_qp_delta(cab: &mut CabacEncoder, last_delta_qp: &mut i32, delta: i32) {
7781 const O: usize = 60;
7782 let ctx_inc = (*last_delta_qp != 0) as usize;
7783 if delta == 0 {
7784 cab.encode_decision(O + ctx_inc, 0);
7785 } else {
7786 cab.encode_decision(O + ctx_inc, 1);
7787 // code = 2|d| - (d>0); the decode's cabac_unary sees code-1.
7788 let code = 2 * delta.unsigned_abs() - (delta > 0) as u32;
7789 cb_unary(cab, O + 2, 1, code - 1);
7790 }
7791 *last_delta_qp = delta;
7792}
7793
7794/// `intra_chroma_pred_mode` (TU cMax=3) — inverse of `parse_intra_chroma_pred_mode_cabac`.
7795fn cb_chroma_pred_mode(cab: &mut CabacEncoder, ctx_inc: usize, mode: u8) {
7796 const C: usize = 64;
7797 if mode == 0 {
7798 cab.encode_decision(C + ctx_inc, 0);
7799 return;
7800 }
7801 cab.encode_decision(C + ctx_inc, 1);
7802 if mode == 1 {
7803 cab.encode_decision(C + 3, 0);
7804 } else if mode == 2 {
7805 cab.encode_decision(C + 3, 1);
7806 cab.encode_decision(C + 3, 0);
7807 } else {
7808 cab.encode_decision(C + 3, 1);
7809 cab.encode_decision(C + 3, 1);
7810 }
7811}
7812
7813/// I-slice `mb_type` — inverse of `parse_mb_type_i_cabac` (ctxIdxOffset 3).
7814fn cb_mb_type_i(
7815 cab: &mut CabacEncoder,
7816 ctx_inc: usize,
7817 use_i4: bool,
7818 i16_mode: u32,
7819 cbp_chroma: u32,
7820 cbp_luma15: bool,
7821) {
7822 const O: usize = 3;
7823 if use_i4 {
7824 cab.encode_decision(O + ctx_inc, 0); // I_NxN
7825 return;
7826 }
7827 cab.encode_decision(O + ctx_inc, 1);
7828 cab.encode_terminate(false); // not I_PCM
7829 cab.encode_decision(O + 3, cbp_luma15 as u32);
7830 if cbp_chroma != 0 {
7831 cab.encode_decision(O + 4, 1);
7832 cab.encode_decision(O + 5, (cbp_chroma == 2) as u32);
7833 } else {
7834 cab.encode_decision(O + 4, 0);
7835 }
7836 cab.encode_decision(O + 6, (i16_mode >> 1) & 1);
7837 cab.encode_decision(O + 7, i16_mode & 1);
7838}
7839
7840/// One `Intra_4x4` pred-mode — inverse of `parse_intra4x4_pred_mode_cabac` (ctx 68).
7841fn cb_intra4x4_pred_mode(cab: &mut CabacEncoder, predicted: u8, actual: u8) {
7842 const IPR: usize = 68;
7843 if actual == predicted {
7844 cab.encode_decision(IPR, 1);
7845 } else {
7846 cab.encode_decision(IPR, 0);
7847 let rem = if actual < predicted { actual } else { actual - 1 } as u32;
7848 cab.encode_decision(IPR + 1, rem & 1);
7849 cab.encode_decision(IPR + 1, (rem >> 1) & 1);
7850 cab.encode_decision(IPR + 1, (rem >> 2) & 1);
7851 }
7852}
7853
7854/// `coded_block_pattern` — inverse of `parse_cbp_cabac` (ctxIdxOffset 73).
7855pub fn cb_cbp(cab: &mut CabacEncoder, top: Option<u8>, left: Option<u8>, cbp: u32) {
7856 const CBP: usize = 73;
7857 let t = |m: u32| top.map_or(0u32, |c| ((c as u32 & m) == 0) as u32);
7858 let l = |m: u32| left.map_or(0u32, |c| ((c as u32 & m) == 0) as u32);
7859 let nb = |x: u32| (x == 0) as u32;
7860 let b0 = cbp & 1;
7861 let b1 = (cbp >> 1) & 1;
7862 let b2 = (cbp >> 2) & 1;
7863 let b3 = (cbp >> 3) & 1;
7864 cab.encode_decision(CBP + (l(1 << 1) + (t(1 << 2) << 1)) as usize, b0);
7865 cab.encode_decision(CBP + (nb(b0) + (t(1 << 3) << 1)) as usize, b1);
7866 cab.encode_decision(CBP + (l(1 << 3) + (nb(b0) << 1)) as usize, b2);
7867 cab.encode_decision(CBP + (nb(b2) + (nb(b1) << 1)) as usize, b3);
7868 let cbp_chroma = cbp >> 4;
7869 let ct = top.map_or(0u32, |c| ((c >> 4) != 0) as u32);
7870 let cl = left.map_or(0u32, |c| ((c >> 4) != 0) as u32);
7871 cab.encode_decision(CBP + 4 + (cl + (ct << 1)) as usize, (cbp_chroma != 0) as u32);
7872 if cbp_chroma != 0 {
7873 let ct2 = top.map_or(0u32, |c| ((c >> 4) == 2) as u32);
7874 let cl2 = left.map_or(0u32, |c| ((c >> 4) == 2) as u32);
7875 cab.encode_decision(CBP + 8 + (cl2 + (ct2 << 1)) as usize, (cbp_chroma == 2) as u32);
7876 }
7877}
7878
7879/// One residual block — inverse of `parse_residual_cabac`. `coeffs` is scan-order
7880/// (len >= maxPos+1). Returns totalCoeffNum (for the nzc cache + deblock nnz).
7881#[allow(clippy::too_many_arguments)]
7882fn cb_residual(
7883 cab: &mut CabacEncoder,
7884 nzc: &mut [u8; 48],
7885 cbf_dc: &mut u16,
7886 iz: usize,
7887 rp: usize,
7888 is_intra: bool,
7889 ndc: (Option<u16>, Option<u16>),
7890 coeffs: &[i32],
7891) -> u32 {
7892 // ctxBlockCat 5 is the ONLY category with no coded_block_flag: presence is inferred
7893 // from CodedBlockPatternLuma, so emitting one here would desync the decoder. Same
7894 // asymmetry the decoder's reader documents.
7895 let is8 = rp == CB_RP_LUMA_8X8;
7896 let is_dc = rp == CB_RP_I16_DC || rp == CB_RP_CHROMA_DC || rp == CB_RP_CHROMA_DC + 1;
7897 let (mut na, mut nb) = (is_intra as u8, is_intra as u8);
7898 let scan = CB_NZC_CACHE[iz.min(23)];
7899 if is_dc {
7900 if let Some(t) = ndc.0 {
7901 nb = ((t >> rp) & 1) as u8;
7902 }
7903 if let Some(l) = ndc.1 {
7904 na = ((l >> rp) & 1) as u8;
7905 }
7906 } else {
7907 if nzc[scan - 8] != 0xff {
7908 nb = (nzc[scan - 8] != 0) as u8;
7909 }
7910 if nzc[scan - 1] != 0xff {
7911 na = (nzc[scan - 1] != 0) as u8;
7912 }
7913 }
7914 let maxpos = CB_RES_MAXPOS[rp] as usize;
7915 let coeff_num = coeffs[..=maxpos].iter().filter(|&&c| c != 0).count() as u32;
7916 let cbf = coeff_num != 0;
7917 if !is8 {
7918 cab.encode_decision(85 + CB_RES_CBF[rp] + (na + (nb << 1)) as usize, cbf as u32);
7919 if !cbf {
7920 if !is_dc {
7921 nzc[scan] = 0;
7922 }
7923 return 0;
7924 }
7925 } else if !cbf {
7926 // Caller must not invoke cat 5 for an all-zero block: with no cbf to carry the
7927 // "empty" signal, the decoder would read a significance map that was never
7928 // written. CBP is what suppresses it, upstream.
7929 debug_assert!(false, "cat 5 called with an all-zero block; CBP should have gated it");
7930 nzc[scan] = 0;
7931 return 0;
7932 }
7933 if is_dc {
7934 *cbf_dc |= 1 << rp;
7935 }
7936 // significance map. For the 4x4 categories ctxIdxInc IS the scan position; cat 5
7937 // folds 63 positions onto 15 (sig) / 9 (last) contexts via the Table 9-43 maps,
7938 // at absolute bases rather than `105/166 + off`.
7939 let map = 105 + CB_RES_MAP[rp];
7940 let last = 166 + CB_RES_MAP[rp];
7941 let lastnz = (0..=maxpos).rev().find(|&i| coeffs[i] != 0).unwrap();
7942 for i in 0..maxpos {
7943 let s = coeffs[i] != 0;
7944 let (sig_ctx, last_ctx) = if is8 {
7945 (
7946 rusty_h264_common::cabac_tables::CAT5_SIG_BASE + rusty_h264_common::cabac_tables::SIG8X8[i] as usize,
7947 rusty_h264_common::cabac_tables::CAT5_LAST_BASE + rusty_h264_common::cabac_tables::LAST8X8[i] as usize,
7948 )
7949 } else {
7950 (map + i, last + i)
7951 };
7952 cab.encode_decision(sig_ctx, s as u32);
7953 if s {
7954 let is_last = i == lastnz;
7955 cab.encode_decision(last_ctx, is_last as u32);
7956 if is_last {
7957 break;
7958 }
7959 }
7960 }
7961 // levels (reverse scan)
7962 let one = 227 + CB_RES_ONE[rp];
7963 let abs = 232 + CB_RES_ONE[rp];
7964 let maxc2 = CB_RES_MAXC2[rp];
7965 let (mut c1, mut c2) = (1i32, 0i32);
7966 for i in (0..=maxpos).rev() {
7967 if coeffs[i] != 0 {
7968 let av = coeffs[i].unsigned_abs();
7969 let gt1 = av > 1;
7970 cab.encode_decision(one + c1 as usize, gt1 as u32);
7971 if gt1 {
7972 cb_ueg_level(cab, abs + c2 as usize, av - 2);
7973 c2 = (c2 + 1).min(maxc2);
7974 c1 = 0;
7975 } else if c1 != 0 {
7976 c1 = (c1 + 1).min(4);
7977 }
7978 cab.encode_bypass((coeffs[i] < 0) as u32);
7979 }
7980 }
7981 if is8 {
7982 // One 8x8 covers four consecutive z-order 4x4 cells. Every later
7983 // coded_block_flag ctxIdxInc reads this cache, so all four must carry the
7984 // count -- writing only `scan` would corrupt the NEXT macroblock's contexts.
7985 // Byte-for-byte the decoder's rule; the two must agree or the stream desyncs.
7986 for k in 0..4 {
7987 nzc[CB_NZC_CACHE[(iz + k).min(23)]] = coeff_num as u8;
7988 }
7989 } else if !is_dc {
7990 nzc[scan] = coeff_num as u8;
7991 }
7992 coeff_num
7993}
7994
7995/// Build the 48-entry padded nzc cache from the top/left neighbour MB exports
7996/// (openh264 `WelsFillCacheNonZeroCount`) — identical to the decoder.
7997fn cb_build_nzc(mb_nzc: &[[u8; 24]], top: Option<usize>, left: Option<usize>) -> [u8; 48] {
7998 let mut nzc = [0xffu8; 48];
7999 if let Some(t) = top {
8000 let tn = mb_nzc[t];
8001 nzc[1..5].copy_from_slice(&tn[12..16]);
8002 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
8003 (nzc[6], nzc[7]) = (tn[20], tn[21]);
8004 (nzc[30], nzc[31]) = (tn[22], tn[23]);
8005 }
8006 if let Some(l) = left {
8007 let ln = mb_nzc[l];
8008 (nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
8009 (nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
8010 }
8011 nzc
8012}
8013
8014/// Extract the 24-entry per-MB nzc (raster luma + chroma) for future neighbours.
8015fn cb_export_nzc(nzc: &[u8; 48]) -> [u8; 24] {
8016 let mut mn = [0u8; 24];
8017 for k in 0..4 {
8018 mn[k] = nzc[9 + k];
8019 mn[4 + k] = nzc[17 + k];
8020 mn[8 + k] = nzc[25 + k];
8021 mn[12 + k] = nzc[33 + k];
8022 }
8023 (mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
8024 (mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
8025 for v in mn.iter_mut() {
8026 if *v == 0xff {
8027 *v = 0;
8028 }
8029 }
8030 mn
8031}
8032
8033/// Per-frame CABAC neighbour state (I-slice): one entry per macroblock, mirroring
8034/// the arrays the decoder's `decode_slice_data_cabac` maintains.
8035struct CabacState {
8036 cat: Vec<u8>, // 2 = I_16x16, 0 = I_NxN, 100 = inter (mb_type / skip ctxInc)
8037 cmode: Vec<i32>, // per-MB chroma mode (chroma-pred ctxInc)
8038 mb_cbp: Vec<u8>, // per-MB cbp byte (cbp ctxInc)
8039 cbf_dc: Vec<u16>, // per-MB DC coded_block_flag mask (residual DC ctxInc)
8040 mb_nzc: Vec<[u8; 24]>, // per-MB nzc export (residual AC ctxInc)
8041 // Inter (P/B) neighbour state — mirrors the decoder's WelsFillCacheInterCabac.
8042 mb_mvd: Vec<[[i16; 2]; 16]>, // per-MB per-4x4 List-0 mvd (raster), for the mvd ctxInc cache
8043 mb_ref: Vec<[i8; 16]>, // per-MB per-4x4 List-0 ref idx (raster); -1 = unavailable
8044 mb_mvd1: Vec<[[i16; 2]; 16]>, // B: per-MB per-4x4 List-1 mvd
8045 mb_ref1: Vec<[i8; 16]>, // B: per-MB per-4x4 List-1 ref idx
8046 mb_skip: Vec<bool>, // per-MB mb_skip_flag (skip ctxInc)
8047 mb_direct: Vec<bool>, // B: per-MB B_Direct/B_Skip (B mb_type ctxInc)
8048 mb_t8x8: Vec<bool>, // per-MB transform_size_8x8_flag (ctxIdxOffset 399 ctxInc)
8049 last_delta_qp: i32,
8050}
8051
8052impl CabacState {
8053 /// Reset a recycled state to `new(n)`'s exact contents on retained
8054 /// capacity (D13's encoder twin, 11.11).
8055 fn refill(&mut self, n: usize) {
8056 fn rf<T: Clone>(v: &mut Vec<T>, n: usize, val: T) {
8057 v.clear();
8058 v.resize(n, val);
8059 }
8060 rf(&mut self.cat, n, 0);
8061 rf(&mut self.cmode, n, 0);
8062 rf(&mut self.mb_cbp, n, 0);
8063 rf(&mut self.cbf_dc, n, 0);
8064 rf(&mut self.mb_nzc, n, [0u8; 24]);
8065 rf(&mut self.mb_mvd, n, [[0i16; 2]; 16]);
8066 rf(&mut self.mb_ref, n, [-1i8; 16]);
8067 rf(&mut self.mb_mvd1, n, [[0i16; 2]; 16]);
8068 rf(&mut self.mb_ref1, n, [-1i8; 16]);
8069 rf(&mut self.mb_skip, n, false);
8070 rf(&mut self.mb_direct, n, false);
8071 rf(&mut self.mb_t8x8, n, false);
8072 self.last_delta_qp = 0;
8073 }
8074
8075 /// Pooled form of `new(n)`: ~13 vecs (hundreds of KB per slice) recycled
8076 /// from this thread's previous slice.
8077 fn pooled(n: usize) -> Self {
8078 let mut cs = enc_scratch::take_cs().unwrap_or_else(|| CabacState::new(0));
8079 cs.refill(n);
8080 cs
8081 }
8082
8083 fn new(n: usize) -> Self {
8084 CabacState {
8085 cat: vec![0; n],
8086 cmode: vec![0; n],
8087 mb_cbp: vec![0; n],
8088 cbf_dc: vec![0; n],
8089 mb_nzc: vec![[0u8; 24]; n],
8090 mb_mvd: vec![[[0i16; 2]; 16]; n],
8091 mb_ref: vec![[-1i8; 16]; n],
8092 mb_mvd1: vec![[[0i16; 2]; 16]; n],
8093 mb_ref1: vec![[-1i8; 16]; n],
8094 mb_skip: vec![false; n],
8095 mb_direct: vec![false; n],
8096 mb_t8x8: vec![false; n],
8097 last_delta_qp: 0,
8098 }
8099 }
8100}
8101
8102/// Emit one planned intra macroblock as CABAC (I-slice). Mirrors the decoder's
8103/// I-slice MB body exactly: `mb_type`, then per luma-type the intra modes / cbp /
8104/// `mb_qp_delta` / residual in spec order, maintaining `cs` and `fe.nnz_y`.
8105fn emit_mb_cabac_i(
8106 fe: &mut FrameEncoder,
8107 cab: &mut CabacEncoder,
8108 cs: &mut CabacState,
8109 plan: &MbPlan,
8110 mb_x: usize,
8111 mb_y: usize,
8112) {
8113 let mb_w = fe.mb_w;
8114 let addr = mb_y * mb_w + mb_x;
8115 let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
8116 let left = if mb_x > 0 { Some(addr - 1) } else { None };
8117
8118 // ---- mb_type (I-slice prefix; carries I_16x16 pred-mode/cbp) ----
8119 let li = left.map_or(0, |a| (cs.cat[a] >= 2) as usize);
8120 let ti = top.map_or(0, |a| (cs.cat[a] >= 2) as usize);
8121 let acct = crate::bitacct::enabled();
8122 let t0 = if acct { cab.pos() } else { 0 };
8123 if plan.use_i4 {
8124 cb_mb_type_i(cab, li + ti, true, 0, 0, false);
8125 } else {
8126 cb_mb_type_i(cab, li + ti, false, plan.i16_mode as u32, plan.cbp_chroma, plan.i16_cbp15);
8127 }
8128 if acct {
8129 crate::bitacct::add(crate::bitacct::B::MbType, cab.pos() - t0);
8130 }
8131 let t1 = if acct { cab.pos() } else { 0 };
8132 emit_intra_body_cabac(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
8133 if acct {
8134 crate::bitacct::add(crate::bitacct::B::IntraBody, cab.pos() - t1);
8135 }
8136}
8137
8138/// The intra macroblock body (chroma pred mode, intra modes, cbp, mb_qp_delta,
8139/// residual) shared by I-slice intra and P/B-slice intra — everything AFTER the
8140/// slice-specific `mb_type` prefix (which already carries the I_16x16 pred-mode/cbp).
8141/// Commits a raster-local per-block nnz record into the frame grid as four
8142/// row copies (E28, inline-execution.md 11.16): the emit loops run in SCAN
8143/// order for the CABAC contexts, so they collect locally and commit here —
8144/// one bounds check per row instead of one per block.
8145#[inline]
8146fn nnz_commit_rows(fe: &mut FrameEncoder, mb_x: usize, mb_y: usize, loc: &[u8; 16]) {
8147 let w4 = fe.mb_w * 4;
8148 for lby in 0..4 {
8149 let d = (mb_y * 4 + lby) * w4 + mb_x * 4;
8150 fe.nnz_y[d..d + 4].copy_from_slice(&loc[lby * 4..lby * 4 + 4]);
8151 }
8152}
8153
8154#[allow(clippy::too_many_arguments)]
8155fn emit_intra_body_cabac(
8156 fe: &mut FrameEncoder,
8157 cab: &mut CabacEncoder,
8158 cs: &mut CabacState,
8159 plan: &MbPlan,
8160 mb_x: usize,
8161 mb_y: usize,
8162 addr: usize,
8163 top: Option<usize>,
8164 left: Option<usize>,
8165) {
8166 let cbp_chroma = plan.cbp_chroma;
8167 // chroma-pred-mode ctxInc from neighbour chroma modes (1..=3).
8168 let cci = left.map_or(0, |a| (1..=3).contains(&cs.cmode[a]) as usize)
8169 + top.map_or(0, |a| (1..=3).contains(&cs.cmode[a]) as usize);
8170
8171 let mut nzc;
8172 let mut cbfdc = 0u16;
8173 let ndc = (top.map(|a| cs.cbf_dc[a]), left.map(|a| cs.cbf_dc[a]));
8174
8175 if !plan.use_i4 {
8176 // ---- I_16x16 ----
8177 cb_chroma_pred_mode(cab, cci, plan.chroma_mode);
8178 cs.cmode[addr] = plan.chroma_mode as i32;
8179 cs.cat[addr] = 2;
8180 cs.mb_cbp[addr] = ((cbp_chroma as u8) << 4) | if plan.i16_cbp15 { 15 } else { 0 };
8181 nzc = cb_build_nzc(&cs.mb_nzc, top, left);
8182
8183 let delta = fe.qp_delta();
8184 cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
8185
8186 // luma DC
8187 let dc_scan = scan_4x4_dcac(&plan.i16_dc_levels);
8188 cb_residual(cab, &mut nzc, &mut cbfdc, 0, CB_RP_I16_DC, true, ndc, &dc_scan);
8189 // luma AC
8190 let mut nnz_loc = [0u8; 16];
8191 for (iz, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
8192 let total = if plan.i16_cbp15 {
8193 let ac = scan_4x4_ac(&plan.i16_q[lby * 4 + lbx]);
8194 cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_I16_AC, true, ndc, &ac)
8195 } else {
8196 nzc[CB_NZC_CACHE[iz]] = 0;
8197 0
8198 };
8199 nnz_loc[lby * 4 + lbx] = total as u8;
8200 }
8201 nnz_commit_rows(fe, mb_x, mb_y, &nnz_loc);
8202 cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, true, plan.cbp_chroma, &plan.c_dc_levels, &plan.c_q_blocks, mb_x, mb_y);
8203 } else if let Some(i8) = plan.i8.as_ref() {
8204 // ---- I_NxN with transform_size_8x8_flag = 1 (I_8x8, High profile) ----
8205 // ORDER IS LOAD-BEARING: for I_NxN the flag precedes the intra pred modes
8206 // (spec 7.3.5), because the modes themselves are per-8x8 when it is set.
8207 // ctxIdx = 399 + condTermFlagA + condTermFlagB, each 1 when that neighbour
8208 // MB carries the flag -- the exact mirror of the decoder's read.
8209 let ta = left.map_or(0, |x| cs.mb_t8x8[x] as usize);
8210 let tb = top.map_or(0, |x| cs.mb_t8x8[x] as usize);
8211 cab.encode_decision(399 + ta + tb, 1);
8212 cs.mb_t8x8[addr] = true;
8213 for b8 in 0..4usize {
8214 let (bx, by) = (mb_x * 4 + (b8 % 2) * 2, mb_y * 4 + (b8 / 2) * 2);
8215 let predicted = predict_i4_mode(fe, bx, by);
8216 cb_intra4x4_pred_mode(cab, predicted, i8.modes[b8]);
8217 }
8218 cb_chroma_pred_mode(cab, cci, plan.chroma_mode);
8219 cs.cmode[addr] = plan.chroma_mode as i32;
8220 cs.cat[addr] = 0;
8221 let cbp = i8.cbp_luma | (cbp_chroma << 4);
8222 cb_cbp(cab, top.map(|a| cs.mb_cbp[a]), left.map(|a| cs.mb_cbp[a]), cbp);
8223 cs.mb_cbp[addr] = cbp as u8;
8224 nzc = cb_build_nzc(&cs.mb_nzc, top, left);
8225
8226 if cbp == 0 {
8227 cs.last_delta_qp = 0;
8228 nnz_commit_rows(fe, mb_x, mb_y, &[0u8; 16]);
8229 } else {
8230 let delta = fe.qp_delta();
8231 cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
8232 let mut nnz_loc = [0u8; 16];
8233 for b8 in 0..4usize {
8234 let (b8x, b8y) = (b8 % 2, b8 / 2);
8235 // Unlike CAVLC -- which has no 8x8 entropy model and must split the
8236 // block into four interleaved 4x4 sub-blocks -- CABAC codes the 8x8
8237 // as ONE 64-coefficient ctxBlockCat-5 block.
8238 let total = if i8.cbp_luma & (1 << b8) != 0 {
8239 let scan8 = scan_8x8_fwd(&i8.q[b8]);
8240 cb_residual(cab, &mut nzc, &mut cbfdc, b8 * 4, CB_RP_LUMA_8X8, true, ndc, &scan8)
8241 } else {
8242 for k in 0..4 {
8243 nzc[CB_NZC_CACHE[b8 * 4 + k]] = 0;
8244 }
8245 0
8246 };
8247 for sy in 0..2 {
8248 for sx in 0..2 {
8249 nnz_loc[(b8y * 2 + sy) * 4 + b8x * 2 + sx] = total as u8;
8250 }
8251 }
8252 }
8253 nnz_commit_rows(fe, mb_x, mb_y, &nnz_loc);
8254 cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, true, plan.cbp_chroma, &plan.c_dc_levels, &plan.c_q_blocks, mb_x, mb_y);
8255 }
8256 } else {
8257 // ---- I_NxN (I_4x4) ----
8258 let i4 = plan.i4.as_ref().unwrap();
8259 if fe.transform_8x8 {
8260 let ta = left.map_or(0, |x| cs.mb_t8x8[x] as usize);
8261 let tb = top.map_or(0, |x| cs.mb_t8x8[x] as usize);
8262 cab.encode_decision(399 + ta + tb, 0);
8263 }
8264 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
8265 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
8266 let predicted = predict_i4_mode(fe, bx, by);
8267 cb_intra4x4_pred_mode(cab, predicted, i4.modes[lby * 4 + lbx]);
8268 }
8269 cb_chroma_pred_mode(cab, cci, plan.chroma_mode);
8270 cs.cmode[addr] = plan.chroma_mode as i32;
8271 cs.cat[addr] = 0;
8272 let cbp = i4.cbp_luma | (cbp_chroma << 4);
8273 cb_cbp(cab, top.map(|a| cs.mb_cbp[a]), left.map(|a| cs.mb_cbp[a]), cbp);
8274 cs.mb_cbp[addr] = cbp as u8;
8275 nzc = cb_build_nzc(&cs.mb_nzc, top, left);
8276
8277 if cbp == 0 {
8278 cs.last_delta_qp = 0;
8279 nnz_commit_rows(fe, mb_x, mb_y, &[0u8; 16]);
8280 } else {
8281 let delta = fe.qp_delta();
8282 cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
8283 let mut nnz_loc = [0u8; 16];
8284 for id8 in 0..4usize {
8285 for id4 in 0..4usize {
8286 let iz = id8 * 4 + id4;
8287 let (lbx, lby) = LUMA_4X4_SCAN_XY[iz];
8288 let total = if i4.cbp_luma & (1 << id8) != 0 {
8289 let sc = scan_4x4_dcac(&i4.q[lby * 4 + lbx]);
8290 cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_LUMA_4X4, true, ndc, &sc)
8291 } else {
8292 nzc[CB_NZC_CACHE[iz]] = 0;
8293 0
8294 };
8295 nnz_loc[lby * 4 + lbx] = total as u8;
8296 }
8297 }
8298 nnz_commit_rows(fe, mb_x, mb_y, &nnz_loc);
8299 cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, true, plan.cbp_chroma, &plan.c_dc_levels, &plan.c_q_blocks, mb_x, mb_y);
8300 }
8301 }
8302
8303 cs.cbf_dc[addr] = cbfdc;
8304 cs.mb_nzc[addr] = cb_export_nzc(&nzc);
8305}
8306
8307/// Chroma DC + AC residual (shared by intra I_16x16/I_NxN and inter) — matches the
8308/// decoder's chroma residual order. `is_intra` selects the coded_block_flag default
8309/// (nA=nB default to is_intra). Populates the chroma nnz grid for deblock.
8310#[allow(clippy::too_many_arguments)]
8311fn cb_emit_chroma_residual(
8312 cab: &mut CabacEncoder,
8313 fe: &mut FrameEncoder,
8314 nzc: &mut [u8; 48],
8315 cbfdc: &mut u16,
8316 ndc: (Option<u16>, Option<u16>),
8317 is_intra: bool,
8318 cbp_chroma: u32,
8319 c_dc_levels: &[[i32; 4]; 2],
8320 c_q: &[[[i32; 16]; 4]; 2],
8321 mb_x: usize,
8322 mb_y: usize,
8323) {
8324 let w2 = fe.mb_w * 2;
8325 if cbp_chroma >= 1 {
8326 for i in 0..2usize {
8327 cb_residual(cab, nzc, cbfdc, 16 + i * 4, CB_RP_CHROMA_DC + i, is_intra, ndc, &c_dc_levels[i]);
8328 }
8329 }
8330 if cbp_chroma == 2 {
8331 for i in 0..2usize {
8332 for (id4, &(bx, by)) in CHROMA_4X4_SCAN_XY.iter().enumerate() {
8333 let ac = scan_4x4_ac(&c_q[i][by * 2 + bx]);
8334 let total = cb_residual(
8335 cab, nzc, cbfdc, 16 + i * 4 + id4, CB_RP_CHROMA_AC + i, is_intra, ndc, &ac,
8336 );
8337 fe.nnz_c[i][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total as u8;
8338 }
8339 }
8340 }
8341}
8342
8343/// CABAC all-intra slice-data coder (IDR / I-slice). Mirrors `encode_slice_data`'s
8344/// setup + deblock + `RefFrame` construction, but codes every MB via `plan_mb` +
8345/// `emit_mb_cabac_i` into a CABAC bitstream. `w` already holds the byte-aligned
8346/// slice header; the CABAC bytes are appended after `cabac_alignment_one_bit`.
8347pub(crate) fn encode_slice_data_cabac_intra(
8348 w: &mut BitWriter,
8349 cfg: &EncoderConfig,
8350 frame: &YuvFrame,
8351 qp: u8,
8352 qpo: &[i32],
8353 aq_probe: Option<&YuvFrame>,
8354) -> crate::RefFrame {
8355 let mut fe = FrameEncoder::new(cfg);
8356 fe.qp = qp;
8357 fe.qpc = chroma_qp(qp);
8358 fe.cur_qp = qp;
8359 if cfg.cabac_dz_div > 0 {
8360 fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
8361 }
8362 let (sy, su, sv) = coded_source(cfg, frame);
8363 // Great Gate P1: the shared per-frame signal vector. Intra has no coding
8364 // reference, but the batch path hands the previous SOURCE frame as the AQ
8365 // grain probe (docs/gate-ledger.md aq-grain-veto) — without it the veto
8366 // fails open and the temporal signals stay cold.
8367 let probe_y: Option<Vec<u8>> = aq_probe.map(|f| coded_source(cfg, f).0.into_owned());
8368 let sig = FrameSignals::new(&sy, fe.cw, fe.mb_w, fe.mb_h, probe_y.as_deref());
8369 apply_screen_t8_veto(&mut fe, &sig);
8370 let mut aq_qp = aq_qp_map(&sig, qp, fe.aq_strength);
8371 apply_mbtree_qpo(&mut aq_qp, qpo); // mb-tree temporal AQ (empty = byte-identical)
8372 signals::harvest(&sig, 'I', qp, &signals::GateDecisions::default());
8373 let mut mb_qpy = enc_scratch::take_qpy();
8374 mb_qpy.clear();
8375 mb_qpy.resize(fe.mb_w * fe.mb_h, qp);
8376
8377 // CABAC trellis (RDOQ): structure-adaptive. ON only for ALL-INTRA streams
8378 // (gop_size<=1), where each IDR is independent so trading a little distortion for
8379 // rate is a clean −0.5..−1.3% BD-rate win. OFF inside a GOP: there the I-frame is
8380 // a REFERENCE, and degrading it costs the dependent P-frames more than the I-frame
8381 // saves (measured ~+0.1% net) — so the safe end is a true no-op (never regresses).
8382 fe.rdoq_strength = if cfg.gop_size <= 1 { cfg.cabac_rdoq } else { 0.0 };
8383 // Contexts init from SliceQPY (the slice qp), init_idc unused for I, is_i = true.
8384 let mut cab = CabacEncoder::new_with_out(qp as i32, 0, true, enc_scratch::take_payload());
8385 let mut cs = CabacState::pooled(fe.mb_w * fe.mb_h);
8386 let total = fe.mb_w * fe.mb_h;
8387 // E15 round 2: slice-constant accountant knob (twin of the P/B hoists).
8388 let acct = crate::bitacct::enabled();
8389
8390 for mb_y in 0..fe.mb_h {
8391 for mb_x in 0..fe.mb_w {
8392 let mb_idx = mb_y * fe.mb_w + mb_x;
8393 let mb_qp = aq_qp[mb_idx];
8394 fe.qp = mb_qp;
8395 fe.qpc = chroma_qp(mb_qp);
8396 let plan = plan_mb(&mut fe, mb_x, mb_y, &sy, &su, &sv);
8397 emit_mb_cabac_i(&mut fe, &mut cab, &mut cs, &plan, mb_x, mb_y);
8398 mb_qpy[mb_idx] = fe.cur_qp;
8399 // end_of_slice_flag (EncodeTerminate): 1 on the last MB, else 0.
8400 cb_term_acct(&mut cab, mb_idx + 1 == total, acct);
8401 }
8402 }
8403 enc_scratch::put_cs(cs);
8404
8405 // Append CABAC slice data after cabac_alignment_one_bit (pad header with 1-bits).
8406 while !w.is_byte_aligned() {
8407 w.write_bit(true);
8408 }
8409 let payload = cab.into_bytes();
8410 w.write_aligned_bytes(&payload);
8411 enc_scratch::put_payload(payload);
8412
8413 // Deblock the reconstruction (all-intra: BS derives from intra-ness) -> reference.
8414 let mut ref_id = enc_scratch::take_refid();
8415 ref_id.clear();
8416 ref_id.extend(fe.ref_idx_y.iter().map(|&r| if r >= 0 { r } else { i32::MIN }));
8417 let info = rusty_h264_common::deblock::BlockInfo {
8418 inter: &fe.inter_y,
8419 nnz: &fe.nnz_y,
8420 mv: &fe.mv_y,
8421 ref_id: &ref_id,
8422 mv1: &[],
8423 ref_id1: &[],
8424 w4: fe.mb_w * 4,
8425 t8x8: &[],
8426 poc0: &[],
8427 poc1: &[],
8428 bs: &[], kind: &[],
8429 };
8430 rusty_h264_common::deblock::filter_frame(
8431 &mut fe.rec_y, &mut fe.rec_u, &mut fe.rec_v, fe.mb_w, fe.mb_h, &mb_qpy, 0, 0, 0, &info,
8432 );
8433 enc_scratch::put_qpy(mb_qpy);
8434 enc_scratch::put_refid(ref_id);
8435 let w4 = fe.mb_w * 4;
8436 crate::RefFrame {
8437 y: fe.rec_y,
8438 u: fe.rec_u,
8439 v: fe.rec_v,
8440 poc: 0,
8441 frame_num: 0,
8442 mv: fe.mv_y,
8443 ref_idx: fe.ref_idx_y,
8444 mv1: Vec::new(),
8445 ref_idx1: Vec::new(),
8446 w4,
8447 // Filtered lazily on first sub-pel search use (see `RefFrame::hpel`).
8448 hpel: std::sync::OnceLock::new(),
8449 }
8450}
8451
8452// ============================================================================
8453// CABAC P-slice entropy coding — the forward inverse of the decoder's
8454// decode_slice_data_cabac P-slice path. mb_skip_flag / mb_type_p / mvd (UEG3) /
8455// inter residual, plus intra-in-P (the shared intra body under a P mb_type prefix).
8456// Scope: 1 reference (no ref_idx), P_16x16/16x8/8x16 (no P_8x8/sub_mb_type) — the
8457// modes the encoder's decision produces.
8458// ============================================================================
8459
8460
8461/// UEG3 mvd suffix — inverse of `decode_ueg_mv(base)` (TU prefix at base+{0,1,2,3,3..},
8462/// cMax 7, then EG3 bypass). `v` is the value decode_ueg_mv returns.
8463fn cb_ueg_mv(cab: &mut CabacEncoder, base: usize, v: u32) {
8464 const P2C: [usize; 8] = [0, 1, 2, 3, 3, 3, 3, 3];
8465 if v == 0 {
8466 cab.encode_decision(base, 0);
8467 return;
8468 }
8469 cab.encode_decision(base, 1);
8470 if v <= 7 {
8471 // (v-1) ones then a terminating 0, at base+P2C[count] for count = 1..
8472 let mut count = 1;
8473 for _ in 0..v - 1 {
8474 cab.encode_decision(base + P2C[count], 1);
8475 count += 1;
8476 }
8477 cab.encode_decision(base + P2C[count], 0);
8478 } else {
8479 // prefix maxes out: 7 ones (count 1..7) then EG3(v-8).
8480 let mut count = 1;
8481 for _ in 0..7 {
8482 cab.encode_decision(base + P2C[count], 1);
8483 count += 1;
8484 }
8485 let acct = crate::bitacct::enabled();
8486 let tb = if acct { cab.pos() } else { 0 };
8487 cb_exp_bypass(cab, 3, v - 8);
8488 if acct {
8489 crate::bitacct::add(crate::bitacct::B::MvdBypass, cab.pos() - tb);
8490 }
8491 }
8492}
8493
8494/// One `mvd` component — inverse of `parse_mvd_cabac(comp, ctx_inc)` (ctxIdxOffset
8495/// 40 for x, 47 for y).
8496fn cb_mvd(cab: &mut CabacEncoder, comp: usize, ctx_inc: usize, d: i32) {
8497 // One accountant-knob read per call (was three; runs per MVD component).
8498 let acct = crate::bitacct::enabled();
8499 let th = if acct { cab.pos() } else { u64::MAX };
8500 let base = 40 + comp * 7;
8501 if d == 0 {
8502 cab.encode_decision(base + ctx_inc, 0);
8503 if th != u64::MAX {
8504 crate::bitacct::add_mvd_sample(0, cab.pos() - th);
8505 }
8506 return;
8507 }
8508 cab.encode_decision(base + ctx_inc, 1);
8509 cb_ueg_mv(cab, base + 3, d.unsigned_abs() - 1); // decode adds 1 back
8510 let ts = if acct { cab.pos() } else { 0 };
8511 cab.encode_bypass((d < 0) as u32);
8512 if acct {
8513 crate::bitacct::add(crate::bitacct::B::MvdSign, cab.pos() - ts);
8514 }
8515 if th != u64::MAX {
8516 crate::bitacct::add_mvd_sample(d.unsigned_abs(), cab.pos() - th);
8517 }
8518}
8519
8520/// `mb_skip_flag` — inverse of `parse_mb_skip_cabac` (ctx 11 P + neighbour-not-skip).
8521fn cb_mb_skip(cab: &mut CabacEncoder, ctx_inc: usize, skip: bool) {
8522 cab.encode_decision(ctx_inc, skip as u32);
8523}
8524
8525/// `ref_idx_l0` (P) — inverse of `parse_ref_idx_cabac`. Unary binarization,
8526/// ctxIdxOffset 54: binIdx 0 → `ctx0` (condTermFlagA + 2·condTermFlagB, condTermFlagN =
8527/// neighbour partition's ref_idx > 0), binIdx 1 → 4, binIdx ≥2 → 5 (spec 9.3.3.1.1.6).
8528pub fn cb_ref_idx(cab: &mut CabacEncoder, ctx0: usize, r: u32) {
8529 const B: usize = 54;
8530 let mut v = r;
8531 let mut bin_idx = 0u32;
8532 loop {
8533 let bin = (v > 0) as u32;
8534 let ctx = match bin_idx {
8535 0 => ctx0,
8536 1 => 4,
8537 _ => 5,
8538 };
8539 cab.encode_decision(B + ctx, bin);
8540 if bin == 0 {
8541 break;
8542 }
8543 v -= 1;
8544 bin_idx += 1;
8545 }
8546}
8547
8548/// P-slice inter `mb_type` (0/1/2 = P_L0_16x16 / P_16x8 / P_8x16) — inverse of the
8549/// inter branch of `parse_mb_type_p_cabac` (ctx base 11).
8550fn cb_mb_type_p_inter(cab: &mut CabacEncoder, mode: u8) {
8551 const S: usize = 11;
8552 cab.encode_decision(S + 3, 0); // inter (prefix bit 0)
8553 match mode {
8554 0 => {
8555 cab.encode_decision(S + 4, 0);
8556 cab.encode_decision(S + 5, 0);
8557 }
8558 3 => {
8559 // P_8x8 (bins "0 0 1")
8560 cab.encode_decision(S + 4, 0);
8561 cab.encode_decision(S + 5, 1);
8562 }
8563 1 => {
8564 cab.encode_decision(S + 4, 1);
8565 cab.encode_decision(S + 6, 1);
8566 }
8567 _ => {
8568 // mode == 2 (P_8x16)
8569 cab.encode_decision(S + 4, 1);
8570 cab.encode_decision(S + 6, 0);
8571 }
8572 }
8573}
8574
8575/// P `sub_mb_type` CABAC — inverse of `parse_sub_mb_type_p_cabac` (ctx base 21).
8576/// Only 0 = P_L0_8x8 (bin "1") is emitted (8×8 sub-partitions only).
8577fn cb_sub_mb_type_p(cab: &mut CabacEncoder, sub_type: u8) {
8578 const S: usize = 21;
8579 // Inverse of `parse_sub_mb_type_p_cabac`: b(S)=1 → 0 (8×8);
8580 // b(S)=0, b(S+1)=0 → 1 (8×4); b(S)=0, b(S+1)=1 → 3−b(S+2) (2 = 4×8, 3 = 4×4).
8581 match sub_type {
8582 0 => cab.encode_decision(S, 1),
8583 1 => {
8584 cab.encode_decision(S, 0);
8585 cab.encode_decision(S + 1, 0);
8586 }
8587 2 => {
8588 cab.encode_decision(S, 0);
8589 cab.encode_decision(S + 1, 1);
8590 cab.encode_decision(S + 2, 1);
8591 }
8592 3 => {
8593 cab.encode_decision(S, 0);
8594 cab.encode_decision(S + 1, 1);
8595 cab.encode_decision(S + 2, 0);
8596 }
8597 _ => unreachable!("invalid P sub_mb_type"),
8598 }
8599}
8600
8601/// P-slice intra `mb_type` prefix — inverse of the intra branch of
8602/// `parse_mb_type_p_cabac` (ctx base 11). Carries the I_16x16 pred-mode/cbp exactly
8603/// like the I-slice mb_type, so the shared intra body re-emits neither.
8604fn cb_mb_type_p_intra(cab: &mut CabacEncoder, plan: &MbPlan) {
8605 const S: usize = 11;
8606 cab.encode_decision(S + 3, 1); // intra (prefix bit 1)
8607 if plan.use_i4 {
8608 cab.encode_decision(S + 6, 0); // I_4x4
8609 return;
8610 }
8611 cab.encode_decision(S + 6, 1); // I_16x16
8612 cab.encode_terminate(false); // not I_PCM
8613 cab.encode_decision(S + 7, plan.i16_cbp15 as u32);
8614 if plan.cbp_chroma != 0 {
8615 cab.encode_decision(S + 8, 1);
8616 cab.encode_decision(S + 8, (plan.cbp_chroma == 2) as u32);
8617 } else {
8618 cab.encode_decision(S + 8, 0);
8619 }
8620 cab.encode_decision(S + 9, (plan.i16_mode as u32 >> 1) & 1);
8621 cab.encode_decision(S + 9, plan.i16_mode as u32 & 1);
8622}
8623
8624/// P-slice partition layout: `(part_idx, z-blocks)` per motion partition (matches
8625/// the decoder's `part!` invocations). part_idx = the partition's top-left z-block
8626/// (its `CACHE30` slot for the mvd ctxInc); z-blocks = every 4x4 it covers.
8627fn p_partition_layout(mode: u8) -> &'static [(usize, &'static [usize])] {
8628 match mode {
8629 1 => &[(0, &[0, 1, 2, 3, 4, 5, 6, 7]), (8, &[8, 9, 10, 11, 12, 13, 14, 15])],
8630 2 => &[(0, &[0, 1, 2, 3, 8, 9, 10, 11]), (4, &[4, 5, 6, 7, 12, 13, 14, 15])],
8631 // P_8x8: four 8×8 quads (z-order 4×4 blocks), part order == inter_partitions(3).
8632 3 => &[(0, &[0, 1, 2, 3]), (4, &[4, 5, 6, 7]), (8, &[8, 9, 10, 11]), (12, &[12, 13, 14, 15])],
8633 _ => &[(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])],
8634 }
8635}
8636
8637/// Sub-partition geometry per `sub_mb_type` (P): pixel rects within an 8×8, in
8638/// decode order. VERBATIM the decoder's `sub_mb_partitions` (spec Table 7-17) —
8639/// the decoder's parse is the contract this encoder inverts (Great Gate P3.3).
8640fn sub_mb_partitions_p(sub_type: u8) -> &'static [(usize, usize, usize, usize)] {
8641 match sub_type {
8642 0 => &[(0, 0, 8, 8)],
8643 1 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
8644 2 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
8645 _ => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)],
8646 }
8647}
8648
8649/// Per-sub-partition `(part_idx, zblocks)` for the mvd cache/context emission —
8650/// the sub-partition refinement of `p_partition_layout(3)`'s quads. `part_idx` =
8651/// the sub-partition's first 4×4 in MB z-order (its top-left), `zblocks` = the
8652/// 4×4s it covers; quad `p8`'s blocks are `4·p8 ..= 4·p8+3` in z-order
8653/// (0=TL, 1=TR, 2=BL, 3=BR within the quad).
8654fn p_sub_partition_layout(p8: usize, sub_type: u8) -> &'static [(usize, &'static [usize])] {
8655 const T: [[&[(usize, &[usize])]; 4]; 4] = [
8656 [
8657 &[(0, &[0, 1, 2, 3])],
8658 &[(0, &[0, 1]), (2, &[2, 3])],
8659 &[(0, &[0, 2]), (1, &[1, 3])],
8660 &[(0, &[0]), (1, &[1]), (2, &[2]), (3, &[3])],
8661 ],
8662 [
8663 &[(4, &[4, 5, 6, 7])],
8664 &[(4, &[4, 5]), (6, &[6, 7])],
8665 &[(4, &[4, 6]), (5, &[5, 7])],
8666 &[(4, &[4]), (5, &[5]), (6, &[6]), (7, &[7])],
8667 ],
8668 [
8669 &[(8, &[8, 9, 10, 11])],
8670 &[(8, &[8, 9]), (10, &[10, 11])],
8671 &[(8, &[8, 10]), (9, &[9, 11])],
8672 &[(8, &[8]), (9, &[9]), (10, &[10]), (11, &[11])],
8673 ],
8674 [
8675 &[(12, &[12, 13, 14, 15])],
8676 &[(12, &[12, 13]), (14, &[14, 15])],
8677 &[(12, &[12, 14]), (13, &[13, 15])],
8678 &[(12, &[12]), (13, &[13]), (14, &[14]), (15, &[15])],
8679 ],
8680 ];
8681 T[p8][sub_type as usize]
8682}
8683
8684/// Emit one motion partition's `mvd` (x,y) and splat it into the 30-entry cache +
8685/// per-MB raster mvd/ref grids — inverse of the decoder's `parse_mvd_partition`.
8686#[allow(clippy::too_many_arguments)]
8687fn cb_emit_mvd_partition(
8688 cab: &mut CabacEncoder,
8689 part_idx: usize,
8690 zblocks: &[usize],
8691 mvdc: &mut [[i16; 2]; 30],
8692 refc: &mut [i8; 30],
8693 mmvd: &mut [[i16; 2]; 16],
8694 mref: &mut [i8; 16],
8695 mvd: (i32, i32),
8696 ref_idx: i8, // the partition's ref_idx_l0 (0 for single-ref) — stored for neighbour context
8697) {
8698 let s = CB_CACHE30[part_idx];
8699 let ctx = |comp: usize| -> usize {
8700 let mut a = 0i32;
8701 if refc[s - 6] >= 0 {
8702 a += mvdc[s - 6][comp].unsigned_abs() as i32;
8703 }
8704 if refc[s - 1] >= 0 {
8705 a += mvdc[s - 1][comp].unsigned_abs() as i32;
8706 }
8707 if a >= 3 {
8708 1 + (a > 32) as usize
8709 } else {
8710 0
8711 }
8712 };
8713 cb_mvd(cab, 0, ctx(0), mvd.0);
8714 cb_mvd(cab, 1, ctx(1), mvd.1);
8715 let (mx, my) = (mvd.0 as i16, mvd.1 as i16);
8716 for &zb in zblocks {
8717 mvdc[CB_CACHE30[zb]] = [mx, my];
8718 refc[CB_CACHE30[zb]] = ref_idx;
8719 mmvd[CB_G_SCAN4[zb]] = [mx, my];
8720 mref[CB_G_SCAN4[zb]] = ref_idx;
8721 }
8722}
8723
8724/// Emit one planned INTER macroblock as CABAC (P-slice, mb_skip_flag already coded
8725/// as 0). `mode`/`parts` + `plan` from `plan_inter_mb`. 1-ref: no ref_idx.
8726fn emit_mb_cabac_p_inter(
8727 fe: &mut FrameEncoder,
8728 cab: &mut CabacEncoder,
8729 cs: &mut CabacState,
8730 mode: u8,
8731 plan: &InterPlan,
8732 mb_x: usize,
8733 mb_y: usize,
8734 num_refs: usize,
8735) {
8736 let mb_w = fe.mb_w;
8737 let addr = mb_y * mb_w + mb_x;
8738 let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
8739 let left = if mb_x > 0 { Some(addr - 1) } else { None };
8740
8741 // Bit accountant (instrument #6): each tap is a `pos()` delta — exact coded
8742 // bits for that element — behind an atomic-bool check when disabled.
8743 let acct = crate::bitacct::enabled();
8744 let mut t0 = if acct { cab.pos() } else { 0 };
8745 cb_mb_type_p_inter(cab, mode);
8746 // P_8x8: four sub_mb_type, spec order before ref_idx/mvd ([0;4] = all 8×8 =
8747 // the pre-P3.3 emission, byte-identical).
8748 if mode == 3 {
8749 for &st in &plan.sub_types {
8750 cb_sub_mb_type_p(cab, st);
8751 }
8752 }
8753 if acct {
8754 crate::bitacct::add(crate::bitacct::B::MbType, cab.pos() - t0);
8755 t0 = cab.pos();
8756 }
8757
8758 // ---- mb_pred (spec 7.3.5.1): all ref_idx_l0 FIRST, then all mvd_l0 ----
8759 let mut mvdc = [[0i16; 2]; 30];
8760 let mut refc = [-1i8; 30];
8761 cb_fill_inter_cache(&cs.mb_ref, &cs.mb_mvd, &mut refc, &mut mvdc, top, left, addr, mb_w);
8762 let mut mmvd = [[0i16; 2]; 16];
8763 let mut mref = [0i8; 16];
8764 let layout = p_partition_layout(mode);
8765 // Phase 1: ref_idx_l0 per partition, only when the slice has >1 active reference.
8766 // Update refc after each so a later partition's ref context sees the earlier one.
8767 if num_refs > 1 {
8768 for (part, &(part_idx, zblocks)) in layout.iter().enumerate() {
8769 let r = plan.plan_refs[part];
8770 let s = CB_CACHE30[part_idx];
8771 let ctx0 = (refc[s - 1] > 0) as usize + 2 * (refc[s - 6] > 0) as usize;
8772 cb_ref_idx(cab, ctx0, r as u32);
8773 for &zb in zblocks {
8774 refc[CB_CACHE30[zb]] = r as i8;
8775 }
8776 }
8777 }
8778 if acct {
8779 crate::bitacct::add(crate::bitacct::B::RefIdx, cab.pos() - t0);
8780 t0 = cab.pos();
8781 }
8782 // Phase 2: mvd per partition (carries the ref into refc/mref for neighbour context).
8783 if mode == 3 && plan.sub_types != [0u8; 4] {
8784 // Sub-partitioned P_8x8: one mvd per sub-partition, decode order, the
8785 // quad ref for context (P3.3 -- layout from `p_sub_partition_layout`).
8786 let mut k = 0usize;
8787 for p8 in 0..4usize {
8788 for &(part_idx, zblocks) in p_sub_partition_layout(p8, plan.sub_types[p8]) {
8789 cb_emit_mvd_partition(
8790 cab, part_idx, zblocks, &mut mvdc, &mut refc, &mut mmvd, &mut mref,
8791 plan.mvds[k], plan.plan_refs[p8] as i8,
8792 );
8793 k += 1;
8794 }
8795 }
8796 } else {
8797 for (part, &(part_idx, zblocks)) in layout.iter().enumerate() {
8798 cb_emit_mvd_partition(
8799 cab, part_idx, zblocks, &mut mvdc, &mut refc, &mut mmvd, &mut mref, plan.mvds[part],
8800 plan.plan_refs[part] as i8,
8801 );
8802 }
8803 }
8804 if acct {
8805 crate::bitacct::add(crate::bitacct::B::Mvd, cab.pos() - t0);
8806 }
8807 cs.mb_mvd[addr] = mmvd;
8808 cs.mb_ref[addr] = mref;
8809 cs.cat[addr] = 100;
8810 let allow8 = plan.sub_types == [0u8; 4];
8811 cb_emit_inter_residual(fe, cab, cs, plan, mb_x, mb_y, addr, top, left, allow8);
8812}
8813
8814/// Inter cbp + residual (is_intra = false) — shared by P and B inter MBs. Maintains
8815/// cs.mb_cbp/cbf_dc/mb_nzc/last_delta_qp + fe.nnz_y.
8816#[allow(clippy::too_many_arguments)]
8817fn cb_emit_inter_residual(
8818 fe: &mut FrameEncoder,
8819 cab: &mut CabacEncoder,
8820 cs: &mut CabacState,
8821 plan: &InterPlan,
8822 mb_x: usize,
8823 mb_y: usize,
8824 addr: usize,
8825 top: Option<usize>,
8826 left: Option<usize>,
8827 allow8: bool,
8828) {
8829 let w4 = fe.mb_w * 4;
8830 let cbp = plan.cbp;
8831 let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
8832 let acct = crate::bitacct::enabled();
8833 let mut t0 = if acct { cab.pos() } else { 0 };
8834 cb_cbp(cab, top.map(|a| cs.mb_cbp[a]), left.map(|a| cs.mb_cbp[a]), cbp);
8835 if acct {
8836 crate::bitacct::add(crate::bitacct::B::Cbp, cab.pos() - t0);
8837 }
8838 cs.mb_cbp[addr] = cbp as u8;
8839 // transform_size_8x8_flag, INTER position: after cbp, before mb_qp_delta, and
8840 // only when luma carries coefficients (spec 7.3.5). Contrast the I_NxN position,
8841 // which is before the pred modes -- the two are different points in the syntax,
8842 // which is why this needs its own write rather than a shared helper.
8843 // `plan_inter_mb` enforces the noSubMbPartSizeLessThan8x8Flag half of the
8844 // condition by never selecting t8x8 alongside a sub-8x8 split.
8845 // `allow8` is the spec's noSubMbPartSizeLessThan8x8Flag, mirroring the decoder's
8846 // own `allow8`. It gates the flag's PRESENCE, not just its value: omitting it wrote
8847 // one extra bin on every sub-8x8-split P_8x8 with luma coefficients and desynced
8848 // the stream some macroblocks later (ffmpeg reported it as a bogus intra mode).
8849 let t8_present = cbp_luma > 0 && fe.transform_8x8 && allow8;
8850 if t8_present {
8851 let ta = left.map_or(0, |x| cs.mb_t8x8[x] as usize);
8852 let tb = top.map_or(0, |x| cs.mb_t8x8[x] as usize);
8853 cab.encode_decision(399 + ta + tb, plan.t8x8 as u32);
8854 }
8855 // ABSENT means INFERRED ZERO, and the decoder stores that zero as the neighbour
8856 // ctxIdxInc for later macroblocks. Storing `plan.t8x8` here regardless of
8857 // presence would drift our context from the decoder's on any MB where the flag
8858 // was suppressed -- a desync that only shows up MBs later.
8859 cs.mb_t8x8[addr] = t8_present && plan.t8x8;
8860 let mut nzc = cb_build_nzc(&cs.mb_nzc, top, left);
8861 let mut cbfdc = 0u16;
8862 let ndc = (top.map(|a| cs.cbf_dc[a]), left.map(|a| cs.cbf_dc[a]));
8863
8864 if cbp == 0 {
8865 cs.last_delta_qp = 0;
8866 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
8867 fe.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 0;
8868 }
8869 } else {
8870 let delta = fe.qp_delta();
8871 if acct { t0 = cab.pos(); }
8872 cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
8873 if acct {
8874 crate::bitacct::add(crate::bitacct::B::QpDelta, cab.pos() - t0);
8875 t0 = cab.pos();
8876 }
8877 if plan.t8x8 {
8878 // ctxBlockCat 5: ONE 64-coefficient block per 8x8, no coded_block_flag.
8879 for b8 in 0..4usize {
8880 let (b8x, b8y) = (b8 % 2, b8 / 2);
8881 let total = if cbp_luma & (1 << b8) != 0 {
8882 let scan8 = scan_8x8_fwd(&plan.q8[b8]);
8883 cb_residual(cab, &mut nzc, &mut cbfdc, b8 * 4, CB_RP_LUMA_8X8, false, ndc, &scan8)
8884 } else {
8885 for k in 0..4 {
8886 nzc[CB_NZC_CACHE[b8 * 4 + k]] = 0;
8887 }
8888 0
8889 };
8890 for sy in 0..2 {
8891 for sx in 0..2 {
8892 fe.nnz_y[(mb_y * 4 + b8y * 2 + sy) * w4 + (mb_x * 4 + b8x * 2 + sx)] =
8893 total as u8;
8894 }
8895 }
8896 }
8897 } else {
8898 for id8 in 0..4usize {
8899 for id4 in 0..4usize {
8900 let iz = id8 * 4 + id4;
8901 let (lbx, lby) = LUMA_4X4_SCAN_XY[iz];
8902 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
8903 let total = if cbp_luma & (1 << id8) != 0 {
8904 let sc = scan_4x4_dcac(&plan.q_blocks[lby * 4 + lbx]);
8905 cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_LUMA_4X4, false, ndc, &sc)
8906 } else {
8907 nzc[CB_NZC_CACHE[iz]] = 0;
8908 0
8909 };
8910 fe.nnz_y[by * w4 + bx] = total as u8;
8911 }
8912 }
8913 }
8914 if acct {
8915 crate::bitacct::add(crate::bitacct::B::ResidLuma, cab.pos() - t0);
8916 t0 = cab.pos();
8917 }
8918 cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, false, cbp_chroma, &plan.c_dc_levels, &plan.c_q, mb_x, mb_y);
8919 if acct {
8920 crate::bitacct::add(crate::bitacct::B::ResidChroma, cab.pos() - t0);
8921 }
8922 }
8923 cs.cbf_dc[addr] = cbfdc;
8924 cs.mb_nzc[addr] = cb_export_nzc(&nzc);
8925}
8926
8927/// Emit one planned INTRA macroblock inside a P-slice: the P mb_type prefix (which
8928/// carries the I_16x16 pred-mode/cbp) then the shared intra body.
8929fn emit_mb_cabac_p_intra(
8930 fe: &mut FrameEncoder,
8931 cab: &mut CabacEncoder,
8932 cs: &mut CabacState,
8933 plan: &MbPlan,
8934 mb_x: usize,
8935 mb_y: usize,
8936) {
8937 let mb_w = fe.mb_w;
8938 let addr = mb_y * mb_w + mb_x;
8939 let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
8940 let left = if mb_x > 0 { Some(addr - 1) } else { None };
8941 let acct = crate::bitacct::enabled();
8942 let t0 = if acct { cab.pos() } else { 0 };
8943 cb_mb_type_p_intra(cab, plan);
8944 emit_intra_body_cabac(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
8945 if acct {
8946 // Whole intra MB (mb_type + modes + its residual) — intra MBs are ~5% of
8947 // P-frame MBs; splitting them further is a separate tap set.
8948 crate::bitacct::add(crate::bitacct::B::IntraBody, cab.pos() - t0);
8949 }
8950}
8951
8952/// Emit a P_Skip macroblock's `mb_skip_flag = 1` and update neighbour state. The
8953/// motion grid was committed by `commit_skip`; the mvd/ref cache is LEFT at its
8954/// init (-1 ref) — matching the decoder, which does not touch mb_mvd/mb_ref for a
8955/// P_Skip (so a skip neighbour contributes nothing to a later mvd ctxInc).
8956fn emit_p_skip_cabac(cab: &mut CabacEncoder, cs: &mut CabacState, addr: usize, top: Option<usize>, left: Option<usize>) {
8957 let sctx = 11
8958 + left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
8959 + top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
8960 let acct = crate::bitacct::enabled();
8961 let t0 = if acct { cab.pos() } else { 0 };
8962 cb_mb_skip(cab, sctx, true);
8963 if acct {
8964 crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - t0);
8965 }
8966 cs.mb_skip[addr] = true;
8967 cs.cat[addr] = 100;
8968 cs.last_delta_qp = 0;
8969}
8970
8971/// CABAC P-slice data coder. Mirrors `encode_slice_data`'s decision (P_Skip check +
8972/// fast/quality inter-vs-intra RD) exactly — only the emit differs (per-MB
8973/// mb_skip_flag + CABAC syntax + per-MB end_of_slice terminate).
8974/// Median source macroblock variance for a frame — the TEXTURE dispatch signal for
8975/// the ME lambda scale.
8976///
8977/// Raising the ME rate term biases the search toward cheaper motion vectors, which
8978/// costs texture detail. SSIM is texture-sensitive where PSNR is not, so on maximum-
8979/// texture content a higher lambda improves BD-PSNR while REGRESSING BD-SSIM.
8980/// Measured median MB variance vs BD-SSIM at lme 1.8:
8981/// akiyo 61 (-0.20), foreman 219 (-0.61), city 300 (-0.89), bus 454 (-0.01),
8982/// football 583 (-1.03), **mobile 1554 (+0.45 LOSS)**
8983/// The one loser carries 2.7x the texture of the next clip, so the split is wide.
8984///
8985/// Computed from the SOURCE, so it is available in-slice for BOTH P and B and needs
8986/// no cross-frame state — unlike the B-only direct-win rate, which cannot gate a knob
8987/// that both encoders read and whose carry-forward would be nondeterministic under
8988/// frame-parallel encode. Subsampled 2x2 (16x fewer loads) — a median over ~400
8989/// macroblocks does not need every pixel.
8990// `frame_median_mb_var` lives in `crate::signals` (Great Gate P1) — read through
8991// `FrameSignals::median_var`. NOTE its estimator caveat there: it is deliberately
8992// a DIFFERENT formula from `mb_variance` (the lme clip table was calibrated on it).
8993
8994/// Texture-dispatched ME lambda scale: the calibrated high value on normal content,
8995/// the conservative shipped value on maximum-texture content where it costs SSIM.
8996/// Cached `RFF_LME_Q` env override for [`EncoderConfig::tune_lme_q`] (one binary,
8997/// N sweep arms — and never an `env::var` in a per-frame path).
8998fn lme_q_env() -> Option<f64> {
8999 static E: std::sync::OnceLock<Option<f64>> = std::sync::OnceLock::new();
9000 *E.get_or_init(|| std::env::var("RFF_LME_Q").ok().and_then(|v| v.parse().ok()))
9001}
9002
9003fn me_lambda_scale(cfg: &EncoderConfig, sig: &FrameSignals, per_mb_tex: bool) -> f64 {
9004 let hi = match cfg.tune_lme_hi {
9005 Some(v) if v > 0.0 => v,
9006 _ => return cfg.cabac_lambda_scale,
9007 };
9008 // TWO terms, and each is justified by a DIFFERENT clip it must classify —
9009 // neither alone is sufficient, which is why the texture-only version shipped
9010 // disabled.
9011 //
9012 // clip global-MC resid median var wants hi lme?
9013 // akiyo 1.51 61 yes
9014 // foreman 9.59 219 yes
9015 // city 12.44 300 yes
9016 // football 24.83 583 yes
9017 // TEMPETE 10.52 746 NO <- caught by TEXTURE (650)
9018 // MOBILE 19.50 1554 NO <- caught by TEXTURE
9019 // BUS 27.47 454 NO <- caught by MOTION (20)
9020 //
9021 // mobile is maximum texture: a higher ME rate term biases toward cheaper MVs,
9022 // costing texture detail, and SSIM is texture-sensitive where PSNR is not.
9023 // bus is fast GLOBAL motion (a pan): its cost surface is dominated by one
9024 // global vector, so pushing the rate term drags MVs off it. football is
9025 // chaotic LOCAL motion at similar texture and WANTS the high value, so texture
9026 // cannot separate the two — the global-MC residual can, and in the opposite
9027 // direction, which is exactly why the pair works where either alone fails.
9028 // Great Gate P1 (`tune_lme_q`): when the caller applies the texture veto PER MB
9029 // by percentile, skip the frame-median form here so the two never stack — the
9030 // motion veto below stays frame-level in both forms.
9031 if !per_mb_tex && sig.median_var() >= cfg.tune_lme_tex_thresh.unwrap_or(650) {
9032 return cfg.cabac_lambda_scale;
9033 }
9034 if sig.has_ref() {
9035 let mot = cfg.tune_lme_motion_thresh.unwrap_or(26.0);
9036 if sig.gmc_residual() >= mot {
9037 return cfg.cabac_lambda_scale;
9038 }
9039 }
9040 hi
9041}
9042
9043pub(crate) fn encode_slice_data_cabac_p(
9044 w: &mut BitWriter,
9045 cfg: &EncoderConfig,
9046 frame: &YuvFrame,
9047 qp: u8,
9048 refs: &[crate::RefFrame],
9049 qpo: &[i32],
9050 wp: &[(i32, i32)],
9051) -> crate::RefFrame {
9052 let mut fe = FrameEncoder::new(cfg);
9053 fe.wp = wp.to_vec();
9054 fe.qp = qp;
9055 fe.qpc = chroma_qp(qp);
9056 fe.cur_qp = qp;
9057 if cfg.cabac_dz_div > 0 {
9058 fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
9059 }
9060 // Inter trellis (opt-in, Great Gate P2): P slices are REFERENCES — see
9061 // `cabac_rdoq_p`'s structure-adaptive caveat. 0 = off, byte-identical.
9062 fe.rdoq_strength = 0.0; // set below, once `sig` exists (content-gated)
9063 // RD P_Skip threshold arm (P3 item 2): `RFF_RDSKIP_T` overrides for sweep
9064 // arms and CLI conformance runs, mirroring `RFF_BSKIP_T`. Unset = config.
9065 {
9066 static T: std::sync::OnceLock<Option<f64>> = std::sync::OnceLock::new();
9067 if let Some(t) = *T.get_or_init(|| {
9068 std::env::var("RFF_RDSKIP_T").ok().and_then(|v| v.parse().ok())
9069 }) {
9070 fe.rd_skip = t > 0.0;
9071 fe.rd_skip_fast_t = t;
9072 }
9073 }
9074 let (sy, su, sv) = coded_source(cfg, frame);
9075 // Great Gate P1: ONE lazy signal vector per frame. The lme motion term and
9076 // the me_wide coherence gate below both read `gmc_residual` — memoization
9077 // collapses what used to be TWO full global-MC probes into one.
9078 let sig = FrameSignals::new(&sy, fe.cw, fe.mb_w, fe.mb_h, refs.first().map(|r| &r.y[..]));
9079 apply_screen_t8_veto(&mut fe, &sig);
9080 // CONTENT GATE for P-slice trellis. Flat-on is refuted (loses on 4 of 6 clips at
9081 // strength 32); grain and screen are the two classes where it wins hugely
9082 // (-30.12% / -12.11% BD-SSIM). The discriminator is exactly the reference-structure
9083 // argument in `cabac_rdoq_p`'s doc: a P frame is a reference, so trading its
9084 // distortion for rate propagates -- UNLESS what is being traded away is noise
9085 // (grain) or flat runs (screen), which propagate nothing.
9086 fe.rdoq_strength = if sig.grain_signature() || sig.is_screen() { cfg.cabac_rdoq_p } else { 0.0 };
9087 let lambda = 0.85 * fe.tune_lambda_scale * crate::fastmath::lambda_qp(qp);
9088 // Hoisted to SLICE level: the texture median is O(pixels) and the site below
9089 // sits inside the macroblock loop, where recomputing it would be quadratic.
9090 // Great Gate P1 (opt-in, BD-gate pending — great-gate.md §6 P2): `tune_lme_q` /
9091 // `RFF_LME_Q` converts the lme TEXTURE veto from an absolute frame-median test
9092 // (which cannot separate bus 454 from football 583 — they want opposite values)
9093 // to the population-shaped per-MB form: THIS frame's top-q highest-variance MBs
9094 // take the conservative scale individually. None/unset = frame form, byte-identical.
9095 // SHAPE-RD TEXTURE GUARD (Great Gate). shape-rd wins on 12 of 13 clips on
9096 // BOTH metrics and on 13 of 13 on PSNR; the lone loser is mobile (+1.99
9097 // BD-SSIM while WINNING -0.44 PSNR), the natural-corpus texture extreme.
9098 //
9099 // THIS IS A CONSERVATIVE GUARD, NOT A CAUSAL MODEL -- read before touching.
9100 // Four candidate mechanisms were tested and ALL FOUR REFUTED (gate-ledger):
9101 // texture-causes-the-loss is refuted by `maxtex_plaid`, a SYNTHESIZED clip
9102 // at median_var 2583 -- above mobile's 1494 -- which WINS -1.87 unvetoed.
9103 // dcfrac separated only under an unrelated lambda config. AQ accounts for
9104 // at most a fifth (aq=0 still loses +1.64). Chroma-weighting the RD SSD
9105 // moves it MONOTONICALLY THE WRONG WAY (+2.11 at weight 0).
9106 //
9107 // So median_var does not explain the loss; it merely BOUNDS it. Within the
9108 // 24-clip natural truth table exactly one clip exceeds this threshold and
9109 // that clip regresses, so the guard can only ever forgo a win, never create
9110 // a loss. It costs `maxtex_plaid` its -1.87 -- accepted, because synthetic
9111 // single-frequency texture is not content anyone ships. Threshold sits in
9112 // the open natural gap (highest winner foreman_qcif 793 -> mobile 1494).
9113 // Delete this guard the moment a real mechanism is found.
9114 let shape_rd_tex_veto = sig.median_var() > shape_rd_tex_max();
9115 let lme_q = lme_q_env().or(cfg.tune_lme_q).filter(|&q| q > 0.0);
9116 let lme_scale = me_lambda_scale(cfg, &sig, lme_q.is_some());
9117 let lme_mb_thresh: Option<i64> = match lme_q {
9118 Some(q) if lme_scale != cfg.cabac_lambda_scale => Some(sig.var_percentile_thresh(q)),
9119 _ => None,
9120 };
9121 let num_refs = refs.len();
9122 // me_wide content gate (pure-pan → global-MC residual ≈ 0 → off; see encode_slice_data).
9123 if fe.me_wide && !refs.is_empty() {
9124 let coh = sig.gmc_residual();
9125 if std::env::var("RFF_ME_COH_DBG").is_ok() {
9126 eprintln!("ME_COH qp{qp} residual={coh:.2}");
9127 }
9128 if coh < fe.me_wide_coh {
9129 fe.me_wide = false;
9130 }
9131 }
9132 // me_wide HEAD-ROOM GATE (the dispatcher the truth table asked for). The rescue
9133 // only pays where a wide search actually beats a predictor-local one; measure
9134 // that directly per frame and route the frame. `RFF_ME_HR` sets the threshold
9135 // (percent); 0 disables the gate and restores the always-on behaviour.
9136 // Skip the probe entirely when the gate is disabled: it must not tax the
9137 // default path (`RFF_ME_HR=0`), which stays byte-identical to pre-gate output.
9138 if fe.me_wide && !refs.is_empty() && (me_wide_hr_thresh() > 0.0 || me_wide_hr_dbg()) {
9139 let hr = sig.headroom();
9140 if me_wide_hr_dbg() {
9141 eprintln!("ME_HR qp{qp} headroom={hr:.2}");
9142 }
9143 if me_wide_hr_thresh() > 0.0 && hr < me_wide_hr_thresh() {
9144 fe.me_wide = false;
9145 }
9146 }
9147 // Track-B B2 DISPATCH — same probe/route as the CAVLC driver above (the two
9148 // drivers must stay in lockstep; the U5-struct bug came from patching one).
9149 if me_sadfp_mode() == 1 && !fe.fast && !refs.is_empty() {
9150 let (mg, dc) = sig.mgain_dc();
9151 if me_sadt_dbg() {
9152 eprintln!("B2_MG qp{qp} mgain={mg:.3} dcfrac={dc:.3}");
9153 }
9154 fe.sadfp = mg >= me_sadt() && dc <= me_sad_dcmax();
9155 // H-24: the mv-cost SHAPE rides the same probe (its BD sign-flip tracks
9156 // motion for the same physical reason B2's does).
9157 if mv_smooth_mode() == 1 {
9158 // dcfrac veto mirrors B2's: crew-class FLASH frames satisfy the mgain
9159 // test but SAD/mvd statistics mislead there (H-13/H-26).
9160 fe.mv_smooth = mg >= mv_smooth_t() && dc <= me_sad_dcmax();
9161 }
9162 // H-13: near-static frames skip the split searches entirely.
9163 let smg = split_mg();
9164 if smg > 0.0 {
9165 fe.do_splits = mg >= smg;
9166 }
9167 }
9168 if fe.satd_q > 0.0 {
9169 fe.satd_var_thresh = sig.var_percentile_thresh(fe.satd_q);
9170 }
9171 let mut aq_qp = aq_qp_map(&sig, qp, fe.aq_strength);
9172 apply_mbtree_qpo(&mut aq_qp, qpo); // mb-tree temporal AQ (empty = byte-identical)
9173 signals::harvest(
9174 &sig,
9175 'P',
9176 qp,
9177 &signals::GateDecisions {
9178 me_wide: fe.me_wide,
9179 sadfp: fe.sadfp,
9180 mv_smooth: fe.mv_smooth,
9181 do_splits: fe.do_splits,
9182 lme_scale,
9183 satd_thresh: fe.satd_var_thresh,
9184 },
9185 );
9186 let mut mb_qpy = enc_scratch::take_qpy();
9187 mb_qpy.clear();
9188 mb_qpy.resize(fe.mb_w * fe.mb_h, qp);
9189
9190 // Same online free-skip dispatch as the CAVLC path gates the greedy P_Skip on
9191 // (see `encode_slice_data`): measured over the frame so far, within-frame so it
9192 // stays deterministic under GOP-parallel encode.
9193 // Online split-payoff census (see `sub8_pay_cfg`): `seen` = macroblocks that
9194 // ran the split search, `paid` = those whose split survived the RD trial.
9195 let (sub8_minpay, sub8_learn) = sub8_pay_cfg();
9196 let (mut sub8_seen, mut sub8_gain) = (0usize, 0f64);
9197 let mut sub8_paying = true;
9198 let mut greedy_free = 0usize;
9199 let mut greedy_seen = 0usize;
9200 let mut greedy_on = fe.greedy_min_free == 0;
9201 let greedy_learn = (fe.mb_w * fe.mb_h / 8).max(64);
9202 let mut cab =
9203 CabacEncoder::new_with_out(qp as i32, cfg.cabac_init_idc, false, enc_scratch::take_payload()); // P-slice
9204 let mut cs = CabacState::pooled(fe.mb_w * fe.mb_h);
9205 let total = fe.mb_w * fe.mb_h;
9206
9207 // E15 hammer (inline-execution.md 11.8): the asm read showed this driver's
9208 // "1,730 un-vectorised xmm ops" are 24% SEH unwind DIRECTIVES and ~1,200
9209 // scalar f64 moves/compares of RD currency spilled around 461 calls - there
9210 // is no pixel loop to vectorise. The lever is Law 5: every line below was
9211 // re-read per MACROBLOCK (several per CODED macroblock) though each is
9212 // slice-constant. All are cached atomics or pure values - hoisting is
9213 // byte-identical by the same process-constant contract the pre-loop gates
9214 // above already assume.
9215 let lme_base = lambda.sqrt();
9216 let grain = sig.grain_signature();
9217 let split_t_v = split_t();
9218 let shape_rd = shape_rd_on().unwrap_or(cfg.tune_shape_rd);
9219 let want_split_knob = sub8x8_split_on() || cfg.tune_sub8x8_split;
9220 let sub8_gveto = sub8_grain_veto_on();
9221 let sub8_rd = sub8_rd_on() || cfg.tune_sub8_rd;
9222 let use_rd_frame = (intra_rd_on() || cfg.tune_intra_rd) && (!intra_rd_grain_gate() || grain);
9223 let acct = crate::bitacct::enabled();
9224 let greedy_zero = fe.greedy_min_free == 0;
9225 // lme tex veto operands, resolved once (mb_vars is built whenever the
9226 // threshold exists; the map keeps the never-built path allocation-free).
9227 let lme_vars = lme_mb_thresh.map(|t| (sig.mb_vars(), t));
9228 // REFUTED, do not retry (E15 r2): binding `&aq_qp[..total]` removed ZERO
9229 // of the loop's bounds checks (43 -> 43) - the `mb_idx = mb_y*w + mb_x`
9230 // shape defeats the slice-length hint here just as runtime-extent row
9231 // slices did in the decoder campaign.
9232
9233 // ② residue naming: the CABAC driver's MB loop was untapped (the CAVLC twin
9234 // has this scope) — `EncMbLoop − Σ(per-MB stages)` is the per-MB glue.
9235 let _g_loop = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMbLoop);
9236 for mb_y in 0..fe.mb_h {
9237 for mb_x in 0..fe.mb_w {
9238 let mb_idx = mb_y * fe.mb_w + mb_x;
9239 let addr = mb_idx;
9240 let top = if mb_y > 0 { Some(addr - fe.mb_w) } else { None };
9241 let left = if mb_x > 0 { Some(addr - 1) } else { None };
9242 let mb_qp = aq_qp[mb_idx];
9243 fe.qp = mb_qp;
9244 fe.qpc = chroma_qp(mb_qp);
9245 // LAMBDA MUST MATCH THE QP THIS MB IS ACTUALLY QUANTIZED AT. The
9246 // slice `lambda` is built ONCE from the FRAME qp, but AQ (default
9247 // strength 1.0) and mb-tree rewrite `fe.qp` per macroblock on the
9248 // line above. Every RD site below compares SSD_recon against
9249 // lambda*bits, so using the frame lambda misprices rate by
9250 // 2^((qp_frame-qp_mb)/3) -- and AQ moves QP FURTHEST on the
9251 // highest-variance macroblocks, so the error is largest exactly
9252 // where the shape/split decision is hardest. Same family as the
9253 // SATD-vs-recon-SSE wrong-proxy bug that this campaign already
9254 // fixed twice: the currency has to match the decision.
9255 let lam_mb = if cfg.tune_rd_lambda_mb {
9256 0.85 * fe.tune_lambda_scale * crate::fastmath::lambda_qp(fe.qp)
9257 } else {
9258 lambda
9259 };
9260
9261 // ---- P_Skip check (identical logic to encode_slice_data) ----
9262 let mut inter: Option<InterChoice> = None;
9263 // P3.3: sub_mb_type per 8x8 quad when `inter` is mode 3 ([0;4] = all
9264 // 8x8). A companion local rather than an InterChoice field so every
9265 // other constructor site stays untouched.
9266 let mut inter_subs = [0u8; 4];
9267 let mut did_skip = false;
9268 if num_refs > 0 {
9269 let mv_skip = fe.skip_mv(mb_x, mb_y);
9270 let skip_y = fe.skip_predict_luma(refs, mb_x, mb_y, mv_skip);
9271 let luma_free = fe.skip_luma_is_free(&sy, mb_x, mb_y, &skip_y);
9272 // (fast && !luma_free) never READS the chroma prediction — the
9273 // old zero-array arm was a dead 128-byte fill per MB (E15 r2 W18).
9274 let skip_c: Option<[[u8; 64]; 2]> = if luma_free || !fe.fast {
9275 Some(fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip))
9276 } else {
9277 None
9278 };
9279 let is_free = luma_free
9280 && skip_c.as_ref().is_some_and(|c| fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, c));
9281 let skip_sad = if fe.fast {
9282 0
9283 } else {
9284 let (lx, ly) = (mb_x * 16, mb_y * 16);
9285 let mut s = 0u32;
9286 for dy in 0..16 {
9287 let src = &sy[(ly + dy) * fe.cw + lx..][..16];
9288 let p = &skip_y[dy * 16..][..16];
9289 s += src.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
9290 }
9291 s
9292 };
9293 greedy_seen += 1;
9294 if greedy_seen >= greedy_learn {
9295 greedy_on = greedy_zero
9296 || greedy_free * 100 >= greedy_seen * fe.greedy_min_free as usize;
9297 }
9298 if is_free {
9299 // `is_free` implies the Some arm above.
9300 fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, skip_c.as_ref().unwrap());
9301 if !fe.fast {
9302 fe.mb_was_skip[mb_idx] = true;
9303 fe.mb_skip_sad[mb_idx] = skip_sad;
9304 }
9305 greedy_free += 1;
9306 did_skip = true;
9307 } else {
9308 signals::census::work(signals::census::W_MB_CODED);
9309 let (lx, ly) = (mb_x * 16, mb_y * 16);
9310 let nb = {
9311 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMvPred);
9312 fe.mv_neighbors_block(mb_x as isize * 4, mb_y as isize * 4, 4)
9313 };
9314 // Per-MB tex veto (`tune_lme_q`): top-q variance MBs take the
9315 // conservative scale; None = the frame-level `lme_scale` exactly.
9316 let lme = lme_base
9317 * match lme_vars {
9318 Some((v, t)) if v[mb_idx] >= t => cfg.cabac_lambda_scale,
9319 _ => lme_scale,
9320 };
9321 if fe.fast {
9322 // Dedup (E15 W9): `sig.mb_vars()` IS memoized `mb_variance`
9323 // over the same planes; every path here with `satd_q > 0`
9324 // already built it via the percentile threshold.
9325 fe.mb_use_satd = fe.satd_q > 0.0
9326 && sig.mb_vars()[mb_idx] >= fe.satd_var_thresh;
9327 let (r16, mv16, cost_inter) =
9328 fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
9329 let cost_intra = if fe.mb_use_satd {
9330 fe.best_i16_satd(&sy, mb_x, mb_y)
9331 } else {
9332 fe.best_i16_sad(&sy, mb_x, mb_y)
9333 } + (lme * fe.tune_intra_penalty) as i64;
9334 inter = if cost_intra < cost_inter {
9335 None
9336 } else {
9337 Some((0, vec![(r16, mv16)]))
9338 };
9339 } else {
9340 // Quality preset: greedy P_Skip, then 16x16 baseline + sub-partitions + intra.
9341 if fe.greedy_skip && greedy_on && skip_sad < fe.pred_skip_sad(mb_x, mb_y) {
9342 // Quality preset: the Some arm always ran above.
9343 fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, skip_c.as_ref().unwrap());
9344 fe.mb_was_skip[mb_idx] = true;
9345 fe.mb_skip_sad[mb_idx] = skip_sad;
9346 did_skip = true;
9347 } else {
9348 let (r16, mv16, c16) =
9349 fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
9350 let mut best_c = c16;
9351 let mut pick: Option<InterChoice> = Some((0, vec![(r16, mv16)]));
9352 // P3.3: the winning P_8x8 candidate's sub_mb_types
9353 // ([0;4] unless a split arm won).
9354 let mut pick_subs = [0u8; 4];
9355 // Probe #3: every shape the SATD search evaluated, so
9356 // the RD re-rank can score them all. Cheap to collect
9357 // (the parts vectors already exist); empty unless the
9358 // probe is on, so the default path allocates nothing.
9359 let mut shape_cands: Vec<(u8, Vec<(i32, (i32, i32))>, [u8; 4])> =
9360 Vec::new();
9361 // Some(true) = the RD re-rank says INTRA; Some(false) =
9362 // it says the chosen shape; None = probe off, use SATD.
9363 let mut shape_rd_intra: Option<bool> = None;
9364 if shape_rd {
9365 shape_cands.push((0u8, vec![(r16, mv16)], [0u8; 4]));
9366 }
9367 const QSTEP16: [i64; 6] = [10, 11, 13, 14, 16, 18];
9368 let qstep16 = QSTEP16[(fe.qp % 6) as usize] << (fe.qp / 6);
9369 let split_gate = ((30 * (qstep16 + 160)) >> 3) * 2;
9370 if fe.do_splits && c16 > split_gate && (split_t_v <= 0.0 || (c16 as f64) >= split_t_v * lme) {
9371 let (rt, mvt, ct) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 8, &[mv16], lme);
9372 let (rb, mvb, cb) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly + 8, 16, 8, &[mv16], lme);
9373 let (rl, mvl, cl) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 8, 16, &[mv16], lme);
9374 let (rr, mvr, cr) = fe.best_part(refs, &sy, &nb, num_refs, lx + 8, ly, 8, 16, &[mv16], lme);
9375 if shape_rd {
9376 shape_cands.push((1u8, vec![(rt, mvt), (rb, mvb)], [0u8; 4]));
9377 shape_cands.push((2u8, vec![(rl, mvl), (rr, mvr)], [0u8; 4]));
9378 }
9379 if ct + cb < best_c {
9380 best_c = ct + cb;
9381 pick = Some((1u8, vec![(rt, mvt), (rb, mvb)]));
9382 }
9383 if cl + cr < best_c {
9384 best_c = cl + cr;
9385 pick = Some((2u8, vec![(rl, mvl), (rr, mvr)]));
9386 }
9387 // P_8x8: four 8×8 sub-partitions (see the CAVLC path).
9388 // P3.3: with RFF_SUB8X8_SPLIT=1 each quad also
9389 // trials 8x4/4x8/4x4 (single-ref only: ref_idx is
9390 // per-QUAD syntax, and best_part searches refs per
9391 // part -- mixed sub-part refs are unrepresentable).
9392 // Per-quad arm cost = sub-part J sum + REAL
9393 // sub_mb_type bins (0->1, 1->2, 2/3->3) priced at
9394 // lme -- the uncharged-syntax lesson. The all-8x8
9395 // total is arithmetically IDENTICAL to the old
9396 // `lme*4 + sum(c)` form.
9397 // GRAIN VETO, third consumer (P3.3 gate). With the
9398 // decision re-priced in the RD currency the corpus
9399 // has exactly two remaining losers and both are
9400 // grain: splitting noise buys prediction error the
9401 // quantizer discards, and no amount of correct
9402 // pricing makes fitting noise worthwhile. Frame
9403 // grain -> no split arm (byte-identical to the
9404 // all-8x8 P_8x8 the encoder shipped before P3.3).
9405 let want_split = want_split_knob && num_refs == 1;
9406 let grain_veto = sub8_gveto && grain;
9407 if want_split {
9408 signals::census::bump(signals::census::SUB8_GRAIN, grain_veto);
9409 }
9410 let split_arm = want_split && !grain_veto && sub8_paying;
9411 if fe.sub8x8 && !split_arm {
9412 // Knob OFF: the EXACT legacy pricing, including
9413 // its single `(lme*4.0) as i64` truncation --
9414 // per-quad `lme as i64` truncates 4x and picks
9415 // DIFFERENT candidates (byte-identity bug found
9416 // at review, not by the gate).
9417 let mut c8 = (lme * 4.0) as i64;
9418 let mut p8 = Vec::with_capacity(4);
9419 for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
9420 let (r, mv, c) = fe.best_part(
9421 refs, &sy, &nb, num_refs, lx + qx, ly + qy, 8, 8, &[mv16], lme,
9422 );
9423 c8 += c;
9424 p8.push((r, mv));
9425 }
9426 if shape_rd {
9427 shape_cands.push((3u8, p8.clone(), [0u8; 4]));
9428 }
9429 if c8 < best_c {
9430 best_c = c8;
9431 pick = Some((3u8, p8));
9432 }
9433 } else if fe.sub8x8 {
9434 debug_assert!(split_arm);
9435 let mut c8 = 0i64;
9436 let mut p8: Vec<(i32, (i32, i32))> = Vec::with_capacity(4);
9437 let mut subs = [0u8; 4];
9438 // The all-8x8 arm, kept whatever the SATD search
9439 // picks -- it is the RD probe's other candidate.
9440 let mut p8_flat: Vec<(i32, (i32, i32))> = Vec::with_capacity(4);
9441 let mut c8_flat = 0i64;
9442 let mut hrows: Vec<sub8_harvest::Row> = Vec::new();
9443 for (q, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
9444 let (r, mv, c) = fe.best_part(
9445 refs, &sy, &nb, num_refs, lx + qx, ly + qy, 8, 8, &[mv16], lme,
9446 );
9447 let j8 = c + lme as i64; // + 1 sub_mb_type bin
9448 let mut q_best = j8;
9449 let mut q_parts: Vec<(i32, (i32, i32))> = vec![(r, mv)];
9450 let mut q_st = 0u8;
9451 let mut j_split = i64::MAX; // best split arm, win or lose
9452 {
9453 for st in 1u8..=3 {
9454 let bins = if st == 1 { 2.0 } else { 3.0 };
9455 let mut cs = (lme * bins) as i64;
9456 let mut ps: Vec<(i32, (i32, i32))> = Vec::with_capacity(4);
9457 for &(srx, sry, srw, srh) in sub_mb_partitions_p(st) {
9458 let (rr, mm, cc) = fe.best_part(
9459 refs, &sy, &nb, num_refs,
9460 lx + qx + srx, ly + qy + sry, srw, srh,
9461 &[mv16, mv], lme,
9462 );
9463 cs += cc;
9464 ps.push((rr, mm));
9465 }
9466 j_split = j_split.min(cs);
9467 if cs < q_best {
9468 q_best = cs;
9469 q_parts = ps;
9470 q_st = st;
9471 }
9472 }
9473 }
9474 if sub8_harvest::enabled() {
9475 hrows.push(sub8_harvest::Row {
9476 j8,
9477 jsplit: j_split,
9478 st: q_st,
9479 lme,
9480 mbvar: sig.mb_vars()[mb_idx],
9481 mvdiv: (mv.0 - mv16.0).abs() + (mv.1 - mv16.1).abs(),
9482 });
9483 }
9484 signals::census::bump(
9485 signals::census::SUB8_SPLIT, q_st != 0,
9486 );
9487 c8 += q_best;
9488 c8_flat += j8;
9489 subs[q] = q_st;
9490 p8.extend(q_parts);
9491 p8_flat.push((r, mv));
9492 }
9493 // RD RE-PRICE (probe): the SATD search has picked
9494 // `subs`; ask the CODED macroblock which arm it
9495 // actually prefers. Both arms are planned for real
9496 // (transform+quantize+reconstruct) and scored
9497 // J = SSD_recon + lambda*bits, with the macroblock
9498 // state snapshotted and restored around each trial.
9499 // RD J the surviving split SAVED over all-8x8,
9500 // in lambda units — the census's value unit.
9501 let mut split_gain = 0f64;
9502 if sub8_rd && subs != [0u8; 4] {
9503 let mut snap = enc_scratch::take_snap_a();
9504 fe.save_mb_into(mb_x, mb_y, &mut snap);
9505 let pa = fe.plan_inter_mb(refs, &sy, &su, &sv, mb_x, mb_y, 3, &p8, None, subs);
9506 let ja = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y) as f64
9507 + lam_mb * plan_rate_bits(&pa, subs);
9508 // E11 (11.16a): the restore BETWEEN trials is
9509 // redundant — `plan_inter_mb` fully overwrites
9510 // every MbState field it writes (recon + the
9511 // four P grids), and the rest (nnz, modes,
9512 // cur_qp) move only at EMIT. One final restore
9513 // suffices; byte-identity gated with the RD
9514 // knobs FORCED ON.
9515 let pb = fe.plan_inter_mb(refs, &sy, &su, &sv, mb_x, mb_y, 3, &p8_flat, None, [0u8; 4]);
9516 let jb = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y) as f64
9517 + lam_mb * plan_rate_bits(&pb, [0u8; 4]);
9518 fe.load_mb(mb_x, mb_y, &snap);
9519 enc_scratch::put_snap_a(snap);
9520 signals::census::bump(
9521 signals::census::SUB8_RD_REVERT, jb <= ja,
9522 );
9523 if sub8_regret::enabled() {
9524 // R1 pre-check: keep the MAGNITUDE, which
9525 // the `split_gain` path below discards on
9526 // the revert branch.
9527 sub8_regret::record(
9528 ja, jb, lam_mb,
9529 subs.iter().filter(|&&x| x != 0).count(),
9530 );
9531 }
9532 if jb <= ja {
9533 // The split was a SATD mirage on this MB.
9534 subs = [0u8; 4];
9535 p8 = std::mem::take(&mut p8_flat);
9536 c8 = c8_flat;
9537 } else {
9538 split_gain = (jb - ja).max(0.0) / lam_mb.max(1e-9);
9539 }
9540 sub8_harvest::flush(&hrows, subs != [0u8; 4]);
9541 } else {
9542 sub8_harvest::flush(&hrows, subs != [0u8; 4]);
9543 }
9544 if sub8_minpay > 0 {
9545 sub8_seen += 1;
9546 // Value, not a tally: how much RD J this
9547 // macroblock's surviving split actually saved
9548 // over the all-8x8 arm. Zero when the split
9549 // lost — a searched-and-rejected MB is cost
9550 // with no payoff, which is what we want the
9551 // mean to reflect.
9552 sub8_gain += split_gain;
9553 if sub8_seen >= sub8_learn {
9554 sub8_paying = sub8_gain
9555 >= sub8_seen as f64 * sub8_minpay as f64;
9556 }
9557 }
9558 if shape_rd {
9559 shape_cands.push((3u8, p8.clone(), subs));
9560 }
9561 if c8 < best_c {
9562 best_c = c8;
9563 pick = Some((3u8, p8));
9564 pick_subs = subs;
9565 }
9566 }
9567 }
9568 // SHAPE RD RE-RANK (probe #3). The SATD search above
9569 // has produced `pick`; ask the CODED macroblock to
9570 // re-rank the shapes it actually considered. Each
9571 // candidate is planned for real and scored
9572 // J = SSD_recon + lambda*bits, state restored between
9573 // trials. Candidates are collected as (mode, parts,
9574 // subs) so the sub-8x8 winner competes on equal terms.
9575 if shape_rd
9576 && shape_cands.len() > 1
9577 && !shape_rd_tex_veto
9578 {
9579 let mut snap0 = enc_scratch::take_snap_a();
9580 fe.save_mb_into(mb_x, mb_y, &mut snap0);
9581 let mut best_j = f64::INFINITY;
9582 let mut best_i = 0usize;
9583 for (i, (m, parts, subs)) in shape_cands.iter().enumerate() {
9584 let pl = fe.plan_inter_mb(
9585 refs, &sy, &su, &sv, mb_x, mb_y, *m, parts, None, *subs,
9586 );
9587 let j = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y) as f64
9588 + lam_mb * plan_rate_bits(&pl, *subs);
9589 if j < best_j {
9590 best_j = j;
9591 best_i = i;
9592 }
9593 }
9594 // E11 (11.16a): ONE restore after the loop — each
9595 // candidate's plan fully overwrites the last (see the
9596 // sub8-RD note); the intra trial below then starts
9597 // from the same pre-trial state as before.
9598 fe.load_mb(mb_x, mb_y, &snap0);
9599 enc_scratch::put_snap_a(snap0);
9600 let (m, parts, subs) = shape_cands[best_i].clone();
9601 signals::census::bump(
9602 signals::census::SHAPE_RD_FLIP,
9603 pick.as_ref().map(|p| p.0) != Some(m),
9604 );
9605 pick = Some((m, parts));
9606 pick_subs = subs;
9607 // INTRA COMPETES IN THE SAME CURRENCY. Writing the
9608 // RD J back into `best_c` and letting the SATD
9609 // intra test below read it mixes two scales —
9610 // an SSD+lambda*bits value dwarfs a SATD one, so
9611 // intra won essentially every macroblock and the
9612 // probe measured +17..+56% BD. Caught because a
9613 // better cost function CANNOT lose 50%
9614 // (codec-measurement §7: an impossible number is
9615 // the instrument asking for help). One decision,
9616 // one currency: score intra by RD here and skip
9617 // the SATD comparison entirely.
9618 let (ssd_i, bits_i) =
9619 fe.trial_intra(&sy, &su, &sv, mb_x, mb_y, true);
9620 let j_intra = ssd_i as f64 + lam_mb * bits_i as f64;
9621 shape_rd_intra = Some(j_intra < best_j);
9622 }
9623 // U5-struct: refine ONLY the winning shape (see the twin
9624 // block in the CAVLC driver). This site is the CABAC path —
9625 // which is now the DEFAULT, so omitting it here left sub-pel
9626 // deferred but never refined on every default encode.
9627 if fe.sp_defer.get() {
9628 if let Some((mode, parts)) = pick.as_mut() {
9629 // P3.3: a sub-split winner's regions are its
9630 // SUB-partitions. Skipping the refine for them
9631 // instead would leave exactly those macroblocks
9632 // on INTEGER-pel motion while every other shape
9633 // got sub-pel — the same "deferring DELETES the
9634 // refinement" failure (+91..+145% BD) the
9635 // preset guard above exists to prevent. Inert
9636 // today (sp_defer is off unless set explicitly)
9637 // but a landmine under that knob.
9638 let split_regions: Vec<(usize, usize, usize, usize)> =
9639 if *mode == 3 && pick_subs != [0u8; 4] {
9640 (0..4)
9641 .flat_map(|p8: usize| {
9642 let (bx, by) = ((p8 % 2) * 8, (p8 / 2) * 8);
9643 sub_mb_partitions_p(pick_subs[p8])
9644 .iter()
9645 .map(move |&(sx, sy, sw, sh)| (bx + sx, by + sy, sw, sh))
9646 })
9647 .collect()
9648 } else {
9649 Vec::new()
9650 };
9651 let regions: &[(usize, usize, usize, usize)] = if !split_regions.is_empty() {
9652 &split_regions
9653 } else {
9654 match mode {
9655 1 => &[(0, 0, 16, 8), (0, 8, 16, 8)],
9656 2 => &[(0, 0, 8, 16), (8, 0, 8, 16)],
9657 3 => &[(0, 0, 8, 8), (8, 0, 8, 8), (0, 8, 8, 8), (8, 8, 8, 8)],
9658 _ => &[(0, 0, 16, 16)],
9659 }
9660 };
9661 // sub_mb_type bins, charged exactly as the search did.
9662 let mut tot = if *mode == 3 {
9663 if pick_subs == [0u8; 4] {
9664 (lme * 4.0) as i64
9665 } else {
9666 pick_subs.iter().map(|&st| {
9667 if st == 0 { lme as i64 }
9668 else { (lme * if st == 1 { 2.0 } else { 3.0 }) as i64 }
9669 }).sum()
9670 }
9671 } else { 0 };
9672 for (i, &(qx, qy, pw, ph)) in regions.iter().enumerate() {
9673 let (r, mv) = parts[i];
9674 let (m2, c2) = fe.refine_part(
9675 refs, &sy, &nb, num_refs, lx + qx, ly + qy, pw, ph, lme, r, mv,
9676 );
9677 parts[i] = (r, m2);
9678 tot += c2;
9679 }
9680 best_c = tot;
9681 }
9682 }
9683 let c_intra = fe.best_i16_satd(&sy, mb_x, mb_y)
9684 + (lme * fe.tune_intra_penalty) as i64;
9685 let satd_says_intra = c_intra < best_c;
9686 // GRAIN-GATED (4th consumer of `grain_signature`).
9687 // Measured flip rates: grain 18.4%, screen 12.6%,
9688 // foreman 1.1%, harbour 0.3%, akiyo 0.0% — the SATD
9689 // proxy is essentially RIGHT on natural content and
9690 // badly wrong on noise, so paying 1.71x CPU
9691 // everywhere buys ~0 off grain. Gated: grain wins
9692 // -4.73 PSNR / -5.22 SSIM, everything else is
9693 // byte-identical. screen_text is deliberately NOT
9694 // included: its metrics disagree (+0.53 PSNR /
9695 // -1.48 SSIM), and a split verdict is not a win.
9696 let use_rd = use_rd_frame;
9697 let take_intra = if let Some(rd_says) = shape_rd_intra {
9698 // The shape re-rank already priced intra against the
9699 // winning shape in the RD currency — reuse it rather
9700 // than paying a second trial or mixing scales.
9701 rd_says
9702 } else if use_rd {
9703 // RD arm: plan BOTH candidates for real and compare
9704 // J = SSD_recon + lambda*bits. `trial_intra` restores
9705 // the macroblock, so the loser leaves no trace.
9706 let (ssd_i, bits_i) = fe.trial_intra(&sy, &su, &sv, mb_x, mb_y, true);
9707 let j_intra = ssd_i as f64 + lambda * bits_i as f64;
9708 let j_inter = match pick.as_ref() {
9709 Some((m, parts)) => {
9710 let mut snap = enc_scratch::take_snap_a();
9711 fe.save_mb_into(mb_x, mb_y, &mut snap);
9712 let pl = fe.plan_inter_mb(
9713 refs, &sy, &su, &sv, mb_x, mb_y, *m, parts, None, pick_subs,
9714 );
9715 let j = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y) as f64
9716 + lambda * plan_rate_bits(&pl, pick_subs);
9717 fe.load_mb(mb_x, mb_y, &snap);
9718 enc_scratch::put_snap_a(snap);
9719 j
9720 }
9721 None => f64::INFINITY,
9722 };
9723 j_intra < j_inter
9724 } else {
9725 satd_says_intra
9726 };
9727 if use_rd {
9728 signals::census::bump(
9729 signals::census::INTRA_RD_FLIP,
9730 take_intra != satd_says_intra,
9731 );
9732 }
9733 inter = if take_intra { None } else { pick };
9734 if matches!(inter, Some((3, _))) {
9735 inter_subs = pick_subs; // P3.3: ride with the winner
9736 }
9737 fe.mb_was_skip[mb_idx] = false;
9738 fe.mb_skip_sad[mb_idx] = skip_sad;
9739 }
9740 }
9741 }
9742 // ---- RD P_Skip, CABAC port (Great Gate P3 item 2 —
9743 // docs/gate-ledger.md rdskip-preset-gate) ------------------------
9744 // THRESHOLD form only: the CAVLC driver's trial-encode-and-splice
9745 // arm does not transfer to an arithmetic coder, but its fast gate
9746 // (`SSD(skip) ≤ T·λ` — take the null arm without pricing the coded
9747 // one) is exactly the λ-priced-distortion form the RD B_Skip gate
9748 // already proved under CABAC. Distortion is the skip's
9749 // RECONSTRUCTION SSD (a P_Skip's recon IS its prediction), never
9750 // SAD — the wrong-sign-proxy lesson. Gated on the ONLINE free-skip
9751 // census (engage where free skips are COMMON = temporally
9752 // redundant content, the CAVLC fit's separating signal; the same
9753 // `greedy_*` counters already track it here). `tune_rd_skip` off
9754 // (default) = byte-identical; `fast_t ≤ 0` is inert on this path
9755 // (no full-compare arm exists under CABAC — recorded limitation).
9756 if !did_skip
9757 && fe.rd_skip
9758 && fe.rd_skip_fast_t > 0.0
9759 && inter.is_some()
9760 && greedy_seen >= greedy_learn
9761 && greedy_free * 100 >= greedy_seen * fe.rd_skip_min_free as usize
9762 {
9763 // Fast preset only built the chroma prediction on the free
9764 // path; the RD decision needs the real one.
9765 let skip_cp = if fe.fast {
9766 fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip)
9767 } else {
9768 skip_c.unwrap() // quality preset: always Some
9769 };
9770 let ssd_s = fe.pred_ssd(&sy, &su, &sv, mb_x, mb_y, &skip_y, &skip_cp);
9771 if (ssd_s as f64) <= lambda * fe.rd_skip_fast_t {
9772 fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_cp);
9773 if !fe.fast {
9774 fe.mb_was_skip[mb_idx] = true;
9775 fe.mb_skip_sad[mb_idx] = skip_sad;
9776 }
9777 did_skip = true;
9778 }
9779 }
9780 }
9781
9782 // ---- emit ----
9783 if did_skip {
9784 emit_p_skip_cabac(&mut cab, &mut cs, addr, top, left);
9785 mb_qpy[mb_idx] = fe.cur_qp;
9786 cb_term_acct(&mut cab, mb_idx + 1 == total, acct);
9787 continue;
9788 }
9789 // mb_skip_flag = 0
9790 let sctx = 11
9791 + left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
9792 + top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
9793 let tskip = if acct { cab.pos() } else { 0 };
9794 cb_mb_skip(&mut cab, sctx, false);
9795 if acct {
9796 crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - tskip);
9797 }
9798 cs.mb_skip[addr] = false;
9799 match inter {
9800 Some((mode, parts)) => {
9801 let plan = fe.plan_inter_mb(refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts, None, inter_subs);
9802 // ② residue naming: the CABAC entropy EMIT was untapped on the
9803 // (default) CABAC driver — the whole encoder-side arithmetic
9804 // coder was landing in `mgmt/other`.
9805 let _ge = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
9806 emit_mb_cabac_p_inter(&mut fe, &mut cab, &mut cs, mode, &plan, mb_x, mb_y, num_refs);
9807 signals::census::commit_mb(plan.t8x8);
9808 }
9809 None => {
9810 fe.intra_in_p = true;
9811 let plan = plan_mb(&mut fe, mb_x, mb_y, &sy, &su, &sv);
9812 let _ge = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
9813 let t8 = plan.i8.is_some() && plan.use_i4;
9814 emit_mb_cabac_p_intra(&mut fe, &mut cab, &mut cs, &plan, mb_x, mb_y);
9815 signals::census::commit_mb(t8);
9816 }
9817 }
9818 mb_qpy[mb_idx] = fe.cur_qp;
9819 cb_term_acct(&mut cab, mb_idx + 1 == total, acct);
9820 }
9821 }
9822 enc_scratch::put_cs(cs);
9823
9824 while !w.is_byte_aligned() {
9825 w.write_bit(true);
9826 }
9827 let payload = cab.into_bytes();
9828 w.write_aligned_bytes(&payload);
9829 enc_scratch::put_payload(payload);
9830
9831 // Deblock -> inter reference (same as encode_slice_data).
9832 let mut ref_id = enc_scratch::take_refid();
9833 ref_id.clear();
9834 ref_id.extend(fe.ref_idx_y.iter().map(|&r| if r >= 0 { r } else { i32::MIN }));
9835 let info = rusty_h264_common::deblock::BlockInfo {
9836 inter: &fe.inter_y,
9837 nnz: &fe.nnz_y,
9838 mv: &fe.mv_y,
9839 ref_id: &ref_id,
9840 mv1: &[],
9841 ref_id1: &[],
9842 w4: fe.mb_w * 4,
9843 t8x8: &[],
9844 poc0: &[],
9845 poc1: &[],
9846 bs: &[], kind: &[],
9847 };
9848 rusty_h264_common::deblock::filter_frame(
9849 &mut fe.rec_y, &mut fe.rec_u, &mut fe.rec_v, fe.mb_w, fe.mb_h, &mb_qpy, 0, 0, 0, &info,
9850 );
9851 enc_scratch::put_qpy(mb_qpy);
9852 enc_scratch::put_refid(ref_id);
9853 let w4 = fe.mb_w * 4;
9854 crate::RefFrame {
9855 y: fe.rec_y,
9856 u: fe.rec_u,
9857 v: fe.rec_v,
9858 poc: 0,
9859 frame_num: 0,
9860 mv: fe.mv_y,
9861 ref_idx: fe.ref_idx_y,
9862 mv1: Vec::new(),
9863 ref_idx1: Vec::new(),
9864 w4,
9865 // Filtered lazily on first sub-pel search use (see `RefFrame::hpel`).
9866 hpel: std::sync::OnceLock::new(),
9867 }
9868}
9869
9870// ============================================================================
9871// CABAC B-slice entropy coding — inverse of the decoder's decode_slice_data_cabac
9872// B-slice path. Scope: the modes the encoder's B decision produces — B_Skip,
9873// B_Direct_16x16 (0), B_L0/L1/Bi_16x16 (1/2/3) — no sub_mb_type, no intra-in-B.
9874// The new piece vs P is the dual-list (L0 + L1) mvd/ref neighbour cache.
9875// ============================================================================
9876
9877/// Per-MB slice terminate + bit-accountant tap — the block was a byte-for-byte
9878/// twin at SIX sites across the P and B CABAC drivers (E15 rounds 1-2).
9879#[inline]
9880fn cb_term_acct(cab: &mut CabacEncoder, last: bool, acct: bool) {
9881 let tt = if acct { cab.pos() } else { 0 };
9882 cab.encode_terminate(last);
9883 if acct {
9884 crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
9885 }
9886}
9887
9888/// Fill one list's 30-entry mvd/ref neighbour cache from the per-MB export grids
9889/// (openh264 WelsFillCacheInterCabac). Shared by P (List-0) and B (both lists).
9890fn cb_fill_inter_cache(
9891 mb_ref: &[[i8; 16]],
9892 mb_mvd: &[[[i16; 2]; 16]],
9893 refc: &mut [i8; 30],
9894 mvdc: &mut [[i16; 2]; 30],
9895 top: Option<usize>,
9896 left: Option<usize>,
9897 addr: usize,
9898 mb_w: usize,
9899) {
9900 if let Some(l) = left {
9901 // Row-bind once: the outer runtime index carried a bounds check per
9902 // access (8 in the emitted body); one per array per side suffices.
9903 let (rr, rm) = (&mb_ref[l], &mb_mvd[l]);
9904 for (ci, bi) in [(6usize, 3usize), (12, 7), (18, 11), (24, 15)] {
9905 refc[ci] = rr[bi];
9906 mvdc[ci] = rm[bi];
9907 }
9908 }
9909 if let Some(t) = top {
9910 for (ci, bi) in [(1usize, 12usize), (2, 13), (3, 14), (4, 15)] {
9911 refc[ci] = mb_ref[t][bi];
9912 mvdc[ci] = mb_mvd[t][bi];
9913 }
9914 }
9915 let mb_x = addr % mb_w;
9916 let mb_y = addr / mb_w;
9917 if mb_x > 0 && mb_y > 0 {
9918 let a = addr - mb_w - 1;
9919 (refc[0], mvdc[0]) = (mb_ref[a][15], mb_mvd[a][15]);
9920 }
9921 if mb_y > 0 && mb_x + 1 < mb_w {
9922 let a = addr - mb_w + 1;
9923 (refc[5], mvdc[5]) = (mb_ref[a][12], mb_mvd[a][12]);
9924 }
9925}
9926
9927/// B-slice `mb_type` for the encoder's B modes (0 = B_Direct_16x16, 1 = B_L0_16x16,
9928/// 2 = B_L1_16x16, 3 = B_Bi_16x16) — inverse of `parse_mb_type_b_cabac` (ctx 27).
9929/// B `mb_type` (Table 7-14) for a two-partition macroblock. `p0`/`p1` are 1=L0,
9930/// 2=L1, 3=Bi; `mvmode` 1 = 16x8, 2 = 8x16 (the odd types).
9931pub fn b_part_mb_type(p0: u8, p1: u8, mvmode: u8) -> u32 {
9932 let base = match (p0, p1) {
9933 (1, 1) => 4,
9934 (2, 2) => 6,
9935 (1, 2) => 8,
9936 (2, 1) => 10,
9937 (1, 3) => 12,
9938 (2, 3) => 14,
9939 (3, 1) => 16,
9940 (3, 2) => 18,
9941 _ => 20, // (Bi, Bi)
9942 };
9943 base + if mvmode == 2 { 1 } else { 0 }
9944}
9945
9946/// The two partition rects `(x, y, w, h)` and their z-order block lists for a B
9947/// 16x8 / 8x16 macroblock — the same split the decoder's `b_inter_layout` uses.
9948fn b_part_layout(mvmode: u8) -> ([(usize, usize, usize, usize); 2], [(usize, &'static [usize]); 2]) {
9949 if mvmode == 1 {
9950 ([(0, 0, 16, 8), (0, 8, 16, 8)],
9951 [(0, &[0, 1, 2, 3, 4, 5, 6, 7][..]), (8, &[8, 9, 10, 11, 12, 13, 14, 15][..])])
9952 } else {
9953 ([(0, 0, 8, 16), (8, 0, 8, 16)],
9954 [(0, &[0, 1, 2, 3, 8, 9, 10, 11][..]), (4, &[4, 5, 6, 7, 12, 13, 14, 15][..])])
9955 }
9956}
9957
9958/// B `mb_type` CABAC — the exact inverse of the decoder's `parse_mb_type_b_cabac`
9959/// (ctx base 27). Accepts the FULL spec range 0..=22, not just the four 16x16
9960/// modes, so the B 16x8 / 8x16 / 8x8 partitions become emittable.
9961///
9962/// Binarization, derived from the decoder branch-for-branch:
9963/// ```text
9964/// prefix (non-direct): B+ctx_inc = 1 ; B+3 = 1
9965/// 4-bit m4: B+4 = bit3 ; B+5 = bit2 ; B+5 = bit1 ; B+5 = bit0
9966/// type 3..10 -> m4 = type - 3 (m4 < 8 -> bit3 = 0) 4 bins
9967/// type 11 -> m4 = 14 (B_Bi_8x16) 4 bins
9968/// type 22 -> m4 = 15 (B_8x8) 4 bins
9969/// type 12..21 -> v = type + 4 ; m4 = v >> 1 ; B+5 = v & 1 5 bins
9970/// ```
9971/// The decoder returns `m + 3` for `m < 8`, escapes at 13 (intra) / 14 / 15, and
9972/// otherwise reads a 5th bin and returns `m - 4`; the mapping above reproduces
9973/// every one of those branches. For 0..=3 the value equals the old `dir`, so
9974/// existing call sites are unchanged.
9975pub fn cb_mb_type_b(cab: &mut CabacEncoder, ctx_inc: usize, mb_type: u32) {
9976 const B: usize = 27;
9977 if mb_type == 0 {
9978 cab.encode_decision(B + ctx_inc, 0); // B_Direct_16x16
9979 return;
9980 }
9981 cab.encode_decision(B + ctx_inc, 1);
9982 if mb_type <= 2 {
9983 cab.encode_decision(B + 3, 0);
9984 cab.encode_decision(B + 5, mb_type - 1); // 16x16 L0 / L1
9985 return;
9986 }
9987 cab.encode_decision(B + 3, 1);
9988 let (m4, extra) = if mb_type <= 10 {
9989 (mb_type - 3, None)
9990 } else if mb_type == 11 {
9991 (14, None) // B_Bi_8x16
9992 } else if mb_type == 22 {
9993 (15, None) // B_8x8
9994 } else {
9995 let v = mb_type + 4;
9996 (v >> 1, Some(v & 1))
9997 };
9998 cab.encode_decision(B + 4, (m4 >> 3) & 1);
9999 cab.encode_decision(B + 5, (m4 >> 2) & 1);
10000 cab.encode_decision(B + 5, (m4 >> 1) & 1);
10001 cab.encode_decision(B + 5, m4 & 1);
10002 if let Some(e) = extra {
10003 cab.encode_decision(B + 5, e);
10004 }
10005}
10006
10007const CB_ALL16: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
10008
10009/// Emit one planned INTER B macroblock (mb_skip_flag already coded 0). `dir` is the
10010/// B direction 0/1/2/3; `plan.mvds` holds mvd_l0 then mvd_l1 (per used list).
10011/// B-slice INTRA `mb_type` — the inverse of the decoder's `parse_mb_type_b`
10012/// `m == 13` escape + `parse_intra_mb_type_cabac(cab, 32)`. Prefix bins walk to
10013/// m4 = 0b1101 = 13, then the intra suffix at ctx base 32 (NOT the I-slice
10014/// layout: bin0 has no ctxInc, cbp/chroma/mode bins share base+1..base+3).
10015fn cb_mb_type_b_intra(cab: &mut CabacEncoder, ctx_inc: usize, plan: &MbPlan) {
10016 const B: usize = 27;
10017 cab.encode_decision(B + ctx_inc, 1); // not B_Direct_16x16
10018 cab.encode_decision(B + 3, 1); // not 16x16 L0/L1
10019 cab.encode_decision(B + 4, 1); // m = 1101 = 13 -> intra escape
10020 cab.encode_decision(B + 5, 1);
10021 cab.encode_decision(B + 5, 0);
10022 cab.encode_decision(B + 5, 1);
10023 const O: usize = 32; // intra suffix ctx base (Table 9-37 suffix)
10024 if plan.use_i4 {
10025 cab.encode_decision(O, 0); // I_NxN
10026 return;
10027 }
10028 cab.encode_decision(O, 1);
10029 cab.encode_terminate(false); // not I_PCM
10030 cab.encode_decision(O + 1, plan.i16_cbp15 as u32);
10031 if plan.cbp_chroma != 0 {
10032 cab.encode_decision(O + 2, 1);
10033 cab.encode_decision(O + 2, (plan.cbp_chroma == 2) as u32);
10034 } else {
10035 cab.encode_decision(O + 2, 0);
10036 }
10037 cab.encode_decision(O + 3, (plan.i16_mode as u32 >> 1) & 1);
10038 cab.encode_decision(O + 3, plan.i16_mode as u32 & 1);
10039}
10040
10041fn emit_mb_cabac_b(
10042 fe: &mut FrameEncoder,
10043 cab: &mut CabacEncoder,
10044 cs: &mut CabacState,
10045 dir: u8,
10046 bsplit: Option<(u8, [(u8, (i32, i32), (i32, i32)); 2])>,
10047 plan: &InterPlan,
10048 mb_x: usize,
10049 mb_y: usize,
10050) {
10051 let mb_w = fe.mb_w;
10052 let addr = mb_y * mb_w + mb_x;
10053 let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
10054 let left = if mb_x > 0 { Some(addr - 1) } else { None };
10055
10056 let bci = left.map_or(0, |a| (!cs.mb_direct[a]) as usize)
10057 + top.map_or(0, |a| (!cs.mb_direct[a]) as usize);
10058 let bmt = match bsplit {
10059 Some((mvmode, parts2)) => b_part_mb_type(parts2[0].0, parts2[1].0, mvmode),
10060 None => dir as u32,
10061 };
10062 cb_mb_type_b(cab, bci, bmt);
10063
10064 // Dual-list mvd/ref caches (L0 = mb_ref/mb_mvd, L1 = mb_ref1/mb_mvd1).
10065 let mut mvdc0 = [[0i16; 2]; 30];
10066 let mut refc0 = [-1i8; 30];
10067 let mut mvdc1 = [[0i16; 2]; 30];
10068 let mut refc1 = [-1i8; 30];
10069 cb_fill_inter_cache(&cs.mb_ref, &cs.mb_mvd, &mut refc0, &mut mvdc0, top, left, addr, mb_w);
10070 cb_fill_inter_cache(&cs.mb_ref1, &cs.mb_mvd1, &mut refc1, &mut mvdc1, top, left, addr, mb_w);
10071 let mut mmvd0 = [[0i16; 2]; 16];
10072 let mut mref0 = [-1i8; 16];
10073 let mut mmvd1 = [[0i16; 2]; 16];
10074 let mut mref1 = [-1i8; 16];
10075 let (use0, use1) = (dir == 1 || dir == 3, dir == 2 || dir == 3);
10076 if let Some((mvmode, parts2)) = bsplit {
10077 // Two partitions: mvds arrive LIST-major from the plan (spec 7.3.5.1), and
10078 // `cb_emit_mvd_partition` needs each partition's z-order block list so a
10079 // later macroblock's mvd ctxInc sees the right neighbours. B here runs a
10080 // single L0 and single L1, so num_ref_idx_active is 1 and NO ref_idx is
10081 // coded — only the mvds.
10082 let (_, zb) = b_part_layout(mvmode);
10083 let mut k = 0;
10084 for list in 0..2 {
10085 for part in 0..2 {
10086 let pred = parts2[part].0;
10087 let used = if list == 0 { pred == 1 || pred == 3 } else { pred == 2 || pred == 3 };
10088 if !used {
10089 continue;
10090 }
10091 let (pidx, blocks) = zb[part];
10092 if list == 0 {
10093 cb_emit_mvd_partition(cab, pidx, blocks, &mut mvdc0, &mut refc0, &mut mmvd0, &mut mref0, plan.mvds[k], 0);
10094 } else {
10095 cb_emit_mvd_partition(cab, pidx, blocks, &mut mvdc1, &mut refc1, &mut mmvd1, &mut mref1, plan.mvds[k], 0);
10096 }
10097 k += 1;
10098 }
10099 }
10100 } else if dir == 0 {
10101 // B_Direct_16x16: no coded motion; ref 0 in both lists (mvd stays 0) so a
10102 // later MB's mvd ctxInc sums |0|.
10103 mref0 = [0i8; 16];
10104 mref1 = [0i8; 16];
10105 } else {
10106 // mvd parse order: list-major (L0 then L1); a single 16x16 partition (idx 0).
10107 let mut k = 0;
10108 if use0 {
10109 cb_emit_mvd_partition(cab, 0, &CB_ALL16, &mut mvdc0, &mut refc0, &mut mmvd0, &mut mref0, plan.mvds[k], 0);
10110 k += 1;
10111 }
10112 if use1 {
10113 cb_emit_mvd_partition(cab, 0, &CB_ALL16, &mut mvdc1, &mut refc1, &mut mmvd1, &mut mref1, plan.mvds[k], 0);
10114 }
10115 }
10116 cs.mb_mvd[addr] = mmvd0;
10117 cs.mb_ref[addr] = mref0;
10118 cs.mb_mvd1[addr] = mmvd1;
10119 cs.mb_ref1[addr] = mref1;
10120 cs.mb_direct[addr] = dir == 0 && bsplit.is_none();
10121 cs.cat[addr] = 100;
10122 // The B rule (R6-5), mirroring the decoder's `allow8` and `plan_inter_mb`'s
10123 // `allow_t8`: direct_8x8_inference_flag = 1 in our SPS, so B_Direct_16x16
10124 // partitions count as 8×8 and every shape we emit may carry the flag
10125 // (`bsplit` only produces 16x8 / 8x16, both >= 8x8; no B_8x8 sub-types).
10126 let allow8 = true;
10127 cb_emit_inter_residual(fe, cab, cs, plan, mb_x, mb_y, addr, top, left, allow8);
10128}
10129
10130/// Emit a B_Skip macroblock's mb_skip_flag = 1 (ctx 24 base) + neighbour state. The
10131/// direct motion was committed by `commit_direct_motion`; ref 0 in both lists, mvd 0
10132/// (matching the decoder's decode_b_skip handling).
10133fn emit_b_skip_cabac(cab: &mut CabacEncoder, cs: &mut CabacState, addr: usize, top: Option<usize>, left: Option<usize>) {
10134 let sctx = 24
10135 + left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
10136 + top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
10137 let acct = crate::bitacct::enabled();
10138 let t0 = if acct { cab.pos() } else { 0 };
10139 cb_mb_skip(cab, sctx, true);
10140 if acct {
10141 crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - t0);
10142 }
10143 cs.mb_skip[addr] = true;
10144 cs.cat[addr] = 100;
10145 cs.mb_direct[addr] = true;
10146 cs.mb_ref[addr] = [0i8; 16];
10147 cs.mb_ref1[addr] = [0i8; 16];
10148 cs.last_delta_qp = 0;
10149}
10150
10151/// CABAC B-slice data coder. Mirrors `encode_slice_data_b`'s B_Skip-free check +
10152/// L0/L1/Bi/Direct RD decision verbatim; only the emit differs (per-MB
10153/// mb_skip_flag + CABAC + per-MB terminate). B is non-reference → no deblock/return.
10154#[allow(clippy::too_many_arguments)]
10155/// B-slice mode census (env `RFF_BSTATS=1`), so our B_Skip / B_Direct / coded
10156/// split can be compared directly with x264's `mb B ... direct:N% skip:N%` line.
10157/// Counts only; no effect on the bitstream.
10158/// SEQUENCE-SCOPED sub-pel override, set once by `encode_all`.
10159///
10160/// REINSTATED after D5i. It was reverted on the finding that it "did not land", which
10161/// was WRONG: the acceptance test demanded byte-identity with `--preset fast`, and that
10162/// is unreachable because `Preset::Fast` ALSO sets `rd_skip_min_free` to 60 (mb16.rs
10163/// ~1777) which this flag cannot touch. The veto only ever controlled sub-pel, so it
10164/// must be judged against the same preset with sub-pel off -- which is what it does.
10165pub struct SeqFastPath;
10166static SEQ_FAST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
10167impl SeqFastPath {
10168 pub(crate) fn set(on: bool) -> Self {
10169 SEQ_FAST.store(on, std::sync::atomic::Ordering::Relaxed);
10170 SeqFastPath
10171 }
10172 pub(crate) fn get() -> bool {
10173 SEQ_FAST.load(std::sync::atomic::Ordering::Relaxed)
10174 }
10175}
10176impl Drop for SeqFastPath {
10177 fn drop(&mut self) {
10178 SEQ_FAST.store(false, std::sync::atomic::Ordering::Relaxed);
10179 }
10180}
10181
10182pub mod bstats {
10183 use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
10184 pub static SKIP: AtomicU64 = AtomicU64::new(0);
10185 pub static CODED: AtomicU64 = AtomicU64::new(0);
10186 /// Of the NOT-free macroblocks, how often direct still won the mode decision.
10187 /// B_Skip rides direct-mode motion, so this is the physical quality of the
10188 /// thing the skip is betting on -- the candidate dispatch signal for how hard
10189 /// to push the skip.
10190 pub static DIRWIN: AtomicU64 = AtomicU64::new(0);
10191 /// Of the NOT-free macroblocks, how often a 16×8 / 8×16 partition beat every
10192 /// 16×16 mode. x264 puts 13.5% of its B macroblocks here; this is the column
10193 /// that makes ours comparable.
10194 pub static SPLIT: AtomicU64 = AtomicU64::new(0);
10195 /// Of the NOT-free macroblocks, how often intra beat every inter candidate
10196 /// (the m4 == 13 escape) — scene cuts / occlusion inside a GOP.
10197 pub static INTRA: AtomicU64 = AtomicU64::new(0);
10198 pub fn on() -> bool {
10199 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10200 *E.get_or_init(|| std::env::var_os("RFF_BSTATS").is_some())
10201 }
10202 pub fn bump(c: &AtomicU64) {
10203 if on() {
10204 c.fetch_add(1, Relaxed);
10205 }
10206 }
10207 /// NOTE: this splits B_Skip from everything-else only. It does NOT separate
10208 /// B_Direct_16x16 (chosen inside the coded path) from genuinely coded modes, so
10209 /// it is comparable to x264's `skip:` column but NOT to its `direct:` column.
10210 pub fn dump() {
10211 let (s, c) = (SKIP.load(Relaxed), CODED.load(Relaxed));
10212 let t = (s + c).max(1) as f64;
10213 eprintln!(
10214 "B-slice census: B_Skip {:.1}% not-skipped {:.1}% direct-wins-of-coded {:.1}% 16x8/8x16-of-coded {:.1}% intra-of-coded {:.1}% (n={})",
10215 s as f64 * 100.0 / t, c as f64 * 100.0 / t,
10216 DIRWIN.load(Relaxed) as f64 * 100.0 / c.max(1) as f64,
10217 SPLIT.load(Relaxed) as f64 * 100.0 / c.max(1) as f64,
10218 INTRA.load(Relaxed) as f64 * 100.0 / c.max(1) as f64, s + c
10219 );
10220 }
10221}
10222
10223pub(crate) fn encode_slice_data_cabac_b(
10224 w: &mut BitWriter,
10225 cfg: &EncoderConfig,
10226 frame: &YuvFrame,
10227 qp: u8,
10228 poc: i32,
10229 l0: &crate::RefFrame,
10230 l1: &crate::RefFrame,
10231 qpo: &[i32],
10232 // b-pyramid: a REFERENCE B must produce a deblocked reconstruction for
10233 // the DPB, exactly like a P anchor; a leaf B returns `None` and skips the
10234 // filter (the decoder deblocks for display).
10235 is_ref: bool,
10236) -> Option<crate::RefFrame> {
10237 let mut fe = FrameEncoder::new(cfg);
10238 fe.qp = qp;
10239 fe.qpc = chroma_qp(qp);
10240 fe.cur_qp = qp;
10241 // Recycled per-thread scratch (the P/I coders already pool this exact
10242 // buffer); `clear` + `resize` reproduces the fresh `vec![qp; n]` contents.
10243 let mut mb_qpy = enc_scratch::take_qpy();
10244 mb_qpy.clear();
10245 mb_qpy.resize(cfg.mb_width() * cfg.mb_height(), qp);
10246 if cfg.cabac_dz_div > 0 {
10247 fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
10248 }
10249 // Inter trellis (opt-in, Great Gate P2): B is NON-REFERENCE — the clean
10250 // arm per the structure-adaptive law. 0 = off, byte-identical.
10251 fe.rdoq_strength = cfg.cabac_rdoq_b;
10252 fe.bi_w = implicit_bi_weights(poc, l0.poc, l1.poc);
10253 let (sy, su, sv) = coded_source(cfg, frame);
10254 // Great Gate P1: the shared per-frame signal vector (List-0 anchor as ref).
10255 let sig = FrameSignals::new(&sy, fe.cw, fe.mb_w, fe.mb_h, Some(&l0.y[..]));
10256 apply_screen_t8_veto(&mut fe, &sig);
10257 let lambda = 0.85 * fe.tune_lambda_scale * crate::fastmath::lambda_qp(qp);
10258 // B path keeps the frame-median tex veto even under `tune_lme_q` (its `lme` is
10259 // hoisted, not per-MB) — recorded limitation until the knob clears its BD gate.
10260 let lme_scale = me_lambda_scale(cfg, &sig, false);
10261 let lme = lambda.sqrt() * lme_scale;
10262 let refs = std::slice::from_ref(l0);
10263 if fe.satd_q > 0.0 {
10264 fe.satd_var_thresh = sig.var_percentile_thresh(fe.satd_q);
10265 }
10266 let mut aq_qp = aq_qp_map(&sig, qp, fe.aq_strength);
10267 apply_mbtree_qpo(&mut aq_qp, qpo); // mb-tree temporal AQ (empty = byte-identical)
10268 signals::harvest(
10269 &sig,
10270 'B',
10271 qp,
10272 &signals::GateDecisions {
10273 lme_scale,
10274 satd_thresh: fe.satd_var_thresh,
10275 ..Default::default()
10276 },
10277 );
10278
10279 let mut cab =
10280 CabacEncoder::new_with_out(qp as i32, cfg.cabac_init_idc, false, enc_scratch::take_payload());
10281 let mut cs = CabacState::pooled(fe.mb_w * fe.mb_h);
10282 let total = fe.mb_w * fe.mb_h;
10283 // E15 round 2: slice-constant accountant knob, was re-read up to 10x per MB.
10284 let acct = crate::bitacct::enabled();
10285 // RD B_Skip knobs + the online free-skip census that dispatches it.
10286 let bskip_t = std::env::var("RFF_BSKIP_T").ok().and_then(|v| v.parse::<f64>().ok())
10287 .or(cfg.tune_bskip_rd)
10288 .unwrap_or(0.0);
10289 let bskip_busy_pct = std::env::var("RFF_BSKIP_BUSY").ok().and_then(|v| v.parse::<usize>().ok())
10290 .or(cfg.tune_bskip_busy_pct)
10291 .unwrap_or(60);
10292 let (mut b_seen, mut b_free) = (0usize, 0usize);
10293 // B 16x8/8x16 partition search. Opt-in until the 4-QP per-clip table clears.
10294 let bsplit_env = std::env::var("RFF_BSPLIT").ok().and_then(|v| v.parse::<u32>().ok());
10295 let bsplit_on = bsplit_env.map(|v| v == 1).unwrap_or(cfg.tune_b_split);
10296 let bsplit_probe = bsplit_env.filter(|&v| v >= 2).unwrap_or(0);
10297 // Online DIRECT-WIN rate: of the macroblocks that were not exactly-free, how
10298 // often direct still won the mode decision. B_Skip rides direct-mode motion,
10299 // so this measures the quality of the thing the skip bets on.
10300 let (mut b_coded, mut b_dirwin) = (0usize, 0usize);
10301 let bskip_dirwin_pct = std::env::var("RFF_BSKIP_DIRWIN").ok().and_then(|v| v.parse::<usize>().ok())
10302 .or(cfg.tune_bskip_dirwin_pct)
10303 .unwrap_or(10);
10304
10305 for mb_y in 0..fe.mb_h {
10306 for mb_x in 0..fe.mb_w {
10307 let mb_idx = mb_y * fe.mb_w + mb_x;
10308 let addr = mb_idx;
10309 let top = if mb_y > 0 { Some(addr - fe.mb_w) } else { None };
10310 let left = if mb_x > 0 { Some(addr - 1) } else { None };
10311 let mb_qp = aq_qp[mb_idx];
10312 fe.qp = mb_qp;
10313 fe.qpc = chroma_qp(mb_qp);
10314 let (lx, ly) = (mb_x * 16, mb_y * 16);
10315 let (pbx, pby) = (mb_x as isize * 4, mb_y as isize * 4);
10316 // Dedup (E15 W9): memoized twin, already built when satd_q > 0.
10317 fe.mb_use_satd =
10318 fe.satd_q > 0.0 && sig.mb_vars()[mb_y * fe.mb_w + mb_x] >= fe.satd_var_thresh;
10319 let n0 = fe.mv_neighbors_block_list(pbx, pby, 4, 0);
10320 let n1 = fe.mv_neighbors_block_list(pbx, pby, 4, 1);
10321 let pmv0 = predict_partition_mv(0, 0, n0[0], n0[1], n0[2], 0);
10322 let pmv1 = predict_partition_mv(0, 0, n1[0], n1[1], n1[2], 0);
10323 let (dp, dc, dmotion) = fe.b_direct(l0, l1, mb_x, mb_y);
10324 // B_Skip: free direct prediction → mb_skip_flag = 1.
10325 let free_skip = fe.skip_luma_is_free(&sy, mb_x, mb_y, &dp)
10326 && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &dc);
10327 b_seen += 1;
10328 if free_skip {
10329 b_free += 1;
10330 }
10331 if free_skip {
10332 bstats::bump(&bstats::SKIP);
10333 fe.commit_direct_motion(mb_x, mb_y, &dmotion);
10334 fe.commit_pred_pixels(mb_x, mb_y, &dp, &dc);
10335 emit_b_skip_cabac(&mut cab, &mut cs, addr, top, left);
10336 mb_qpy[mb_idx] = fe.cur_qp;
10337 cb_term_acct(&mut cab, mb_idx + 1 == total, acct);
10338 continue;
10339 }
10340 bstats::bump(&bstats::CODED);
10341 let d_direct = fe.pred_dist(&sy, lx, ly, &dp);
10342 let (mv0, j0) = fe.motion_search(l0, &sy, lx, ly, 16, 16, &[pmv0], lme, None);
10343 let (mv1, j1) = fe.motion_search(l1, &sy, lx, ly, 16, 16, &[pmv1], lme, None);
10344 let d_bi = fe.bi_dist(l0, l1, &sy, lx, ly, mv0, mv1);
10345 let r_bi = mvd_bits(mv0.0 - pmv0.0) + mvd_bits(mv0.1 - pmv0.1)
10346 + mvd_bits(mv1.0 - pmv1.0) + mvd_bits(mv1.1 - pmv1.1);
10347 let j_bi = d_bi + (lme * r_bi as f64) as i64;
10348 let (mut dir, mut best) = (0u8, d_direct);
10349 if j0 < best { dir = 1; best = j0; }
10350 if j1 < best { dir = 2; best = j1; }
10351 if j_bi < best { dir = 3; best = j_bi; }
10352 if dir == 0 { bstats::bump(&bstats::DIRWIN); }
10353 b_coded += 1;
10354 if dir == 0 {
10355 b_dirwin += 1;
10356 }
10357 // ---- RD B_Skip (env RFF_BSKIP_T; unset = byte-identical) ----------
10358 // Our B_Skip previously required the direct residual to quantize to
10359 // EXACTLY zero. Measured against x264 at qp27: that reaches 93.5% of B
10360 // macroblocks on akiyo and 34.5% on foreman -- at or ABOVE x264 -- but
10361 // collapses to 7.8% on mobile where x264 still finds 27.4%. The deficit
10362 // is BUSY-CONTENT-ONLY, a sign flip, so this is a DISPATCH, not a new
10363 // constant: engage only where the free-skip rate is low.
10364 //
10365 // Two terms, both required:
10366 // * `dir == 0` -- direct actually WON the mode decision. `best` starts
10367 // at `d_direct` and only falls, so without this the test would fire
10368 // on macroblocks the search proved are better coded.
10369 // * distortion under T*lambda -- the residual is not worth its bits.
10370 // Gated on the ONLINE free-skip rate of this frame so far (the same
10371 // signal shape the P path's rd_skip uses, inverted: engage where free
10372 // skips are RARE, which is exactly where we under-skip).
10373 if bskip_t > 0.0
10374 && dir == 0
10375 && b_seen >= 32
10376 && b_free * 100 < b_seen * bskip_busy_pct
10377 // DIRECT-WIN FLOOR. Measured truth table at T=48 (BD-PSNR):
10378 // football 7.0% direct-win -> +0.08 LOSS <- the only loser
10379 // foreman 14.1% -> -0.17 win
10380 // bus 21.4% -> -0.05 win
10381 // akiyo 24.7% -> inert
10382 // tempete 30.9% -> -0.16 win
10383 // mobile 43.1% -> -0.38 win
10384 // The one regressing clip has by far the lowest direct-win rate, and
10385 // the term is justified by exactly that clip: where direct almost
10386 // never wins the mode decision, its prediction is unreliable and
10387 // skipping on it costs more than the bits it saves.
10388 && b_coded >= 32
10389 && b_dirwin * 100 >= b_coded * bskip_dirwin_pct
10390 && (d_direct as f64) <= bskip_t * lambda
10391 {
10392 bstats::bump(&bstats::SKIP);
10393 fe.commit_direct_motion(mb_x, mb_y, &dmotion);
10394 fe.commit_pred_pixels(mb_x, mb_y, &dp, &dc);
10395 emit_b_skip_cabac(&mut cab, &mut cs, addr, top, left);
10396 mb_qpy[mb_idx] = fe.cur_qp;
10397 cb_term_acct(&mut cab, mb_idx + 1 == total, acct);
10398 continue;
10399 }
10400 // mb_skip_flag = 0, then the coded B MB.
10401 let sctx = 24
10402 + left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
10403 + top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
10404 let tskip = if acct { cab.pos() } else { 0 };
10405 cb_mb_skip(&mut cab, sctx, false);
10406 if acct {
10407 crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - tskip);
10408 }
10409 cs.mb_skip[addr] = false;
10410 // ---- intra-in-B (the m4 == 13 escape) ------------------------------
10411 // Same SATD heuristic as the P path: a B macroblock the inter search
10412 // cannot predict (scene cut inside the GOP, occlusion) is coded intra
10413 // instead of as a bad inter guess. Compared against the 16x16-level
10414 // winner with the same signalling penalty the P decision charges.
10415 let c_intra = fe.best_i16_satd(&sy, mb_x, mb_y)
10416 + (lme * fe.tune_intra_penalty) as i64;
10417 if c_intra < best {
10418 bstats::bump(&bstats::INTRA);
10419 fe.intra_in_p = true;
10420 let plan = plan_mb(&mut fe, mb_x, mb_y, &sy, &su, &sv);
10421 let bci = left.map_or(0, |a| (!cs.mb_direct[a]) as usize)
10422 + top.map_or(0, |a| (!cs.mb_direct[a]) as usize);
10423 cb_mb_type_b_intra(&mut cab, bci, &plan);
10424 emit_intra_body_cabac(&mut fe, &mut cab, &mut cs, &plan, mb_x, mb_y, addr, top, left);
10425 mb_qpy[mb_idx] = fe.cur_qp;
10426 cb_term_acct(&mut cab, mb_idx + 1 == total, acct);
10427 continue;
10428 }
10429 // ---- B 16x8 / 8x16 partition search --------------------------------
10430 // x264 puts 13.5% of its B macroblocks here (`B16..8: 31.1 13.5 8.2`);
10431 // we had none, which is why the B bucket kept reading as a CODING gap
10432 // after every constant in it had been swept flat. Each half runs the
10433 // SAME 16x16 motion search that already exists, then the 9 (p0,p1)
10434 // pairings are priced against the 16x16 winner.
10435 let mut bsplit: Option<(u8, [(u8, (i32, i32), (i32, i32)); 2])> = None;
10436 // ORACLE PROBE (RFF_BSPLIT=2/3): force a 16x8 (2) or 8x16 (3) whose two
10437 // halves carry the SAME pred and the SAME motion as the 16x16 winner.
10438 // That is semantically identical to the 16x16 macroblock, so the
10439 // reconstruction MUST match it bit for bit -- any quality loss under this
10440 // probe is emit/predict PLUMBING drift and nothing to do with the mode
10441 // decision. (Separating those two is otherwise guesswork: both present as
10442 // "quality fell at the same rate".)
10443 if bsplit_probe > 0 && dir != 0 {
10444 let m = if bsplit_probe == 2 { 1u8 } else { 2u8 };
10445 bsplit = Some((m, [(dir, mv0, mv1); 2]));
10446 } else if bsplit_on {
10447 for mvmode in 1u8..=2 {
10448 let (rects, _) = b_part_layout(mvmode);
10449 let mut cand = [(0u8, (0i32, 0i32), (0i32, 0i32)); 2];
10450 let mut jsum = 0i64;
10451 for (part, &(rx, ry, rw, rh)) in rects.iter().enumerate() {
10452 let (px, py) = (lx + rx, ly + ry);
10453 let (m0, c0) = fe.motion_search(l0, &sy, px, py, rw, rh, &[pmv0], lme, None);
10454 let (m1, c1) = fe.motion_search(l1, &sy, px, py, rw, rh, &[pmv1], lme, None);
10455 // Bi for this rect: blend distortion + both mvd rates.
10456 let dbi = fe.bi_dist_rect(l0, l1, &sy, px, py, rw, rh, m0, m1);
10457 let rbi = mvd_bits(m0.0 - pmv0.0) + mvd_bits(m0.1 - pmv0.1)
10458 + mvd_bits(m1.0 - pmv1.0) + mvd_bits(m1.1 - pmv1.1);
10459 let jbi = dbi + (lme * rbi as f64) as i64;
10460 let (mut bp, mut bj) = (1u8, c0);
10461 if c1 < bj { bp = 2; bj = c1; }
10462 if jbi < bj { bp = 3; bj = jbi; }
10463 cand[part] = (bp, m0, m1);
10464 jsum += bj;
10465 }
10466 // ~4 extra bins for the longer mb_type binarization.
10467 let jsplit = jsum + (lme * 4.0) as i64;
10468 if jsplit < best {
10469 best = jsplit;
10470 bsplit = Some((mvmode, cand));
10471 }
10472 }
10473 if bsplit.is_some() {
10474 bstats::bump(&bstats::SPLIT);
10475 }
10476 }
10477 let bspec = if let Some((mvmode, parts2)) = bsplit {
10478 BInter { dir, l1, mv0, mv1, mvmode, parts2 }
10479 } else {
10480 BInter { dir, l1, mv0, mv1, mvmode: 0, parts2: [(0, (0, 0), (0, 0)); 2] }
10481 };
10482 let plan = fe.plan_inter_mb(refs, &sy, &su, &sv, mb_x, mb_y, 0, &[], Some(bspec), [0u8; 4]);
10483 emit_mb_cabac_b(&mut fe, &mut cab, &mut cs, dir, bsplit, &plan, mb_x, mb_y);
10484 mb_qpy[mb_idx] = fe.cur_qp;
10485 cb_term_acct(&mut cab, mb_idx + 1 == total, acct);
10486 }
10487 }
10488 enc_scratch::put_cs(cs);
10489
10490 while !w.is_byte_aligned() {
10491 w.write_bit(true);
10492 }
10493 let payload = cab.into_bytes();
10494 w.write_aligned_bytes(&payload);
10495 enc_scratch::put_payload(payload);
10496 if !is_ref {
10497 // Leaf B: non-reference — no deblock, no RefFrame (the decoder
10498 // deblocks for display).
10499 enc_scratch::put_qpy(mb_qpy);
10500 return None;
10501 }
10502 // REFERENCE B (b-pyramid): deblock and package the recon exactly like a P
10503 // anchor, but with BOTH lists' motion in the boundary-strength derivation
10504 // (raw per-block indices + single-entry index→POC maps — the two-list bS
10505 // rule compares PICTURES, and an index alone cannot cross lists).
10506 let mut ref_id = enc_scratch::take_refid();
10507 ref_id.clear();
10508 ref_id.extend(fe.ref_idx_y.iter().map(|&r| if r >= 0 { r } else { i32::MIN }));
10509 // List-1 grid from the recycled slot — was a fresh Vec per ref-B slice.
10510 let mut ref_id1 = enc_scratch::take_refid1();
10511 ref_id1.clear();
10512 ref_id1.extend(fe.ref_idx1_y.iter().map(|&r| if r >= 0 { r } else { i32::MIN }));
10513 let poc0 = [l0.poc];
10514 let poc1 = [l1.poc];
10515 let info = rusty_h264_common::deblock::BlockInfo {
10516 inter: &fe.inter_y,
10517 nnz: &fe.nnz_y,
10518 mv: &fe.mv_y,
10519 ref_id: &ref_id,
10520 mv1: &fe.mv1_y,
10521 ref_id1: &ref_id1,
10522 w4: fe.mb_w * 4,
10523 t8x8: &[],
10524 poc0: &poc0,
10525 poc1: &poc1,
10526 bs: &[],
10527 kind: &[],
10528 };
10529 rusty_h264_common::deblock::filter_frame(
10530 &mut fe.rec_y, &mut fe.rec_u, &mut fe.rec_v, fe.mb_w, fe.mb_h, &mb_qpy, 0, 0, 0, &info,
10531 );
10532 enc_scratch::put_refid(ref_id);
10533 enc_scratch::put_refid1(ref_id1);
10534 enc_scratch::put_qpy(mb_qpy);
10535 let w4 = fe.mb_w * 4;
10536 Some(crate::RefFrame {
10537 y: fe.rec_y,
10538 u: fe.rec_u,
10539 v: fe.rec_v,
10540 poc: 0,
10541 frame_num: 0,
10542 mv: fe.mv_y,
10543 ref_idx: fe.ref_idx_y,
10544 mv1: fe.mv1_y,
10545 ref_idx1: fe.ref_idx1_y,
10546 w4,
10547 hpel: std::sync::OnceLock::new(),
10548 })
10549}
10550
10551/// Minimal all-B_Skip CABAC B-slice (the rare no-bracketing-anchor fallback in
10552/// `code_picture`): every MB is mb_skip_flag = 1. B is non-reference so the recon
10553/// is irrelevant; this only needs to be a legal CABAC slice.
10554pub fn encode_all_skip_b_cabac(w: &mut BitWriter, cfg: &EncoderConfig, qp: u8, n: usize) {
10555 let mut cab =
10556 CabacEncoder::new_with_out(qp as i32, cfg.cabac_init_idc, false, enc_scratch::take_payload());
10557 for i in 0..n {
10558 // ctxInc = 24 + (left avail & not-skip) + (top avail & not-skip). Every
10559 // neighbour is either a skip (contributes 0) or unavailable (0) → always 24.
10560 cab.encode_decision(24, 1);
10561 cab.encode_terminate(i + 1 == n);
10562 }
10563 while !w.is_byte_aligned() {
10564 w.write_bit(true);
10565 }
10566 let payload = cab.into_bytes();
10567 w.write_aligned_bytes(&payload);
10568 enc_scratch::put_payload(payload);
10569}