#![allow(dead_code)]
#![allow(clippy::needless_range_loop)]
use super::cost::{LevelCostArray, LevelCosts, RD_DISTO_MULT};
use super::psy::PsyConfig;
use super::quantize::{VP8Matrix, quantdiv, quantization_bias};
use super::tables::{
MAX_LEVEL, MAX_VARIABLE_LEVEL, VP8_LEVEL_FIXED_COSTS, VP8_WEIGHT_TRELLIS, VP8_ZIGZAG,
};
const MAX_COST: i64 = i64::MAX / 2;
#[derive(Clone, Copy, Default)]
struct TrellisNode {
prev: i8, sign: bool, level: i16, }
#[derive(Clone, Copy)]
struct TrellisScoreState<'a> {
score: i64, costs: Option<&'a LevelCostArray>, }
impl Default for TrellisScoreState<'_> {
fn default() -> Self {
Self {
score: MAX_COST,
costs: None,
}
}
}
#[inline]
pub(crate) fn rd_score_trellis(lambda: u32, rate: i64, distortion: i64) -> i64 {
rate * lambda as i64 + (RD_DISTO_MULT as i64) * distortion
}
#[inline]
fn level_cost_with_table(costs: Option<&LevelCostArray>, level: i32) -> u32 {
let abs_level = level.unsigned_abs() as usize;
let fixed = VP8_LEVEL_FIXED_COSTS[abs_level.min(MAX_LEVEL)] as u32;
let variable = match costs {
Some(table) => table[abs_level.min(MAX_VARIABLE_LEVEL)] as u32,
None => 0,
};
fixed + variable
}
#[inline(always)]
fn level_cost_fast(costs: &LevelCostArray, level: usize) -> u32 {
let fixed = VP8_LEVEL_FIXED_COSTS[level] as u32;
let variable = costs[level.min(MAX_VARIABLE_LEVEL)] as u32;
fixed + variable
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::needless_range_loop)] #[allow(private_interfaces)] pub fn trellis_quantize_block(
coeffs: &mut [i32; 16],
out: &mut [i32; 16],
mtx: &VP8Matrix,
lambda: u32,
first: usize,
level_costs: &LevelCosts,
ctype: usize,
ctx0: usize,
psy_config: &PsyConfig,
) -> bool {
const NUM_NODES: usize = 2;
let mut nodes = [[TrellisNode::default(); NUM_NODES]; 16];
let mut score_states = [[TrellisScoreState::default(); NUM_NODES]; 2];
let mut ss_cur_idx = 0usize;
let mut ss_prev_idx = 1usize;
let thresh = (mtx.q[1] as i64 * mtx.q[1] as i64 / 4) as i32;
let mut last = first as i32 - 1;
for n in (first..16).rev() {
let j = VP8_ZIGZAG[n];
let err = coeffs[j] * coeffs[j];
if err > thresh {
last = n as i32;
break;
}
}
if last < 15 {
last += 1;
}
let mut best_path = [-1i32; 3];
let skip_cost = level_costs.get_skip_eob_cost(ctype, first, ctx0) as i64;
let mut best_score = rd_score_trellis(lambda, skip_cost, 0);
let initial_costs = level_costs.get_cost_table(ctype, first, ctx0);
let init_rate = if ctx0 == 0 {
level_costs.get_init_cost(ctype, first, ctx0) as i64
} else {
0
};
let init_score = rd_score_trellis(lambda, init_rate, 0);
for state in &mut score_states[ss_cur_idx][..NUM_NODES] {
*state = TrellisScoreState {
score: init_score,
costs: Some(initial_costs),
};
}
for n in first..=last as usize {
let j = VP8_ZIGZAG[n];
let q = mtx.q[j] as i32;
let iq = mtx.iq[j];
let neutral_bias = quantization_bias(0x00);
let sign = coeffs[j] < 0;
let abs_coeff = if sign { -coeffs[j] } else { coeffs[j] };
let coeff_with_sharpen = abs_coeff + mtx.sharpen[j] as i32;
let level0 = quantdiv(coeff_with_sharpen as u32, iq, neutral_bias).min(MAX_LEVEL as i32);
let thresh_bias = quantization_bias(0x80);
let thresh_level =
quantdiv(coeff_with_sharpen as u32, iq, thresh_bias).min(MAX_LEVEL as i32);
core::mem::swap(&mut ss_cur_idx, &mut ss_prev_idx);
for delta in 0..NUM_NODES {
let node = &mut nodes[n][delta];
let level = level0 + delta as i32;
let ctx = (level as usize).min(2);
let next_costs = if n + 1 < 16 {
Some(level_costs.get_cost_table(ctype, n + 1, ctx))
} else {
None
};
score_states[ss_cur_idx][delta] = TrellisScoreState {
score: MAX_COST,
costs: next_costs,
};
if level < 0 || level > thresh_level {
continue;
}
let new_error = coeff_with_sharpen - level * q;
let orig_error_sq = (coeff_with_sharpen * coeff_with_sharpen) as i64;
let new_error_sq = (new_error * new_error) as i64;
let weight = VP8_WEIGHT_TRELLIS[j] as i64;
let mut delta_distortion = weight * (new_error_sq - orig_error_sq);
if level == 0 && abs_coeff > 0 && psy_config.psy_trellis_strength > 0 {
let is_chroma = ctype == 3;
let below_jnd = if is_chroma {
psy_config.is_below_jnd_uv(j, coeffs[j])
} else {
psy_config.is_below_jnd_y(j, coeffs[j])
};
if !below_jnd {
let energy = abs_coeff as i64;
let psy_weight = psy_config.trellis_weights[j] as i64;
let psy_penalty =
(psy_config.psy_trellis_strength as i64 * psy_weight * energy) >> 16;
delta_distortion += psy_penalty;
}
}
let base_score = rd_score_trellis(lambda, 0, delta_distortion);
let level_usize = level as usize;
let (best_cur_score, best_prev) = {
let ss_prev = &score_states[ss_prev_idx];
let cost0 = if let Some(costs) = ss_prev[0].costs {
level_cost_fast(costs, level_usize) as i64
} else {
VP8_LEVEL_FIXED_COSTS[level_usize] as i64
};
let score0 = ss_prev[0].score + cost0 * lambda as i64;
let cost1 = if let Some(costs) = ss_prev[1].costs {
level_cost_fast(costs, level_usize) as i64
} else {
VP8_LEVEL_FIXED_COSTS[level_usize] as i64
};
let score1 = ss_prev[1].score + cost1 * lambda as i64;
if score1 < score0 {
(score1 + base_score, 1i8)
} else {
(score0 + base_score, 0i8)
}
};
node.sign = sign;
node.level = level as i16;
node.prev = best_prev;
score_states[ss_cur_idx][delta].score = best_cur_score;
if level != 0 && best_cur_score < best_score {
let eob_cost = if n < 15 {
level_costs.get_eob_cost(ctype, n, ctx) as i64
} else {
0
};
let terminal_score = best_cur_score + rd_score_trellis(lambda, eob_cost, 0);
if terminal_score < best_score {
best_score = terminal_score;
best_path[0] = n as i32;
best_path[1] = delta as i32;
best_path[2] = best_prev as i32;
}
}
}
}
if first == 1 {
out[1..].fill(0);
coeffs[1..].fill(0);
} else {
out.fill(0);
coeffs.fill(0);
}
if best_path[0] == -1 {
return false;
}
let mut has_nz = false;
let mut best_node_delta = best_path[1] as usize;
let mut n = best_path[0] as usize;
nodes[n][best_node_delta].prev = best_path[2] as i8;
loop {
let node = &nodes[n][best_node_delta];
let j = VP8_ZIGZAG[n];
let level = if node.sign {
-node.level as i32
} else {
node.level as i32
};
out[n] = level;
has_nz |= level != 0;
coeffs[j] = level * mtx.q[j] as i32;
if n == first {
break;
}
best_node_delta = node.prev as usize;
n -= 1;
}
has_nz
}