intrabc: Add initial skeleton

Missing features:
* RDOPT (Forced on for certain blocks)
* Any form of border extension
* Non MI sized right and bottom edges
* MV prediction

Present features:
* Force intrabc for some blocks
* Carry intrabc in the bitstream
* Validate DV is causal
* Reconstruct intrabc block assuming border extension is unnecessary

Change-Id: Ib1f6868e89bfacc2a4edfc876485bad1b347263b
diff --git a/av1/common/blockd.h b/av1/common/blockd.h
index 32c01d4..6bbb4c5 100644
--- a/av1/common/blockd.h
+++ b/av1/common/blockd.h
@@ -324,6 +324,9 @@
 #if CONFIG_PALETTE
   PALETTE_MODE_INFO palette_mode_info;
 #endif  // CONFIG_PALETTE
+#if CONFIG_INTRABC
+  uint8_t use_intrabc;
+#endif  // CONFIG_INTRABC
 
 // Only for INTER blocks
 #if CONFIG_DUAL_FILTER
@@ -396,6 +399,12 @@
   b_mode_info bmi[4];
 } MODE_INFO;
 
+#if CONFIG_INTRABC
+static INLINE int is_intrabc_block(const MB_MODE_INFO *mbmi) {
+  return mbmi->use_intrabc;
+}
+#endif
+
 static INLINE PREDICTION_MODE get_y_mode(const MODE_INFO *mi, int block) {
 #if CONFIG_CB4X4
   (void)block;
@@ -406,6 +415,9 @@
 }
 
 static INLINE int is_inter_block(const MB_MODE_INFO *mbmi) {
+#if CONFIG_INTRABC
+  if (is_intrabc_block(mbmi)) return 1;
+#endif
   return mbmi->ref_frame[0] > INTRA_FRAME;
 }
 
@@ -911,6 +923,10 @@
                                   int block, TX_SIZE tx_size) {
   const MODE_INFO *const mi = xd->mi[0];
   const MB_MODE_INFO *const mbmi = &mi->mbmi;
+#if CONFIG_INTRABC
+  // TODO(aconverse@google.com): Revisit this decision
+  if (is_intrabc_block(mbmi)) return DCT_DCT;
+#endif  // CONFIG_INTRABC
 #if !CONFIG_TXK_SEL
   const int block_raster_idx = av1_block_index_to_raster_order(tx_size, block);
   if (FIXED_TX_TYPE)
diff --git a/av1/common/entropymode.h b/av1/common/entropymode.h
index e31a39a..3fb5bdf 100644
--- a/av1/common/entropymode.h
+++ b/av1/common/entropymode.h
@@ -69,6 +69,10 @@
 #define PALETTE_MAX_BLOCK_SIZE (64 * 64)
 #endif  // CONFIG_PALETTE
 
+#if CONFIG_INTRABC
+#define INTRABC_PROB 192
+#endif  // CONFIG_INTRABC
+
 struct AV1Common;
 
 typedef struct {
@@ -219,6 +223,9 @@
 #else
   nmv_context nmvc;
 #endif
+#if CONFIG_INTRABC
+  nmv_context ndvc;
+#endif
   int initialized;
 #if CONFIG_EXT_TX
   aom_prob inter_ext_tx_prob[EXT_TX_SETS_INTER][EXT_TX_SIZES][TX_TYPES - 1];
@@ -381,6 +388,9 @@
 #else
   nmv_context_counts mv;
 #endif
+#if CONFIG_INTRABC
+  nmv_context_counts dv;
+#endif
 #if CONFIG_DELTA_Q
   unsigned int delta_q[DELTA_Q_PROBS][2];
 #endif
diff --git a/av1/common/entropymv.c b/av1/common/entropymv.c
index e3aecba..3e71f6f 100644
--- a/av1/common/entropymv.c
+++ b/av1/common/entropymv.c
@@ -332,7 +332,10 @@
   }
 #else
   cm->fc->nmvc = default_nmv_context;
-#endif
+#endif  // CONFIG_REF_MV
+#if CONFIG_INTRABC
+  cm->fc->ndvc = default_nmv_context;
+#endif  // CONFIG_INTRABC
 #if CONFIG_GLOBAL_MOTION
   av1_copy(cm->fc->global_motion_types_prob, default_global_motion_types_prob);
 #endif  // CONFIG_GLOBAL_MOTION
diff --git a/av1/common/mvref_common.h b/av1/common/mvref_common.h
index a8abcba..7c1161d 100644
--- a/av1/common/mvref_common.h
+++ b/av1/common/mvref_common.h
@@ -515,6 +515,57 @@
                 int *pts, int *pts_inref);
 #endif  // CONFIG_WARPED_MOTION
 
+#if CONFIG_INTRABC
+static INLINE void av1_find_ref_dv(int_mv *ref_dv, int mi_row, int mi_col) {
+  // TODO(aconverse@google.com): Handle tiles and such
+  (void)mi_col;
+  if (mi_row < MAX_MIB_SIZE) {
+    ref_dv->as_mv.row = 0;
+    ref_dv->as_mv.col = -MI_SIZE * MAX_MIB_SIZE;
+  } else {
+    ref_dv->as_mv.row = -MI_SIZE * MAX_MIB_SIZE;
+    ref_dv->as_mv.col = 0;
+  }
+}
+
+static INLINE int is_dv_valid(const MV dv, const TileInfo *const tile,
+                              int mi_row, int mi_col, BLOCK_SIZE bsize) {
+  const int bw = block_size_wide[bsize];
+  const int bh = block_size_high[bsize];
+  const int SCALE_PX_TO_MV = 8;
+  // Disallow subpixel for now
+  // SUBPEL_MASK is not the correct scale
+  if ((dv.row & (SCALE_PX_TO_MV - 1) || dv.col & (SCALE_PX_TO_MV - 1)))
+    return 0;
+  // Is the source top-left inside the current tile?
+  const int src_top_edge = mi_row * MI_SIZE * SCALE_PX_TO_MV + dv.row;
+  const int tile_top_edge = tile->mi_row_start * MI_SIZE * SCALE_PX_TO_MV;
+  if (src_top_edge < tile_top_edge) return 0;
+  const int src_left_edge = mi_col * MI_SIZE * SCALE_PX_TO_MV + dv.col;
+  const int tile_left_edge = tile->mi_col_start * MI_SIZE * SCALE_PX_TO_MV;
+  if (src_left_edge < tile_left_edge) return 0;
+  // Is the bottom right inside the current tile?
+  const int src_bottom_edge = (mi_row * MI_SIZE + bh) * SCALE_PX_TO_MV + dv.row;
+  const int tile_bottom_edge = tile->mi_row_end * MI_SIZE * SCALE_PX_TO_MV;
+  if (src_bottom_edge > tile_bottom_edge) return 0;
+  const int src_right_edge = (mi_col * MI_SIZE + bw) * SCALE_PX_TO_MV + dv.col;
+  const int tile_right_edge = tile->mi_col_end * MI_SIZE * SCALE_PX_TO_MV;
+  if (src_right_edge > tile_right_edge) return 0;
+  // Is the bottom right within an already coded SB?
+  const int active_sb_top_edge =
+      (mi_row & ~MAX_MIB_MASK) * MI_SIZE * SCALE_PX_TO_MV;
+  const int active_sb_bottom_edge =
+      ((mi_row & ~MAX_MIB_MASK) + MAX_MIB_SIZE) * MI_SIZE * SCALE_PX_TO_MV;
+  const int active_sb_left_edge =
+      (mi_col & ~MAX_MIB_MASK) * MI_SIZE * SCALE_PX_TO_MV;
+  if (src_bottom_edge > active_sb_bottom_edge) return 0;
+  if (src_bottom_edge > active_sb_top_edge &&
+      src_right_edge > active_sb_left_edge)
+    return 0;
+  return 1;
+}
+#endif  // CONFIG_INTRABC
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
diff --git a/av1/common/reconinter.c b/av1/common/reconinter.c
index 9245b6c..eee3458 100644
--- a/av1/common/reconinter.c
+++ b/av1/common/reconinter.c
@@ -906,6 +906,18 @@
 #endif  // CONFIG_MOTION_VAR
   const int is_compound = has_second_ref(&mi->mbmi);
   int ref;
+#if CONFIG_INTRABC
+  const int is_intrabc = is_intrabc_block(&mi->mbmi);
+  struct scale_factors sf_identity;
+#if CONFIG_HIGHBITDEPTH
+  av1_setup_scale_factors_for_frame(
+      &sf_identity, 64, 64, 64, 64,
+      xd->cur_buf->flags & YV12_FLAG_HIGHBITDEPTH);
+#else
+  av1_setup_scale_factors_for_frame(&sf_identity, 64, 64, 64, 64);
+#endif  // CONFIG_HIGHBITDEPTH
+  assert(IMPLIES(is_intrabc, !is_compound));
+#endif  // CONFIG_INTRABC
 #if CONFIG_GLOBAL_MOTION
   int is_global[2];
   for (ref = 0; ref < 1 + is_compound; ++ref) {
@@ -946,9 +958,15 @@
       for (idx = 0; idx < b8_s; idx += b4_w) {
         const int chr_idx = (idy * 2) + idx;
         for (ref = 0; ref < 1 + is_compound; ++ref) {
+          struct buf_2d *const dst_buf = &pd->dst;
+#if CONFIG_INTRABC
+          const struct scale_factors *const sf =
+              is_intrabc ? &sf_identity : &xd->block_refs[ref]->sf;
+          struct buf_2d *const pre_buf = is_intrabc ? dst_buf : &pd->pre[ref];
+#else
           const struct scale_factors *const sf = &xd->block_refs[ref]->sf;
           struct buf_2d *const pre_buf = &pd->pre[ref];
-          struct buf_2d *const dst_buf = &pd->dst;
+#endif  // CONFIG_INTRABC
           uint8_t *dst = dst_buf->buf;
           const MV mv = mi->bmi[chr_idx].as_mv[ref].as_mv;
           const MV mv_q4 = clamp_mv_to_umv_border_sb(
@@ -1038,8 +1056,14 @@
 #endif  // CONFIG_CONVOLVE_ROUND
 
     for (ref = 0; ref < 1 + is_compound; ++ref) {
+#if CONFIG_INTRABC
+      const struct scale_factors *const sf =
+          is_intrabc ? &sf_identity : &xd->block_refs[ref]->sf;
+      struct buf_2d *const pre_buf = is_intrabc ? dst_buf : &pd->pre[ref];
+#else
       const struct scale_factors *const sf = &xd->block_refs[ref]->sf;
       struct buf_2d *const pre_buf = &pd->pre[ref];
+#endif  // CONFIG_INTRABC
 #if CONFIG_CB4X4
       const MV mv = mi->mbmi.mv[ref].as_mv;
 #else
@@ -1090,8 +1114,14 @@
     ConvolveParams conv_params = get_conv_params(ref, plane);
 #endif  // CONFIG_CONVOLVE_ROUND
     for (ref = 0; ref < 1 + is_compound; ++ref) {
+#if CONFIG_INTRABC
+      const struct scale_factors *const sf =
+          is_intrabc ? &sf_identity : &xd->block_refs[ref]->sf;
+      struct buf_2d *const pre_buf = is_intrabc ? dst_buf : &pd->pre[ref];
+#else
       const struct scale_factors *const sf = &xd->block_refs[ref]->sf;
       struct buf_2d *const pre_buf = &pd->pre[ref];
+#endif  // CONFIG_INTRABC
 #if CONFIG_GLOBAL_MOTION || CONFIG_WARPED_MOTION
       WarpTypesAllowed warp_types;
 #if CONFIG_GLOBAL_MOTION
diff --git a/av1/common/reconinter.h b/av1/common/reconinter.h
index 55cb239..15f5ffa 100644
--- a/av1/common/reconinter.h
+++ b/av1/common/reconinter.h
@@ -57,6 +57,7 @@
       av1_get_interp_filter_params(interp_filter);
 #endif
 
+  assert(sf);
 #if CONFIG_DUAL_FILTER
   if (interp_filter_params_x.taps == SUBPEL_TAPS &&
       interp_filter_params_y.taps == SUBPEL_TAPS && w > 2 && h > 2 &&
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c
index 91b140d..8d14095 100644
--- a/av1/decoder/decodeframe.c
+++ b/av1/decoder/decodeframe.c
@@ -1728,13 +1728,24 @@
 
     for (ref = 0; ref < 1 + has_second_ref(mbmi); ++ref) {
       const MV_REFERENCE_FRAME frame = mbmi->ref_frame[ref];
-      RefBuffer *ref_buf = &cm->frame_refs[frame - LAST_FRAME];
+      if (frame < LAST_FRAME) {
+#if CONFIG_INTRABC
+        assert(is_intrabc_block(mbmi));
+        assert(frame == INTRA_FRAME);
+        assert(ref == 0);
+#else
+        assert(0);
+#endif  // CONFIG_INTRABC
+      } else {
+        RefBuffer *ref_buf = &cm->frame_refs[frame - LAST_FRAME];
 
-      xd->block_refs[ref] = ref_buf;
-      if ((!av1_is_valid_scale(&ref_buf->sf)))
-        aom_internal_error(xd->error_info, AOM_CODEC_UNSUP_BITSTREAM,
-                           "Reference frame has invalid dimensions");
-      av1_setup_pre_planes(xd, ref, ref_buf->buf, mi_row, mi_col, &ref_buf->sf);
+        xd->block_refs[ref] = ref_buf;
+        if ((!av1_is_valid_scale(&ref_buf->sf)))
+          aom_internal_error(xd->error_info, AOM_CODEC_UNSUP_BITSTREAM,
+                             "Reference frame has invalid dimensions");
+        av1_setup_pre_planes(xd, ref, ref_buf->buf, mi_row, mi_col,
+                             &ref_buf->sf);
+      }
     }
 
 #if CONFIG_CB4X4
diff --git a/av1/decoder/decodemv.c b/av1/decoder/decodemv.c
index 3a2ab56..24e7cee 100644
--- a/av1/decoder/decodemv.c
+++ b/av1/decoder/decodemv.c
@@ -902,6 +902,32 @@
   }
 }
 
+#if CONFIG_INTRABC
+static INLINE void read_mv(aom_reader *r, MV *mv, const MV *ref,
+                           nmv_context *ctx, nmv_context_counts *counts,
+                           int allow_hp);
+
+static INLINE int is_mv_valid(const MV *mv);
+
+static INLINE int assign_dv(AV1_COMMON *cm, MACROBLOCKD *xd, int_mv *mv,
+                            const int_mv *ref_mv, int mi_row, int mi_col,
+                            BLOCK_SIZE bsize, aom_reader *r) {
+#if CONFIG_EC_ADAPT
+  FRAME_CONTEXT *ec_ctx = xd->tile_ctx;
+  (void)cm;
+#else
+  FRAME_CONTEXT *ec_ctx = cm->fc;
+#endif
+  FRAME_COUNTS *counts = xd->counts;
+  nmv_context_counts *const dv_counts = counts ? &counts->dv : NULL;
+  read_mv(r, &mv->as_mv, &ref_mv->as_mv, &ec_ctx->ndvc, dv_counts, 0);
+  int valid = is_mv_valid(&mv->as_mv) &&
+              is_dv_valid(mv->as_mv, &xd->tile, mi_row, mi_col, bsize);
+  // TODO(aconverse@google.com): additional validation
+  return valid;
+}
+#endif  // CONFIG_INTRABC
+
 static void read_intra_frame_mode_info(AV1_COMMON *const cm,
                                        MACROBLOCKD *const xd, int mi_row,
                                        int mi_col, aom_reader *r) {
@@ -942,6 +968,21 @@
   mbmi->ref_frame[0] = INTRA_FRAME;
   mbmi->ref_frame[1] = NONE_FRAME;
 
+#if CONFIG_INTRABC
+  if (bsize >= BLOCK_8X8 && cm->allow_screen_content_tools) {
+    mbmi->use_intrabc = aom_read(r, INTRABC_PROB, ACCT_STR);
+    if (mbmi->use_intrabc) {
+      int_mv dv_ref;
+      mbmi->mode = mbmi->uv_mode = DC_PRED;
+      mbmi->interp_filter = BILINEAR;
+      av1_find_ref_dv(&dv_ref, mi_row, mi_col);
+      xd->corrupted |=
+          !assign_dv(cm, xd, &mbmi->mv[0], &dv_ref, mi_row, mi_col, bsize, r);
+      return;
+    }
+  }
+#endif  // CONFIG_INTRABC
+
 #if CONFIG_CB4X4
   (void)i;
   mbmi->mode =
@@ -2283,6 +2324,10 @@
   MV_REF *frame_mvs = cm->cur_frame->mvs + mi_row * cm->mi_cols + mi_col;
   int w, h;
 
+#if CONFIG_INTRABC
+  mi->mbmi.use_intrabc = 0;
+#endif  // CONFIG_INTRABC
+
   if (frame_is_intra_only(cm)) {
     read_intra_frame_mode_info(cm, xd, mi_row, mi_col, r);
 #if CONFIG_REF_MV
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c
index ed2d5f3..a24b79e 100644
--- a/av1/encoder/bitstream.c
+++ b/av1/encoder/bitstream.c
@@ -221,6 +221,9 @@
                                 const MODE_INFO *mi, const MODE_INFO *above_mi,
                                 const MODE_INFO *left_mi, int block,
                                 PREDICTION_MODE mode, aom_writer *w) {
+#if CONFIG_INTRABC
+  assert(!is_intrabc_block(&mi->mbmi));
+#endif  // CONFIG_INTRABC
 #if CONFIG_EC_MULTISYMBOL
   aom_write_symbol(w, av1_intra_mode_ind[mode],
                    get_y_mode_cdf(frame_ctx, mi, above_mi, left_mi, block),
@@ -2094,6 +2097,21 @@
       !xd->lossless[mbmi->segment_id])
     write_selected_tx_size(cm, xd, w);
 
+#if CONFIG_INTRABC
+  if (bsize >= BLOCK_8X8 && cm->allow_screen_content_tools) {
+    int use_intrabc = is_intrabc_block(mbmi);
+    aom_write(w, use_intrabc, INTRABC_PROB);
+    if (use_intrabc) {
+      assert(mbmi->mode == DC_PRED);
+      assert(mbmi->uv_mode == DC_PRED);
+      int_mv dv_ref;
+      av1_find_ref_dv(&dv_ref, mi_row, mi_col);
+      av1_encode_dv(w, &mbmi->mv[0].as_mv, &dv_ref.as_mv, &ec_ctx->ndvc);
+      return;
+    }
+  }
+#endif  // CONFIG_INTRABC
+
   if (bsize >= BLOCK_8X8 || unify_bsize) {
     write_intra_mode_kf(cm, ec_ctx, mi, above_mi, left_mi, 0, mbmi->mode, w);
   } else {
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index 8ab42d2..23beab2 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -5882,6 +5882,11 @@
   const int unify_bsize = 0;
   const BLOCK_SIZE block_size = AOMMAX(bsize, BLOCK_8X8);
 #endif
+#if CONFIG_INTRABC
+  // TODO(aconverse@google.com): Remove this when the non-synthetic encoder
+  // side intrabc is added
+  assert(IMPLIES(is_intrabc_block(mbmi), mbmi->skip));
+#endif
 
 #if CONFIG_PVQ
   x->pvq_speed = 0;
@@ -5964,7 +5969,11 @@
     set_ref_ptrs(cm, xd, mbmi->ref_frame[0], mbmi->ref_frame[1]);
     for (ref = 0; ref < 1 + is_compound; ++ref) {
       YV12_BUFFER_CONFIG *cfg = get_ref_frame_buffer(cpi, mbmi->ref_frame[ref]);
+#if CONFIG_INTRABC
+      assert(IMPLIES(!is_intrabc_block(mbmi), cfg));
+#else
       assert(cfg != NULL);
+#endif  // !CONFIG_INTRABC
       av1_setup_pre_planes(xd, ref, cfg, mi_row, mi_col,
                            &xd->block_refs[ref]->sf);
     }
diff --git a/av1/encoder/encodemv.c b/av1/encoder/encodemv.c
index 7a37379..a2a53f8 100644
--- a/av1/encoder/encodemv.c
+++ b/av1/encoder/encodemv.c
@@ -278,6 +278,25 @@
   }
 }
 
+#if CONFIG_INTRABC
+void av1_encode_dv(aom_writer *w, const MV *mv, const MV *ref,
+                   nmv_context *mvctx) {
+  const MV diff = { mv->row - ref->row, mv->col - ref->col };
+  const MV_JOINT_TYPE j = av1_get_mv_joint(&diff);
+
+#if CONFIG_EC_MULTISYMBOL
+  aom_write_symbol(w, j, mvctx->joint_cdf, MV_JOINTS);
+#else
+  av1_write_token(w, av1_mv_joint_tree, mvctx->joints, &mv_joint_encodings[j]);
+#endif
+  if (mv_joint_vertical(j))
+    encode_mv_component(w, diff.row, &mvctx->comps[0], 0);
+
+  if (mv_joint_horizontal(j))
+    encode_mv_component(w, diff.col, &mvctx->comps[1], 0);
+}
+#endif  // CONFIG_INTRABC
+
 void av1_build_nmv_cost_table(int *mvjoint, int *mvcost[2],
                               const nmv_context *ctx, int usehp) {
   av1_cost_tokens(mvjoint, ctx->joints, av1_mv_joint_tree);
diff --git a/av1/encoder/encodemv.h b/av1/encoder/encodemv.h
index 5fea652..6d44214 100644
--- a/av1/encoder/encodemv.h
+++ b/av1/encoder/encodemv.h
@@ -31,6 +31,11 @@
 
 void av1_update_mv_count(ThreadData *td);
 
+#if CONFIG_INTRABC
+void av1_encode_dv(aom_writer *w, const MV *mv, const MV *ref,
+                   nmv_context *mvctx);
+#endif  // CONFIG_INTRABC
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
diff --git a/av1/encoder/rdopt.c b/av1/encoder/rdopt.c
index 1091683..f94f1f4 100644
--- a/av1/encoder/rdopt.c
+++ b/av1/encoder/rdopt.c
@@ -9166,6 +9166,39 @@
   return 0;  // The rate-distortion cost will be re-calculated by caller.
 }
 
+#if CONFIG_INTRABC
+// This is a dummy function that forces intrabc on for testing purposes
+// TODO(aconverse@google.com): Implement a real intrabc search
+static int64_t rd_pick_intrabc_mode_sb(const AV1_COMP *cpi, MACROBLOCK *x,
+                                       RD_COST *rd_cost, BLOCK_SIZE bsize,
+                                       int64_t best_rd) {
+  MACROBLOCKD *const xd = &x->e_mbd;
+  (void)best_rd;
+  if (bsize >= BLOCK_8X8 && cpi->common.allow_screen_content_tools) {
+    if (xd->mb_to_top_edge == -MAX_SB_SIZE * 8) {
+      MB_MODE_INFO *mbmi = &xd->mi[0]->mbmi;
+      mbmi->use_intrabc = 1;
+      mbmi->mode = DC_PRED;
+      mbmi->uv_mode = DC_PRED;
+      mbmi->mv[0].as_mv.row = -MAX_SB_SIZE * 8;
+      mbmi->mv[0].as_mv.col = 0;
+      mbmi->interp_filter = BILINEAR;
+      mbmi->skip = 1;
+      x->skip = 1;
+      const int mi_row = -xd->mb_to_top_edge / (8 * MI_SIZE);
+      const int mi_col = -xd->mb_to_left_edge / (8 * MI_SIZE);
+      av1_build_inter_predictors_sb(xd, mi_row, mi_col, NULL, bsize);
+      rd_cost->rate = 1;
+      rd_cost->dist = 0;
+      rd_cost->rdcost =
+          RDCOST(x->rdmult, x->rddiv, rd_cost->rate, rd_cost->dist);
+      return rd_cost->rdcost;
+    }
+  }
+  return INT64_MAX;
+}
+#endif  // CONFIG_INTRABC
+
 void av1_rd_pick_intra_mode_sb(const AV1_COMP *cpi, MACROBLOCK *x,
                                RD_COST *rd_cost, BLOCK_SIZE bsize,
                                PICK_MODE_CONTEXT *ctx, int64_t best_rd) {
@@ -9181,6 +9214,9 @@
   ctx->skip = 0;
   xd->mi[0]->mbmi.ref_frame[0] = INTRA_FRAME;
   xd->mi[0]->mbmi.ref_frame[1] = NONE_FRAME;
+#if CONFIG_INTRABC
+  xd->mi[0]->mbmi.use_intrabc = 0;
+#endif  // CONFIG_INTRABC
 
   if (bsize >= BLOCK_8X8 || unify_bsize) {
     if (rd_pick_intra_sby_mode(cpi, x, &rate_y, &rate_y_tokenonly, &dist_y,
@@ -9220,6 +9256,12 @@
     rd_cost->dist = dist_y + dist_uv;
   }
 
+#if CONFIG_INTRABC
+  if (rd_pick_intrabc_mode_sb(cpi, x, rd_cost, bsize, best_rd) < best_rd) {
+    ctx->skip = x->skip;  // FIXME where is the proper place to set this?!
+  }
+#endif
+
   ctx->mic = *xd->mi[0];
   ctx->mbmi_ext = *x->mbmi_ext;
   rd_cost->rdcost = RDCOST(x->rdmult, x->rddiv, rd_cost->rate, rd_cost->dist);
diff --git a/test/intrabc_test.cc b/test/intrabc_test.cc
new file mode 100644
index 0000000..84cfa5c
--- /dev/null
+++ b/test/intrabc_test.cc
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2017, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. 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 www.aomedia.org/license/patent.
+ */
+
+#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+
+#include "./aom_config.h"
+#include "av1/common/enums.h"
+#include "av1/common/mv.h"
+#include "av1/common/mvref_common.h"
+#include "av1/common/tile_common.h"
+
+namespace {
+TEST(IntrabcTest, DvValidation) {
+  struct DvTestCase {
+    MV dv;
+    int mi_row_offset;
+    int mi_col_offset;
+    BLOCK_SIZE bsize;
+    bool valid;
+  };
+  const int kSubPelScale = 8;
+  const int kTileMaxMibWidth = 8;
+  const DvTestCase kDvCases[] = {
+#if CONFIG_EXT_PARTITION
+    { { 0, 0 }, 0, 0, BLOCK_128X128, false },
+#endif
+    { { 0, 0 }, 0, 0, BLOCK_64X64, false },
+    { { 0, 0 }, 0, 0, BLOCK_32X32, false },
+    { { 0, 0 }, 0, 0, BLOCK_16X16, false },
+    { { 0, 0 }, 0, 0, BLOCK_8X8, false },
+    { { 0, 0 }, 0, 0, BLOCK_4X4, false },
+    { { -MAX_SB_SIZE * kSubPelScale, -MAX_SB_SIZE * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_16X16,
+      true },
+    { { 0, -MAX_SB_SIZE * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_16X16,
+      true },
+    { { -MAX_SB_SIZE * kSubPelScale, 0 },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_16X16,
+      true },
+    { { MAX_SB_SIZE * kSubPelScale, 0 },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_16X16,
+      false },
+    { { 0, MAX_SB_SIZE * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_16X16,
+      false },
+    { { -32 * kSubPelScale, -32 * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_32X32,
+      true },
+    { { -32 * kSubPelScale, -32 * kSubPelScale },
+      32 / MI_SIZE,
+      32 / MI_SIZE,
+      BLOCK_32X32,
+      false },
+    { { -32 * kSubPelScale - kSubPelScale / 2, -32 * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_32X32,
+      false },
+    { { -33 * kSubPelScale, -32 * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_32X32,
+      true },
+    { { -32 * kSubPelScale, -32 * kSubPelScale - kSubPelScale / 2 },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_32X32,
+      false },
+    { { -32 * kSubPelScale, -33 * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_32X32,
+      true },
+    { { -MAX_SB_SIZE * kSubPelScale, -MAX_SB_SIZE * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      true },
+    { { -(MAX_SB_SIZE + 1) * kSubPelScale, -MAX_SB_SIZE * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      false },
+    { { -MAX_SB_SIZE * kSubPelScale, -(MAX_SB_SIZE + 1) * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      false },
+    { { -(MAX_SB_SIZE - 1) * kSubPelScale, -MAX_SB_SIZE * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      true },
+    { { -MAX_SB_SIZE * kSubPelScale, -(MAX_SB_SIZE - 1) * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      true },
+    { { -(MAX_SB_SIZE - 1) * kSubPelScale, -(MAX_SB_SIZE - 1) * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      false },
+    { { -MAX_SB_SIZE * kSubPelScale, MAX_SB_SIZE * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      true },
+    { { -MAX_SB_SIZE * kSubPelScale,
+        (kTileMaxMibWidth - 2) * MAX_SB_SIZE * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      true },
+    { { -MAX_SB_SIZE * kSubPelScale,
+        ((kTileMaxMibWidth - 2) * MAX_SB_SIZE + 1) * kSubPelScale },
+      MAX_SB_SIZE / MI_SIZE,
+      MAX_SB_SIZE / MI_SIZE,
+      BLOCK_LARGEST,
+      false },
+  };
+  TileInfo tile;
+  tile.mi_row_start = 8 * MAX_MIB_SIZE;
+  tile.mi_row_end = 16 * MAX_MIB_SIZE;
+  tile.mi_col_start = 24 * MAX_MIB_SIZE;
+  tile.mi_col_end = tile.mi_col_start + kTileMaxMibWidth * MAX_MIB_SIZE;
+  for (int i = 0; i < static_cast<int>(GTEST_ARRAY_SIZE_(kDvCases)); ++i) {
+    EXPECT_EQ(kDvCases[i].valid,
+              is_dv_valid(kDvCases[i].dv, &tile,
+                          tile.mi_row_start + kDvCases[i].mi_row_offset,
+                          tile.mi_col_start + kDvCases[i].mi_col_offset,
+                          kDvCases[i].bsize))
+        << "DvCases[" << i << "]";
+  }
+}
+}  // namespace
diff --git a/test/test.mk b/test/test.mk
index c60cc3d..b9bbb9d 100644
--- a/test/test.mk
+++ b/test/test.mk
@@ -148,6 +148,7 @@
 LIBAOM_TEST_SRCS-$(HAVE_SSE4_1)        += simd_sse4_test.cc
 LIBAOM_TEST_SRCS-$(HAVE_NEON)          += simd_neon_test.cc
 LIBAOM_TEST_SRCS-yes                   += intrapred_test.cc
+LIBAOM_TEST_SRCS-$(CONFIG_INTRABC)     += intrabc_test.cc
 #LIBAOM_TEST_SRCS-$(CONFIG_AV1_DECODER) += av1_thread_test.cc
 LIBAOM_TEST_SRCS-$(CONFIG_AV1_ENCODER) += dct16x16_test.cc
 LIBAOM_TEST_SRCS-$(CONFIG_AV1_ENCODER) += dct32x32_test.cc