/*
 * Copyright (c) 2021, Alliance for Open Media. All rights reserved
 *
 * This source code is subject to the terms of the BSD 3-Clause Clear License
 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause
 * Clear License was not distributed with this source code in the LICENSE file,
 * you can obtain it at aomedia.org/license/software-license/bsd-3-c-c/.
 * If the Alliance for Open Media Patent License 1.0 was not distributed with
 * this source code in the PATENTS file, you can obtain it at
 * aomedia.org/license/patent-license/.
 */

#include "av2/common/tip.h"
#include "config/avm_scale_rtcd.h"
#include "config/avm_dsp_rtcd.h"
#include "av2/common/reconinter.h"

// Percentage threshold of number of blocks with available motion
// projection in a frame to allow TIP mode
#define TIP_ENABLE_COUNT_THRESHOLD 60

static void tip_temporal_scale_motion_field(AV2_COMMON *cm,
                                            const int ref_frames_offset) {
  const int mvs_rows =
      ROUND_POWER_OF_TWO(cm->mi_params.mi_rows, TMVP_SHIFT_BITS);
  const int mvs_cols =
      ROUND_POWER_OF_TWO(cm->mi_params.mi_cols, TMVP_SHIFT_BITS);
  for (int blk_row = 0; blk_row < mvs_rows; ++blk_row) {
    for (int blk_col = 0; blk_col < mvs_cols; ++blk_col) {
      TPL_MV_REF *tpl_mvs = cm->tpl_mvs_rows[blk_row] + blk_col;
      if (tpl_mvs->mfmv0.as_int != INVALID_MV) {
        int_mv this_refmv;
        get_mv_projection_clamp(&this_refmv.as_mv, tpl_mvs->mfmv0.as_mv,
                                ref_frames_offset, tpl_mvs->ref_frame_offset,
                                -REFMVS_LIMIT, REFMVS_LIMIT);
        tpl_mvs->mfmv0.as_int = this_refmv.as_int;
        tpl_mvs->ref_frame_offset = ref_frames_offset;
      } else {
        tpl_mvs->ref_frame_offset = ref_frames_offset;
      }
    }
  }
}

// Compute number of rows/columns/stride in TMVP unit (8x8)
static void compute_tmvp_mi_info(const AV2_COMMON *cm, int *mvs_rows,
                                 int *mvs_cols, int *mvs_stride,
                                 int *sb_tmvp_size) {
  *mvs_rows = ROUND_POWER_OF_TWO(cm->mi_params.mi_rows, TMVP_SHIFT_BITS);
  *mvs_cols = ROUND_POWER_OF_TWO(cm->mi_params.mi_cols, TMVP_SHIFT_BITS);
  *mvs_stride = *mvs_cols;

  const SequenceHeader *const seq_params = &cm->seq_params;
  const int mf_sb_size_log2 =
      get_mf_sb_size_log2(block_size_high[seq_params->sb_size],
                          cm->mib_size_log2, cm->tmvp_sample_step);

  const int mf_sb_size = (1 << mf_sb_size_log2);
  *sb_tmvp_size = (mf_sb_size >> TMVP_MI_SZ_LOG2);
}

static void tip_fill_motion_field_holes(AV2_COMMON *cm) {
  int mvs_rows = 0;
  int mvs_cols = 0;
  int mvs_stride = 0;
  int sb_tmvp_size = 0;
  assert(cm->tmvp_sample_step > 0);
  int sample = cm->tmvp_sample_step;

  compute_tmvp_mi_info(cm, &mvs_rows, &mvs_cols, &mvs_stride, &sb_tmvp_size);

  const int dirs[4][2] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };
  for (int sb_row = 0; sb_row < mvs_rows; sb_row += sb_tmvp_size) {
    const int start_row = sb_row;
    const int end_row = AVMMIN(sb_row + sb_tmvp_size, mvs_rows);
    for (int sb_col = 0; sb_col < mvs_cols; sb_col += sb_tmvp_size) {
      const int start_col = sb_col;
      const int end_col = AVMMIN(sb_col + sb_tmvp_size, mvs_cols);
      for (int row = start_row; row < end_row; row += sample) {
        for (int col = start_col; col < end_col; col += sample) {
          const TPL_MV_REF *cur_tpl_mvs = cm->tpl_mvs_rows[row] + col;
          if (cur_tpl_mvs->mfmv0.as_int != INVALID_MV) {
            for (int dir = 0; dir < 4; ++dir) {
              const int neighbor_row = row + dirs[dir][0] * sample;
              const int neighbor_col = col + dirs[dir][1] * sample;
              if (neighbor_row >= start_row && neighbor_row < end_row &&
                  neighbor_col >= start_col && neighbor_col < end_col &&
                  cm->tpl_mvs_rows[neighbor_row][neighbor_col].mfmv0.as_int ==
                      INVALID_MV) {
                cm->tpl_mvs_rows[neighbor_row][neighbor_col].mfmv0.as_int =
                    cur_tpl_mvs->mfmv0.as_int;
                cm->tpl_mvs_rows[neighbor_row][neighbor_col].ref_frame_offset =
                    cur_tpl_mvs->ref_frame_offset;
              }
            }
          }
        }
      }
    }
  }
}

static void tip_blk_average_filter_mv(AV2_COMMON *cm) {
  int mvs_rows = 0;
  int mvs_cols = 0;
  int mvs_stride = 0;
  int sb_tmvp_size = 0;
  assert(cm->tmvp_sample_step > 0);
  int sample = cm->tmvp_sample_step;

  compute_tmvp_mi_info(cm, &mvs_rows, &mvs_cols, &mvs_stride, &sb_tmvp_size);

#define DIV_SHIFT_BITS 16
  // Avoid the division operation. Maximum 5 MVs would be used for MV smoothing.
  // weight_div_mult[i] where i is the number of MVs are available.
  // The value of weight_div_mult[i] is to implement 1/i without division with
  // the shift of (weight_div_mult[i] >> 16)
  const int weight_div_mult[6] = { 0, 65536, 32768, 21845, 16384, 13107 };
  const int sb_stride = MAX_SB_TMVP_SIZE;
  int_mv tpl_mfs[MAX_SB_TMVP_SIZE * MAX_SB_TMVP_SIZE];
  for (int sb_row = 0; sb_row < mvs_rows; sb_row += sb_tmvp_size) {
    const int start_row = sb_row;
    const int end_row = AVMMIN(sb_row + sb_tmvp_size, mvs_rows);
    for (int sb_col = 0; sb_col < mvs_cols; sb_col += sb_tmvp_size) {
      const int start_col = sb_col;
      const int end_col = AVMMIN(sb_col + sb_tmvp_size, mvs_cols);
      for (int row = start_row; row < end_row; row += sample) {
        const int i0 = row - sample;
        const int i1 = row + sample;
        for (int col = start_col; col < end_col; col += sample) {
          const int j0 = col - sample;
          const int j1 = col + sample;
          int weights = 0;
          int sum_mv_row = 0;
          int sum_mv_col = 0;
          if (cm->tpl_mvs_rows[row][col].mfmv0.as_int != INVALID_MV) {
            weights++;
            sum_mv_row += cm->tpl_mvs_rows[row][col].mfmv0.as_mv.row;
            sum_mv_col += cm->tpl_mvs_rows[row][col].mfmv0.as_mv.col;
          }

          if (i0 >= start_row) {
            if (cm->tpl_mvs_rows[i0][col].mfmv0.as_int != INVALID_MV) {
              weights++;
              sum_mv_row += cm->tpl_mvs_rows[i0][col].mfmv0.as_mv.row;
              sum_mv_col += cm->tpl_mvs_rows[i0][col].mfmv0.as_mv.col;
            }
          }

          if (i1 < end_row) {
            if (cm->tpl_mvs_rows[i1][col].mfmv0.as_int != INVALID_MV) {
              weights++;
              sum_mv_row += cm->tpl_mvs_rows[i1][col].mfmv0.as_mv.row;
              sum_mv_col += cm->tpl_mvs_rows[i1][col].mfmv0.as_mv.col;
            }
          }

          if (j0 >= start_col) {
            if (cm->tpl_mvs_rows[row][j0].mfmv0.as_int != INVALID_MV) {
              weights++;
              sum_mv_row += cm->tpl_mvs_rows[row][j0].mfmv0.as_mv.row;
              sum_mv_col += cm->tpl_mvs_rows[row][j0].mfmv0.as_mv.col;
            }
          }

          if (j1 < end_col) {
            if (cm->tpl_mvs_rows[row][j1].mfmv0.as_int != INVALID_MV) {
              weights++;
              sum_mv_row += cm->tpl_mvs_rows[row][j1].mfmv0.as_mv.row;
              sum_mv_col += cm->tpl_mvs_rows[row][j1].mfmv0.as_mv.col;
            }
          }

          const int blk_pos_in_sb = (row - sb_row) * sb_stride + (col - sb_col);
          if (weights) {
            const int scale_factor = weight_div_mult[weights];
            const int64_t scale_mv_row = (int64_t)sum_mv_row * scale_factor;
            const int64_t scale_mv_col = (int64_t)sum_mv_col * scale_factor;
            tpl_mfs[blk_pos_in_sb].as_mv.row =
                (MV_COMP_DATA_TYPE)ROUND_POWER_OF_TWO_SIGNED_64(scale_mv_row,
                                                                DIV_SHIFT_BITS);
            tpl_mfs[blk_pos_in_sb].as_mv.col =
                (MV_COMP_DATA_TYPE)ROUND_POWER_OF_TWO_SIGNED_64(scale_mv_col,
                                                                DIV_SHIFT_BITS);
          } else {
            tpl_mfs[blk_pos_in_sb].as_int = INVALID_MV;
          }
        }
      }

      for (int row = start_row; row < end_row; row += sample) {
        for (int col = start_col; col < end_col; col += sample) {
          const int blk_pos_in_sb = (row - sb_row) * sb_stride + (col - sb_col);
          cm->tpl_mvs_rows[row][col].mfmv0.as_int =
              tpl_mfs[blk_pos_in_sb].as_int;
        }
      }
    }
  }
}

static void tip_config_tip_parameter(AV2_COMMON *cm) {
  TIP *tip_ref = &cm->tip_ref;
  const OrderHintInfo *const order_hint_info = &cm->seq_params.order_hint_info;
  const int cur_order_hint = cm->cur_frame->display_order_hint;

  MV_REFERENCE_FRAME nearest_rf[2] = { tip_ref->ref_frame[0],
                                       tip_ref->ref_frame[1] };

  RefCntBuffer *ref0_frame_buf = get_ref_frame_buf(cm, nearest_rf[0]);
  const int ref0_frame_order_hint = ref0_frame_buf->display_order_hint;
  const int cur_to_ref0_offset =
      get_relative_dist(order_hint_info, cur_order_hint, ref0_frame_order_hint);

  RefCntBuffer *ref1_frame_buf = get_ref_frame_buf(cm, nearest_rf[1]);
  const int ref1_frame_order_hint = ref1_frame_buf->display_order_hint;
  const int cur_to_ref1_offset =
      get_relative_dist(order_hint_info, cur_order_hint, ref1_frame_order_hint);

  int ref_frames_offset = 0;
  if (cm->has_both_sides_refs) {
    ref_frames_offset = get_relative_dist(
        order_hint_info, ref1_frame_order_hint, ref0_frame_order_hint);
  } else {
    ref_frames_offset = get_relative_dist(
        order_hint_info, ref0_frame_order_hint, ref1_frame_order_hint);
  }
  tip_ref->ref_frame_buffer[0] = ref0_frame_buf;
  tip_ref->ref_frame_buffer[1] = ref1_frame_buf;
  tip_ref->ref_scale_factor[0] = get_ref_scale_factors_const(cm, nearest_rf[0]);
  tip_ref->ref_scale_factor[1] = get_ref_scale_factors_const(cm, nearest_rf[1]);
  tip_ref->ref_frames_offset_sf[0] =
      tip_derive_scale_factor(cur_to_ref0_offset, ref_frames_offset);
  tip_ref->ref_frames_offset_sf[1] =
      tip_derive_scale_factor(cur_to_ref1_offset, ref_frames_offset);
  tip_ref->ref_frames_offset = AVMMIN(ref_frames_offset, MAX_FRAME_DISTANCE);
  tip_ref->ref_offset[0] = cur_to_ref0_offset;
  tip_ref->ref_offset[1] = cur_to_ref1_offset;
  tip_ref->ref_order_hint[0] = ref0_frame_order_hint;
  tip_ref->ref_order_hint[1] = ref1_frame_order_hint;
}

void av2_setup_tip_motion_field(AV2_COMMON *cm) {
  if (cm->features.tip_frame_mode) {
    tip_config_tip_parameter(cm);
    tip_temporal_scale_motion_field(cm, cm->tip_ref.ref_frames_offset);
    if (cm->features.allow_tip_hole_fill) {
      tip_fill_motion_field_holes(cm);
      tip_blk_average_filter_mv(cm);
    }
    av2_fill_tpl_mvs_sample_gap(cm);

    cm->features.use_optflow_tip =
        cm->features.tip_frame_mode && cm->has_both_sides_refs;
  }
}

static void enc_check_enable_tip_mode(AV2_COMMON *cm) {
  const TPL_MV_REF *tpl_mvs_base = cm->tpl_mvs;
  const int mvs_rows =
      ROUND_POWER_OF_TWO(cm->mi_params.mi_rows, TMVP_SHIFT_BITS);
  const int mvs_cols =
      ROUND_POWER_OF_TWO(cm->mi_params.mi_cols, TMVP_SHIFT_BITS);
  const int mvs_stride = mvs_cols;

  assert(cm->tmvp_sample_step > 0);
  const int sample = cm->tmvp_sample_step;

  int count = 0;
  for (int blk_row = 0; blk_row < mvs_rows; blk_row += sample) {
    for (int blk_col = 0; blk_col < mvs_cols; blk_col += sample) {
      const int tpl_offset = blk_row * mvs_stride + blk_col;
      const TPL_MV_REF *tpl_mvs = tpl_mvs_base + tpl_offset;
      if (tpl_mvs->mfmv0.as_int != INVALID_MV) {
        ++count;
      }
    }
  }

  count *= sample * sample;

  // Percentage of number of blocks with available motion field
  const int percent = (count * 100) / (mvs_rows * mvs_cols);
  if (percent < TIP_ENABLE_COUNT_THRESHOLD) {
    cm->features.tip_frame_mode = TIP_FRAME_DISABLED;
  } else {
    cm->features.tip_frame_mode = TIP_FRAME_AS_REF;
  }
}

static void enc_decide_tip_mode(AV2_COMMON *cm) {
  TIP *tip_ref = &cm->tip_ref;
  if (cm->current_frame.frame_type == KEY_FRAME ||
      cm->current_frame.frame_type == INTRA_ONLY_FRAME ||
      cm->current_frame.frame_type == S_FRAME) {
    cm->features.tip_frame_mode = TIP_FRAME_DISABLED;
    tip_ref->ref_frame[0] = NONE_FRAME;
    tip_ref->ref_frame[1] = NONE_FRAME;
    return;
  }

  MV_REFERENCE_FRAME nearest_rf[2] = { tip_ref->ref_frame[0],
                                       tip_ref->ref_frame[1] };

  const int tip_mode_allowed = nearest_rf[0] != NONE_FRAME &&
                               nearest_rf[1] != NONE_FRAME &&
                               is_ref_motion_field_eligible_by_frame_size(
                                   cm, get_ref_frame_buf(cm, nearest_rf[0])) &&
                               is_ref_motion_field_eligible_by_frame_size(
                                   cm, get_ref_frame_buf(cm, nearest_rf[1]));

  if (tip_mode_allowed && (is_ref_motion_field_eligible_by_frame_type(
                               get_ref_frame_buf(cm, nearest_rf[0])) ||
                           is_ref_motion_field_eligible_by_frame_type(
                               get_ref_frame_buf(cm, nearest_rf[1])))) {
    enc_check_enable_tip_mode(cm);

    if (cm->features.tip_frame_mode) {
      cm->features.allow_tip_hole_fill = cm->seq_params.enable_tip_hole_fill;
    }
  } else {
    cm->features.tip_frame_mode = TIP_FRAME_DISABLED;
    cm->features.allow_tip_hole_fill = false;
    tip_ref->ref_frame[0] = NONE_FRAME;
    tip_ref->ref_frame[1] = NONE_FRAME;
  }
}

void av2_enc_setup_tip_motion_field(AV2_COMMON *cm) {
  enc_decide_tip_mode(cm);
  av2_setup_tip_motion_field(cm);
}

MAKE_BFP_SAD_WRAPPER_COMMON(avm_highbd_sad8x8)
MAKE_BFP_SAD_WRAPPER_COMMON(avm_highbd_sad16x8)
MAKE_BFP_SAD_WRAPPER_COMMON(avm_highbd_sad8x16)
MAKE_BFP_SAD_WRAPPER_COMMON(avm_highbd_sad16x16)

unsigned int get_highbd_sad(const uint16_t *src_ptr, int source_stride,
                            const uint16_t *ref_ptr, int ref_stride, int bd,
                            int bw, int bh) {
  if (bd == 8) {
    if (bw == 8 && bh == 8)
      return avm_highbd_sad8x8_8(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 16 && bh == 8)
      return avm_highbd_sad16x8_8(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 8 && bh == 16)
      return avm_highbd_sad8x16_8(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 16 && bh == 16)
      return avm_highbd_sad16x16_8(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 12 && bh == 12)
      return avm_highbd_sad12x12(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 20 && bh == 12)
      return avm_highbd_sad20x12(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 12 && bh == 20)
      return avm_highbd_sad12x20(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 20 && bh == 20)
      return avm_highbd_sad20x20(src_ptr, source_stride, ref_ptr, ref_stride);
    else {
      assert(0);
      return 0;
    }
  } else if (bd == 10) {
    if (bw == 8 && bh == 8)
      return avm_highbd_sad8x8_10(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 16 && bh == 8)
      return avm_highbd_sad16x8_10(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 8 && bh == 16)
      return avm_highbd_sad8x16_10(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 16 && bh == 16)
      return avm_highbd_sad16x16_10(src_ptr, source_stride, ref_ptr,
                                    ref_stride);
    else {
      assert(0);
      return 0;
    }
  } else if (bd == 12) {
    if (bw == 8 && bh == 8)
      return avm_highbd_sad8x8_12(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 16 && bh == 8)
      return avm_highbd_sad16x8_12(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 8 && bh == 16)
      return avm_highbd_sad8x16_12(src_ptr, source_stride, ref_ptr, ref_stride);
    else if (bw == 16 && bh == 16)
      return avm_highbd_sad16x16_12(src_ptr, source_stride, ref_ptr,
                                    ref_stride);
    else {
      assert(0);
      return 0;
    }
  } else {
    assert(0);
    return 0;
  }
}
// Build an 8x8 block in the TIP frame
static AVM_INLINE void tip_build_inter_predictors_8x8(
    const AV2_COMMON *cm, MACROBLOCKD *xd, int plane, BLOCK_SIZE unit_bsize,
    const MV mv[2], int mi_x, int mi_y, uint16_t **mc_buf,
    CONV_BUF_TYPE *tmp_conv_dst, CalcSubpelParamsFunc calc_subpel_params_func,
    uint16_t *dst, int dst_stride, uint16_t *dst0_16_refinemv,
    uint16_t *dst1_16_refinemv, ReferenceArea ref_area[2]) {
  // TODO(any): currently this only works for y plane
  assert(plane == 0);

  int bw = block_size_wide[unit_bsize];
  int bh = block_size_high[unit_bsize];

  const int bd = cm->seq_params.bit_depth;

  const int ss_x = plane ? cm->seq_params.subsampling_x : 0;
  const int ss_y = plane ? cm->seq_params.subsampling_y : 0;
  const int comp_pixel_x = (mi_x >> ss_x);
  const int comp_pixel_y = (mi_y >> ss_y);
  const int comp_bw = bw >> ss_x;
  const int comp_bh = bh >> ss_y;

  MB_MODE_INFO mbmi_buf;
  av2_zero(mbmi_buf);
  MB_MODE_INFO *mbmi = &mbmi_buf;

  int_mv mv_refined[2 * 4];
  memset(mv_refined, 0, 2 * 4 * sizeof(int_mv));

  CONV_BUF_TYPE *org_buf = xd->tmp_conv_dst;
  xd->tmp_conv_dst = tmp_conv_dst;

  mbmi->mv[0].as_mv = mv[0];
  mbmi->mv[1].as_mv = mv[1];
  mbmi->ref_frame[0] = TIP_FRAME;
  mbmi->ref_frame[1] = NONE_FRAME;
  mbmi->interp_fltr = cm->tip_interp_filter;
  mbmi->use_intrabc[xd->tree_type == CHROMA_PART] = 0;
  mbmi->use_intrabc[0] = 0;
  mbmi->motion_mode = SIMPLE_TRANSLATION;
  mbmi->sb_type[PLANE_TYPE_Y] = unit_bsize;
  mbmi->interinter_comp.type = COMPOUND_AVERAGE;
  mbmi->max_mv_precision = MV_PRECISION_ONE_EIGHTH_PEL;
  mbmi->pb_mv_precision = MV_PRECISION_ONE_EIGHTH_PEL;
  mbmi->morph_pred = 0;

  uint16_t *refinemv_ref0 = NULL;
  uint16_t *refinemv_ref1 = NULL;

  MV best_mv_ref[2] = { { mbmi->mv[0].as_mv.row, mbmi->mv[0].as_mv.col },
                        { mbmi->mv[1].as_mv.row, mbmi->mv[1].as_mv.col } };

  int apply_refinemv = (is_refinemv_allowed_tip_blocks(cm, mbmi) && plane == 0);
  assert(IMPLIES(bw > 16, !apply_refinemv));

  if (apply_refinemv) {
    uint16_t *dst_ref0 = NULL, *dst_ref1 = NULL;
    dst_ref0 = &dst0_16_refinemv[0];
    dst_ref1 = &dst1_16_refinemv[0];
    mbmi->refinemv_flag = 1;

    apply_mv_refinement(cm, xd, plane, mbmi, bw, bh, mi_x, mi_y, mc_buf, mv,
                        calc_subpel_params_func, comp_pixel_x, comp_pixel_y,
                        dst_ref0, dst_ref1, &refinemv_ref0, &refinemv_ref1,
                        best_mv_ref, bw, bh, ref_area);
    REFINEMV_SUBMB_INFO *refinemv_subinfo = &xd->refinemv_subinfo[0];
    fill_subblock_refine_mv(refinemv_subinfo, bw, bh, best_mv_ref[0],
                            best_mv_ref[1]);
  }

  if (plane == 0) {
    mv_refined[0].as_mv = convert_mv_to_1_16th_pel(&best_mv_ref[0]);
    mv_refined[1].as_mv = convert_mv_to_1_16th_pel(&best_mv_ref[1]);
  }

  // Arrays to hold optical flow offsets.
  int vxy_bufs[4 * 4] = { 0 };
  int *vx0 = vxy_bufs;
  int *vx1 = vxy_bufs + (4 * 1);
  int *vy0 = vxy_bufs + (4 * 2);
  int *vy1 = vxy_bufs + (4 * 3);

  // Pointers to gradient and dst buffers
  int16_t *gx0 = xd->opfl_gxy_bufs,
          *gy0 = xd->opfl_gxy_bufs + (MAX_SB_SQUARE * 1),
          *gx1 = xd->opfl_gxy_bufs + (MAX_SB_SQUARE * 2),
          *gy1 = xd->opfl_gxy_bufs + (MAX_SB_SQUARE * 3);
  uint16_t *dst0 = NULL, *dst1 = NULL;

  dst0 = xd->opfl_dst_bufs;
  dst1 = xd->opfl_dst_bufs + MAX_SB_SQUARE;
  int do_pred = 1;
  int opfl_dst_stride = bw;
  if (refinemv_ref0 != NULL && refinemv_ref1 != NULL) {
    dst0 = refinemv_ref0;
    dst1 = refinemv_ref1;
    opfl_dst_stride = REFINEMV_SUBBLOCK_WIDTH +
                      2 * (SUBBLK_REF_EXT_LINES + DMVR_SEARCH_EXT_LINES);
    do_pred = 0;
  }

  int do_opfl =
      is_optflow_refinement_enabled(cm, xd, mbmi, plane, 1 /* tip_ref_frame */);
  assert(IMPLIES(bw > 8, !do_opfl));

  const unsigned int sad_thres =
      cm->features.tip_frame_mode == TIP_FRAME_AS_OUTPUT ? 15 : 6;

  const int use_4x4 = 0;
  if (do_opfl) {
    if (do_pred) {
      assert(opfl_dst_stride == bw);
      InterPredParams params0, params1;
      av2_opfl_build_inter_predictor(cm, xd, plane, mbmi, bw, bh, mi_x, mi_y,
                                     mc_buf, &params0, calc_subpel_params_func,
                                     0, dst0, &best_mv_ref[0], bw, bh);
      av2_opfl_build_inter_predictor(cm, xd, plane, mbmi, bw, bh, mi_x, mi_y,
                                     mc_buf, &params1, calc_subpel_params_func,
                                     1, dst1, &best_mv_ref[1], bw, bh);
    }
    const unsigned int sad = get_highbd_sad(dst0, opfl_dst_stride, dst1,
                                            opfl_dst_stride, bd, bw, bh);
    if (sad < sad_thres) {
      do_opfl = 0;
    }
  }

  if (do_opfl) {
    // Initialize refined mv
    const MV mv0 = best_mv_ref[0];
    const MV mv1 = best_mv_ref[1];

    for (int mvi = 0; mvi < 4; mvi++) {
      mv_refined[mvi * 2].as_mv = mv0;
      mv_refined[mvi * 2 + 1].as_mv = mv1;
    }
    // Refine MV using optical flow. The final output MV will be in 1/16
    // precision.
    av2_get_optflow_based_mv(cm, xd, plane, mbmi, mv_refined, bw, bh, mi_x,
                             mi_y, 0 /* build_for_decode */, mc_buf,
                             calc_subpel_params_func, gx0, gy0, gx1, gy1, vx0,
                             vy0, vx1, vy1, dst0, dst1, opfl_dst_stride, 0,
                             use_4x4, best_mv_ref, bw, bh);
    xd->opfl_vxy_bufs[0] = *vx0;
    xd->opfl_vxy_bufs[N_OF_OFFSETS * 1] = *vx1;
    xd->opfl_vxy_bufs[N_OF_OFFSETS * 2] = *vy0;
    xd->opfl_vxy_bufs[N_OF_OFFSETS * 3] = *vy1;
  }

  if (plane == 0) {
    xd->mv_refined[0] = mv_refined[0];
    xd->mv_refined[1] = mv_refined[1];
  }

  BacpBlockData bacp_block_data[2 * N_OF_OFFSETS];
  const struct scale_factors *const sf0 = cm->tip_ref.ref_scale_factor[0];
  const struct scale_factors *const sf1 = cm->tip_ref.ref_scale_factor[1];
  uint8_t use_bacp = cm->features.enable_imp_msk_bld && !av2_is_scaled(sf0) &&
                     !av2_is_scaled(sf1);

  for (int ref = 0; ref < 2; ++ref) {
    const struct scale_factors *const sf = cm->tip_ref.ref_scale_factor[ref];
    struct buf_2d *const pred_buf = &xd->plane[plane].pre[ref];

    InterPredParams inter_pred_params;
    av2_init_inter_params(&inter_pred_params, comp_bw, comp_bh, comp_pixel_y,
                          comp_pixel_x, ss_x, ss_y, bd, 0, sf, pred_buf,
                          cm->tip_interp_filter);

    if (apply_refinemv || do_opfl) {
      inter_pred_params.use_ref_padding = 1;
      inter_pred_params.ref_area = &ref_area[ref];
    }

    inter_pred_params.comp_mode = UNIFORM_COMP;

    inter_pred_params.border_data.enable_bacp = use_bacp;
    inter_pred_params.border_data.bacp_block_data =
        &bacp_block_data[0];  // Always point to the first ref
    inter_pred_params.sb_type = mbmi->sb_type[PLANE_TYPE_Y];
    inter_pred_params.mask_comp = mbmi->interinter_comp;

    inter_pred_params.conv_params =
        get_conv_params_no_round(ref, plane, tmp_conv_dst, MAX_SB_SIZE, 1, bd);

    if (do_opfl) {
      av2_opfl_rebuild_inter_predictor(
          dst, dst_stride, plane, mv_refined, &inter_pred_params, xd, mi_x,
          mi_y, 0 /* build_for_decode */, cm, bw, ref, mc_buf,
          calc_subpel_params_func, use_4x4, mbmi, bh, mv, 0);
    } else {
      const MV mv_1_16th_pel = convert_mv_to_1_16th_pel(&best_mv_ref[ref]);
      av2_build_one_inter_predictor(dst, dst_stride, &mv_1_16th_pel,
                                    &inter_pred_params, xd, mi_x, mi_y, ref,
                                    mc_buf, calc_subpel_params_func);
    }
  }

  xd->tmp_conv_dst = org_buf;
}

static AVM_INLINE void tip_build_inter_predictors_8x8_and_bigger(
    const AV2_COMMON *cm, MACROBLOCKD *xd, int plane, TIP_PLANE *tip_plane,
    const MV mv[2], int bw, int bh, int mi_x, int mi_y, uint16_t **mc_buf,
    CONV_BUF_TYPE *tmp_conv_dst, CalcSubpelParamsFunc calc_subpel_params_func) {
  TIP_PLANE *const tip = &tip_plane[plane];
  struct buf_2d *const dst_buf = &tip->dst;
  uint16_t *const dst = dst_buf->buf;

  BLOCK_SIZE unit_bsize = get_tip_bsize_from_bw_bh(bw, bh);
  int unit_bw = block_size_wide[unit_bsize];
  int unit_bh = block_size_high[unit_bsize];
  xd->tree_type = SHARED_PART;
  const int width = (cm->mi_params.mi_cols << MI_SIZE_LOG2);
  const int height = (cm->mi_params.mi_rows << MI_SIZE_LOG2);
  xd->mb_to_top_edge = -GET_MV_SUBPEL(mi_y);
  xd->mb_to_bottom_edge = GET_MV_SUBPEL(height - bh - mi_y);
  xd->mb_to_left_edge = -GET_MV_SUBPEL(mi_x);
  xd->mb_to_right_edge = GET_MV_SUBPEL(width - bw - mi_x);
  const int ss_x = plane ? cm->seq_params.subsampling_x : 0;
  const int ss_y = plane ? cm->seq_params.subsampling_y : 0;
  const int comp_pixel_x = (mi_x >> ss_x);
  const int comp_pixel_y = (mi_y >> ss_y);
  const int comp_bw = bw >> ss_x;
  const int comp_bh = bh >> ss_y;

  const int has_both_sides_refs = cm->has_both_sides_refs;
  const int tip_wtd_index = cm->tip_global_wtd_index;
  const int8_t tip_weight = tip_weighting_factors[tip_wtd_index];
  const int is_compound = tip_weight != TIP_SINGLE_WTD;

  uint16_t
      dst0_16_refinemv[2 *
                       (REFINEMV_SUBBLOCK_WIDTH +
                        2 * (SUBBLK_REF_EXT_LINES + DMVR_SEARCH_EXT_LINES)) *
                       (REFINEMV_SUBBLOCK_HEIGHT +
                        2 * (SUBBLK_REF_EXT_LINES + DMVR_SEARCH_EXT_LINES))];
  uint16_t
      dst1_16_refinemv[2 *
                       (REFINEMV_SUBBLOCK_WIDTH +
                        2 * (SUBBLK_REF_EXT_LINES + DMVR_SEARCH_EXT_LINES)) *
                       (REFINEMV_SUBBLOCK_HEIGHT +
                        2 * (SUBBLK_REF_EXT_LINES + DMVR_SEARCH_EXT_LINES))];

  const int apply_refinemv =
      (cm->seq_params.enable_refinemv && cm->seq_params.enable_tip_refinemv &&
       plane == 0 && has_both_sides_refs && is_compound &&
       tip_weight == TIP_EQUAL_WTD);

  ReferenceArea ref_area[2];
  const int do_opfl = cm->features.opfl_refine_type != REFINE_NONE &&
                      cm->seq_params.enable_tip_refinemv &&
                      cm->features.use_optflow_tip && plane == 0;
  int is_tip_mv_refine_disabled_for_unit_size_16x16 =
      is_tip_mv_refinement_disabled_for_unit_size_16x16(
          unit_bh, cm->seq_params.enable_tip_refinemv,
          cm->features.tip_frame_mode);
  const int do_ref_area_pad = cm->seq_params.enable_tip_refinemv &&
                              cm->has_both_sides_refs &&
                              !is_tip_mv_refine_disabled_for_unit_size_16x16;
  if (do_ref_area_pad) {
    MB_MODE_INFO *mbmi = avm_calloc(1, sizeof(*mbmi));
    mbmi->mv[0].as_mv = mv[0];
    mbmi->mv[1].as_mv = mv[1];
    mbmi->ref_frame[0] = TIP_FRAME;
    mbmi->ref_frame[1] = NONE_FRAME;
    mbmi->interp_fltr = EIGHTTAP_REGULAR;
    mbmi->use_intrabc[xd->tree_type == CHROMA_PART] = 0;
    mbmi->use_intrabc[0] = 0;
    mbmi->morph_pred = 0;
    mbmi->motion_mode = SIMPLE_TRANSLATION;
    mbmi->sb_type[PLANE_TYPE_Y] = unit_bsize;
    mbmi->interinter_comp.type = COMPOUND_AVERAGE;
    mbmi->max_mv_precision = MV_PRECISION_ONE_EIGHTH_PEL;
    mbmi->pb_mv_precision = MV_PRECISION_ONE_EIGHTH_PEL;
    const int mi_row = -xd->mb_to_top_edge >> MI_SUBPEL_SIZE_LOG2;
    const int mi_col = -xd->mb_to_left_edge >> MI_SUBPEL_SIZE_LOG2;
    mbmi->chroma_ref_info.mi_row_chroma_base = mi_row;
    mbmi->chroma_ref_info.mi_col_chroma_base = mi_col;
    av2_get_reference_area_with_padding(cm, xd, plane, mbmi, mv, comp_bw,
                                        comp_bh, mi_x, mi_y, ref_area, comp_bw,
                                        comp_bh);
    avm_free(mbmi);
  }

  int dst_stride = dst_buf->stride;
  if (plane == 0 && !is_tip_mv_refine_disabled_for_unit_size_16x16 &&
      is_any_mv_refinement_allowed_in_tip(cm) && is_compound &&
      tip_weight == TIP_EQUAL_WTD && (do_opfl || apply_refinemv)) {
    if (bw != unit_bw || bh != unit_bh) {
      for (int h = 0; h < bh; h += unit_bh) {
        for (int w = 0; w < bw; w += unit_bw) {
          dst_buf->buf = dst + h * dst_stride + w;
          tip_build_inter_predictors_8x8_and_bigger(
              cm, xd, plane, tip_plane, mv, unit_bw, unit_bh, mi_x + w,
              mi_y + h, mc_buf, tmp_conv_dst, calc_subpel_params_func);
        }
      }
      dst_buf->buf = dst;
      return;
    }
    tip_build_inter_predictors_8x8(
        cm, xd, plane, unit_bsize, mv, mi_x, mi_y, mc_buf, tmp_conv_dst,
        calc_subpel_params_func, dst, dst_stride, dst0_16_refinemv,
        dst1_16_refinemv, ref_area);
    return;
  }

  const int bd = cm->seq_params.bit_depth;

  if (plane == 0) {
    xd->mv_refined[0].as_mv = convert_mv_to_1_16th_pel(&mv[0]);
    xd->mv_refined[1].as_mv = convert_mv_to_1_16th_pel(&mv[1]);
  }

  BacpBlockData bacp_block_data[2 * N_OF_OFFSETS];
  const struct scale_factors *const sf0 = cm->tip_ref.ref_scale_factor[0];
  const struct scale_factors *const sf1 = cm->tip_ref.ref_scale_factor[1];
  uint8_t use_bacp = is_compound && tip_weight == TIP_EQUAL_WTD &&
                     cm->features.enable_imp_msk_bld && !av2_is_scaled(sf0) &&
                     !av2_is_scaled(sf1);

  for (int ref = 0; ref < 1 + is_compound; ++ref) {
    const struct scale_factors *const sf = cm->tip_ref.ref_scale_factor[ref];
    struct buf_2d *const pred_buf = &xd->plane[plane].pre[ref];

    InterPredParams inter_pred_params;
    av2_init_inter_params(&inter_pred_params, comp_bw, comp_bh, comp_pixel_y,
                          comp_pixel_x, ss_x, ss_y, bd, 0, sf, pred_buf,
                          cm->tip_interp_filter);

    if (is_compound) {
      inter_pred_params.comp_mode = UNIFORM_COMP;
    }

    inter_pred_params.border_data.enable_bacp = use_bacp;
    inter_pred_params.border_data.bacp_block_data =
        &bacp_block_data[0];  // Always point to the first ref
    inter_pred_params.sb_type = unit_bsize;
    if (is_compound) {
      inter_pred_params.mask_comp.type = COMPOUND_AVERAGE;
    }

    inter_pred_params.conv_params = get_conv_params_no_round(
        ref, plane, tmp_conv_dst, MAX_SB_SIZE, is_compound, bd);

    set_tip_interp_weight_factor(cm, ref, &inter_pred_params);

    if (do_ref_area_pad) {
      inter_pred_params.use_ref_padding = 1;
      inter_pred_params.ref_area = &ref_area[ref];
    }

    const MV mv_1_16th_pel =
        plane ? xd->mv_refined[ref].as_mv : convert_mv_to_1_16th_pel(&mv[ref]);

    av2_build_one_inter_predictor(dst, dst_buf->stride, &mv_1_16th_pel,
                                  &inter_pred_params, xd, mi_x, mi_y, ref,
                                  mc_buf, calc_subpel_params_func);
  }
}

static AVM_INLINE void tip_component_build_inter_predictors(
    const AV2_COMMON *cm, MACROBLOCKD *xd, int plane, TIP_PLANE *tip_plane,
    const MV mv[2], int bw, int bh, int mi_x, int mi_y, uint16_t **mc_buf,
    CONV_BUF_TYPE *tmp_conv_dst, CalcSubpelParamsFunc calc_subpel_params_func) {
  tip_build_inter_predictors_8x8_and_bigger(
      cm, xd, plane, tip_plane, mv, bw, bh, mi_x, mi_y, mc_buf, tmp_conv_dst,
      calc_subpel_params_func);
}

static INLINE void tip_setup_pred_plane(struct buf_2d *dst, uint16_t *src,
                                        int width, int height, int crop_width,
                                        int crop_height, int stride,
                                        int tpl_row, int tpl_col,
                                        const struct scale_factors *scale,
                                        int subsampling_x, int subsampling_y) {
  const int x = tpl_col >> subsampling_x;
  const int y = tpl_row >> subsampling_y;
  dst->buf = src + scaled_buffer_offset(x, y, stride, scale);
  dst->buf0 = src;
  dst->width = width;
  dst->height = height;
  dst->crop_width = crop_width;
  dst->crop_height = crop_height;
  dst->stride = stride;
}

static AVM_INLINE void tip_component_setup_dst_planes(
    AV2_COMMON *const cm, TIP_PLANE *const tip_plane, const int plane,
    const int tpl_row, const int tpl_col) {
  const YV12_BUFFER_CONFIG *src = &cm->tip_ref.tip_frame->buf;
  TIP_PLANE *const pd = &tip_plane[plane];
  int is_uv = 0;
  int subsampling_x = 0;
  int subsampling_y = 0;
  if (plane > 0) {
    is_uv = 1;
    subsampling_x = cm->seq_params.subsampling_x;
    subsampling_y = cm->seq_params.subsampling_y;
  }
  tip_setup_pred_plane(&pd->dst, src->buffers[plane], src->widths[is_uv],
                       src->heights[is_uv], src->crop_widths[is_uv],
                       src->crop_heights[is_uv], src->strides[is_uv], tpl_row,
                       tpl_col, NULL, subsampling_x, subsampling_y);
}

void av2_tip_setup_tip_frame_row(AV2_COMMON *cm, MACROBLOCKD *xd, int blk_row,
                                 int blk_col_start, int blk_row_end,
                                 int blk_col_end, int mvs_stride,
                                 int unit_blk_size, int max_allow_blk_size,
                                 uint16_t **mc_buf, CONV_BUF_TYPE *tmp_conv_dst,
                                 CalcSubpelParamsFunc calc_subpel_params_func,
                                 int copy_refined_mvs) {
  TIP *tip_ref = &cm->tip_ref;
  const TPL_MV_REF *tpl_mvs_base = cm->tpl_mvs;
  int enable_tip_refinemv = cm->seq_params.enable_tip_refinemv;

  MV zero_mv[2];
  memset(zero_mv, 0, sizeof(zero_mv));

  const int step = (unit_blk_size >> TMVP_MI_SZ_LOG2);
  for (int blk_col = blk_col_start; blk_col < blk_col_end; blk_col += step) {
    const int tpl_offset = blk_row * mvs_stride;
    const TPL_MV_REF *tpl_mvs = tpl_mvs_base + tpl_offset;
    const int tpl_row = blk_row << TMVP_MI_SZ_LOG2;
    const int tpl_col = blk_col << TMVP_MI_SZ_LOG2;

    int blk_width = unit_blk_size;
    int blk_height = unit_blk_size;
    if (is_tip_mv_refinement_disabled_for_unit_size_16x16(
            unit_blk_size, enable_tip_refinemv, cm->features.tip_frame_mode)) {
      blk_width = get_tip_block_width_with_same_mv(
          cm, tpl_mvs, unit_blk_size, blk_col, blk_col_end, max_allow_blk_size);
    }

    tpl_mvs = tpl_mvs + blk_col;
    MV mv[2];
    if (tpl_mvs->mfmv0.as_int != 0 && tpl_mvs->mfmv0.as_int != INVALID_MV) {
      tip_get_mv_projection(&mv[0], tpl_mvs->mfmv0.as_mv,
                            tip_ref->ref_frames_offset_sf[0]);
      tip_get_mv_projection(&mv[1], tpl_mvs->mfmv0.as_mv,
                            tip_ref->ref_frames_offset_sf[1]);
      mv[0].row = (MV_COMP_DATA_TYPE)clamp(
          mv[0].row + cm->tip_global_motion.as_mv.row, MV_LOW + 1, MV_UPP - 1);
      mv[0].col = (MV_COMP_DATA_TYPE)clamp(
          mv[0].col + cm->tip_global_motion.as_mv.col, MV_LOW + 1, MV_UPP - 1);
      mv[1].row = (MV_COMP_DATA_TYPE)clamp(
          mv[1].row + cm->tip_global_motion.as_mv.row, MV_LOW + 1, MV_UPP - 1);
      mv[1].col = (MV_COMP_DATA_TYPE)clamp(
          mv[1].col + cm->tip_global_motion.as_mv.col, MV_LOW + 1, MV_UPP - 1);
    } else {
      mv[0] = cm->tip_global_motion.as_mv;
      mv[1] = cm->tip_global_motion.as_mv;
    }

    for (int plane = 0; plane < av2_num_planes(cm); plane++) {
      if (plane == 0 && copy_refined_mvs) {
        REFINEMV_SUBMB_INFO *refinemv_subinfo = &xd->refinemv_subinfo[0];
        fill_subblock_refine_mv(refinemv_subinfo, blk_width, blk_height, mv[0],
                                mv[1]);
        xd->opfl_vxy_bufs[0] = 0;
        xd->opfl_vxy_bufs[N_OF_OFFSETS * 1] = 0;
        xd->opfl_vxy_bufs[N_OF_OFFSETS * 2] = 0;
        xd->opfl_vxy_bufs[N_OF_OFFSETS * 3] = 0;
      }

      setup_pred_planes_for_tip(&cm->tip_ref, xd, plane, plane + 1,
                                tpl_col >> MI_SIZE_LOG2,
                                tpl_row >> MI_SIZE_LOG2);

      tip_component_setup_dst_planes(cm, xd->tip_plane, plane, tpl_row,
                                     tpl_col);
      tip_component_build_inter_predictors(
          cm, xd, plane, xd->tip_plane, mv, blk_width, blk_height, tpl_col,
          tpl_row, mc_buf, tmp_conv_dst, calc_subpel_params_func);

      if (plane == 0 && copy_refined_mvs) {
        MB_MODE_INFO mbmi;
        av2_zero(mbmi);
        mbmi.sb_type[PLANE_TYPE_Y] =
            get_tip_bsize_from_bw_bh(blk_width, blk_height);
        mbmi.ref_frame[0] = TIP_FRAME;
        mbmi.ref_frame[1] = NONE_FRAME;
        mbmi.mode = NEWMV;
        mbmi.skip_mode = 0;
        mbmi.motion_mode = SIMPLE_TRANSLATION;
        mbmi.interinter_comp.type = COMPOUND_AVERAGE;
        mbmi.cwp_idx = 0;
        mbmi.refinemv_flag = 0;

        // Save the MVs before refinement into the TMVP list.
        mbmi.mv[0].as_mv = cm->tip_global_motion.as_mv;
        mbmi.mv[1].as_mv = cm->tip_global_motion.as_mv;

        const int x_inside_boundary =
            AVMMIN(blk_width >> MI_SIZE_LOG2, (blk_col_end - blk_col)
                                                  << TMVP_SHIFT_BITS);
        const int y_inside_boundary =
            AVMMIN(step << TMVP_SHIFT_BITS, (blk_row_end - blk_row)
                                                << TMVP_SHIFT_BITS);
        if (cm->seq_params.order_hint_info.enable_ref_frame_mvs) {
          if (enable_refined_mvs_in_tmvp(cm, xd, &mbmi)) {
            av2_copy_frame_refined_mvs(cm, xd, &mbmi,
                                       blk_row << TMVP_SHIFT_BITS,
                                       blk_col << TMVP_SHIFT_BITS,
                                       x_inside_boundary, y_inside_boundary);
          } else {
            av2_copy_frame_mvs(cm, xd, &mbmi, blk_row << TMVP_SHIFT_BITS,
                               blk_col << TMVP_SHIFT_BITS, x_inside_boundary,
                               y_inside_boundary);
          }
        }
      }
    }
    blk_col += (blk_width >> TMVP_MI_SZ_LOG2) - step;
  }
}

// Frame processing for TIP
static void tip_setup_tip_frame_plane(
    AV2_COMMON *cm, MACROBLOCKD *xd, int blk_row_start, int blk_col_start,
    int blk_row_end, int blk_col_end, int mvs_stride, int unit_blk_size,
    int max_allow_blk_size, uint16_t **mc_buf, CONV_BUF_TYPE *tmp_conv_dst,
    CalcSubpelParamsFunc calc_subpel_params_func, int copy_refined_mvs) {
  const int step = (unit_blk_size >> TMVP_MI_SZ_LOG2);
  for (int blk_row = blk_row_start; blk_row < blk_row_end; blk_row += step) {
    av2_tip_setup_tip_frame_row(cm, xd, blk_row, blk_col_start, blk_row_end,
                                blk_col_end, mvs_stride, unit_blk_size,
                                max_allow_blk_size, mc_buf, tmp_conv_dst,
                                calc_subpel_params_func, copy_refined_mvs);
  }
}

static AVM_INLINE void tip_setup_tip_frame_planes(
    AV2_COMMON *cm, MACROBLOCKD *xd, int blk_row_start, int blk_col_start,
    int blk_row_end, int blk_col_end, int mvs_stride, uint16_t **mc_buf,
    CONV_BUF_TYPE *tmp_conv_dst, CalcSubpelParamsFunc calc_subpel_params_func,
    int copy_refined_mvs) {
  int unit_blk_size = (get_unit_bsize_for_tip_frame(
                           cm->features.tip_frame_mode, cm->tip_interp_filter,
                           cm->seq_params.enable_tip_refinemv) == BLOCK_16X16)
                          ? 16
                          : 8;
  tip_setup_tip_frame_plane(cm, xd, blk_row_start, blk_col_start, blk_row_end,
                            blk_col_end, mvs_stride, unit_blk_size,
                            MAX_BLOCK_SIZE_WITH_SAME_MV, mc_buf, tmp_conv_dst,
                            calc_subpel_params_func, copy_refined_mvs);

  avm_extend_frame_borders(&cm->tip_ref.tip_frame->buf, av2_num_planes(cm),
                           cm->decoding);
}

void av2_setup_tip_frame(AV2_COMMON *cm, MACROBLOCKD *xd, uint16_t **mc_buf,
                         CONV_BUF_TYPE *tmp_conv_dst,
                         CalcSubpelParamsFunc calc_subpel_params_func,
                         int copy_refined_mvs) {
  const int mvs_rows =
      ROUND_POWER_OF_TWO(cm->mi_params.mi_rows, TMVP_SHIFT_BITS);
  const int mvs_cols =
      ROUND_POWER_OF_TWO(cm->mi_params.mi_cols, TMVP_SHIFT_BITS);
  tip_setup_tip_frame_planes(cm, xd, 0, 0, mvs_rows, mvs_cols, mvs_cols, mc_buf,
                             tmp_conv_dst, calc_subpel_params_func,
                             copy_refined_mvs);
}
