RealVideo 4 encoder
[nihav.git] / nihav-realmedia / src / codecs / rv40enc / dsp / loopfilt.rs
diff --git a/nihav-realmedia/src/codecs/rv40enc/dsp/loopfilt.rs b/nihav-realmedia/src/codecs/rv40enc/dsp/loopfilt.rs
new file mode 100644 (file)
index 0000000..5fa25ef
--- /dev/null
@@ -0,0 +1,643 @@
+use nihav_core::frame::NASimpleVideoFrame;
+use super::super::types::DeblockInfo;
+use super::clip8;
+
+const Y_TOP_ROW_MASK:   u32 = 0x000F;
+const Y_BOT_ROW_MASK:   u32 = 0xF000;
+const Y_LEFT_COL_MASK:  u32 = 0x1111;
+const Y_RIGHT_COL_MASK: u32 = 0x8888;
+const C_TOP_ROW_MASK:   u8 = 0x3;
+const C_BOT_ROW_MASK:   u8 = 0xC;
+const C_LEFT_COL_MASK:  u8 = 0x5;
+const C_RIGHT_COL_MASK: u8 = 0xA;
+
+macro_rules! test_bit {
+    ($pat: expr, $x: expr) => ( (($pat >> $x) & 1) != 0 )
+}
+
+pub fn loop_filter_frame(dst: &mut NASimpleVideoFrame<u8>, dblk: &[DeblockInfo], mb_w: usize, mb_h: usize) {
+    let small_frame = dst.width[0] * dst.height[0] <= 176 * 144;
+
+    let mut mb_pos = 0;
+    for mb_y in 0..mb_h {
+        let is_last_row = mb_y == mb_h - 1;
+        let mut left_q: usize = 0;
+        let mut left_cbp_y = 0;
+        let mut left_cbp_c = 0;
+        let mut left_dbk_y = 0;
+
+        for mb_x in 0..mb_w {
+            let q = usize::from(dblk[mb_pos].q);
+            let alpha = RV40_ALPHA_TAB[q];
+            let beta  = RV40_BETA_TAB[q];
+            let beta_y = if small_frame { beta * 4 } else { beta * 3 };
+            let beta_c = beta * 3;
+
+            let is_strong = dblk[mb_pos].is_strong;
+            let top_is_strong = mb_y > 0 && dblk[mb_pos - mb_w].is_strong;
+            let left_is_strong = mb_x > 0 && dblk[mb_pos - 1].is_strong;
+            let bot_is_strong = !is_last_row && dblk[mb_pos + mb_w].is_strong;
+
+            let cur_dbk_y = dblk[mb_pos].deblock_y;
+            let cur_cbp_y = if is_strong { 0xFFFF } else { u32::from(dblk[mb_pos].cbp_y) };
+
+            let (top_cbp_y, top_dbk_y) = if mb_y > 0 {
+                    (if top_is_strong { 0xFFFF } else { u32::from(dblk[mb_pos - mb_w].cbp_y) }, dblk[mb_pos - mb_w].deblock_y)
+                } else {
+                    (0, 0)
+                };
+            let bot_dbk_y = if !is_last_row {
+                    dblk[mb_pos + mb_w].deblock_y
+                } else {
+                    0
+                };
+
+            let y_to_deblock = (cur_dbk_y as u32) | ((bot_dbk_y as u32) << 16);
+            let mut y_h_deblock = y_to_deblock | ((cur_cbp_y << 4) & !Y_TOP_ROW_MASK) | ((top_cbp_y & Y_BOT_ROW_MASK) >> 12);
+            let mut y_v_deblock = y_to_deblock | ((cur_cbp_y << 1) & !Y_LEFT_COL_MASK) | ((left_cbp_y & Y_RIGHT_COL_MASK) >> 3);
+
+            if mb_x == 0 {
+                y_v_deblock &= !Y_LEFT_COL_MASK;
+            }
+            if mb_y == 0 {
+                y_h_deblock &= !Y_TOP_ROW_MASK;
+            }
+            if is_last_row || is_strong || bot_is_strong {
+                y_h_deblock &= !(Y_TOP_ROW_MASK << 16);
+            }
+
+            for y in 0..4 {
+                let yoff = dst.offset[0] + mb_x * 16 + (mb_y * 16 + y * 4) * dst.stride[0];
+                for x in 0..4 {
+                    let bpos = x + y * 4;
+                    let ver_strong = (x == 0) && (mb_x > 0) && (is_strong || left_is_strong);
+
+                    let cur_strength: usize;
+                    if is_strong {
+                        cur_strength = 2;
+                    } else if test_bit!(cur_dbk_y, bpos) {
+                        cur_strength = 1;
+                    } else {
+                        cur_strength = 0;
+                    }
+
+                    let left_strength: usize;
+                    if x > 0 {
+                        if is_strong {
+                            left_strength = 2;
+                        } else if test_bit!(cur_dbk_y, bpos - 1) {
+                            left_strength = 1;
+                        } else {
+                            left_strength = 0;
+                        }
+                    } else if mb_x > 0 {
+                        if left_is_strong {
+                            left_strength = 2;
+                        } else if test_bit!(left_dbk_y, bpos + 3) {
+                            left_strength = 1;
+                        } else {
+                            left_strength = 0;
+                        }
+                    } else {
+                        left_strength = 0;
+                    }
+
+                    let bot_strength: usize;
+                    if y < 3 {
+                        if is_strong {
+                            bot_strength = 2;
+                        } else if test_bit!(cur_dbk_y, bpos + 4) {
+                            bot_strength = 1;
+                        } else {
+                            bot_strength = 0;
+                        }
+                    } else if !is_last_row {
+                        if dblk[mb_pos + mb_w].is_strong {
+                            bot_strength = 2;
+                        } else if test_bit!(bot_dbk_y, x) {
+                            bot_strength = 1;
+                        } else {
+                            bot_strength = 0;
+                        }
+                    } else {
+                        bot_strength = 0;
+                    }
+
+                    let top_strength: usize;
+                    if y > 0 {
+                        if is_strong {
+                            top_strength = 2;
+                        } else if test_bit!(cur_dbk_y, bpos - 4) {
+                            top_strength = 1;
+                        } else {
+                            top_strength = 0;
+                        }
+                    } else if mb_y > 0 {
+                        if top_is_strong {
+                            top_strength = 2;
+                        } else if test_bit!(top_dbk_y, bpos + 12) {
+                            top_strength = 1;
+                        } else {
+                            top_strength = 0;
+                        }
+                    } else {
+                        top_strength = 0;
+                    }
+
+                    let l_q = if x > 0 { q } else { left_q };
+                    let top_q = if mb_y > 0 { usize::from(dblk[mb_pos - mb_w].q) } else { 0 };
+
+                    let lim_cur     = RV40_FILTER_CLIP_TBL [cur_strength][q];
+                    let lim_top     = RV40_FILTER_CLIP_TBL [top_strength][top_q];
+                    let lim_left    = RV40_FILTER_CLIP_TBL[left_strength][l_q];
+                    let lim_bottom  = RV40_FILTER_CLIP_TBL [bot_strength][q];
+
+                    let dmode = if y > 0 { x + y * 4 } else { x * 4 };
+
+                    if test_bit!(y_h_deblock, bpos + 4) {
+                        rv40_loop_filter4_h(dst.data, yoff + 4 * dst.stride[0] + x * 4, dst.stride[0],
+                                            dmode, lim_cur, lim_bottom, alpha, beta, beta_y, false, false);
+                    }
+                    if test_bit!(y_v_deblock, bpos) && !ver_strong {
+                        rv40_loop_filter4_v(dst.data, yoff + x * 4, dst.stride[0],
+                                            dmode, lim_left, lim_cur, alpha, beta, beta_y, false, false);
+                    }
+                    if (y == 0) && test_bit!(y_h_deblock, bpos) && (is_strong || top_is_strong) {
+                        rv40_loop_filter4_h(dst.data, yoff + x * 4, dst.stride[0],
+                                            dmode, lim_top, lim_cur, alpha, beta, beta_y, false, true);
+                    }
+                    if test_bit!(y_v_deblock, bpos) && ver_strong {
+                        rv40_loop_filter4_v(dst.data, yoff + x * 4, dst.stride[0],
+                                            dmode, lim_left, lim_cur, alpha, beta, beta_y, false, true);
+                    }
+                }
+            }
+
+            let cur_cbp_c = dblk[mb_pos].cbp_c;
+            let top_cbp_c = if mb_y > 0 {
+                    if top_is_strong { 0xFF } else { dblk[mb_pos - mb_w].cbp_c }
+                } else {
+                    0
+                };
+            let bot_cbp_c = if !is_last_row {
+                    dblk[mb_pos + mb_w].cbp_c
+                } else {
+                    0
+                };
+            for comp in 1..3 {
+                let cshift = (comp - 1) * 4;
+                let c_cur_cbp  = (cur_cbp_c  >> cshift) & 0xF;
+                let c_top_cbp  = (top_cbp_c  >> cshift) & 0xF;
+                let c_left_cbp = (left_cbp_c >> cshift) & 0xF;
+                let c_bot_cbp  = (bot_cbp_c  >> cshift) & 0xF;
+
+                let c_deblock = c_cur_cbp | (c_bot_cbp << 4);
+                let mut c_v_deblock = c_deblock | ((c_cur_cbp << 1) & !C_LEFT_COL_MASK) | ((c_left_cbp & C_RIGHT_COL_MASK) >> 1);
+                let mut c_h_deblock = c_deblock | ((c_cur_cbp & C_TOP_ROW_MASK) << 2) | ((c_top_cbp & C_BOT_ROW_MASK) >> 2);
+                if mb_x == 0 {
+                    c_v_deblock &= !C_LEFT_COL_MASK;
+                }
+                if mb_y == 0 {
+                    c_h_deblock &= !C_TOP_ROW_MASK;
+                }
+                if is_last_row || is_strong || bot_is_strong {
+                    c_h_deblock &= !(C_TOP_ROW_MASK << 4);
+                }
+
+                for y in 0..2 {
+                    let coff = dst.offset[comp] + mb_x * 8 + (mb_y * 8 + y * 4) * dst.stride[comp];
+                    for x in 0..2 {
+                        let bpos = x + y * 2;
+
+                        let ver_strong = (x == 0) && (is_strong || left_is_strong);
+
+                        let cur_strength: usize;
+                        if is_strong {
+                            cur_strength = 2;
+                        } else if test_bit!(c_cur_cbp, bpos) {
+                            cur_strength = 1;
+                        } else {
+                            cur_strength = 0;
+                        }
+
+                        let left_strength: usize;
+                        if x > 0 {
+                            if is_strong {
+                                left_strength = 2;
+                            } else if test_bit!(c_cur_cbp, bpos - 1) {
+                                left_strength = 1;
+                            } else {
+                                left_strength = 0;
+                            }
+                        } else if mb_x > 0 {
+                            if left_is_strong {
+                                left_strength = 2;
+                            } else if test_bit!(c_left_cbp, bpos + 1) {
+                                left_strength = 1;
+                            } else {
+                                left_strength = 0;
+                            }
+                        } else {
+                            left_strength = 0;
+                        }
+
+                        let bot_strength: usize;
+                        if y != 3 {
+                            if is_strong {
+                                bot_strength = 2;
+                            } else if test_bit!(c_cur_cbp, bpos + 2) {
+                                bot_strength = 1;
+                            } else {
+                                bot_strength = 0;
+                            }
+                        } else if !is_last_row {
+                            if dblk[mb_pos + mb_w].is_strong {
+                                bot_strength = 2;
+                            } else if test_bit!(c_bot_cbp, x) {
+                                bot_strength = 1;
+                            } else {
+                                bot_strength = 0;
+                            }
+                        } else {
+                            bot_strength = 0;
+                        }
+
+                        let top_strength: usize;
+                        if y > 0 {
+                            if is_strong {
+                                top_strength = 2;
+                            } else if test_bit!(c_cur_cbp, bpos - 2) {
+                                top_strength = 1;
+                            } else {
+                                top_strength = 0;
+                            }
+                        } else if mb_y > 0 {
+                            if top_is_strong {
+                                top_strength = 2;
+                            } else if test_bit!(c_top_cbp, bpos + 2) {
+                                top_strength = 1;
+                            } else {
+                                top_strength = 0;
+                            }
+                        } else {
+                            top_strength = 0;
+                        }
+
+                        let l_q = if x > 0 { q } else { left_q };
+                        let top_q = if mb_y > 0 { usize::from(dblk[mb_pos - mb_w].q) } else { 0 };
+
+                        let lim_cur     = RV40_FILTER_CLIP_TBL [cur_strength][q];
+                        let lim_top     = RV40_FILTER_CLIP_TBL [top_strength][top_q];
+                        let lim_left    = RV40_FILTER_CLIP_TBL[left_strength][l_q];
+                        let lim_bottom  = RV40_FILTER_CLIP_TBL [bot_strength][q];
+
+                        if test_bit!(c_h_deblock, bpos + 2) {
+                            rv40_loop_filter4_h(dst.data, coff + 4 * dst.stride[comp] + x * 4, dst.stride[comp],
+                                                x * 8, lim_cur, lim_bottom, alpha, beta, beta_c, true, false);
+                        }
+                        if test_bit!(c_v_deblock, bpos) && !ver_strong {
+                            rv40_loop_filter4_v(dst.data, coff + x * 4, dst.stride[comp],
+                                                y * 8, lim_left, lim_cur, alpha, beta, beta_c, true, false);
+                        }
+                        if (y == 0) && test_bit!(c_h_deblock, bpos) && (is_strong || top_is_strong) {
+                            rv40_loop_filter4_h(dst.data, coff + x * 4, dst.stride[comp],
+                                                x * 8, lim_top, lim_cur, alpha, beta, beta_c, true, true);
+                        }
+                        if test_bit!(c_v_deblock, bpos) && ver_strong {
+                            rv40_loop_filter4_v(dst.data, coff + x * 4, dst.stride[comp],
+                                                y * 8, lim_left, lim_cur, alpha, beta, beta_c, true, true);
+                        }
+                    }
+                }
+            }
+
+            left_q = q;
+            left_dbk_y = cur_dbk_y;
+            left_cbp_y = cur_cbp_y;
+            left_cbp_c = cur_cbp_c;
+
+            mb_pos += 1;
+        }
+    }
+}
+
+macro_rules! el {
+    ($src: ident, $o: expr) => ($src[$o] as i16);
+}
+
+fn clip_symm(a: i16, lim: i16) -> i16 {
+    if a < -lim {
+        -lim
+    } else if a > lim {
+        lim
+    } else {
+        a
+    }
+}
+
+fn rv40_weak_loop_filter4(pix: &mut [u8], mut off: usize, step: usize, stride: usize,
+                          filter_p1: bool, filter_q1: bool, alpha: i16, beta: i16,
+                          lim_p0q0: i16, lim_p1: i16, lim_q1: i16) {
+    for _ in 0..4 {
+        let p0 = el!(pix, off -   step);
+        let q0 = el!(pix, off);
+
+        let t = q0 - p0;
+        if t == 0 {
+            off += stride;
+            continue;
+        }
+
+        let u = (alpha * t.wrapping_abs()) >> 7;
+        if u > (if filter_p1 && filter_q1 { 2 } else { 3 }) {
+            off += stride;
+            continue;
+        }
+
+        let p2 = el!(pix, off - 3*step);
+        let p1 = el!(pix, off - 2*step);
+        let q1 = el!(pix, off +   step);
+        let q2 = el!(pix, off + 2*step);
+
+        let strength;
+        if filter_p1 && filter_q1 {
+            strength = (t << 2) + (p1 - q1);
+        } else {
+            strength = t << 2;
+        }
+
+        let diff = clip_symm((strength + 4) >> 3, lim_p0q0);
+        pix[off - step] = clip8(p0 + diff);
+        pix[off       ] = clip8(q0 - diff);
+
+        if filter_p1 && ((p1 - p2).wrapping_abs() <= beta) {
+            let p1_diff = ((p1 - p0) + (p1 - p2) - diff) >> 1;
+            pix[off - 2*step] = clip8(p1 - clip_symm(p1_diff, lim_p1));
+        }
+
+        if filter_q1 && ((q1 - q2).wrapping_abs() <= beta) {
+            let q1_diff = ((q1 - q0) + (q1 - q2) + diff) >> 1;
+            pix[off + step] = clip8(q1 - clip_symm(q1_diff, lim_q1));
+        }
+
+        off += stride;
+    }
+}
+
+fn rv40_weak_loop_filter4_h(pix: &mut [u8], off: usize, stride: usize,
+                            filter_p1: bool, filter_q1: bool, alpha: i16, beta: i16,
+                            lim_p0q0: i16, lim_p1: i16, lim_q1: i16) {
+    rv40_weak_loop_filter4(pix, off, stride, 1, filter_p1, filter_q1, alpha, beta, lim_p0q0, lim_p1, lim_q1);
+}
+#[allow(clippy::eq_op)]
+fn rv40_weak_loop_filter4_v(pix: &mut [u8], off: usize, stride: usize,
+                            filter_p1: bool, filter_q1: bool, alpha: i16, beta: i16,
+                            lim_p0q0: i16, lim_p1: i16, lim_q1: i16) {
+    let src = &mut pix[off - 3..][..stride * 3 + 3 + 3];
+    for ch in src.chunks_mut(stride).take(4) {
+        assert!(ch.len() >= 3 + 3);
+        let p0 = el!(ch, 3 - 1);
+        let q0 = el!(ch, 3);
+
+        let t = q0 - p0;
+        if t == 0 {
+            continue;
+        }
+
+        let u = (alpha * t.wrapping_abs()) >> 7;
+        if u > (if filter_p1 && filter_q1 { 2 } else { 3 }) {
+            continue;
+        }
+
+        let p2 = el!(ch, 3 - 3);
+        let p1 = el!(ch, 3 - 2);
+        let q1 = el!(ch, 3 + 1);
+        let q2 = el!(ch, 3 + 2);
+
+        let strength;
+        if filter_p1 && filter_q1 {
+            strength = (t << 2) + (p1 - q1);
+        } else {
+            strength = t << 2;
+        }
+
+        let diff = clip_symm((strength + 4) >> 3, lim_p0q0);
+        ch[3 - 1] = clip8(p0 + diff);
+        ch[3    ] = clip8(q0 - diff);
+
+        if filter_p1 && ((p1 - p2).wrapping_abs() <= beta) {
+            let p1_diff = ((p1 - p0) + (p1 - p2) - diff) >> 1;
+            ch[3 - 2] = clip8(p1 - clip_symm(p1_diff, lim_p1));
+        }
+
+        if filter_q1 && ((q1 - q2).wrapping_abs() <= beta) {
+            let q1_diff = ((q1 - q0) + (q1 - q2) + diff) >> 1;
+            ch[3 + 1] = clip8(q1 - clip_symm(q1_diff, lim_q1));
+        }
+    }
+}
+
+#[allow(clippy::many_single_char_names)]
+fn sfilter(a: i16, b: i16, c: i16, d: i16, e: i16, dither: i16, clip: bool, lims: i16) -> i16 {
+    let val = (25 * (a + e) + 26 * (b + c + d) + dither) >> 7;
+    if clip {
+        if val < c - lims {
+            c - lims
+        } else if val > c + lims {
+            c + lims
+        } else {
+            val
+        }
+    } else {
+        val
+    }
+}
+
+fn rv40_strong_loop_filter4(pix: &mut [u8], mut off: usize, step: usize, stride: usize,
+                            alpha: i16, lims: i16, dmode: usize, chroma: bool) {
+    for i in 0..4 {
+        let p0 = el!(pix, off -   step);
+        let q0 = el!(pix, off);
+
+        let t = q0 - p0;
+        if t == 0 {
+            off += stride;
+            continue;
+        }
+
+        let fmode = (alpha * t.wrapping_abs()) >> 7;
+        if fmode > 1 {
+            off += stride;
+            continue;
+        }
+
+        let p3 = el!(pix, off - 4*step);
+        let p2 = el!(pix, off - 3*step);
+        let p1 = el!(pix, off - 2*step);
+        let q1 = el!(pix, off +   step);
+        let q2 = el!(pix, off + 2*step);
+        let q3 = el!(pix, off + 3*step);
+
+        let np0 = sfilter(p2, p1, p0, q0, q1,     RV40_DITHER_L[dmode + i], fmode != 0, lims);
+        let nq0 = sfilter(    p1, p0, q0, q1, q2, RV40_DITHER_R[dmode + i], fmode != 0, lims);
+
+        let np1 = sfilter(p3, p2, p1, np0, q0,              RV40_DITHER_L[dmode + i], fmode != 0, lims);
+        let nq1 = sfilter(             p0, nq0, q1, q2, q3, RV40_DITHER_R[dmode + i], fmode != 0, lims);
+
+        pix[off - 2*step] = np1 as u8;
+        pix[off -   step] = np0 as u8;
+        pix[off]          = nq0 as u8;
+        pix[off +   step] = nq1 as u8;
+
+        if !chroma {
+            let np2 = sfilter(np0, np1, p2, p3, p2, 64, false, 0);
+            let nq2 = sfilter(nq0, nq1, q2, q3, q2, 64, false, 0);
+            pix[off - 3*step] = np2 as u8;
+            pix[off + 2*step] = nq2 as u8;
+        }
+
+        off += stride;
+    }
+}
+
+fn rv40_loop_strength(pix: &[u8], off: usize, step: usize, stride: usize,
+                      beta: i16, beta2: i16, edge: bool) -> (bool, bool, bool) {
+    let mut sum_p1p0 = 0;
+    let mut sum_q1q0 = 0;
+
+    let mut off1 = off;
+    for _ in 0..4 {
+        sum_p1p0 += el!(pix, off1 - 2 * step) - el!(pix, off1 - step);
+        sum_q1q0 += el!(pix, off1 +     step) - el!(pix, off1);
+        off1 += stride;
+    }
+
+    let filter_p1 = sum_p1p0.wrapping_abs() < beta * 4;
+    let filter_q1 = sum_q1q0.wrapping_abs() < beta * 4;
+
+    if (!filter_p1 || !filter_q1) || !edge {
+        return (false, filter_p1, filter_q1);
+    }
+
+    let mut sum_p1p2 = 0;
+    let mut sum_q1q2 = 0;
+
+    let mut off1 = off;
+    for _ in 0..4 {
+        sum_p1p2 += el!(pix, off1 - 2 * step) - el!(pix, off1 - 3 * step);
+        sum_q1q2 += el!(pix, off1 +     step) - el!(pix, off1 + 2 * step);
+        off1 += stride;
+    }
+
+    let strong = (sum_p1p2.wrapping_abs() < beta2) && (sum_q1q2.wrapping_abs() < beta2);
+
+    (strong, filter_p1, filter_q1)
+}
+
+fn rv40_loop_strength_h(pix: &[u8], off: usize, stride: usize,
+                        beta: i16, beta2: i16, edge: bool) -> (bool, bool, bool) {
+    rv40_loop_strength(pix, off, stride, 1, beta, beta2, edge)
+}
+
+#[allow(clippy::eq_op)]
+fn rv40_loop_strength_v(pix: &[u8], off: usize, stride: usize,
+                        beta: i16, beta2: i16, edge: bool) -> (bool, bool, bool) {
+    let src = &pix[off - 3..][..stride * 3 + 3 + 3];
+    let mut sum_p1p0 = 0;
+    let mut sum_q1q0 = 0;
+
+    for ch in src.chunks(stride).take(4) {
+        assert!(ch.len() >= 3 + 3);
+        sum_p1p0 += el!(ch, 3 - 2) - el!(ch, 3 - 1);
+        sum_q1q0 += el!(ch, 3 + 1) - el!(ch, 3);
+    }
+
+    let filter_p1 = sum_p1p0.wrapping_abs() < beta * 4;
+    let filter_q1 = sum_q1q0.wrapping_abs() < beta * 4;
+
+    if (!filter_p1 || !filter_q1) || !edge {
+        return (false, filter_p1, filter_q1);
+    }
+
+    let mut sum_p1p2 = 0;
+    let mut sum_q1q2 = 0;
+
+    for ch in src.chunks(stride).take(4) {
+        assert!(ch.len() >= 3 + 3);
+        sum_p1p2 += el!(ch, 3 - 2) - el!(ch, 3 - 3);
+        sum_q1q2 += el!(ch, 3 + 1) - el!(ch, 3 + 2);
+    }
+
+    let strong = (sum_p1p2.wrapping_abs() < beta2) && (sum_q1q2.wrapping_abs() < beta2);
+
+    (strong, filter_p1, filter_q1)
+}
+
+fn rv40_loop_filter4_h(pix: &mut [u8], off: usize, stride: usize,
+                     dmode: usize, lim_p1: i16, lim_q1: i16, alpha: i16, beta: i16, beta2: i16,
+                     chroma: bool, edge: bool) {
+    let (strong, filter_p1, filter_q1) = rv40_loop_strength_h(pix, off, stride, beta, beta2, edge);
+    let lims = (filter_p1 as i16) + (filter_q1 as i16) + ((lim_p1 + lim_q1) >> 1) + 1;
+
+    if strong {
+        rv40_strong_loop_filter4(pix, off, stride, 1, alpha, lims, dmode, chroma);
+    } else if filter_p1 && filter_q1 {
+        rv40_weak_loop_filter4_h(pix, off, stride, true, true, alpha, beta,
+                                 lims, lim_p1, lim_q1);
+    } else if filter_p1 || filter_q1 {
+        rv40_weak_loop_filter4_h(pix, off, stride, filter_p1, filter_q1, alpha, beta,
+                                 lims >> 1, lim_p1 >> 1, lim_q1 >> 1);
+    }
+}
+
+fn rv40_loop_filter4_v(pix: &mut [u8], off: usize, stride: usize,
+                     dmode: usize, lim_p1: i16, lim_q1: i16, alpha: i16, beta: i16, beta2: i16,
+                     chroma: bool, edge: bool) {
+    let (strong, filter_p1, filter_q1) = rv40_loop_strength_v(pix, off, stride, beta, beta2, edge);
+    let lims = (filter_p1 as i16) + (filter_q1 as i16) + ((lim_p1 + lim_q1) >> 1) + 1;
+
+    if strong {
+        rv40_strong_loop_filter4(pix, off, 1, stride, alpha, lims, dmode, chroma);
+    } else if filter_p1 && filter_q1 {
+        rv40_weak_loop_filter4_v(pix, off, stride, true, true, alpha, beta,
+                                 lims, lim_p1, lim_q1);
+    } else if filter_p1 || filter_q1 {
+        rv40_weak_loop_filter4_v(pix, off, stride, filter_p1, filter_q1, alpha, beta,
+                                 lims >> 1, lim_p1 >> 1, lim_q1 >> 1);
+    }
+}
+
+const RV40_DITHER_L: [i16; 16] = [
+    0x40, 0x50, 0x20, 0x60, 0x30, 0x50, 0x40, 0x30,
+    0x50, 0x40, 0x50, 0x30, 0x60, 0x20, 0x50, 0x40
+];
+const RV40_DITHER_R: [i16; 16] = [
+    0x40, 0x30, 0x60, 0x20, 0x50, 0x30, 0x30, 0x40,
+    0x40, 0x40, 0x50, 0x30, 0x20, 0x60, 0x30, 0x40
+];
+
+const RV40_ALPHA_TAB: [i16; 32] = [
+    128, 128, 128, 128, 128, 128, 128, 128,
+    128, 128, 122,  96,  75,  59,  47,  37,
+     29,  23,  18,  15,  13,  11,  10,   9,
+      8,   7,   6,   5,   4,   3,   2,   1
+];
+
+const RV40_BETA_TAB: [i16; 32] = [
+     0,  0,  0,  0,  0,  0,  0,  0,  3,  3,  3,  4,  4,  4,  6,  6,
+     6,  7,  8,  8,  9,  9, 10, 10, 11, 11, 12, 13, 14, 15, 16, 17
+];
+
+const RV40_FILTER_CLIP_TBL: [[i16; 32]; 3] = [
+  [
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 2, 2, 2, 2, 3, 3, 3, 3, 4, 5, 5
+  ], [
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
+    1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 7, 8, 9
+  ]
+];
+