shithub: dav1d

Download patch

ref: 704a5252e43765bc3383d53c63e0ae4cde57d720
parent: 6f979f815bb9256aa4e71f493c71f70dd94ced2f
author: Janne Grunau <janne-vlc@jannau.net>
date: Sun Nov 4 17:19:24 EST 2018

frame threading: signal reference frame decoding errors

Fixes #127

--- a/src/decode.c
+++ b/src/decode.c
@@ -665,8 +665,11 @@
     unsigned seg_id = 8;
 
     assert(f->frame_hdr.primary_ref_frame != PRIMARY_REF_NONE);
-    dav1d_thread_picture_wait(&f->refp[f->frame_hdr.primary_ref_frame],
-                              (by + h4) * 4, PLANE_TYPE_BLOCK);
+    if (dav1d_thread_picture_wait(&f->refp[f->frame_hdr.primary_ref_frame],
+                                  (by + h4) * 4, PLANE_TYPE_BLOCK))
+    {
+        return 8;
+    }
 
     ref_seg_map += by * stride + bx;
     do {
@@ -730,7 +733,7 @@
                                   have_left, have_top, b->ref[0], mask);
                 derive_warpmv(t, bw4, bh4, mask, b->mv[0], &t->warpmv);
             }
-            f->bd_fn.recon_b_inter(t, bs, b);
+            if (f->bd_fn.recon_b_inter(t, bs, b)) return -1;
 
             const uint8_t *const filter = dav1d_filter_dir[b->filter2d];
 #define set_ctx(type, dir, diridx, off, mul, rep_macro) \
@@ -773,9 +776,15 @@
     int seg_pred = 0;
     if (f->frame_hdr.segmentation.enabled) {
         if (!f->frame_hdr.segmentation.update_map) {
-            b->seg_id = f->prev_segmap ?
-                        get_prev_frame_segid(f, t->by, t->bx, w4, h4,
-                                             f->prev_segmap, f->b4_stride) : 0;
+            if (f->prev_segmap) {
+                unsigned seg_id = get_prev_frame_segid(f, t->by, t->bx, w4, h4,
+                                                       f->prev_segmap,
+                                                       f->b4_stride);
+                if (seg_id >= 8) return -1;
+                b->seg_id = seg_id;
+            } else {
+                b->seg_id = 0;
+            }
         } else if (f->frame_hdr.segmentation.seg_data.preskip) {
             if (f->frame_hdr.segmentation.temporal &&
                 (seg_pred = msac_decode_bool_adapt(&ts->msac,
@@ -783,9 +792,16 @@
                                                           t->l.seg_pred[by4]])))
             {
                 // temporal predicted seg_id
-                b->seg_id = f->prev_segmap ?
-                            get_prev_frame_segid(f, t->by, t->bx, w4, h4,
-                                                 f->prev_segmap, f->b4_stride) : 0;
+                if (f->prev_segmap) {
+                    unsigned seg_id = get_prev_frame_segid(f, t->by, t->bx,
+                                                           w4, h4,
+                                                           f->prev_segmap,
+                                                           f->b4_stride);
+                    if (seg_id >= 8) return -1;
+                    b->seg_id = seg_id;
+                } else {
+                    b->seg_id = 0;
+                }
             } else {
                 int seg_ctx;
                 const unsigned pred_seg_id =
@@ -828,9 +844,15 @@
                                                       t->l.seg_pred[by4]])))
         {
             // temporal predicted seg_id
-            b->seg_id = f->prev_segmap ?
-                        get_prev_frame_segid(f, t->by, t->bx, w4, h4,
-                                             f->prev_segmap, f->b4_stride) : 0;
+            if (f->prev_segmap) {
+                unsigned seg_id = get_prev_frame_segid(f, t->by, t->bx, w4, h4,
+                                                       f->prev_segmap,
+                                                       f->b4_stride);
+                if (seg_id >= 8) return -1;
+                b->seg_id = seg_id;
+            } else {
+                b->seg_id = 0;
+            }
         } else {
             int seg_ctx;
             const unsigned pred_seg_id =
@@ -1278,7 +1300,7 @@
         if (f->frame_thread.pass == 1) {
             f->bd_fn.read_coef_blocks(t, bs, b);
         } else {
-            f->bd_fn.recon_b_inter(t, bs, b);
+            if (f->bd_fn.recon_b_inter(t, bs, b)) return -1;
         }
 
         splat_intrabc_mv(f->mvs, f->b4_stride, t->by, t->bx, bs, b->mv[0]);
@@ -1770,7 +1792,7 @@
         if (f->frame_thread.pass == 1) {
             f->bd_fn.read_coef_blocks(t, bs, b);
         } else {
-            f->bd_fn.recon_b_inter(t, bs, b);
+            if (f->bd_fn.recon_b_inter(t, bs, b)) return -1;
         }
 
         const int is_globalmv =
@@ -2227,8 +2249,11 @@
 
     if (c->n_fc > 1 && f->frame_hdr.use_ref_frame_mvs) {
         for (int n = 0; n < 7; n++)
-            dav1d_thread_picture_wait(&f->refp[n], 4 * (t->by + sb_step),
-                                      PLANE_TYPE_BLOCK);
+            if (dav1d_thread_picture_wait(&f->refp[n], 4 * (t->by + sb_step),
+                                          PLANE_TYPE_BLOCK))
+            {
+                return 1;
+            }
         av1_init_ref_mv_tile_row(f->libaom_cm,
                                  ts->tiling.col_start, ts->tiling.col_end,
                                  t->by, imin(t->by + sb_step, f->bh));
@@ -2745,6 +2770,8 @@
                             pthread_mutex_unlock(&ts->tile_thread.lock);
                         }
                         if (progress == TILE_ERROR) {
+                            dav1d_thread_picture_signal(&f->cur, FRAME_ERROR,
+                                                        progress_plane_type);
                             const uint64_t all_mask = ~0ULL >> (64 - f->n_tc);
                             pthread_mutex_lock(&f->tile_thread.lock);
                             while (f->tile_thread.available != all_mask)
@@ -2792,10 +2819,10 @@
         }
     }
 
-    dav1d_thread_picture_signal(&f->cur, UINT_MAX, PLANE_TYPE_ALL);
-
     retval = 0;
 error:
+    dav1d_thread_picture_signal(&f->cur, retval == 0 ? UINT_MAX : FRAME_ERROR,
+                                PLANE_TYPE_ALL);
     for (int i = 0; i < 7; i++) {
         if (f->refp[i].p.data[0])
             dav1d_thread_picture_unref(&f->refp[i]);
--- a/src/picture.c
+++ b/src/picture.c
@@ -40,6 +40,7 @@
 #include "src/picture.h"
 #include "src/ref.h"
 #include "src/thread.h"
+#include "src/thread_task.h"
 
 int default_picture_allocator(Dav1dPicture *const p, void *cookie) {
     assert(cookie == NULL);
@@ -211,13 +212,13 @@
     p->progress = NULL;
 }
 
-void dav1d_thread_picture_wait(const Dav1dThreadPicture *const p,
-                               int y_unclipped, const enum PlaneType plane_type)
+int dav1d_thread_picture_wait(const Dav1dThreadPicture *const p,
+                              int y_unclipped, const enum PlaneType plane_type)
 {
     assert(plane_type != PLANE_TYPE_ALL);
 
     if (!p->t)
-        return;
+        return 0;
 
     // convert to luma units; include plane delay from loopfilters; clip
     const int ss_ver = p->p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
@@ -225,14 +226,16 @@
     y_unclipped += (plane_type != PLANE_TYPE_BLOCK) * 8; // delay imposed by loopfilter
     const unsigned y = iclip(y_unclipped, 1, p->p.p.h);
     atomic_uint *const progress = &p->progress[plane_type != PLANE_TYPE_BLOCK];
+    unsigned state;
 
-    if (atomic_load_explicit(progress, memory_order_acquire) >= y)
-        return;
+    if ((state = atomic_load_explicit(progress, memory_order_acquire)) >= y)
+        return state == FRAME_ERROR;
 
     pthread_mutex_lock(&p->t->lock);
-    while (atomic_load_explicit(progress, memory_order_relaxed) < y)
+    while ((state = atomic_load_explicit(progress, memory_order_relaxed)) < y)
         pthread_cond_wait(&p->t->cond, &p->t->lock);
     pthread_mutex_unlock(&p->t->lock);
+    return state == FRAME_ERROR;
 }
 
 void dav1d_thread_picture_signal(const Dav1dThreadPicture *const p,
--- a/src/picture.h
+++ b/src/picture.h
@@ -74,14 +74,17 @@
  * units, else it is in chroma units.
  * plane_type is used to determine how many pixels delay are
  * introduced by loopfilter processes.
+ *
+ * Returns 0 on success, and 1 if there was an error while decoding p
  */
-void dav1d_thread_picture_wait(const Dav1dThreadPicture *p, int y,
+int dav1d_thread_picture_wait(const Dav1dThreadPicture *p, int y,
                                enum PlaneType plane_type);
 
 /**
  * Signal decoding progress.
  *
- * y is in full-pixel luma units.
+ * y is in full-pixel luma units. FRAME_ERROR is used to signal a decoding
+ * error to frames using this frame as reference frame.
  * plane_type denotes whether we have completed block data (pass 1;
  * PLANE_TYPE_BLOCK), pixel data (pass 2, PLANE_TYPE_Y) or both (no
  * 2-pass decoding; PLANE_TYPE_ALL).
--- a/src/recon.h
+++ b/src/recon.h
@@ -42,7 +42,7 @@
 typedef decl_recon_b_intra_fn(*recon_b_intra_fn);
 
 #define decl_recon_b_inter_fn(name) \
-void (name)(Dav1dTileContext *t, enum BlockSize bs, const Av1Block *b)
+int (name)(Dav1dTileContext *t, enum BlockSize bs, const Av1Block *b)
 typedef decl_recon_b_inter_fn(*recon_b_inter_fn);
 
 #define decl_filter_sbrow_fn(name) \
--- a/src/recon_tmpl.c
+++ b/src/recon_tmpl.c
@@ -488,12 +488,12 @@
     }
 }
 
-static void mc(Dav1dTileContext *const t,
-               pixel *const dst8, coef *const dst16, const ptrdiff_t dst_stride,
-               const int bw4, const int bh4,
-               const int bx, const int by, const int pl,
-               const mv mv, const Dav1dThreadPicture *const refp,
-               const enum Filter2d filter_2d)
+static int mc(Dav1dTileContext *const t,
+              pixel *const dst8, coef *const dst16, const ptrdiff_t dst_stride,
+              const int bw4, const int bh4,
+              const int bx, const int by, const int pl,
+              const mv mv, const Dav1dThreadPicture *const refp,
+              const enum Filter2d filter_2d)
 {
     assert((dst8 != NULL) ^ (dst16 != NULL));
     const Dav1dFrameContext *const f = t->f;
@@ -509,8 +509,11 @@
     int w, h;
 
     if (refp != &f->cur) { // i.e. not for intrabc
-        dav1d_thread_picture_wait(refp, dy + bh4 * v_mul + !!my * 4,
-                                  PLANE_TYPE_Y + !!pl);
+        if (dav1d_thread_picture_wait(refp, dy + bh4 * v_mul + !!my * 4,
+                                      PLANE_TYPE_Y + !!pl))
+        {
+            return -1;
+        }
         w = (f->cur.p.p.w + ss_hor) >> ss_hor;
         h = (f->cur.p.p.h + ss_ver) >> ss_ver;
     } else {
@@ -538,12 +541,13 @@
         f->dsp->mc.mct[filter_2d](dst16, ref, ref_stride, bw4 * h_mul,
                                   bh4 * v_mul, mx << !ss_hor, my << !ss_ver);
     }
+    return 0;
 }
 
-static void obmc(Dav1dTileContext *const t,
-                 pixel *const dst, const ptrdiff_t dst_stride,
-                 const uint8_t *const b_dim, const int pl,
-                 const int bx4, const int by4, const int w4, const int h4)
+static int obmc(Dav1dTileContext *const t,
+                pixel *const dst, const ptrdiff_t dst_stride,
+                const uint8_t *const b_dim, const int pl,
+                const int bx4, const int by4, const int w4, const int h4)
 {
     assert(!(t->bx & 1) && !(t->by & 1));
     const Dav1dFrameContext *const f = t->f;
@@ -564,6 +568,7 @@
     const int ss_ver = !!pl && f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
     const int ss_hor = !!pl && f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int h_mul = 4 >> ss_hor, v_mul = 4 >> ss_ver;
+    int res;
 
     if (t->by > t->ts->tiling.row_start &&
         (!pl || b_dim[0] * h_mul + b_dim[1] * v_mul >= 16))
@@ -577,10 +582,11 @@
             if (a_r->ref[0] > 0) {
                 const int ow4 = iclip(a_b_dim[0], 2, b_dim[0]);
                 const int oh4 = imin(b_dim[1], 16) >> 1;
-                mc(t, lap, NULL, ow4 * h_mul * sizeof(pixel), ow4, oh4,
-                   t->bx + x, t->by, pl, a_r->mv[0],
-                   &f->refp[a_r->ref[0] - 1],
-                   dav1d_filter_2d[t->a->filter[1][bx4 + x + 1]][t->a->filter[0][bx4 + x + 1]]);
+                res = mc(t, lap, NULL, ow4 * h_mul * sizeof(pixel), ow4, oh4,
+                         t->bx + x, t->by, pl, a_r->mv[0],
+                         &f->refp[a_r->ref[0] - 1],
+                         dav1d_filter_2d[t->a->filter[1][bx4 + x + 1]][t->a->filter[0][bx4 + x + 1]]);
+                if (res) return res;
                 f->dsp->mc.blend(&dst[x * h_mul], dst_stride, lap,
                                  h_mul * ow4, v_mul * oh4,
                                  obmc_masks[imin(b_dim[3], 4) - ss_ver], 1);
@@ -600,10 +606,11 @@
             if (l_r->ref[0] > 0) {
                 const int ow4 = imin(b_dim[0], 16) >> 1;
                 const int oh4 = iclip(l_b_dim[1], 2, b_dim[1]);
-                mc(t, lap, NULL, h_mul * ow4 * sizeof(pixel), ow4, oh4,
-                   t->bx, t->by + y, pl, l_r->mv[0],
-                   &f->refp[l_r->ref[0] - 1],
-                   dav1d_filter_2d[t->l.filter[1][by4 + y + 1]][t->l.filter[0][by4 + y + 1]]);
+                res = mc(t, lap, NULL, h_mul * ow4 * sizeof(pixel), ow4, oh4,
+                         t->bx, t->by + y, pl, l_r->mv[0],
+                         &f->refp[l_r->ref[0] - 1],
+                         dav1d_filter_2d[t->l.filter[1][by4 + y + 1]][t->l.filter[0][by4 + y + 1]]);
+                if (res) return res;
                 f->dsp->mc.blend(&dst[y * v_mul * PXSTRIDE(dst_stride)], dst_stride,
                                  lap, h_mul * ow4, v_mul * oh4,
                                  obmc_masks[imin(b_dim[2], 4) - ss_hor], 0);
@@ -611,13 +618,14 @@
             }
             y += imax(l_b_dim[1], 2);
         }
+    return 0;
 }
 
-static void warp_affine(Dav1dTileContext *const t,
-                        pixel *dst8, coef *dst16, const ptrdiff_t dstride,
-                        const uint8_t *const b_dim, const int pl,
-                        const Dav1dThreadPicture *const refp,
-                        const WarpedMotionParams *const wmp)
+static int warp_affine(Dav1dTileContext *const t,
+                       pixel *dst8, coef *dst16, const ptrdiff_t dstride,
+                       const uint8_t *const b_dim, const int pl,
+                       const Dav1dThreadPicture *const refp,
+                       const WarpedMotionParams *const wmp)
 {
     assert((dst8 != NULL) ^ (dst16 != NULL));
     const Dav1dFrameContext *const f = t->f;
@@ -649,8 +657,11 @@
             const pixel *ref_ptr;
             ptrdiff_t ref_stride = refp->p.stride[!!pl];
 
-            dav1d_thread_picture_wait(refp, dy + 4 + 8,
-                                      PLANE_TYPE_Y + !!pl);
+            if (dav1d_thread_picture_wait(refp, dy + 4 + 8,
+                                          PLANE_TYPE_Y + !!pl))
+            {
+                return -1;
+            }
             if (dx < 3 || dx + 8 + 4 > width || dy < 3 || dy + 8 + 4 > height) {
                 f->dsp->mc.emu_edge(t->emu_edge, 160 * sizeof(pixel),
                                     refp->p.data[pl], ref_stride,
@@ -670,6 +681,7 @@
         if (dst8) dst8  += 8 * PXSTRIDE(dstride);
         else      dst16 += 8 * dstride;
     }
+    return 0;
 }
 
 void bytefn(dav1d_recon_b_intra)(Dav1dTileContext *const t, const enum BlockSize bs,
@@ -1054,7 +1066,7 @@
     }
 }
 
-void bytefn(dav1d_recon_b_inter)(Dav1dTileContext *const t, const enum BlockSize bs,
+int bytefn(dav1d_recon_b_inter)(Dav1dTileContext *const t, const enum BlockSize bs,
                                  const Av1Block *const b)
 {
     Dav1dTileState *const ts = t->ts;
@@ -1072,6 +1084,7 @@
                            (bh4 > ss_ver || t->by & 1);
     const int chr_layout_idx = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I400 ? 0 :
                                DAV1D_PIXEL_LAYOUT_I444 - f->cur.p.p.layout;
+    int res;
 
     // prediction
     const int cbh4 = (bh4 + ss_ver) >> ss_ver, cbw4 = (bw4 + ss_hor) >> ss_hor;
@@ -1081,13 +1094,16 @@
         4 * ((t->bx >> ss_hor) + (t->by >> ss_ver) * PXSTRIDE(f->cur.p.stride[1]));
     if (!(f->frame_hdr.frame_type & 1)) {
         // intrabc
-        mc(t, dst, NULL, f->cur.p.stride[0],
-           bw4, bh4, t->bx, t->by, 0, b->mv[0], &f->cur, FILTER_2D_BILINEAR);
-        if (has_chroma) for (int pl = 1; pl < 3; pl++)
-            mc(t, ((pixel *) f->cur.p.data[pl]) + uvdstoff, NULL, f->cur.p.stride[1],
-               bw4 << (bw4 == ss_hor), bh4 << (bh4 == ss_ver),
-               t->bx & ~ss_hor, t->by & ~ss_ver,
-               pl, b->mv[0], &f->cur, FILTER_2D_BILINEAR);
+        res = mc(t, dst, NULL, f->cur.p.stride[0],
+                 bw4, bh4, t->bx, t->by, 0, b->mv[0], &f->cur, FILTER_2D_BILINEAR);
+        if (res) return res;
+        if (has_chroma) for (int pl = 1; pl < 3; pl++) {
+            res = mc(t, ((pixel *)f->cur.p.data[pl]) + uvdstoff, NULL, f->cur.p.stride[1],
+                     bw4 << (bw4 == ss_hor), bh4 << (bh4 == ss_ver),
+                     t->bx & ~ss_hor, t->by & ~ss_ver,
+                     pl, b->mv[0], &f->cur, FILTER_2D_BILINEAR);
+            if (res) return res;
+        }
     } else if (b->comp_type == COMP_INTER_NONE) {
         const Dav1dThreadPicture *const refp = &f->refp[b->ref[0]];
         const enum Filter2d filter_2d = b->filter2d;
@@ -1098,14 +1114,18 @@
              (b->motion_mode == MM_WARP &&
               t->warpmv.type > WM_TYPE_TRANSLATION)))
         {
-            warp_affine(t, dst, NULL, f->cur.p.stride[0], b_dim, 0, refp,
-                        b->motion_mode == MM_WARP ? &t->warpmv :
-                            &f->frame_hdr.gmv[b->ref[0]]);
+            res = warp_affine(t, dst, NULL, f->cur.p.stride[0], b_dim, 0, refp,
+                              b->motion_mode == MM_WARP ? &t->warpmv :
+                                  &f->frame_hdr.gmv[b->ref[0]]);
+            if (res) return res;
         } else {
-            mc(t, dst, NULL, f->cur.p.stride[0],
-               bw4, bh4, t->bx, t->by, 0, b->mv[0], refp, filter_2d);
-            if (b->motion_mode == MM_OBMC)
-                obmc(t, dst, f->cur.p.stride[0], b_dim, 0, bx4, by4, w4, h4);
+            res = mc(t, dst, NULL, f->cur.p.stride[0],
+                     bw4, bh4, t->bx, t->by, 0, b->mv[0], refp, filter_2d);
+            if (res) return res;
+            if (b->motion_mode == MM_OBMC) {
+                res = obmc(t, dst, f->cur.p.stride[0], b_dim, 0, bx4, by4, w4, h4);
+                if (res) return res;
+            }
         }
         if (b->interintra_type) {
             ALIGN_STK_32(pixel, tl_edge_buf, 65,);
@@ -1154,14 +1174,16 @@
             assert(ss_hor == 1);
             int h_off = 0, v_off = 0;
             if (bw4 == 1 && bh4 == ss_ver) {
-                for (int pl = 0; pl < 2; pl++)
-                    mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff,
-                       NULL, f->cur.p.stride[1],
-                       bw4, bh4, t->bx - 1, t->by - 1, 1 + pl,
-                       r[-(f->b4_stride + 1)].mv[0],
-                       &f->refp[r[-(f->b4_stride + 1)].ref[0] - 1],
-                       f->frame_thread.pass != 2 ? t->tl_4x4_filter :
-                           f->frame_thread.b[((t->by - 1) * f->b4_stride) + t->bx - 1].filter2d);
+                for (int pl = 0; pl < 2; pl++) {
+                    res = mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff,
+                             NULL, f->cur.p.stride[1],
+                             bw4, bh4, t->bx - 1, t->by - 1, 1 + pl,
+                             r[-(f->b4_stride + 1)].mv[0],
+                             &f->refp[r[-(f->b4_stride + 1)].ref[0] - 1],
+                             f->frame_thread.pass != 2 ? t->tl_4x4_filter :
+                                 f->frame_thread.b[((t->by - 1) * f->b4_stride) + t->bx - 1].filter2d);
+                    if (res) return res;
+                }
                 v_off = 2 * PXSTRIDE(f->cur.p.stride[1]);
                 h_off = 2;
             }
@@ -1168,29 +1190,35 @@
             if (bw4 == 1) {
                 const enum Filter2d left_filter_2d =
                     dav1d_filter_2d[t->l.filter[1][by4]][t->l.filter[0][by4]];
-                for (int pl = 0; pl < 2; pl++)
-                    mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff + v_off, NULL,
-                       f->cur.p.stride[1], bw4, bh4, t->bx - 1,
-                       t->by, 1 + pl, r[-1].mv[0], &f->refp[r[-1].ref[0] - 1],
-                       f->frame_thread.pass != 2 ? left_filter_2d :
-                           f->frame_thread.b[(t->by * f->b4_stride) + t->bx - 1].filter2d);
+                for (int pl = 0; pl < 2; pl++) {
+                    res = mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff + v_off, NULL,
+                             f->cur.p.stride[1], bw4, bh4, t->bx - 1,
+                             t->by, 1 + pl, r[-1].mv[0], &f->refp[r[-1].ref[0] - 1],
+                             f->frame_thread.pass != 2 ? left_filter_2d :
+                                 f->frame_thread.b[(t->by * f->b4_stride) + t->bx - 1].filter2d);
+                    if (res) return res;
+                }
                 h_off = 2;
             }
             if (bh4 == ss_ver) {
                 const enum Filter2d top_filter_2d =
                     dav1d_filter_2d[t->a->filter[1][bx4]][t->a->filter[0][bx4]];
-                for (int pl = 0; pl < 2; pl++)
-                    mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff + h_off, NULL,
-                       f->cur.p.stride[1], bw4, bh4, t->bx, t->by - 1,
-                       1 + pl, r[-f->b4_stride].mv[0],
-                       &f->refp[r[-f->b4_stride].ref[0] - 1],
-                       f->frame_thread.pass != 2 ? top_filter_2d :
-                           f->frame_thread.b[((t->by - 1) * f->b4_stride) + t->bx].filter2d);
+                for (int pl = 0; pl < 2; pl++) {
+                    res = mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff + h_off, NULL,
+                             f->cur.p.stride[1], bw4, bh4, t->bx, t->by - 1,
+                             1 + pl, r[-f->b4_stride].mv[0],
+                             &f->refp[r[-f->b4_stride].ref[0] - 1],
+                             f->frame_thread.pass != 2 ? top_filter_2d :
+                                 f->frame_thread.b[((t->by - 1) * f->b4_stride) + t->bx].filter2d);
+                    if (res) return res;
+                }
                 v_off = 2 * PXSTRIDE(f->cur.p.stride[1]);
             }
-            for (int pl = 0; pl < 2; pl++)
-                mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff + h_off + v_off, NULL, f->cur.p.stride[1],
-                   bw4, bh4, t->bx, t->by, 1 + pl, b->mv[0], refp, filter_2d);
+            for (int pl = 0; pl < 2; pl++) {
+                res = mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff + h_off + v_off, NULL, f->cur.p.stride[1],
+                         bw4, bh4, t->bx, t->by, 1 + pl, b->mv[0], refp, filter_2d);
+                if (res) return res;
+            }
         } else {
             if (imin(cbw4, cbh4) > 1 && !f->frame_hdr.force_integer_mv &&
                 ((b->inter_mode == GLOBALMV &&
@@ -1198,21 +1226,26 @@
                  (b->motion_mode == MM_WARP &&
                   t->warpmv.type > WM_TYPE_TRANSLATION)))
             {
-                for (int pl = 0; pl < 2; pl++)
-                    warp_affine(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff, NULL,
-                                f->cur.p.stride[1], b_dim, 1 + pl, refp,
-                                b->motion_mode == MM_WARP ? &t->warpmv :
-                                    &f->frame_hdr.gmv[b->ref[0]]);
+                for (int pl = 0; pl < 2; pl++) {
+                    res = warp_affine(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff, NULL,
+                                      f->cur.p.stride[1], b_dim, 1 + pl, refp,
+                                      b->motion_mode == MM_WARP ? &t->warpmv :
+                                          &f->frame_hdr.gmv[b->ref[0]]);
+                    if (res) return res;
+                }
             } else {
                 for (int pl = 0; pl < 2; pl++) {
-                    mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff,
-                       NULL, f->cur.p.stride[1],
-                       bw4 << (bw4 == ss_hor), bh4 << (bh4 == ss_ver),
-                       t->bx & ~ss_hor, t->by & ~ss_ver,
-                       1 + pl, b->mv[0], refp, filter_2d);
-                    if (b->motion_mode == MM_OBMC)
-                        obmc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff,
-                             f->cur.p.stride[1], b_dim, 1 + pl, bx4, by4, w4, h4);
+                    res = mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff,
+                             NULL, f->cur.p.stride[1],
+                             bw4 << (bw4 == ss_hor), bh4 << (bh4 == ss_ver),
+                             t->bx & ~ss_hor, t->by & ~ss_ver,
+                             1 + pl, b->mv[0], refp, filter_2d);
+                    if (res) return res;
+                    if (b->motion_mode == MM_OBMC) {
+                        res = obmc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff,
+                                   f->cur.p.stride[1], b_dim, 1 + pl, bx4, by4, w4, h4);
+                        if (res) return res;
+                    }
                 }
             }
             if (b->interintra_type) {
@@ -1273,11 +1306,13 @@
             if (b->inter_mode == GLOBALMV_GLOBALMV && !f->frame_hdr.force_integer_mv &&
                 f->frame_hdr.gmv[b->ref[i]].type > WM_TYPE_TRANSLATION)
             {
-                warp_affine(t, NULL, tmp[i], bw4 * 4, b_dim, 0, refp,
-                            &f->frame_hdr.gmv[b->ref[i]]);
+                res = warp_affine(t, NULL, tmp[i], bw4 * 4, b_dim, 0, refp,
+                                  &f->frame_hdr.gmv[b->ref[i]]);
+                if (res) return res;
             } else {
-                mc(t, NULL, tmp[i], 0, bw4, bh4, t->bx, t->by, 0,
-                   b->mv[i], refp, filter_2d);
+                res = mc(t, NULL, tmp[i], 0, bw4, bh4, t->bx, t->by, 0,
+                         b->mv[i], refp, filter_2d);
+                if (res) return res;
             }
         }
         switch (b->comp_type) {
@@ -1314,11 +1349,13 @@
                     imin(cbw4, cbh4) > 1 && !f->frame_hdr.force_integer_mv &&
                     f->frame_hdr.gmv[b->ref[i]].type > WM_TYPE_TRANSLATION)
                 {
-                    warp_affine(t, NULL, tmp[i], bw4 * 2, b_dim, 1 + pl,
-                                refp, &f->frame_hdr.gmv[b->ref[i]]);
+                    res = warp_affine(t, NULL, tmp[i], bw4 * 2, b_dim, 1 + pl,
+                                      refp, &f->frame_hdr.gmv[b->ref[i]]);
+                    if (res) return res;
                 } else {
-                    mc(t, NULL, tmp[i], 0, bw4, bh4, t->bx, t->by,
-                       1 + pl, b->mv[i], refp, filter_2d);
+                    res = mc(t, NULL, tmp[i], 0, bw4, bh4, t->bx, t->by,
+                             1 + pl, b->mv[i], refp, filter_2d);
+                    if (res) return res;
                 }
             }
             pixel *const uvdst = ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff;
@@ -1368,7 +1405,7 @@
             case_set(cbw4, a->, 0, cbx4);
 #undef set_ctx
         }
-        return;
+        return 0;
     }
 
     const TxfmInfo *const uvtx = &dav1d_txfm_dimensions[b->uvtx];
@@ -1465,6 +1502,7 @@
             }
         }
     }
+    return 0;
 }
 
 void bytefn(dav1d_filter_sbrow)(Dav1dFrameContext *const f, const int sby) {
--- a/src/thread_task.h
+++ b/src/thread_task.h
@@ -32,6 +32,7 @@
 
 #include "src/internal.h"
 
+#define FRAME_ERROR (UINT_MAX - 1)
 #define TILE_ERROR (INT_MAX - 1)
 
 int dav1d_decode_frame(Dav1dFrameContext *f);