/// Motion field estimation (AV1 §7.9.1) and its projection process (§7.9.2):
/// when a frame sets `use_ref_frame_mvs`, the motion vectors saved on the
/// reference frames are projected forward or backward onto an 8x8 luma grid, one
/// grid per reference name, so that later blocks can ask for a temporal
/// candidate at their own grid cell.
///
/// The arithmetic is `av1_mv_projection`; this module is the driver that decides
/// which reference frames are projected, in which direction, and in what order.
///|
/// The estimated grid: one pair of motion vectors per 8x8 luma cell per
/// reference name, with `av1_mv_invalid` marking a cell no projection reached.
pub(all) struct Av1MotionFieldGrid {
refs : Array[Int]
/// `mv_rows[name][y * w8 + x]`, row-major over the 8x8 grid.
mv_rows : Array[Array[Int]]
mv_cols : Array[Array[Int]]
w8 : Int
h8 : Int
}
///|
/// `project_mvs` (§7.9.2) for one source reference name. Returns whether the
/// source was a usable projection source; a `false` result leaves the grid
/// untouched, exactly as the specification requires.
///
/// `order_hint` is the current frame's hint and `order_hints` the per-name hints
/// of the current frame, `ref_frame_idx` maps a name onto its slot, and `refs`
/// is indexed by reference name.
fn av1_project_mvs(
grid : Av1MotionFieldGrid,
sequence : Av1SequenceInfo,
src : Int,
dst_sign : Int,
order_hint : Int,
order_hints : Array[Int],
refs : Array[Av1RefFrame?],
mi_cols : Int,
mi_rows : Int,
) -> Bool {
let source = match av1_inter_reference_by_name(refs, src) {
Some(value) => value
None => return false
}
// A key or intra-only frame has no motion field to project, and a differently
// sized one cannot be addressed on this frame's grid.
if source.mi_rows != mi_rows ||
source.mi_cols != mi_cols ||
source.frame_type == av1_intra_only_frame ||
source.frame_type == av1_key_frame {
return false
}
let ref_to_cur = av1_relative_dist(sequence, order_hints[src], order_hint)
if ref_to_cur > av1_mv_max_frame_distance ||
ref_to_cur < -av1_mv_max_frame_distance {
return false
}
for y8 in 0.. av1_mv_max_frame_distance ||
ref_offset < -av1_mv_max_frame_distance {
continue
}
let mv_row = source.mf_mvs[unit * 2]
let mv_col = source.mf_mvs[unit * 2 + 1]
let projected = av1_mv_projection(
mv_row,
mv_col,
ref_to_cur * dst_sign,
ref_offset,
)
let (pos_x8, pos_y8, ok) = av1_mv_block_position(
x8,
y8,
projected.0,
projected.1,
dst_sign,
mi_cols,
mi_rows,
)
if !ok {
continue
}
// Each destination reference name sees its own projection of the same
// saved vector, because the distances differ.
for dst in grid.refs {
let ref_to_dst = av1_relative_dist(
sequence,
order_hint,
order_hints[dst],
)
let moved = av1_mv_projection(mv_row, mv_col, ref_to_dst, ref_offset)
let slot = dst - av1_last_frame
if slot < 0 || slot >= grid.mv_rows.length() {
continue
}
grid.mv_rows[slot][pos_y8 * grid.w8 + pos_x8] = moved.0
grid.mv_cols[slot][pos_y8 * grid.w8 + pos_x8] = moved.1
}
}
}
true
}
///|
/// The reference frame stored under a name, or `None` when the buffer never
/// filled it.
fn av1_inter_reference_by_name(
refs : Array[Av1RefFrame?],
name : Int,
) -> Av1RefFrame? {
if name < av1_last_frame || name >= refs.length() {
None
} else {
refs[name]
}
}
///|
/// `motion_field_estimation` (§7.9.1). Builds the per-reference 8x8 grids for a
/// frame that sets `use_ref_frame_mvs`.
pub fn av1_motion_field_estimation(
sequence : Av1SequenceInfo,
order_hint : Int,
order_hints : Array[Int],
refs : Array[Av1RefFrame?],
mi_cols : Int,
mi_rows : Int,
) -> Av1MotionFieldGrid {
let w8 = mi_cols >> 1
let h8 = mi_rows >> 1
let names : Array[Int] = []
for name in av1_last_frame..<=av1_alt_ref_frame {
names.push(name)
}
let grid : Av1MotionFieldGrid = {
refs: names,
mv_rows: Array::makei(names.length(), fn(_) {
Array::make(w8 * h8, av1_mv_invalid)
}),
mv_cols: Array::makei(names.length(), fn(_) {
Array::make(w8 * h8, av1_mv_invalid)
}),
w8,
h8,
}
// An ALTREF-of-LAST that is this frame's GOLDEN identifies the overlay
// case: AV1 §7.9.1 excludes LAST's motion field from temporal projection.
// Only the boolean return of project_mvs is discarded; its grid writes are
// the operation's result and must not occur when useLast is false.
match av1_inter_reference_by_name(refs, av1_last_frame) {
Some(last) =>
if last.saved_order_hints.length() > av1_alt_ref_frame &&
last.saved_order_hints[av1_alt_ref_frame] !=
order_hints[av1_golden_frame] {
let _ = av1_project_mvs(
grid, sequence, av1_last_frame, -1, order_hint, order_hints, refs, mi_cols,
mi_rows,
)
}
None => ()
}
let mut stamp = av1_mv_stack_stamp
if av1_relative_dist(sequence, order_hints[av1_bwd_ref_frame], order_hint) > 0 {
if av1_project_mvs(
grid, sequence, av1_bwd_ref_frame, 1, order_hint, order_hints, refs, mi_cols,
mi_rows,
) {
stamp = stamp - 1
}
}
if av1_relative_dist(sequence, order_hints[av1_alt_ref2_frame], order_hint) >
0 {
if av1_project_mvs(
grid, sequence, av1_alt_ref2_frame, 1, order_hint, order_hints, refs, mi_cols,
mi_rows,
) {
stamp = stamp - 1
}
}
if av1_relative_dist(sequence, order_hints[av1_alt_ref_frame], order_hint) > 0 &&
stamp >= 0 {
if av1_project_mvs(
grid, sequence, av1_alt_ref_frame, 1, order_hint, order_hints, refs, mi_cols,
mi_rows,
) {
stamp = stamp - 1
}
}
if stamp >= 0 {
let _ = av1_project_mvs(
grid, sequence, av1_last2_frame, -1, order_hint, order_hints, refs, mi_cols,
mi_rows,
)
}
grid
}