Make lpf_sb work with loopfilter_level

Make lpf_sb compatible with loopfilter_level, when USE_GUESS_LEVEL = 1.

Filter levels will be selected based on q index and applied for
filtering on Y, U, V planes separately.

Current model only allows to guess one filter level.
Now Y_vert = Y_horz = U = V. In the future, we need to retrain the
model and get filter levels for Y_vert, Y_horz, U and V separately.

When USE_GUESS_LEVEL = 0, lpf_sb can't work with loopfilter_level yet.

Change-Id: Icd774a147c07a4035cf8204a8754b2a99668bbfd
diff --git a/av1/common/av1_loopfilter.c b/av1/common/av1_loopfilter.c
index d563aaf..43941cf 100644
--- a/av1/common/av1_loopfilter.c
+++ b/av1/common/av1_loopfilter.c
@@ -350,8 +350,13 @@
 #endif
                                 const MB_MODE_INFO *mbmi) {
 #if CONFIG_LPF_SB
+#if CONFIG_LOOPFILTER_LEVEL
+  const int lvl_idx = plane == 0 ? dir_idx : plane + 1;
+  return cm->mi[mi_row * cm->mi_stride + mi_col].mbmi.filt_lvl[lvl_idx];
+#else
   return cm->mi[mi_row * cm->mi_stride + mi_col].mbmi.filt_lvl;
 #endif
+#endif
 
   const int segment_id = mbmi->segment_id;
   if (cm->delta_lf_present_flag) {
@@ -429,6 +434,9 @@
 
 #if CONFIG_LPF_SB
 void av1_loop_filter_sb_level_init(AV1_COMMON *cm, int mi_row, int mi_col,
+#if CONFIG_LOOPFILTER_LEVEL
+                                   int plane, int dir,
+#endif
                                    int lvl) {
   const int mi_row_start = AOMMAX(0, mi_row - FILT_BOUNDARY_MI_OFFSET);
   const int mi_col_start = AOMMAX(0, mi_col - FILT_BOUNDARY_MI_OFFSET);
@@ -440,9 +448,14 @@
   int row, col;
   for (row = mi_row_start; row < mi_row_end; ++row) {
     for (col = mi_col_start; col < mi_col_end; ++col) {
-      // Note: can't use cm->mi_grid_visible. Because for each partition,
-      // all visible pointers will point to the first of the partition.
+// Note: can't use cm->mi_grid_visible. Because for each partition,
+// all visible pointers will point to the first of the partition.
+#if CONFIG_LOOPFILTER_LEVEL
+      const int lvl_idx = plane == 0 ? dir : plane + 1;
+      cm->mi[row * cm->mi_stride + col].mbmi.filt_lvl[lvl_idx] = lvl;
+#else
       cm->mi[row * cm->mi_stride + col].mbmi.filt_lvl = lvl;
+#endif  // CONFIG_LOOPFILTER_LEVEL
     }
   }
 }
@@ -939,7 +952,11 @@
       txsize_vert_map[uv_txsize_lookup[block_size][mbmi->tx_size][1][1]];
 #if CONFIG_EXT_DELTA_Q
 #if CONFIG_LOOPFILTER_LEVEL
+#if CONFIG_LPF_SB
+  const int filter_level = get_filter_level(cm, lfi_n, 0, 0, 0, 0, mbmi);
+#else
   const int filter_level = get_filter_level(cm, lfi_n, 0, 0, mbmi);
+#endif
 #else
 #if CONFIG_LPF_SB
   const int filter_level = get_filter_level(cm, lfi_n, 0, 0, mbmi);
@@ -1033,7 +1050,11 @@
   const BLOCK_SIZE block_size = mbmi->sb_type;
 #if CONFIG_EXT_DELTA_Q
 #if CONFIG_LOOPFILTER_LEVEL
+#if CONFIG_LPF_SB
+  const int filter_level = get_filter_level(cm, lfi_n, 0, 0, 0, 0, mbmi);
+#else
   const int filter_level = get_filter_level(cm, lfi_n, 0, 0, mbmi);
+#endif
 #else
 #if CONFIG_LPF_SB
   const int filter_level = get_filter_level(cm, lfi_n, 0, 0, mbmi);
@@ -1507,8 +1528,13 @@
 // Filter level can vary per MI
 #if CONFIG_EXT_DELTA_Q
 #if CONFIG_LOOPFILTER_LEVEL
+#if CONFIG_LPF_SB
+    if (!(lfl_r[c_step] = get_filter_level(cm, &cm->lf_info, 0, 0, 0, 0, mbmi)))
+      continue;
+#else
     if (!(lfl_r[c_step] = get_filter_level(cm, &cm->lf_info, 0, 0, mbmi)))
       continue;
+#endif
 #else
 #if CONFIG_LPF_SB
     if (!(lfl_r[c_step] =
@@ -2169,8 +2195,13 @@
 
 #if CONFIG_EXT_DELTA_Q
 #if CONFIG_LOOPFILTER_LEVEL
+#if CONFIG_LPF_SB
+    const uint32_t curr_level = get_filter_level(cm, &cm->lf_info, edge_dir,
+                                                 plane, mi_row, mi_col, mbmi);
+#else
     const uint32_t curr_level =
         get_filter_level(cm, &cm->lf_info, edge_dir, plane, mbmi);
+#endif
 #else
 #if CONFIG_LPF_SB
     const uint32_t curr_level =
@@ -2211,8 +2242,14 @@
 
 #if CONFIG_EXT_DELTA_Q
 #if CONFIG_LOOPFILTER_LEVEL
+#if CONFIG_LPF_SB
+          const uint32_t pv_lvl =
+              get_filter_level(cm, &cm->lf_info, edge_dir, plane, mi_row,
+                               mi_col, &mi_prev->mbmi);
+#else
           const uint32_t pv_lvl = get_filter_level(cm, &cm->lf_info, edge_dir,
                                                    plane, &mi_prev->mbmi);
+#endif
 #else
 #if CONFIG_LPF_SB
           const uint32_t pv_lvl = get_filter_level(cm, &cm->lf_info, pv_row,
@@ -2620,8 +2657,12 @@
 #endif
 
 #if CONFIG_LPF_SB
-  if (partial_frame && !frame_filter_level) return;
+#if CONFIG_LOOPFILTER_LEVEL
+  if (partial_frame && !frame_filter_level && !frame_filter_level_r) return;
 #else
+  if (partial_frame && !frame_filter_level) return;
+#endif  // CONFIG_LOOPFILTER_LEVEL
+#else   // !CONFIG_LPF_SB
 #if CONFIG_LOOPFILTER_LEVEL
   if (!frame_filter_level && !frame_filter_level_r) return;
 #else
@@ -2645,7 +2686,14 @@
     end_mi_row = AOMMIN(mi_row_range, cm->mi_rows);
     end_mi_col = AOMMIN(mi_col_range, cm->mi_cols);
 
+#if CONFIG_LOOPFILTER_LEVEL
+    av1_loop_filter_sb_level_init(cm, mi_row, mi_col, y_only, 0,
+                                  frame_filter_level);
+    av1_loop_filter_sb_level_init(cm, mi_row, mi_col, y_only, 1,
+                                  frame_filter_level_r);
+#else
     av1_loop_filter_sb_level_init(cm, mi_row, mi_col, frame_filter_level);
+#endif
   } else {
     start_mi_row = 0;
     mi_rows_to_filter = cm->mi_rows;
diff --git a/av1/common/av1_loopfilter.h b/av1/common/av1_loopfilter.h
index 75dda7d..78305ec 100644
--- a/av1/common/av1_loopfilter.h
+++ b/av1/common/av1_loopfilter.h
@@ -148,6 +148,9 @@
 #if CONFIG_LPF_SB
 void av1_loop_filter_frame(YV12_BUFFER_CONFIG *frame, struct AV1Common *cm,
                            struct macroblockd *mbd, int filter_level,
+#if CONFIG_LOOPFILTER_LEVEL
+                           int frame_filter_level_r,
+#endif
                            int y_only, int partial_frame, int mi_row,
                            int mi_col);
 
@@ -158,6 +161,9 @@
                           int col_start, int col_end, int y_only);
 
 void av1_loop_filter_sb_level_init(struct AV1Common *cm, int mi_row, int mi_col,
+#if CONFIG_LOOPFILTER_LEVEL
+                                   int plane, int dir,
+#endif
                                    int lvl);
 #else
 void av1_loop_filter_frame(YV12_BUFFER_CONFIG *frame, struct AV1Common *cm,
diff --git a/av1/common/blockd.h b/av1/common/blockd.h
index 1cd667a..b2465ea 100644
--- a/av1/common/blockd.h
+++ b/av1/common/blockd.h
@@ -356,11 +356,19 @@
 
   BOUNDARY_TYPE boundary_info;
 #if CONFIG_LPF_SB
+#if CONFIG_LOOPFILTER_LEVEL
+  // 0: y plane vert, 1: y plane horz, 2: u plane, 3: v plane
+  uint8_t filt_lvl[4];
+  int reuse_sb_lvl[4];
+  int sign[4];
+  int delta[4];
+#else
   uint8_t filt_lvl;
   int reuse_sb_lvl;
   int sign;
   int delta;
-#endif
+#endif  // CONFIG_LOOPFILTER_LEVEL
+#endif  // CONFIG_LPF_SB
 
 #if CONFIG_JNT_COMP
   int compound_idx;
diff --git a/av1/common/entropymode.c b/av1/common/entropymode.c
index f211b9d..47c22b8 100644
--- a/av1/common/entropymode.c
+++ b/av1/common/entropymode.c
@@ -2829,6 +2829,64 @@
 #endif  // CONFIG_KF_CTX
 
 #if CONFIG_LPF_SB
+#if CONFIG_LOOPFILTER_LEVEL
+static const aom_cdf_prob
+    default_lpf_reuse_cdf[4][LPF_REUSE_CONTEXT][CDF_SIZE(2)] = {
+      { { AOM_CDF2(4259) }, { AOM_CDF2(728) } },
+      { { AOM_CDF2(4259) }, { AOM_CDF2(728) } },
+      { { AOM_CDF2(4259) }, { AOM_CDF2(728) } },
+      { { AOM_CDF2(4259) }, { AOM_CDF2(728) } },
+    };
+
+static const aom_cdf_prob
+    default_lpf_delta_cdf[4][LPF_DELTA_CONTEXT][CDF_SIZE(DELTA_RANGE)] = {
+      { { AOM_CDF8(100, 688, 2128, 4642, 7895, 11851, 17050) },
+        { AOM_CDF8(100, 1291, 4358, 7425, 10654, 13559, 18563) },
+        { AOM_CDF8(100, 1086, 4982, 9134, 13031, 16991, 23123) },
+        { AOM_CDF8(100, 1068, 3395, 7973, 12512, 17967, 22812) },
+        { AOM_CDF8(100, 442, 2809, 7178, 12535, 17450, 22417) },
+        { AOM_CDF8(100, 561, 2246, 6050, 11103, 16592, 21353) },
+        { AOM_CDF8(100, 345, 2399, 5559, 9682, 13992, 20126) },
+        { AOM_CDF8(100, 337, 1540, 3573, 6438, 10196, 16320) } },
+      { { AOM_CDF8(100, 688, 2128, 4642, 7895, 11851, 17050) },
+        { AOM_CDF8(100, 1291, 4358, 7425, 10654, 13559, 18563) },
+        { AOM_CDF8(100, 1086, 4982, 9134, 13031, 16991, 23123) },
+        { AOM_CDF8(100, 1068, 3395, 7973, 12512, 17967, 22812) },
+        { AOM_CDF8(100, 442, 2809, 7178, 12535, 17450, 22417) },
+        { AOM_CDF8(100, 561, 2246, 6050, 11103, 16592, 21353) },
+        { AOM_CDF8(100, 345, 2399, 5559, 9682, 13992, 20126) },
+        { AOM_CDF8(100, 337, 1540, 3573, 6438, 10196, 16320) } },
+      { { AOM_CDF8(100, 688, 2128, 4642, 7895, 11851, 17050) },
+        { AOM_CDF8(100, 1291, 4358, 7425, 10654, 13559, 18563) },
+        { AOM_CDF8(100, 1086, 4982, 9134, 13031, 16991, 23123) },
+        { AOM_CDF8(100, 1068, 3395, 7973, 12512, 17967, 22812) },
+        { AOM_CDF8(100, 442, 2809, 7178, 12535, 17450, 22417) },
+        { AOM_CDF8(100, 561, 2246, 6050, 11103, 16592, 21353) },
+        { AOM_CDF8(100, 345, 2399, 5559, 9682, 13992, 20126) },
+        { AOM_CDF8(100, 337, 1540, 3573, 6438, 10196, 16320) } },
+      { { AOM_CDF8(100, 688, 2128, 4642, 7895, 11851, 17050) },
+        { AOM_CDF8(100, 1291, 4358, 7425, 10654, 13559, 18563) },
+        { AOM_CDF8(100, 1086, 4982, 9134, 13031, 16991, 23123) },
+        { AOM_CDF8(100, 1068, 3395, 7973, 12512, 17967, 22812) },
+        { AOM_CDF8(100, 442, 2809, 7178, 12535, 17450, 22417) },
+        { AOM_CDF8(100, 561, 2246, 6050, 11103, 16592, 21353) },
+        { AOM_CDF8(100, 345, 2399, 5559, 9682, 13992, 20126) },
+        { AOM_CDF8(100, 337, 1540, 3573, 6438, 10196, 16320) } },
+    };
+
+static const aom_cdf_prob
+    default_lpf_sign_cdf[4][LPF_REUSE_CONTEXT][LPF_SIGN_CONTEXT][CDF_SIZE(2)] =
+        {
+          { { { AOM_CDF2(100) }, { AOM_CDF2(11932) } },
+            { { AOM_CDF2(14785) }, { AOM_CDF2(8145) } } },
+          { { { AOM_CDF2(100) }, { AOM_CDF2(11932) } },
+            { { AOM_CDF2(14785) }, { AOM_CDF2(8145) } } },
+          { { { AOM_CDF2(100) }, { AOM_CDF2(11932) } },
+            { { AOM_CDF2(14785) }, { AOM_CDF2(8145) } } },
+          { { { AOM_CDF2(100) }, { AOM_CDF2(11932) } },
+            { { AOM_CDF2(14785) }, { AOM_CDF2(8145) } } },
+        };
+#else
 static const aom_cdf_prob default_lpf_reuse_cdf[LPF_REUSE_CONTEXT][CDF_SIZE(
     2)] = { { AOM_CDF2(4259) }, { AOM_CDF2(728) } };
 
@@ -2847,6 +2905,7 @@
       { { AOM_CDF2(100) }, { AOM_CDF2(11932) } },
       { { AOM_CDF2(14785) }, { AOM_CDF2(8145) } }
     };
+#endif  // CONFIG_LOOPFILTER_LEVEL
 #endif  // CONFIG_LPF_SB
 
 #if CONFIG_EXT_INTRA_MOD
diff --git a/av1/common/entropymode.h b/av1/common/entropymode.h
index acb23c8..9b9f156 100644
--- a/av1/common/entropymode.h
+++ b/av1/common/entropymode.h
@@ -319,9 +319,16 @@
   aom_cdf_prob cfl_alpha_cdf[CFL_ALPHA_CONTEXTS][CDF_SIZE(CFL_ALPHABET_SIZE)];
 #endif
 #if CONFIG_LPF_SB
+#if CONFIG_LOOPFILTER_LEVEL
+  aom_cdf_prob lpf_reuse_cdf[4][LPF_REUSE_CONTEXT][CDF_SIZE(2)];
+  aom_cdf_prob lpf_delta_cdf[4][LPF_DELTA_CONTEXT][CDF_SIZE(DELTA_RANGE)];
+  aom_cdf_prob lpf_sign_cdf[4][LPF_REUSE_CONTEXT][LPF_SIGN_CONTEXT]
+                           [CDF_SIZE(2)];
+#else
   aom_cdf_prob lpf_reuse_cdf[LPF_REUSE_CONTEXT][CDF_SIZE(2)];
   aom_cdf_prob lpf_delta_cdf[LPF_DELTA_CONTEXT][CDF_SIZE(DELTA_RANGE)];
   aom_cdf_prob lpf_sign_cdf[LPF_REUSE_CONTEXT][LPF_SIGN_CONTEXT][CDF_SIZE(2)];
+#endif
 #endif  // CONFIG_LPF_SB
 } FRAME_CONTEXT;
 
@@ -429,9 +436,15 @@
                                     [FILTER_INTRA_MODES];
 #endif  // CONFIG_FILTER_INTRA
 #if CONFIG_LPF_SB
+#if CONFIG_LOOPFILTER_LEVEL
+  unsigned int lpf_reuse[4][LPF_REUSE_CONTEXT][2];
+  unsigned int lpf_delta[4][LPF_DELTA_CONTEXT][DELTA_RANGE];
+  unsigned int lpf_sign[4][LPF_REUSE_CONTEXT][LPF_SIGN_CONTEXT][2];
+#else
   unsigned int lpf_reuse[LPF_REUSE_CONTEXT][2];
   unsigned int lpf_delta[LPF_DELTA_CONTEXT][DELTA_RANGE];
   unsigned int lpf_sign[LPF_REUSE_CONTEXT][LPF_SIGN_CONTEXT][2];
+#endif  // CONFIG_LOOPFILTER_LEVEL
 #endif  // CONFIG_LPF_SB
 } FRAME_COUNTS;
 
diff --git a/av1/common/enums.h b/av1/common/enums.h
index 3425c97..2d6184b 100644
--- a/av1/common/enums.h
+++ b/av1/common/enums.h
@@ -106,7 +106,7 @@
 #define FILT_BOUNDARY_OFFSET 0
 #define FILT_BOUNDARY_MI_OFFSET (FILT_BOUNDARY_OFFSET >> MI_SIZE_LOG2)
 
-#define USE_GUESS_LEVEL 0
+#define USE_GUESS_LEVEL 1
 #define USE_LOOP_FILTER_SUPERBLOCK 1
 #endif  // CONFIG_LPF_SB
 
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c
index ef5f370..2c9128b 100644
--- a/av1/decoder/decodeframe.c
+++ b/av1/decoder/decodeframe.c
@@ -792,6 +792,77 @@
 #endif  // CONFIG_EXT_PARTITION_TYPES
 
 #if CONFIG_LPF_SB
+#if CONFIG_LOOPFILTER_LEVEL
+  if (bsize == cm->sb_size && !USE_GUESS_LEVEL) {
+    int lvl_idx;
+    int filt_lvl[4];
+    if (mi_row == 0 && mi_col == 0) {
+      filt_lvl[0] = aom_read_literal(r, 6, ACCT_STR);
+      filt_lvl[1] = aom_read_literal(r, 6, ACCT_STR);
+      if (filt_lvl[0] || filt_lvl[1]) {
+        filt_lvl[2] = aom_read_literal(r, 6, ACCT_STR);
+        filt_lvl[3] = aom_read_literal(r, 6, ACCT_STR);
+      }
+      for (lvl_idx = 0; lvl_idx < 4; ++lvl_idx) {
+        cm->mi_grid_visible[0]->mbmi.reuse_sb_lvl[lvl_idx] = 0;
+        cm->mi_grid_visible[0]->mbmi.delta[lvl_idx] = 0;
+        cm->mi_grid_visible[0]->mbmi.sign[lvl_idx] = 0;
+      }
+    } else {
+      int prev_mi_row, prev_mi_col;
+      if (mi_col - MAX_MIB_SIZE < 0) {
+        prev_mi_row = mi_row - MAX_MIB_SIZE;
+        prev_mi_col = mi_col;
+      } else {
+        prev_mi_row = mi_row;
+        prev_mi_col = mi_col - MAX_MIB_SIZE;
+      }
+
+      MB_MODE_INFO *curr_mbmi =
+          &cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]->mbmi;
+      MB_MODE_INFO *prev_mbmi =
+          &cm->mi_grid_visible[prev_mi_row * cm->mi_stride + prev_mi_col]->mbmi;
+      for (lvl_idx = 0; lvl_idx < 4; ++lvl_idx) {
+        const uint8_t prev_lvl = prev_mbmi->filt_lvl[lvl_idx];
+
+        const int reuse_ctx = prev_mbmi->reuse_sb_lvl[lvl_idx];
+        const int reuse_prev_lvl = aom_read_symbol(
+            r, xd->tile_ctx->lpf_reuse_cdf[lvl_idx][reuse_ctx], 2, ACCT_STR);
+        curr_mbmi->reuse_sb_lvl[lvl_idx] = reuse_prev_lvl;
+
+        if (reuse_prev_lvl) {
+          filt_lvl[lvl_idx] = prev_lvl;
+          curr_mbmi->delta[lvl_idx] = 0;
+          curr_mbmi->sign[lvl_idx] = 0;
+        } else {
+          const int delta_ctx = prev_mbmi->delta[lvl_idx];
+          unsigned int delta = aom_read_symbol(
+              r, xd->tile_ctx->lpf_delta_cdf[lvl_idx][delta_ctx], DELTA_RANGE,
+              ACCT_STR);
+          curr_mbmi->delta[lvl_idx] = delta;
+          delta *= LPF_STEP;
+
+          if (delta) {
+            const int sign_ctx = prev_mbmi->sign[lvl_idx];
+            const int sign = aom_read_symbol(
+                r, xd->tile_ctx->lpf_sign_cdf[lvl_idx][reuse_ctx][sign_ctx], 2,
+                ACCT_STR);
+            curr_mbmi->sign[lvl_idx] = sign;
+            filt_lvl[lvl_idx] = sign ? prev_lvl + delta : prev_lvl - delta;
+          } else {
+            filt_lvl[lvl_idx] = prev_lvl;
+            curr_mbmi->sign[lvl_idx] = 0;
+          }
+        }
+      }
+    }
+
+    av1_loop_filter_sb_level_init(cm, mi_row, mi_col, 0, 0, filt_lvl[0]);
+    av1_loop_filter_sb_level_init(cm, mi_row, mi_col, 0, 1, filt_lvl[1]);
+    av1_loop_filter_sb_level_init(cm, mi_row, mi_col, 1, 0, filt_lvl[2]);
+    av1_loop_filter_sb_level_init(cm, mi_row, mi_col, 2, 0, filt_lvl[3]);
+  }
+#else
   if (bsize == cm->sb_size && !USE_GUESS_LEVEL) {
     int filt_lvl;
     if (mi_row == 0 && mi_col == 0) {
@@ -846,7 +917,8 @@
 
     av1_loop_filter_sb_level_init(cm, mi_row, mi_col, filt_lvl);
   }
-#endif
+#endif  // CONFIG_LOOPFILTER_LEVEL
+#endif  // CONFIG_LPF_SB
 
 #if CONFIG_LOOP_RESTORATION
   for (int plane = 0; plane < av1_num_planes(cm); ++plane) {
@@ -1114,12 +1186,18 @@
 #endif  // CONFIG_INTRABC && !CONFIG_LPF_SB
   struct loopfilter *lf = &cm->lf;
 #if CONFIG_LOOPFILTER_LEVEL
-  lf->filter_level[0] = aom_rb_read_literal(rb, 6);
-  lf->filter_level[1] = aom_rb_read_literal(rb, 6);
-  if (lf->filter_level[0] || lf->filter_level[1]) {
-    lf->filter_level_u = aom_rb_read_literal(rb, 6);
-    lf->filter_level_v = aom_rb_read_literal(rb, 6);
+#if CONFIG_LPF_SB
+  if (USE_GUESS_LEVEL) {
+#endif  // CONFIG_LPF_SB
+    lf->filter_level[0] = aom_rb_read_literal(rb, 6);
+    lf->filter_level[1] = aom_rb_read_literal(rb, 6);
+    if (lf->filter_level[0] || lf->filter_level[1]) {
+      lf->filter_level_u = aom_rb_read_literal(rb, 6);
+      lf->filter_level_v = aom_rb_read_literal(rb, 6);
+    }
+#if CONFIG_LPF_SB
   }
+#endif  // CONFIG_LPF_SB
 #else
 #if CONFIG_LPF_SB
   if (USE_GUESS_LEVEL) lf->filter_level = aom_rb_read_literal(rb, 6);
@@ -2216,11 +2294,30 @@
                            cm->sb_size);
 #if CONFIG_LPF_SB
           if (USE_LOOP_FILTER_SUPERBLOCK) {
+#if CONFIG_LOOPFILTER_LEVEL
+            if (USE_GUESS_LEVEL) {
+              if (cm->lf.filter_level[0] || cm->lf.filter_level[1]) {
+                av1_loop_filter_frame(get_frame_new_buffer(cm), cm, &pbi->mb,
+                                      cm->lf.filter_level[0],
+                                      cm->lf.filter_level[1], 0, 1, mi_row,
+                                      mi_col);
+                av1_loop_filter_frame(get_frame_new_buffer(cm), cm, &pbi->mb,
+                                      cm->lf.filter_level_u,
+                                      cm->lf.filter_level_u, 1, 1, mi_row,
+                                      mi_col);
+                av1_loop_filter_frame(get_frame_new_buffer(cm), cm, &pbi->mb,
+                                      cm->lf.filter_level_v,
+                                      cm->lf.filter_level_v, 2, 1, mi_row,
+                                      mi_col);
+              }
+            }
+#else
             // apply deblocking filtering right after each superblock is decoded
             const int filter_lvl =
                 cm->mi[mi_row * cm->mi_stride + mi_col].mbmi.filt_lvl;
             av1_loop_filter_frame(get_frame_new_buffer(cm), cm, &pbi->mb,
                                   filter_lvl, 0, 1, mi_row, mi_col);
+#endif  // CONFIG_LOOPFILTER_LEVEL
           }
 #endif  // CONFIG_LPF_SB
         }
@@ -2253,7 +2350,7 @@
 #if CONFIG_OBU
     if (endTile == cm->tile_rows * cm->tile_cols - 1)
 #endif
-#if CONFIG_LOOPFILTER_LEVEL
+#if CONFIG_LOOPFILTER_LEVEL && !CONFIG_LPF_SB
       if (cm->lf.filter_level[0] || cm->lf.filter_level[1]) {
         av1_loop_filter_frame(get_frame_new_buffer(cm), cm, &pbi->mb,
                               cm->lf.filter_level[0], cm->lf.filter_level[1], 0,
@@ -2265,7 +2362,7 @@
                               cm->lf.filter_level_v, cm->lf.filter_level_v, 2,
                               0);
       }
-#else
+#elif !CONFIG_LPF_SB
     av1_loop_filter_frame(get_frame_new_buffer(cm), cm, &pbi->mb,
                           cm->lf.filter_level, 0, 0);
 #endif  // CONFIG_LOOPFILTER_LEVEL
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c
index ba271c7..3c305e2 100644
--- a/av1/encoder/bitstream.c
+++ b/av1/encoder/bitstream.c
@@ -2298,11 +2298,26 @@
 #if CONFIG_LPF_SB
   // send filter level for each superblock (64x64)
   if (bsize == cm->sb_size && !USE_GUESS_LEVEL) {
+    int lvl_idx;
     if (mi_row == 0 && mi_col == 0) {
+#if CONFIG_LOOPFILTER_LEVEL
+      aom_write_literal(w, cm->mi[0].mbmi.filt_lvl[0], 6);
+      aom_write_literal(w, cm->mi[0].mbmi.filt_lvl[1], 6);
+      if (cm->mi[0].mbmi.filt_lvl[0] || cm->mi[0].mbmi.filt_lvl[1]) {
+        aom_write_literal(w, cm->mi[0].mbmi.filt_lvl[2], 6);
+        aom_write_literal(w, cm->mi[0].mbmi.filt_lvl[3], 6);
+      }
+      for (lvl_idx = 0; lvl_idx < 4; ++lvl_idx) {
+        cm->mi_grid_visible[0]->mbmi.reuse_sb_lvl[lvl_idx] = 0;
+        cm->mi_grid_visible[0]->mbmi.delta[lvl_idx] = 0;
+        cm->mi_grid_visible[0]->mbmi.sign[lvl_idx] = 0;
+      }
+#else
       aom_write_literal(w, cm->mi[0].mbmi.filt_lvl, 6);
       cm->mi_grid_visible[0]->mbmi.reuse_sb_lvl = 0;
       cm->mi_grid_visible[0]->mbmi.delta = 0;
       cm->mi_grid_visible[0]->mbmi.sign = 0;
+#endif
     } else {
       int prev_mi_row, prev_mi_col;
       if (mi_col - MAX_MIB_SIZE < 0) {
@@ -2317,6 +2332,44 @@
       MB_MODE_INFO *prev_mbmi =
           &cm->mi_grid_visible[prev_mi_row * cm->mi_stride + prev_mi_col]->mbmi;
 
+#if CONFIG_LOOPFILTER_LEVEL
+      for (lvl_idx = 0; lvl_idx < 4; ++lvl_idx) {
+        const uint8_t curr_lvl = curr_mbmi->filt_lvl[lvl_idx];
+        const uint8_t prev_lvl = prev_mbmi->filt_lvl[lvl_idx];
+
+        const int reuse_prev_lvl = curr_lvl == prev_lvl;
+        const int reuse_ctx = prev_mbmi->reuse_sb_lvl[lvl_idx];
+        curr_mbmi->reuse_sb_lvl[lvl_idx] = reuse_prev_lvl;
+        aom_write_symbol(w, reuse_prev_lvl,
+                         xd->tile_ctx->lpf_reuse_cdf[lvl_idx][reuse_ctx], 2);
+        cpi->td.counts->lpf_reuse[lvl_idx][reuse_ctx][reuse_prev_lvl]++;
+
+        if (reuse_prev_lvl) {
+          curr_mbmi->delta[lvl_idx] = 0;
+          curr_mbmi->sign[lvl_idx] = 0;
+        } else {
+          const unsigned int delta = abs(curr_lvl - prev_lvl) / LPF_STEP;
+          const int delta_ctx = prev_mbmi->delta[lvl_idx];
+          curr_mbmi->delta[lvl_idx] = delta;
+          aom_write_symbol(w, delta,
+                           xd->tile_ctx->lpf_delta_cdf[lvl_idx][delta_ctx],
+                           DELTA_RANGE);
+          cpi->td.counts->lpf_delta[lvl_idx][delta_ctx][delta]++;
+
+          if (delta) {
+            const int sign = curr_lvl > prev_lvl;
+            const int sign_ctx = prev_mbmi->sign[lvl_idx];
+            curr_mbmi->sign[lvl_idx] = sign;
+            aom_write_symbol(
+                w, sign,
+                xd->tile_ctx->lpf_sign_cdf[lvl_idx][reuse_ctx][sign_ctx], 2);
+            cpi->td.counts->lpf_sign[lvl_idx][reuse_ctx][sign_ctx][sign]++;
+          } else {
+            curr_mbmi->sign[lvl_idx] = 0;
+          }
+        }
+      }
+#else
       const uint8_t curr_lvl = curr_mbmi->filt_lvl;
       const uint8_t prev_lvl = prev_mbmi->filt_lvl;
 
@@ -2349,6 +2402,7 @@
           curr_mbmi->sign = 0;
         }
       }
+#endif  // CONFIG_LOOPFILTER_LEVEL
     }
   }
 #endif
@@ -2586,12 +2640,18 @@
 
 // Encode the loop filter level and type
 #if CONFIG_LOOPFILTER_LEVEL
-  aom_wb_write_literal(wb, lf->filter_level[0], 6);
-  aom_wb_write_literal(wb, lf->filter_level[1], 6);
-  if (lf->filter_level[0] || lf->filter_level[1]) {
-    aom_wb_write_literal(wb, lf->filter_level_u, 6);
-    aom_wb_write_literal(wb, lf->filter_level_v, 6);
+#if CONFIG_LPF_SB
+  if (USE_GUESS_LEVEL) {
+#endif  // CONFIG_LPF_SB
+    aom_wb_write_literal(wb, lf->filter_level[0], 6);
+    aom_wb_write_literal(wb, lf->filter_level[1], 6);
+    if (lf->filter_level[0] || lf->filter_level[1]) {
+      aom_wb_write_literal(wb, lf->filter_level_u, 6);
+      aom_wb_write_literal(wb, lf->filter_level_v, 6);
+    }
+#if CONFIG_LPF_SB
   }
+#endif  // CONFIG_LPF_SB
 #else
 #if CONFIG_LPF_SB
   if (USE_GUESS_LEVEL) aom_wb_write_literal(wb, lf->filter_level, 6);
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index f984652..f1d516e 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -3256,6 +3256,23 @@
     }
 #if CONFIG_LPF_SB
     if (USE_LOOP_FILTER_SUPERBLOCK) {
+#if CONFIG_LOOPFILTER_LEVEL
+      int filter_lvl[4];
+
+      if (USE_GUESS_LEVEL) {
+        struct loopfilter *lf = &cpi->common.lf;
+        filter_lvl[0] = lf->filter_level[0];
+        filter_lvl[1] = lf->filter_level[1];
+        filter_lvl[2] = lf->filter_level_u;
+        filter_lvl[3] = lf->filter_level_v;
+        av1_loop_filter_frame(cm->frame_to_show, cm, xd, filter_lvl[0],
+                              filter_lvl[1], 0, 1, mi_row, mi_col);
+        av1_loop_filter_frame(cm->frame_to_show, cm, xd, filter_lvl[2],
+                              filter_lvl[2], 1, 1, mi_row, mi_col);
+        av1_loop_filter_frame(cm->frame_to_show, cm, xd, filter_lvl[3],
+                              filter_lvl[3], 2, 1, mi_row, mi_col);
+      }
+#else
       int filter_lvl;
 
       if (USE_GUESS_LEVEL) {
@@ -3283,6 +3300,7 @@
       // if filter_lvl is 0, we still need to set mi info
       if (filter_lvl == 0)
         av1_loop_filter_sb_level_init(cm, mi_row, mi_col, filter_lvl);
+#endif  // CONFIG_LOOPFILTER_LEVEL
     }
 #endif  // CONFIG_LPF_SB
   }
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 0aac2cb..f6228bd 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -4709,10 +4709,10 @@
 #endif
 #endif  // CONFIG_LPF_SB
   {
-#if CONFIG_LPF_SB
+#if CONFIG_LPF_SB && !CONFIG_LOOPFILTER_LEVEL
     av1_loop_filter_frame(cm->frame_to_show, cm, xd, lf->filter_level, 0, 0, 0,
                           0);
-#else
+#elif !CONFIG_LPF_SB
 #if CONFIG_LOOPFILTER_LEVEL
     av1_loop_filter_frame(cm->frame_to_show, cm, xd, lf->filter_level[0],
                           lf->filter_level[1], 0, 0);
diff --git a/av1/encoder/picklpf.c b/av1/encoder/picklpf.c
index b064ddb..12f07de 100644
--- a/av1/encoder/picklpf.c
+++ b/av1/encoder/picklpf.c
@@ -27,7 +27,7 @@
 #include "av1/encoder/encoder.h"
 #include "av1/encoder/picklpf.h"
 
-#if CONFIG_LPF_SB
+#if CONFIG_LPF_SB && !CONFIG_LOOPFILTER_LEVEL
 #if CONFIG_HIGHBITDEPTH
 static int compute_sb_y_sse_highbd(const YV12_BUFFER_CONFIG *src,
                                    const YV12_BUFFER_CONFIG *frame,
@@ -115,7 +115,7 @@
   }
 }
 
-#if CONFIG_LPF_SB
+#if CONFIG_LPF_SB && !CONFIG_LOOPFILTER_LEVEL
 // TODO(chengchen): reduce memory usage by copy superblock instead of frame
 static int try_filter_superblock(const YV12_BUFFER_CONFIG *sd,
                                  AV1_COMP *const cpi, int filt_level,
@@ -230,7 +230,7 @@
   return filt_best;
 }
 
-#else  // CONFIG_LPF_SB
+#elif !CONFIG_LPF_SB  // CONFIG_LPF_SB
 static int64_t try_filter_frame(const YV12_BUFFER_CONFIG *sd,
                                 AV1_COMP *const cpi, int filt_level,
                                 int partial_frame
@@ -401,6 +401,7 @@
                            LPF_PICK_METHOD method) {
   AV1_COMMON *const cm = &cpi->common;
   struct loopfilter *const lf = &cm->lf;
+  (void)sd;
 
   lf->sharpness_level = cm->frame_type == KEY_FRAME ? 0 : cpi->oxcf.sharpness;
 
@@ -450,13 +451,16 @@
     if (cm->bit_depth != AOM_BITS_8 && cm->frame_type == KEY_FRAME)
       filt_guess -= 4;
 #if CONFIG_LOOPFILTER_LEVEL
+    // TODO(chengchen): retrain the model for Y, U, V filter levels
     lf->filter_level[0] = clamp(filt_guess, min_filter_level, max_filter_level);
     lf->filter_level[1] = clamp(filt_guess, min_filter_level, max_filter_level);
+    lf->filter_level_u = clamp(filt_guess, min_filter_level, max_filter_level);
+    lf->filter_level_v = clamp(filt_guess, min_filter_level, max_filter_level);
 #else
     lf->filter_level = clamp(filt_guess, min_filter_level, max_filter_level);
 #endif
   } else {
-#if CONFIG_LPF_SB
+#if CONFIG_LPF_SB && !CONFIG_LOOPFILTER_LEVEL
     int mi_row, mi_col;
     // TODO(chengchen): init last_lvl using previous frame's info?
     int last_lvl = 0;
@@ -485,7 +489,7 @@
         }
       }
     }
-#else  // CONFIG_LPF_SB
+#elif !CONFIG_LPF_SB  // !CONFIG_LPF_SB
 #if CONFIG_LOOPFILTER_LEVEL
     lf->filter_level[0] = lf->filter_level[1] = search_filter_level(
         sd, cpi, method == LPF_PICK_FROM_SUBIMAGE, NULL, 0, 2);