X-Git-Url: https://git.nihav.org/?p=nihav.git;a=blobdiff_plain;f=nihav-duck%2Fsrc%2Fcodecs%2Fvp7enc%2Fframe_coder.rs;fp=nihav-duck%2Fsrc%2Fcodecs%2Fvp7enc%2Fframe_coder.rs;h=080ab95cec4090b13a2c24582d07c815da440268;hp=0000000000000000000000000000000000000000;hb=c5d5793c1fd18882a32acabb8141a221b0a97b61;hpb=b922b48d3b003b2f4b84755541fd9dc4be8f22f6 diff --git a/nihav-duck/src/codecs/vp7enc/frame_coder.rs b/nihav-duck/src/codecs/vp7enc/frame_coder.rs new file mode 100644 index 0000000..080ab95 --- /dev/null +++ b/nihav-duck/src/codecs/vp7enc/frame_coder.rs @@ -0,0 +1,603 @@ +use nihav_core::codecs::*; +use nihav_codec_support::codecs::ZERO_MV; +use super::super::vp78::PredMode; +use super::super::vp78dsp::*; +use super::super::vp7dsp::*; +use super::blocks::*; +use super::coder::*; +use super::mb_coding::*; +use super::models::*; +use super::motion_est::*; +use super::rdo::*; + +const MBT_Q_OFFSET: usize = 3; + +pub struct LoopParams { + pub loop_sharpness: u8, + pub loop_filter_level: u8, + pub lf_simple: bool, +} + +pub struct FrameEncoder { + mb_w: usize, + mb_h: usize, + pub loop_params: LoopParams, + + sblocks: Vec, + res: Vec, + mbtypes: Vec, + recon: Vec, + features: Vec, + has_features: bool, + + pctx: PredContext, + + me_mode: MVSearchMode, + me_range: i16, + mc_buf1: NAVideoBufferRef, + mc_buf2: NAVideoBufferRef, + mv_search_last: Box, + mv_search_gold: Box, +} + +impl FrameEncoder { + pub fn new(mc_buf1: NAVideoBufferRef, mc_buf2: NAVideoBufferRef) -> Self { + let me_mode = MVSearchMode::default(); + + Self { + mb_w: 0, + mb_h: 0, + + sblocks: Vec::new(), + res: Vec::new(), + mbtypes: Vec::new(), + recon: Vec::new(), + features: Vec::new(), + has_features: false, + + pctx: PredContext::new(), + + loop_params: LoopParams { + loop_filter_level: 0, + loop_sharpness: 0, + lf_simple: true, + }, + me_mode, + me_range: 0, + mv_search_last: me_mode.create_search(), + mv_search_gold: me_mode.create_search(), + mc_buf1, mc_buf2, + } + } + pub fn resize(&mut self, mb_w: usize, mb_h: usize) { + self.mb_w = mb_w; + self.mb_h = mb_h; + + self.pctx.resize(mb_w, mb_h); + + self.sblocks.clear(); + self.sblocks.reserve(mb_w * mb_h); + self.res.clear(); + self.res.reserve(mb_w * mb_h); + self.mbtypes.clear(); + self.mbtypes.reserve(mb_w * mb_h); + self.recon.clear(); + self.recon.reserve(mb_w * mb_h); + self.features.clear(); + self.features.reserve(mb_w * mb_h); + } + pub fn set_me_params(&mut self, me_mode: MVSearchMode, me_range: i16, version: u8) { + self.me_range = me_range; + if self.me_mode != me_mode { + self.me_mode = me_mode; + self.mv_search_last = me_mode.create_search(); + self.mv_search_gold = me_mode.create_search(); + } + self.pctx.version = version; + } + pub fn load_frame(&mut self, vbuf: &NAVideoBuffer) { + load_blocks(vbuf, &mut self.sblocks); + } + + pub fn mb_tree_search(&mut self, ref_frm: NAVideoBufferRef, mb_map: &[usize], new_mb_map: &mut [usize], mb_weights: &mut [usize]) { + let mut mv_est = MVEstimator::new(ref_frm, self.mc_buf1.clone(), self.me_range); + self.mv_search_last.preinit(&mv_est); + let mut mb_idx = 0; + new_mb_map.copy_from_slice(mb_map); + for (mb_y, mb_row) in self.sblocks.chunks(self.mb_w).enumerate() { + for (mb_x, blk) in mb_row.iter().enumerate() { + let (mv, _) = self.mv_search_last.search_mb(&mut mv_est, blk, mb_x, mb_y); + + if mv != ZERO_MV { + let new_x = ((((mb_x as isize) * 64 + (mv.x as isize) + 32) >> 6).max(0) as usize).min(self.mb_w - 1); + let new_y = ((((mb_y as isize) * 64 + (mv.y as isize) + 32) >> 6).max(0) as usize).min(self.mb_h - 1); + let nidx = new_x + new_y * self.mb_w; + new_mb_map[mb_idx] = mb_map[nidx]; + } + mb_weights[new_mb_map[mb_idx]] += 1; + mb_idx += 1; + } + } + } + + pub fn intra_blocks(&mut self, base_q: usize, metric: &RateDistMetric, models: &VP7Models, mbt_map: Option<&[usize]>) { + self.mbtypes.clear(); + self.pctx.reset(); + self.pctx.reset_intra(); + self.res.clear(); + self.recon.clear(); + self.features.clear(); + + self.has_features = false; + if base_q > MBT_Q_OFFSET { + if let Some(map) = mbt_map { + let sum: usize = map.iter().sum(); + let size = map.len(); + let avg = (sum + size / 2) / size; + for &val in map.iter() { + if val > avg { + self.features.push(1); + self.has_features = true; + } else { + self.features.push(0); + } + } + } else { + for _ in 0..(self.mb_w * self.mb_h) { + self.features.push(0); + } + } + } + + let mut imctx = IntraModePredCtx { + metric, + models, + tr: [0; 4], + q: base_q, + ipred_y: IPredContext::default(), + ipred_u: IPredContext::default(), + ipred_v: IPredContext::default(), + pctx: BlockPCtx::default(), + }; + + for (mb_y, mb_row) in self.sblocks.chunks_mut(self.mb_w).enumerate() { + imctx.ipred_y.has_top = mb_y != 0; + imctx.ipred_u.has_top = mb_y != 0; + imctx.ipred_v.has_top = mb_y != 0; + + for (mb_x, sblk) in mb_row.iter().enumerate() { + self.pctx.fill_ipred(0, mb_x, &mut imctx.ipred_y); + self.pctx.fill_ipred(1, mb_x, &mut imctx.ipred_u); + self.pctx.fill_ipred(2, mb_x, &mut imctx.ipred_v); + self.pctx.fill_pctx(mb_x, &mut imctx.pctx); + if self.has_features { + imctx.q = if self.features[mb_x + mb_y * self.mb_w] != 0 { + base_q - MBT_Q_OFFSET + } else { + base_q + }; + } + + let mut res = Residue::new(); + let mut newblk = SrcBlock::default(); + + imctx.tr = self.pctx.get_ipred_tr(mb_x); + let mut mb_type = select_intra_mode(sblk, &mut newblk, &mut res, &imctx, MAX_DIST, MBType::InterNoMV(false, [0;4])); + + let use_i4 = match mb_type { + MBType::Intra(best_ymode, best_cmode) => { + sblk.apply_ipred_luma(best_ymode, &imctx.ipred_y, &mut res); + newblk.fill_ipred_luma(best_ymode, &imctx.ipred_y); + sblk.apply_ipred_chroma(best_cmode, &imctx.ipred_u, &imctx.ipred_v, &mut res); + newblk.fill_ipred_chroma(best_cmode, &imctx.ipred_u, &imctx.ipred_v); + res.fdct(); + res.fdct_dc_block(); + + self.pctx.ymodes.set_mode(mb_x, best_ymode); + + false + }, + MBType::Intra4x4(ref i4_modes, ref mut i4ctx, best_cmode) => { + sblk.apply_ipred_chroma(best_cmode, &imctx.ipred_u, &imctx.ipred_v, &mut res); + newblk.fill_ipred_chroma(best_cmode, &imctx.ipred_u, &imctx.ipred_v); + res.fdct(); + + self.pctx.ymodes.set_modes4x4(mb_x, i4_modes, i4ctx); + + true + }, + _ => unreachable!(), + }; + + res.quant(imctx.q); + self.pctx.set_nz(mb_x, &res); + let mut recon = res.clone(); + self.res.push(res); + self.mbtypes.push(mb_type); + + if !use_i4 { + recon.add_residue(&mut newblk); + } else { + recon.add_residue_chroma(&mut newblk); + } + + self.pctx.update_mb(&newblk, mb_x); + self.recon.push(newblk); + } + self.pctx.update_mb_row(); + } + } + pub fn inter_blocks(&mut self, q: usize, metric: &RateDistMetric, models: &VP7Models, last_frame: &NABufferType, gold_frame: &NABufferType) { + self.has_features = false; + + let mut mv_est_last = MVEstimator::new(last_frame.get_vbuf().unwrap(), self.mc_buf1.clone(), self.me_range); + self.mv_search_last.preinit(&mv_est_last); + let mut mv_est_gold = if let Some(gbuf) = gold_frame.get_vbuf() { + let mv_est = MVEstimator::new(gbuf, self.mc_buf2.clone(), self.me_range); + self.mv_search_gold.preinit(&mv_est); + Some(mv_est) + } else { + None + }; + + self.mbtypes.clear(); + self.pctx.reset(); + self.pctx.save_dc_pred(); + self.res.clear(); + self.recon.clear(); + self.features.clear(); + + let mut imctx = IntraModePredCtx { + metric, + models, + tr: [0; 4], + q, + ipred_y: IPredContext::default(), + ipred_u: IPredContext::default(), + ipred_v: IPredContext::default(), + pctx: BlockPCtx::default(), + }; + + for (mb_y, mb_row) in self.sblocks.chunks_mut(self.mb_w).enumerate() { + imctx.ipred_y.has_top = mb_y != 0; + imctx.ipred_u.has_top = mb_y != 0; + imctx.ipred_v.has_top = mb_y != 0; + + for (mb_x, sblk) in mb_row.iter().enumerate() { + self.pctx.fill_ipred(0, mb_x, &mut imctx.ipred_y); + self.pctx.fill_ipred(1, mb_x, &mut imctx.ipred_u); + self.pctx.fill_ipred(2, mb_x, &mut imctx.ipred_v); + self.pctx.fill_pctx(mb_x, &mut imctx.pctx); + + let mut res = Residue::new(); + let mut newblk = SrcBlock::default(); + + let (mvprobs, nearest_mv, near_mv, pred_mv) = self.pctx.find_mv_pred(mb_x, mb_y); + + let (mv, _dist) = self.mv_search_last.search_mb(&mut mv_est_last, sblk, mb_x, mb_y); + + mv_est_last.get_mb(&mut newblk, mb_x, mb_y, mv); + let mv_nits_dist = metric.calc_metric(0, inter_mv_nits(mv, &mvprobs, nearest_mv, near_mv, pred_mv, models)); + let last_dist = calc_inter_mb_dist(sblk, &newblk, &mut res, &imctx, self.pctx.get_y2_dc_pred(true)) + mv_nits_dist; + + let (gmv, gold_dist) = if last_dist > SMALL_DIST { + if let Some(ref mut mv_est) = &mut mv_est_gold { + let (gmv, _gdist) = self.mv_search_gold.search_mb(mv_est, sblk, mb_x, mb_y); + mv_est.get_mb(&mut newblk, mb_x, mb_y, gmv); + let mv_nits_dist = metric.calc_metric(0, inter_mv_nits(gmv, &mvprobs, nearest_mv, near_mv, pred_mv, models)); + let gdist = calc_inter_mb_dist(sblk, &newblk, &mut res, &imctx, self.pctx.get_y2_dc_pred(false)) + mv_nits_dist; + (gmv, gdist) + } else { + (ZERO_MV, MAX_DIST) + } + } else { + (ZERO_MV, MAX_DIST) + }; + + let (last, mut inter_dist, mv, mv_est) = if last_dist < gold_dist { + (true, last_dist, mv, &mut mv_est_last) + } else if let Some (ref mut mv_est) = &mut mv_est_gold { + (false, gold_dist, gmv, mv_est) + } else { + unreachable!() + }; + + let mut mb_type = if mv == ZERO_MV { + MBType::InterNoMV(last, mvprobs) + } else if mv == nearest_mv { + MBType::InterNearest(last, mvprobs) + } else if mv == near_mv { + MBType::InterNear(last, mvprobs) + } else { + MBType::InterMV(last, mvprobs, mv - pred_mv) + }; + if inter_dist > SMALL_DIST { + if let MBType::InterMV(_, _, _) = mb_type { // xxx: maybe do it for all types? + let mv_search = if last { &mut self.mv_search_last } else { &mut self.mv_search_gold }; + if let Some((mbt, dist)) = try_inter_split(sblk, &mut newblk, &mut res, mvprobs, nearest_mv, near_mv, pred_mv, last, mb_x, mb_y, mv_search, mv_est, &mut self.pctx, &imctx, inter_dist) { + mb_type = mbt; + inter_dist = dist; + } + } + } + + if inter_dist > SMALL_DIST { + imctx.tr = self.pctx.get_ipred_tr(mb_x); + mb_type = select_intra_mode(sblk, &mut newblk, &mut res, &imctx, inter_dist, mb_type); + } + + self.mbtypes.push(mb_type); + res.reset(); + match mb_type { + MBType::Intra(ymode, cmode) => { + newblk.fill_ipred_luma(ymode, &imctx.ipred_y); + newblk.fill_ipred_chroma(cmode, &imctx.ipred_u, &imctx.ipred_v); + self.pctx.ymodes.set_mode(mb_x, ymode); + self.pctx.fill_mv(mb_x, mb_y, ZERO_MV); + }, + MBType::Intra4x4(ref i4_modes, ref mut i4ctx, cmode) => { + newblk.fill_ipred_chroma(cmode, &imctx.ipred_u, &imctx.ipred_v); + self.pctx.ymodes.set_modes4x4(mb_x, i4_modes, i4ctx); + self.pctx.fill_mv(mb_x, mb_y, ZERO_MV); + }, + MBType::InterNoMV(_, _) | + MBType::InterNearest(_, _) | + MBType::InterNear(_, _) | + MBType::InterMV(_, _, _) => { + mv_est.get_mb(&mut newblk, mb_x, mb_y, mv); + self.pctx.fill_mv(mb_x, mb_y, mv); + self.pctx.ymodes.set_mode(mb_x, PredMode::Inter); + }, + MBType::InterSplitMV(_, _, _, _, _) => { + self.pctx.ymodes.set_mode(mb_x, PredMode::Inter); + recon_split_mb(&mut newblk, mb_x, mb_y, &self.pctx.mvs, self.pctx.mv_stride, mv_est); + }, + }; + if let MBType::Intra4x4(_, _, _) = mb_type { + res.set_chroma_from_diff(&sblk.chroma, &newblk.chroma); + res.fdct(); + } else { + res.set_luma_from_diff(&sblk.luma, &newblk.luma); + res.set_chroma_from_diff(&sblk.chroma, &newblk.chroma); + res.fdct(); + res.fdct_dc_block(); + if !mb_type.is_intra() { + requant_y2_dc(&mut res.dcs[0], q); + self.pctx.predict_y2_dc(&mut res.dcs[0], last); + } + } + + res.quant(q); + self.pctx.set_nz(mb_x, &res); + let mut recon = res.clone(); + self.res.push(res); + self.features.push(0); + if let MBType::Intra4x4(_, _, _) = mb_type { + recon.add_residue_chroma(&mut newblk); + } else { + recon.add_residue(&mut newblk); + } + self.pctx.update_mb(&newblk, mb_x); + self.recon.push(newblk); + } + self.pctx.update_mb_row(); + } + } + pub fn encode_features(&self, bc: &mut BoolEncoder, q: usize, models: &VP7Models) -> EncoderResult<()> { + if self.has_features { + // first feature - quantiser + bc.put_bool(true, 128)?; + bc.put_byte(models.feature_present[0])?; + for &prob in models.feature_tree_probs[0].iter() { + bc.put_bool(prob != 255, 128)?; + if prob != 255 { + bc.put_byte(prob)?; + } + } + bc.put_bool(true, 128)?; + bc.put_bits((q - MBT_Q_OFFSET) as u32, 7)?; + for _ in 1..4 { + bc.put_bool(false, 128)?; // other quants + } + + // other features ( + for _ in 1..4 { + bc.put_bool(false, 128)?; + } + } else { + for _ in 0..4 { + bc.put_bool(false, 128)?; + } + } + Ok(()) + } + pub fn encode_mb_types(&self, bc: &mut BoolEncoder, is_intra: bool, models: &VP7Models) -> EncoderResult<()> { + for (mb_type, &feature) in self.mbtypes.iter().zip(self.features.iter()) { + if self.has_features { + bc.encode_feature(0, if feature == 0 { None } else { Some(0) }, models)?; + } + bc.encode_mb_type(is_intra, mb_type, models)?; + } + Ok(()) + } + pub fn encode_residues(&mut self, bc: &mut BoolEncoder, models: &VP7Models) -> EncoderResult<()> { + self.pctx.reset(); + //self.pctx.restore_dc_pred(); + for (_mb_y, mb_row) in self.res.chunks(self.mb_w).enumerate() { + for (mb_x, blk) in mb_row.iter().enumerate() { + if blk.has_dc { + let pctx = (self.pctx.nz_y2_left as u8) + (self.pctx.nz_y2_top[mb_x] as u8); + bc.encode_subblock(&blk.dcs, 1, pctx, models)?; + let has_nz = blk.dcs.has_nz(); + self.pctx.nz_y2_left = has_nz; + self.pctx.nz_y2_top[mb_x] = has_nz; + } + let ytype = if blk.has_dc { 0 } else { 3 }; + for (y, blk_row) in blk.luma.chunks(4).enumerate() { + for (x, blk) in blk_row.iter().enumerate() { + let pctx = (self.pctx.nz_y_left[y] as u8) + (self.pctx.nz_y_top[mb_x * 4 + x] as u8); + bc.encode_subblock(blk, ytype, pctx, models)?; + let has_nz = blk.has_nz(); + self.pctx.nz_y_left[y] = has_nz; + self.pctx.nz_y_top[mb_x * 4 + x] = has_nz; + } + } + + for (c, chroma) in blk.chroma.iter().enumerate() { + for (y, blk_row) in chroma.chunks(2).enumerate() { + for (x, blk) in blk_row.iter().enumerate() { + let pctx = (self.pctx.nz_c_left[c][y] as u8) + (self.pctx.nz_c_top[c][mb_x * 2 + x] as u8); + bc.encode_subblock(blk, 2, pctx, models)?; + let has_nz = blk.has_nz(); + self.pctx.nz_c_left[c][y] = has_nz; + self.pctx.nz_c_top[c][mb_x * 2 + x] = has_nz; + } + } + } + } + self.pctx.update_mb_row(); + } + Ok(()) + } + pub fn generate_models(&mut self, is_intra: bool, stats: &mut VP7ModelsStat) { + stats.reset(); + let est = Estimator::new(); + self.pctx.reset(); + if self.has_features { + for &feat in self.features.iter() { + est.estimate_feature(0, if feat == 0 { None } else { Some(0) }, stats); + } + } + for (mbt_row, mb_row) in self.mbtypes.chunks(self.mb_w).zip(self.res.chunks(self.mb_w)) { + for (mb_x, (mbtype, blk)) in mbt_row.iter().zip(mb_row.iter()).enumerate() { + est.estimate_mb_type(is_intra, mbtype, stats); + if blk.has_dc { + let pctx = (self.pctx.nz_y2_left as u8) + (self.pctx.nz_y2_top[mb_x] as u8); + est.estimate_subblock(&blk.dcs, 1, pctx, stats); + let has_nz = blk.dcs.has_nz(); + self.pctx.nz_y2_left = has_nz; + self.pctx.nz_y2_top[mb_x] = has_nz; + } + let ytype = if blk.has_dc { 0 } else { 3 }; + for (y, blk_row) in blk.luma.chunks(4).enumerate() { + for (x, blk) in blk_row.iter().enumerate() { + let pctx = (self.pctx.nz_y_left[y] as u8) + (self.pctx.nz_y_top[mb_x * 4 + x] as u8); + est.estimate_subblock(blk, ytype, pctx, stats); + let has_nz = blk.has_nz(); + self.pctx.nz_y_left[y] = has_nz; + self.pctx.nz_y_top[mb_x * 4 + x] = has_nz; + } + } + + for (c, chroma) in blk.chroma.iter().enumerate() { + for (y, blk_row) in chroma.chunks(2).enumerate() { + for (x, blk) in blk_row.iter().enumerate() { + let pctx = (self.pctx.nz_c_left[c][y] as u8) + (self.pctx.nz_c_top[c][mb_x * 2 + x] as u8); + est.estimate_subblock(blk, 2, pctx, stats); + let has_nz = blk.has_nz(); + self.pctx.nz_c_left[c][y] = has_nz; + self.pctx.nz_c_top[c][mb_x * 2 + x] = has_nz; + } + } + } + } + self.pctx.update_mb_row(); + } + } + pub fn reconstruct_frame(&mut self, frm: &mut NASimpleVideoFrame, is_intra: bool) { + let mut yidx = frm.offset[0]; + let mut uidx = frm.offset[1]; + let mut vidx = frm.offset[2]; + let ystride = frm.stride[0]; + let ustride = frm.stride[1]; + let vstride = frm.stride[2]; + + for (mb_y, (f_row, mb_row)) in self.features.chunks(self.mb_w).zip(self.recon.chunks(self.mb_w)).enumerate() { + for (mb_x, (&feature, sblk)) in f_row.iter().zip(mb_row.iter()).enumerate() { + let dst = &mut frm.data[yidx + mb_x * 16..]; + for (dst, src) in dst.chunks_mut(ystride).zip(sblk.luma.chunks(16)) { + dst[..16].copy_from_slice(src); + } + let dst = &mut frm.data[uidx + mb_x * 8..]; + for (dst, src) in dst.chunks_mut(ustride).zip(sblk.chroma[0].chunks(8)) { + dst[..8].copy_from_slice(src); + } + let dst = &mut frm.data[vidx + mb_x * 8..]; + for (dst, src) in dst.chunks_mut(vstride).zip(sblk.chroma[1].chunks(8)) { + dst[..8].copy_from_slice(src); + } + + let loop_str = if feature != 2 { + self.loop_params.loop_filter_level + } else { 0 }; //todo + loop_filter_mb(frm, mb_x, mb_y, loop_str, &self.loop_params, is_intra); + } + yidx += ystride * 16; + uidx += ustride * 8; + vidx += vstride * 8; + } + } +} + +fn loop_filter_mb(dframe: &mut NASimpleVideoFrame, mb_x: usize, mb_y: usize, loop_str: u8, loop_params: &LoopParams, is_intra: bool) { + const HIGH_EDGE_VAR_THR: [[u8; 64]; 2] = [ + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 + ], [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 + ]]; + + let edge_thr = i16::from(loop_str) + 2; + let luma_thr = i16::from(loop_str); + let chroma_thr = i16::from(loop_str) * 2; + let inner_thr = if loop_params.loop_sharpness == 0 { + i16::from(loop_str) + } else { + let bound1 = i16::from(9 - loop_params.loop_sharpness); + let shift = (loop_params.loop_sharpness + 3) >> 2; + (i16::from(loop_str) >> shift).min(bound1) + }; + let hev_thr = i16::from(HIGH_EDGE_VAR_THR[if is_intra { 1 } else { 0 }][loop_str as usize]); + + let ystride = dframe.stride[0]; + let ustride = dframe.stride[1]; + let vstride = dframe.stride[2]; + let ypos = dframe.offset[0] + mb_x * 16 + mb_y * 16 * ystride; + let upos = dframe.offset[1] + mb_x * 8 + mb_y * 8 * ustride; + let vpos = dframe.offset[2] + mb_x * 8 + mb_y * 8 * vstride; + + let (loop_edge, loop_inner) = if loop_params.lf_simple { + (simple_loop_filter as LoopFilterFunc, simple_loop_filter as LoopFilterFunc) + } else { + (normal_loop_filter_edge as LoopFilterFunc, normal_loop_filter_inner as LoopFilterFunc) + }; + + if mb_x > 0 { + loop_edge(dframe.data, ypos, 1, ystride, 16, edge_thr, inner_thr, hev_thr); + loop_edge(dframe.data, upos, 1, ustride, 8, edge_thr, inner_thr, hev_thr); + loop_edge(dframe.data, vpos, 1, vstride, 8, edge_thr, inner_thr, hev_thr); + } + if mb_y > 0 { + loop_edge(dframe.data, ypos, ystride, 1, 16, edge_thr, inner_thr, hev_thr); + loop_edge(dframe.data, upos, ustride, 1, 8, edge_thr, inner_thr, hev_thr); + loop_edge(dframe.data, vpos, vstride, 1, 8, edge_thr, inner_thr, hev_thr); + } + + for y in 1..4 { + loop_inner(dframe.data, ypos + y * 4 * ystride, ystride, 1, 16, luma_thr, inner_thr, hev_thr); + } + loop_inner(dframe.data, upos + 4 * ustride, ustride, 1, 8, chroma_thr, inner_thr, hev_thr); + loop_inner(dframe.data, vpos + 4 * vstride, vstride, 1, 8, chroma_thr, inner_thr, hev_thr); + + for x in 1..4 { + loop_inner(dframe.data, ypos + x * 4, 1, ystride, 16, luma_thr, inner_thr, hev_thr); + } + loop_inner(dframe.data, upos + 4, 1, ustride, 8, chroma_thr, inner_thr, hev_thr); + loop_inner(dframe.data, vpos + 4, 1, vstride, 8, chroma_thr, inner_thr, hev_thr); +}