shithub: dav1d

Download patch

ref: 0fdee4daee5b70dcbfc8e3f3fd5d82095b57d6bf
parent: d27598e418b0b9916c35b3e5187a5f98c00fe701
author: Ronald S. Bultje <[email protected]>
date: Thu Nov 15 03:11:47 EST 2018

Add support for super-res

Fixes #172.

--- a/src/cdef_apply_tmpl.c
+++ b/src/cdef_apply_tmpl.c
@@ -89,7 +89,7 @@
     const int sbsz = 16;
     const int sb64w = f->sb128w << 1;
     const int damping = f->frame_hdr.cdef.damping + BITDEPTH - 8;
-    const enum Dav1dPixelLayout layout = f->cur.p.p.layout;
+    const enum Dav1dPixelLayout layout = f->cur.p.layout;
     const int uv_idx = DAV1D_PIXEL_LAYOUT_I444 - layout;
     const int has_chroma = layout != DAV1D_PIXEL_LAYOUT_I400;
     const int ss_ver = layout == DAV1D_PIXEL_LAYOUT_I420;
@@ -106,7 +106,7 @@
 
         if (edges & HAVE_BOTTOM) {
             // backup pre-filter data for next iteration
-            backup2lines(f->lf.cdef_line_ptr[!tf], ptrs, f->cur.p.stride,
+            backup2lines(f->lf.cdef_line_ptr[!tf], ptrs, f->cur.stride,
                          8, f->bw * 4, layout);
         }
 
@@ -148,11 +148,11 @@
                 if (last_skip && edges & HAVE_LEFT) {
                     // we didn't backup the prefilter data because it wasn't
                     // there, so do it here instead
-                    backup2x8(lr_bak[bit], bptrs, f->cur.p.stride, 0, layout);
+                    backup2x8(lr_bak[bit], bptrs, f->cur.stride, 0, layout);
                 }
                 if (edges & HAVE_RIGHT) {
                     // backup pre-filter data for next iteration
-                    backup2x8(lr_bak[!bit], bptrs, f->cur.p.stride, 8, layout);
+                    backup2x8(lr_bak[!bit], bptrs, f->cur.stride, 8, layout);
                 }
 
                 // the actual filter
@@ -165,10 +165,10 @@
                 uv_sec_lvl += uv_sec_lvl == 3;
                 uv_sec_lvl <<= BITDEPTH - 8;
                 unsigned variance;
-                const int dir = dsp->cdef.dir(bptrs[0], f->cur.p.stride[0],
+                const int dir = dsp->cdef.dir(bptrs[0], f->cur.stride[0],
                                               &variance);
                 if (y_lvl) {
-                    dsp->cdef.fb[0](bptrs[0], f->cur.p.stride[0], lr_bak[bit][0],
+                    dsp->cdef.fb[0](bptrs[0], f->cur.stride[0], lr_bak[bit][0],
                                     (pixel *const [2]) {
                                         &f->lf.cdef_line_ptr[tf][0][0][bx * 4],
                                         &f->lf.cdef_line_ptr[tf][0][1][bx * 4],
@@ -179,10 +179,10 @@
                 }
                 if (uv_lvl && has_chroma) {
                     const int uvdir =
-                        f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I422 ? dir :
+                        f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I422 ? dir :
                         ((uint8_t[]) { 7, 0, 2, 4, 5, 6, 6, 6 })[dir];
                     for (int pl = 1; pl <= 2; pl++) {
-                        dsp->cdef.fb[uv_idx](bptrs[pl], f->cur.p.stride[1],
+                        dsp->cdef.fb[uv_idx](bptrs[pl], f->cur.stride[1],
                                              lr_bak[bit][pl],
                                              (pixel *const [2]) {
                                                  &f->lf.cdef_line_ptr[tf][pl][0][bx * 4 >> ss_hor],
@@ -209,9 +209,9 @@
             iptrs[2] += sbsz * 4 >> ss_hor;
         }
 
-        ptrs[0] += 8 * PXSTRIDE(f->cur.p.stride[0]);
-        ptrs[1] += 8 * PXSTRIDE(f->cur.p.stride[1]) >> ss_ver;
-        ptrs[2] += 8 * PXSTRIDE(f->cur.p.stride[1]) >> ss_ver;
+        ptrs[0] += 8 * PXSTRIDE(f->cur.stride[0]);
+        ptrs[1] += 8 * PXSTRIDE(f->cur.stride[1]) >> ss_ver;
+        ptrs[2] += 8 * PXSTRIDE(f->cur.stride[1]) >> ss_ver;
         f->lf.top_pre_cdef_toggle ^= 1;
     }
 }
--- a/src/decode.c
+++ b/src/decode.c
@@ -422,11 +422,11 @@
         f->frame_thread.pal[((t->by >> 1) + (t->bx & 1)) * (f->b4_stride >> 1) +
                             ((t->bx >> 1) + (t->by & 1))][pl] : t->pal[pl];
     if (i < pal_sz) {
-        int prev = pal[i++] = msac_decode_bools(&ts->msac, f->cur.p.p.bpc);
+        int prev = pal[i++] = msac_decode_bools(&ts->msac, f->cur.p.bpc);
 
         if (i < pal_sz) {
-            int bits = f->cur.p.p.bpc - 3 + msac_decode_bools(&ts->msac, 2);
-            const int max = (1 << f->cur.p.p.bpc) - 1;
+            int bits = f->cur.p.bpc - 3 + msac_decode_bools(&ts->msac, 2);
+            const int max = (1 << f->cur.p.bpc) - 1;
 
             do {
                 const int delta = msac_decode_bools(&ts->msac, bits);
@@ -478,9 +478,9 @@
         f->frame_thread.pal[((t->by >> 1) + (t->bx & 1)) * (f->b4_stride >> 1) +
                             ((t->bx >> 1) + (t->by & 1))][2] : t->pal[2];
     if (msac_decode_bool(&ts->msac, EC_BOOL_EPROB)) {
-        const int bits = f->cur.p.p.bpc - 4 + msac_decode_bools(&ts->msac, 2);
-        int prev = pal[0] = msac_decode_bools(&ts->msac, f->cur.p.p.bpc);
-        const int max = (1 << f->cur.p.p.bpc) - 1;
+        const int bits = f->cur.p.bpc - 4 + msac_decode_bools(&ts->msac, 2);
+        int prev = pal[0] = msac_decode_bools(&ts->msac, f->cur.p.bpc);
+        const int max = (1 << f->cur.p.bpc) - 1;
         for (int i = 1; i < b->pal_sz[1]; i++) {
             int delta = msac_decode_bools(&ts->msac, bits);
             if (delta && msac_decode_bool(&ts->msac, EC_BOOL_EPROB)) delta = -delta;
@@ -488,7 +488,7 @@
         }
     } else {
         for (int i = 0; i < b->pal_sz[1]; i++)
-            pal[i] = msac_decode_bools(&ts->msac, f->cur.p.p.bpc);
+            pal[i] = msac_decode_bools(&ts->msac, f->cur.p.bpc);
     }
     if (DEBUG_BLOCK_INFO) {
         printf("Post-pal[pl=2]: r=%d ", ts->msac.rng);
@@ -634,7 +634,7 @@
         } else {
             assert(f->frame_hdr.txfm_mode == TX_LARGEST);
         }
-        b->uvtx = dav1d_max_txfm_size_for_bs[bs][f->cur.p.p.layout];
+        b->uvtx = dav1d_max_txfm_size_for_bs[bs][f->cur.p.layout];
     } else {
         assert(imin(bw4, bh4) <= 16 || b->max_ytx == TX_64X64);
         int y, x, y_off, x_off;
@@ -652,7 +652,7 @@
         if (DEBUG_BLOCK_INFO)
             printf("Post-vartxtree[%x/%x]: r=%d\n",
                    b->tx_split[0], b->tx_split[1], t->ts->msac.rng);
-        b->uvtx = dav1d_max_txfm_size_for_bs[bs][f->cur.p.p.layout];
+        b->uvtx = dav1d_max_txfm_size_for_bs[bs][f->cur.p.layout];
     }
 }
 
@@ -694,8 +694,8 @@
         &f->frame_thread.b[t->by * f->b4_stride + t->bx] : &b_mem;
     const uint8_t *const b_dim = dav1d_block_dimensions[bs];
     const int bx4 = t->bx & 31, by4 = t->by & 31;
-    const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-    const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+    const int ss_ver = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_hor = f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int cbx4 = bx4 >> ss_hor, cby4 = by4 >> ss_ver;
     const int bw4 = b_dim[0], bh4 = b_dim[1];
     const int w4 = imin(bw4, f->bw - t->bx), h4 = imin(bh4, f->bh - t->by);
@@ -1138,7 +1138,7 @@
             t_dim = &dav1d_txfm_dimensions[TX_4X4];
         } else {
             b->tx = dav1d_max_txfm_size_for_bs[bs][0];
-            b->uvtx = dav1d_max_txfm_size_for_bs[bs][f->cur.p.p.layout];
+            b->uvtx = dav1d_max_txfm_size_for_bs[bs][f->cur.p.layout];
             t_dim = &dav1d_txfm_dimensions[b->tx];
             if (f->frame_hdr.txfm_mode == TX_SWITCHABLE && t_dim->max > TX_4X4) {
                 const int tctx = get_tx_ctx(t->a, &t->l, t_dim, by4, bx4);
@@ -1166,7 +1166,7 @@
                                    &f->frame_hdr, (const uint8_t (*)[8][2])
                                    &ts->lflvl[b->seg_id][0][0][0],
                                    t->bx, t->by, f->w4, f->h4, bs,
-                                   b->tx, b->uvtx, f->cur.p.p.layout,
+                                   b->tx, b->uvtx, f->cur.p.layout,
                                    &t->a->tx_lpf_y[bx4], &t->l.tx_lpf_y[by4],
                                    has_chroma ? &t->a->tx_lpf_uv[cbx4] : NULL,
                                    has_chroma ? &t->l.tx_lpf_uv[cby4] : NULL);
@@ -1543,7 +1543,7 @@
                 if (f->seq_hdr.jnt_comp) {
                     const int jnt_ctx =
                         get_jnt_comp_ctx(f->seq_hdr.order_hint_n_bits,
-                                         f->cur.p.poc, f->refp[b->ref[0]].p.poc,
+                                         f->cur.poc, f->refp[b->ref[0]].p.poc,
                                          f->refp[b->ref[1]].p.poc, t->a, &t->l,
                                          by4, bx4);
                     b->comp_type = COMP_INTER_WEIGHTED_AVG +
@@ -1833,7 +1833,7 @@
         dav1d_create_lf_mask_inter(t->lf_mask, f->lf.level, f->b4_stride,
                                    &f->frame_hdr, lf_lvls, t->bx, t->by,
                                    f->w4, f->h4, b->skip, bs, b->tx_split,
-                                   b->uvtx, f->cur.p.p.layout,
+                                   b->uvtx, f->cur.p.layout,
                                    &t->a->tx_lpf_y[bx4], &t->l.tx_lpf_y[by4],
                                    has_chroma ? &t->a->tx_lpf_uv[cbx4] : NULL,
                                    has_chroma ? &t->l.tx_lpf_uv[cby4] : NULL);
@@ -1938,7 +1938,7 @@
             const unsigned n_part = bl == BL_8X8 ? N_SUB8X8_PARTITIONS :
                 bl == BL_128X128 ? N_PARTITIONS - 2 : N_PARTITIONS;
             bp = msac_decode_symbol_adapt(&t->ts->msac, pc, n_part);
-            if (f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I422 &&
+            if (f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I422 &&
                 (bp == PARTITION_V || bp == PARTITION_V4 ||
                  bp == PARTITION_T_LEFT_SPLIT || bp == PARTITION_T_RIGHT_SPLIT))
             {
@@ -2139,7 +2139,7 @@
         } else {
             uint16_t cdf[2] = { gather_left_partition_prob(pc, bl), 0 };
             is_split = msac_decode_symbol(&t->ts->msac, cdf, 2);
-            if (f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I422 && !is_split)
+            if (f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I422 && !is_split)
                 return 1;
             if (DEBUG_BLOCK_INFO)
                 printf("poc=%d,y=%d,x=%d,bl=%d,ctx=%d,bp=%d: r=%d\n",
@@ -2230,12 +2230,30 @@
     ts->tiling.row_end = imin(row_sb_end << sb_shift, f->bh);
 
     // Reference Restoration Unit (used for exp coding)
-    Av1Filter *const lf_mask =
-        f->lf.mask + (ts->tiling.row_start >> 5) * f->sb128w + col_sb128_start;
-    const int unit_idx = ((ts->tiling.row_start & 16) >> 3) +
-                         ((ts->tiling.col_start & 16) >> 4);
+    int sb_idx, unit_idx;
+    if (f->frame_hdr.super_res.enabled) {
+        // vertical components only
+        sb_idx = (ts->tiling.row_start >> 5) * f->sr_sb128w;
+        unit_idx = (ts->tiling.row_start & 16) >> 3;
+    } else {
+        sb_idx = (ts->tiling.row_start >> 5) * f->sb128w + col_sb128_start;
+        unit_idx = ((ts->tiling.row_start & 16) >> 3) +
+                   ((ts->tiling.col_start & 16) >> 4);
+    }
     for (int p = 0; p < 3; p++) {
-        ts->lr_ref[p] = &lf_mask->lr[p][unit_idx];
+        if (f->frame_hdr.super_res.enabled) {
+            const int ss_hor = p && f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+            const int d = f->frame_hdr.super_res.width_scale_denominator;
+            const int unit_size_log2 = f->frame_hdr.restoration.unit_size[!!p];
+            const int rnd = (8 << unit_size_log2) - 1, shift = unit_size_log2 + 3;
+            const int x = ((4 * ts->tiling.col_start * d >> ss_hor) + rnd) >> shift;
+            const int px_x = x << (unit_size_log2 + ss_hor);
+            const int u_idx = unit_idx + ((px_x & 64) >> 1);
+            ts->lr_ref[p] = &f->lf.lr_mask[sb_idx + (px_x >> 7)].lr[p][u_idx];
+        } else {
+            ts->lr_ref[p] = &f->lf.lr_mask[sb_idx].lr[p][unit_idx];
+        }
+
         ts->lr_ref[p]->filter_v[0] = 3;
         ts->lr_ref[p]->filter_v[1] = -7;
         ts->lr_ref[p]->filter_v[2] = 15;
@@ -2250,6 +2268,87 @@
         atomic_init(&ts->progress, row_sb_start);
 }
 
+static void read_restoration_info(Dav1dTileContext *const t,
+                                  Av1RestorationUnit *const lr, const int p,
+                                  const enum RestorationType frame_type)
+{
+    const Dav1dFrameContext *const f = t->f;
+    Dav1dTileState *const ts = t->ts;
+
+    if (frame_type == RESTORATION_SWITCHABLE) {
+        const int filter =
+            msac_decode_symbol_adapt(&ts->msac,
+                                     ts->cdf.m.restore_switchable, 3);
+        lr->type = filter ? filter == 2 ? RESTORATION_SGRPROJ :
+                                          RESTORATION_WIENER :
+                            RESTORATION_NONE;
+    } else {
+        const unsigned type =
+            msac_decode_bool_adapt(&ts->msac,
+                                   frame_type == RESTORATION_WIENER ?
+                                       ts->cdf.m.restore_wiener :
+                                       ts->cdf.m.restore_sgrproj);
+        lr->type = type ? frame_type : RESTORATION_NONE;
+    }
+
+    if (lr->type == RESTORATION_WIENER) {
+        lr->filter_v[0] =
+            !p ? msac_decode_subexp(&ts->msac,
+                                    ts->lr_ref[p]->filter_v[0] + 5, 16,
+                                    1) - 5:
+                 0;
+        lr->filter_v[1] =
+            msac_decode_subexp(&ts->msac,
+                               ts->lr_ref[p]->filter_v[1] + 23, 32,
+                               2) - 23;
+        lr->filter_v[2] =
+            msac_decode_subexp(&ts->msac,
+                               ts->lr_ref[p]->filter_v[2] + 17, 64,
+                               3) - 17;
+
+        lr->filter_h[0] =
+            !p ? msac_decode_subexp(&ts->msac,
+                                    ts->lr_ref[p]->filter_h[0] + 5, 16,
+                                    1) - 5:
+                0;
+        lr->filter_h[1] =
+            msac_decode_subexp(&ts->msac,
+                               ts->lr_ref[p]->filter_h[1] + 23, 32,
+                               2) - 23;
+        lr->filter_h[2] =
+            msac_decode_subexp(&ts->msac,
+                               ts->lr_ref[p]->filter_h[2] + 17, 64,
+                               3) - 17;
+        memcpy(lr->sgr_weights, ts->lr_ref[p]->sgr_weights, sizeof(lr->sgr_weights));
+        ts->lr_ref[p] = lr;
+        if (DEBUG_BLOCK_INFO)
+            printf("Post-lr_wiener[pl=%d,v[%d,%d,%d],h[%d,%d,%d]]: r=%d\n",
+                   p, lr->filter_v[0], lr->filter_v[1],
+                   lr->filter_v[2], lr->filter_h[0],
+                   lr->filter_h[1], lr->filter_h[2], ts->msac.rng);
+    } else if (lr->type == RESTORATION_SGRPROJ) {
+        const unsigned idx = msac_decode_bools(&ts->msac, 4);
+        lr->sgr_idx = idx;
+        lr->sgr_weights[0] = dav1d_sgr_params[idx][0] ?
+            msac_decode_subexp(&ts->msac,
+                               ts->lr_ref[p]->sgr_weights[0] + 96, 128,
+                               4) - 96 :
+            0;
+        lr->sgr_weights[1] = dav1d_sgr_params[idx][1] ?
+            msac_decode_subexp(&ts->msac,
+                               ts->lr_ref[p]->sgr_weights[1] + 32, 128,
+                               4) - 32 :
+            iclip(128 - lr->sgr_weights[0], -32, 95);
+        memcpy(lr->filter_v, ts->lr_ref[p]->filter_v, sizeof(lr->filter_v));
+        memcpy(lr->filter_h, ts->lr_ref[p]->filter_h, sizeof(lr->filter_h));
+        ts->lr_ref[p] = lr;
+        if (DEBUG_BLOCK_INFO)
+            printf("Post-lr_sgrproj[pl=%d,idx=%d,w[%d,%d]]: r=%d\n",
+                   p, lr->sgr_idx, lr->sgr_weights[0],
+                   lr->sgr_weights[1], ts->msac.rng);
+    }
+}
+
 int dav1d_decode_tile_sbrow(Dav1dTileContext *const t) {
     const Dav1dFrameContext *const f = t->f;
     const enum BlockLevel root_bl = f->seq_hdr.sb128 ? BL_128X128 : BL_64X64;
@@ -2275,9 +2374,6 @@
         return 0;
     }
 
-    const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-    const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
-
     if (c->n_fc > 1 && f->frame_hdr.use_ref_frame_mvs) {
         for (int n = 0; n < 7; n++)
             if (dav1d_thread_picture_wait(&f->refp[n], 4 * (t->by + sb_step),
@@ -2311,99 +2407,52 @@
         for (int p = 0; p < 3; p++) {
             if (f->frame_hdr.restoration.type[p] == RESTORATION_NONE)
                 continue;
-            const int by = t->by >> (ss_ver & !!p);
-            const int bx = t->bx >> (ss_hor & !!p);
-            const int bh = f->bh >> (ss_ver & !!p);
-            const int bw = f->bw >> (ss_hor & !!p);
 
-            const int unit_size_log2 =
-                f->frame_hdr.restoration.unit_size[!!p];
-            // 4pel unit size
-            const int b_unit_size = 1 << (unit_size_log2 - 2);
-            const unsigned mask = b_unit_size - 1;
-            if (by & mask || bx & mask) continue;
-            const int half_unit = b_unit_size >> 1;
+            const int ss_ver = p && f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+            const int ss_hor = p && f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+            const int unit_size_log2 = f->frame_hdr.restoration.unit_size[!!p];
+            const int y = t->by * 4 >> ss_ver;
+            const int h = (f->cur.p.h + ss_ver) >> ss_ver;
+
+            const int unit_size = 1 << unit_size_log2;
+            const unsigned mask = unit_size - 1;
+            if (y & mask) continue;
+            const int half_unit = unit_size >> 1;
             // Round half up at frame boundaries, if there's more than one
             // restoration unit
-            const int bottom_round = by && by + half_unit > bh;
-            const int right_round = bx && bx + half_unit > bw;
-            if (bottom_round || right_round) continue;
-            const int unit_idx = ((t->by & 16) >> 3) + ((t->bx & 16) >> 4);
-            Av1RestorationUnit *const lr = &t->lf_mask->lr[p][unit_idx];
-            const enum RestorationType frame_type =
-                f->frame_hdr.restoration.type[p];
+            if (y && y + half_unit > h) continue;
 
-            if (frame_type == RESTORATION_SWITCHABLE) {
-                const int filter =
-                    msac_decode_symbol_adapt(&ts->msac,
-                                             ts->cdf.m.restore_switchable, 3);
-                lr->type = filter ? filter == 2 ? RESTORATION_SGRPROJ :
-                                                  RESTORATION_WIENER :
-                                    RESTORATION_NONE;
-            } else {
-                const unsigned type =
-                    msac_decode_bool_adapt(&ts->msac,
-                                           frame_type == RESTORATION_WIENER ?
-                                               ts->cdf.m.restore_wiener :
-                                               ts->cdf.m.restore_sgrproj);
-                lr->type = type ? frame_type : RESTORATION_NONE;
-            }
+            const enum RestorationType frame_type = f->frame_hdr.restoration.type[p];
 
-            if (lr->type == RESTORATION_WIENER) {
-                lr->filter_v[0] =
-                    !p ? msac_decode_subexp(&ts->msac,
-                                            ts->lr_ref[p]->filter_v[0] + 5, 16,
-                                            1) - 5:
-                         0;
-                lr->filter_v[1] =
-                    msac_decode_subexp(&ts->msac,
-                                       ts->lr_ref[p]->filter_v[1] + 23, 32,
-                                       2) - 23;
-                lr->filter_v[2] =
-                    msac_decode_subexp(&ts->msac,
-                                       ts->lr_ref[p]->filter_v[2] + 17, 64,
-                                       3) - 17;
+            if (f->frame_hdr.super_res.enabled) {
+                const int w = (f->sr_cur.p.p.w + ss_hor) >> ss_hor;
+                const int n_units = imax(1, (w + half_unit) >> unit_size_log2);
 
-                lr->filter_h[0] =
-                    !p ? msac_decode_subexp(&ts->msac,
-                                            ts->lr_ref[p]->filter_h[0] + 5, 16,
-                                            1) - 5:
-                        0;
-                lr->filter_h[1] =
-                    msac_decode_subexp(&ts->msac,
-                                       ts->lr_ref[p]->filter_h[1] + 23, 32,
-                                       2) - 23;
-                lr->filter_h[2] =
-                    msac_decode_subexp(&ts->msac,
-                                       ts->lr_ref[p]->filter_h[2] + 17, 64,
-                                       3) - 17;
-                memcpy(lr->sgr_weights, ts->lr_ref[p]->sgr_weights, sizeof(lr->sgr_weights));
-                ts->lr_ref[p] = lr;
-                if (DEBUG_BLOCK_INFO)
-                    printf("Post-lr_wiener[pl=%d,v[%d,%d,%d],h[%d,%d,%d]]: r=%d\n",
-                           p, lr->filter_v[0], lr->filter_v[1],
-                           lr->filter_v[2], lr->filter_h[0],
-                           lr->filter_h[1], lr->filter_h[2], ts->msac.rng);
-            } else if (lr->type == RESTORATION_SGRPROJ) {
-                const unsigned idx = msac_decode_bools(&ts->msac, 4);
-                lr->sgr_idx = idx;
-                lr->sgr_weights[0] = dav1d_sgr_params[idx][0] ?
-                    msac_decode_subexp(&ts->msac,
-                                       ts->lr_ref[p]->sgr_weights[0] + 96, 128,
-                                       4) - 96 :
-                    0;
-                lr->sgr_weights[1] = dav1d_sgr_params[idx][1] ?
-                    msac_decode_subexp(&ts->msac,
-                                       ts->lr_ref[p]->sgr_weights[1] + 32, 128,
-                                       4) - 32 :
-                    iclip(128 - lr->sgr_weights[0], -32, 95);
-                memcpy(lr->filter_v, ts->lr_ref[p]->filter_v, sizeof(lr->filter_v));
-                memcpy(lr->filter_h, ts->lr_ref[p]->filter_h, sizeof(lr->filter_h));
-                ts->lr_ref[p] = lr;
-                if (DEBUG_BLOCK_INFO)
-                    printf("Post-lr_sgrproj[pl=%d,idx=%d,w[%d,%d]]: r=%d\n",
-                           p, lr->sgr_idx, lr->sgr_weights[0],
-                           lr->sgr_weights[1], ts->msac.rng);
+                const int d = f->frame_hdr.super_res.width_scale_denominator;
+                const int rnd = unit_size * 8 - 1, shift = unit_size_log2 + 3;
+                const int x0 = ((4 *  t->bx            * d >> ss_hor) + rnd) >> shift;
+                const int x1 = ((4 * (t->bx + sb_step) * d >> ss_hor) + rnd) >> shift;
+
+                for (int x = x0; x < imin(x1, n_units); x++) {
+                    const int px_x = x << (unit_size_log2 + ss_hor);
+                    const int sb_idx = (t->by >> 5) * f->sr_sb128w + (px_x >> 7);
+                    const int unit_idx = ((t->by & 16) >> 3) + ((px_x & 64) >> 6);
+                    Av1RestorationUnit *const lr = &f->lf.lr_mask[sb_idx].lr[p][unit_idx];
+
+                    read_restoration_info(t, lr, p, frame_type);
+                }
+            } else {
+                const int x = 4 * t->bx >> ss_hor;
+                if (x & mask) continue;
+                const int w = (f->cur.p.w + ss_hor) >> ss_hor;
+                // Round half up at frame boundaries, if there's more than one
+                // restoration unit
+                if (x && x + half_unit > w) continue;
+                const int sb_idx = (t->by >> 5) * f->sr_sb128w + (t->bx >> 5);
+                const int unit_idx = ((t->by & 16) >> 3) + ((t->bx & 16) >> 4);
+                Av1RestorationUnit *const lr = &f->lf.lr_mask[sb_idx].lr[p][unit_idx];
+
+                read_restoration_info(t, lr, p, frame_type);
             }
         }
         if (decode_sb(t, root_bl, c->intra_edge.root[root_bl]))
@@ -2423,8 +2472,8 @@
     int align_h = (f->bh + 31) & ~31;
     memcpy(&f->lf.tx_lpf_right_edge[0][align_h * tile_col + t->by],
            &t->l.tx_lpf_y[t->by & 16], sb_step);
+    const int ss_ver = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
     align_h >>= ss_ver;
-
     memcpy(&f->lf.tx_lpf_right_edge[1][align_h * tile_col + (t->by >> ss_ver)],
            &t->l.tx_lpf_uv[(t->by & 16) >> ss_ver], sb_step >> ss_ver);
 
@@ -2512,22 +2561,13 @@
     // update allocation of block contexts for above
     if (f->sb128w > f->lf.line_sz) {
         dav1d_freep_aligned(&f->lf.cdef_line);
-        dav1d_freep_aligned(&f->lf.lr_lpf_line);
 
         // note that we allocate all pixel arrays as if we were dealing with
         // 10 bits/component data
         uint16_t *ptr = f->lf.cdef_line =
             dav1d_alloc_aligned(f->b4_stride * 4 * 12 * sizeof(uint16_t), 32);
+        if (!ptr) return -ENOMEM;
 
-        uint16_t *lr_ptr = f->lf.lr_lpf_line =
-            dav1d_alloc_aligned(f->b4_stride * 4 * 3 * 12 * sizeof(uint16_t), 32);
-
-        if (!ptr || !lr_ptr) {
-            if (ptr) dav1d_free_aligned(ptr);
-            if (lr_ptr) dav1d_free_aligned(lr_ptr);
-            return -ENOMEM;
-        }
-
         for (int pl = 0; pl <= 2; pl++) {
             f->lf.cdef_line_ptr[0][pl][0] = ptr + f->b4_stride * 4 * 0;
             f->lf.cdef_line_ptr[0][pl][1] = ptr + f->b4_stride * 4 * 1;
@@ -2534,12 +2574,26 @@
             f->lf.cdef_line_ptr[1][pl][0] = ptr + f->b4_stride * 4 * 2;
             f->lf.cdef_line_ptr[1][pl][1] = ptr + f->b4_stride * 4 * 3;
             ptr += f->b4_stride * 4 * 4;
+        }
 
+        f->lf.line_sz = f->sb128w;
+    }
+
+    const ptrdiff_t lr_stride = (f->sr_cur.p.p.w + 31) & ~31;
+    if (lr_stride > f->lf.lr_line_sz) {
+        dav1d_freep_aligned(&f->lf.lr_lpf_line);
+
+        uint16_t *lr_ptr = f->lf.lr_lpf_line =
+            dav1d_alloc_aligned(lr_stride * 3 * 12 * sizeof(uint16_t), 32);
+
+        if (!lr_ptr) return -ENOMEM;
+
+        for (int pl = 0; pl <= 2; pl++) {
             f->lf.lr_lpf_line_ptr[pl] = lr_ptr;
-            lr_ptr += f->b4_stride * 4 * 12;
+            lr_ptr += lr_stride * 12;
         }
 
-        f->lf.line_sz = f->sb128w;
+        f->lf.lr_line_sz = lr_stride;
     }
 
     // update allocation for loopfilter masks
@@ -2579,6 +2633,13 @@
         }
         f->lf.mask_sz = f->sb128w * f->sb128h;
     }
+    f->sr_sb128w = (f->sr_cur.p.p.w + 127) >> 7;
+    if (f->sr_sb128w * f->sb128h > f->lf.lr_mask_sz) {
+        freep(&f->lf.lr_mask);
+        f->lf.lr_mask = malloc(f->sr_sb128w * f->sb128h * sizeof(*f->lf.lr_mask));
+        if (!f->lf.lr_mask) return -ENOMEM;
+        f->lf.lr_mask_sz = f->sr_sb128w * f->sb128h;
+    }
     if (f->frame_hdr.loopfilter.sharpness != f->lf.last_sharpness) {
         dav1d_calc_eih(&f->lf.lim_lut, f->frame_hdr.loopfilter.sharpness);
         f->lf.last_sharpness = f->frame_hdr.loopfilter.sharpness;
@@ -2612,7 +2673,7 @@
         const int order_hint_n_bits = f->seq_hdr.order_hint * f->seq_hdr.order_hint_n_bits;
         const int ret = av1_init_ref_mv_common(f->libaom_cm, f->bw >> 1, f->bh >> 1,
                                                f->b4_stride, f->seq_hdr.sb128,
-                                               f->mvs, f->ref_mvs, f->cur.p.poc, f->refpoc,
+                                               f->mvs, f->ref_mvs, f->cur.poc, f->refpoc,
                                                f->refrefpoc, f->frame_hdr.gmv,
                                                f->frame_hdr.hp, f->frame_hdr.force_integer_mv,
                                                f->frame_hdr.use_ref_frame_mvs,
@@ -2644,9 +2705,9 @@
                 const unsigned ref1poc = f->refp[j].p.poc;
 
                 const unsigned d1 = imin(abs(get_poc_diff(f->seq_hdr.order_hint_n_bits,
-                                                          ref0poc, f->cur.p.poc)), 31);
+                                                          ref0poc, f->cur.poc)), 31);
                 const unsigned d0 = imin(abs(get_poc_diff(f->seq_hdr.order_hint_n_bits,
-                                                          ref1poc, f->cur.p.poc)), 31);
+                                                          ref1poc, f->cur.poc)), 31);
                 const int order = d0 <= d1;
 
                 static const uint8_t quant_dist_weight[3][2] = {
@@ -2672,9 +2733,12 @@
 
     // init loopfilter pointers
     f->lf.mask_ptr = f->lf.mask;
-    f->lf.p[0] = f->cur.p.data[0];
-    f->lf.p[1] = f->cur.p.data[1];
-    f->lf.p[2] = f->cur.p.data[2];
+    f->lf.p[0] = f->cur.data[0];
+    f->lf.p[1] = f->cur.data[1];
+    f->lf.p[2] = f->cur.data[2];
+    f->lf.sr_p[0] = f->sr_cur.p.data[0];
+    f->lf.sr_p[1] = f->sr_cur.p.data[1];
+    f->lf.sr_p[2] = f->sr_cur.p.data[2];
     f->lf.tile_row = 1;
 
     dav1d_cdf_thread_wait(&f->in_cdf);
@@ -2758,7 +2822,7 @@
                     // loopfilter + cdef + restoration
                     if (f->frame_thread.pass != 1)
                         f->bd_fn.filter_sbrow(f, sby);
-                    dav1d_thread_picture_signal(&f->cur, (sby + 1) * f->sb_step * 4,
+                    dav1d_thread_picture_signal(&f->sr_cur, (sby + 1) * f->sb_step * 4,
                                                 progress_plane_type);
                 }
             }
@@ -2802,7 +2866,7 @@
                             pthread_mutex_unlock(&ts->tile_thread.lock);
                         }
                         if (progress == TILE_ERROR) {
-                            dav1d_thread_picture_signal(&f->cur, FRAME_ERROR,
+                            dav1d_thread_picture_signal(&f->sr_cur, FRAME_ERROR,
                                                         progress_plane_type);
                             const uint64_t all_mask = ~0ULL >> (64 - f->n_tc);
                             pthread_mutex_lock(&f->tile_thread.lock);
@@ -2816,7 +2880,7 @@
                     // loopfilter + cdef + restoration
                     if (f->frame_thread.pass != 1)
                         f->bd_fn.filter_sbrow(f, sby);
-                    dav1d_thread_picture_signal(&f->cur, (sby + 1) * f->sb_step * 4,
+                    dav1d_thread_picture_signal(&f->sr_cur, (sby + 1) * f->sb_step * 4,
                                                 progress_plane_type);
                 }
             }
@@ -2855,7 +2919,7 @@
 
     retval = 0;
 error:
-    dav1d_thread_picture_signal(&f->cur, retval == 0 ? UINT_MAX : FRAME_ERROR,
+    dav1d_thread_picture_signal(&f->sr_cur, retval == 0 ? UINT_MAX : FRAME_ERROR,
                                 PLANE_TYPE_ALL);
     for (int i = 0; i < 7; i++) {
         if (f->refp[i].p.data[0])
@@ -2863,7 +2927,8 @@
         dav1d_ref_dec(&f->ref_mvs_ref[i]);
     }
 
-    dav1d_thread_picture_unref(&f->cur);
+    dav1d_picture_unref(&f->cur);
+    dav1d_thread_picture_unref(&f->sr_cur);
     dav1d_cdf_thread_unref(&f->in_cdf);
     if (f->frame_hdr.refresh_context) {
         dav1d_cdf_thread_signal(&f->out_cdf);
@@ -2879,6 +2944,12 @@
     return retval;
 }
 
+static int get_upscale_x0(const int in_w, const int out_w, const int step) {
+    const int err = out_w * step - (in_w << 14);
+    const int x0 = (-((out_w - in_w) << 13) + (out_w >> 1)) / out_w + 128 - (err >> 1);
+    return x0 & 0x3fff;
+}
+
 int dav1d_submit_frame(Dav1dContext *const c) {
     Dav1dFrameContext *f;
     int res = -1;
@@ -2966,9 +3037,9 @@
         for (int i = 0; i < 7; i++) {
             const int refidx = f->frame_hdr.refidx[i];
             if (!c->refs[refidx].p.p.data[0] ||
-                f->frame_hdr.width * 2 < c->refs[refidx].p.p.p.w ||
+                f->frame_hdr.width[0] * 2 < c->refs[refidx].p.p.p.w ||
                 f->frame_hdr.height * 2 < c->refs[refidx].p.p.p.h ||
-                f->frame_hdr.width > c->refs[refidx].p.p.p.w * 16 ||
+                f->frame_hdr.width[0] > c->refs[refidx].p.p.p.w * 16 ||
                 f->frame_hdr.height > c->refs[refidx].p.p.p.h * 16 ||
                 f->seq_hdr.layout != c->refs[refidx].p.p.p.layout ||
                 f->seq_hdr.bpc != c->refs[refidx].p.p.p.bpc)
@@ -2979,16 +3050,16 @@
                 goto error;
             }
             dav1d_thread_picture_ref(&f->refp[i], &c->refs[refidx].p);
-            if (f->frame_hdr.width  != c->refs[refidx].p.p.p.w ||
+            f->ref_coded_width[i] = c->refs[refidx].coded_width;
+            if (f->frame_hdr.width[0] != c->refs[refidx].p.p.p.w ||
                 f->frame_hdr.height != c->refs[refidx].p.p.p.h)
             {
 #define scale_fac(ref_sz, this_sz) \
-    (((ref_sz << 14) + (this_sz >> 1)) / this_sz)
+    ((((ref_sz) << 14) + ((this_sz) >> 1)) / (this_sz))
                 f->svc[i][0].scale = scale_fac(c->refs[refidx].p.p.p.w,
-                                               f->frame_hdr.width);
+                                               f->frame_hdr.width[0]);
                 f->svc[i][1].scale = scale_fac(c->refs[refidx].p.p.p.h,
                                                f->frame_hdr.height);
-#undef scale_fac
                 f->svc[i][0].step = (f->svc[i][0].scale + 8) >> 4;
                 f->svc[i][1].step = (f->svc[i][1].scale + 8) >> 4;
             } else {
@@ -3015,35 +3086,53 @@
     c->n_tile_data = 0;
 
     // allocate frame
-    if ((res = dav1d_thread_picture_alloc(&f->cur, f->frame_hdr.width,
-                                          f->frame_hdr.height,
-                                          f->seq_hdr.layout, f->seq_hdr.bpc,
-                                          c->n_fc > 1 ? &f->frame_thread.td : NULL,
-                                          f->frame_hdr.show_frame,
-                                          &c->allocator)) < 0)
-    {
-        goto error;
+    res = dav1d_thread_picture_alloc(&f->sr_cur, f->frame_hdr.width[1],
+                                     f->frame_hdr.height,
+                                     f->seq_hdr.layout, f->seq_hdr.bpc,
+                                     c->n_fc > 1 ? &f->frame_thread.td : NULL,
+                                     f->frame_hdr.show_frame, &c->allocator);
+    if (res < 0) goto error;
+
+    f->sr_cur.p.poc = f->frame_hdr.frame_offset;
+    f->sr_cur.p.p.type = f->frame_hdr.frame_type;
+    f->sr_cur.p.p.pri = f->seq_hdr.pri;
+    f->sr_cur.p.p.trc = f->seq_hdr.trc;
+    f->sr_cur.p.p.mtrx = f->seq_hdr.mtrx;
+    f->sr_cur.p.p.chr = f->seq_hdr.chr;
+    f->sr_cur.p.p.fullrange = f->seq_hdr.color_range;
+
+    if (f->frame_hdr.super_res.enabled) {
+        res = dav1d_picture_alloc(&f->cur, f->frame_hdr.width[0],
+                                  f->frame_hdr.height, f->seq_hdr.layout,
+                                  f->seq_hdr.bpc, &c->allocator);
+        if (res < 0) goto error;
+        f->cur.poc = f->frame_hdr.frame_offset;
+    } else {
+        dav1d_picture_ref(&f->cur, &f->sr_cur.p);
     }
 
-    f->cur.p.poc = f->frame_hdr.frame_offset;
-    f->cur.p.p.type = f->frame_hdr.frame_type;
-    f->cur.p.p.pri = f->seq_hdr.pri;
-    f->cur.p.p.trc = f->seq_hdr.trc;
-    f->cur.p.p.mtrx = f->seq_hdr.mtrx;
-    f->cur.p.p.chr = f->seq_hdr.chr;
-    f->cur.p.p.fullrange = f->seq_hdr.color_range;
+    if (f->frame_hdr.super_res.enabled) {
+        f->resize_step[0] = scale_fac(f->cur.p.w, f->sr_cur.p.p.w);
+        const int ss_hor = f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+        const int in_cw = (f->cur.p.w + ss_hor) >> ss_hor;
+        const int out_cw = (f->sr_cur.p.p.w + ss_hor) >> ss_hor;
+        f->resize_step[1] = scale_fac(in_cw, out_cw);
+#undef scale_fac
+        f->resize_start[0] = get_upscale_x0(f->cur.p.w, f->sr_cur.p.p.w, f->resize_step[0]);
+        f->resize_start[1] = get_upscale_x0(in_cw, out_cw, f->resize_step[1]);
+    }
 
     // move f->cur into output queue
     if (c->n_fc == 1) {
         if (f->frame_hdr.show_frame)
-            dav1d_picture_ref(&c->out, &f->cur.p);
+            dav1d_picture_ref(&c->out, &f->sr_cur.p);
     } else {
-        dav1d_thread_picture_ref(out_delayed, &f->cur);
+        dav1d_thread_picture_ref(out_delayed, &f->sr_cur);
     }
 
-    f->w4 = (f->frame_hdr.width + 3) >> 2;
+    f->w4 = (f->frame_hdr.width[0] + 3) >> 2;
     f->h4 = (f->frame_hdr.height + 3) >> 2;
-    f->bw = ((f->frame_hdr.width + 7) >> 3) << 1;
+    f->bw = ((f->frame_hdr.width[0] + 7) >> 3) << 1;
     f->bh = ((f->frame_hdr.height + 7) >> 3) << 1;
     f->sb128w = (f->bw + 31) >> 5;
     f->sb128h = (f->bh + 31) >> 5;
@@ -3067,8 +3156,8 @@
             for (int i = 0; i < 7; i++) {
                 const int refidx = f->frame_hdr.refidx[i];
                 if (c->refs[refidx].refmvs != NULL &&
-                    f->refp[i].p.p.w == f->cur.p.p.w &&
-                    f->refp[i].p.p.h == f->cur.p.p.h)
+                    f->ref_coded_width[i] == f->cur.p.w &&
+                    f->refp[i].p.p.h == f->cur.p.h)
                 {
                     f->ref_mvs_ref[i] = c->refs[refidx].refmvs;
                     dav1d_ref_inc(f->ref_mvs_ref[i]);
@@ -3100,7 +3189,7 @@
         if (f->frame_hdr.segmentation.temporal || !f->frame_hdr.segmentation.update_map) {
             const int pri_ref = f->frame_hdr.primary_ref_frame;
             assert(pri_ref != PRIMARY_REF_NONE);
-            const int ref_w = ((f->refp[pri_ref].p.p.w + 7) >> 3) << 1;
+            const int ref_w = ((f->ref_coded_width[pri_ref] + 7) >> 3) << 1;
             const int ref_h = ((f->refp[pri_ref].p.p.h + 7) >> 3) << 1;
             if (ref_w == f->bw && ref_h == f->bh) {
                 f->prev_segmap_ref = c->refs[f->frame_hdr.refidx[pri_ref]].segmap;
@@ -3147,7 +3236,8 @@
         if (f->frame_hdr.refresh_frame_flags & (1 << i)) {
             if (c->refs[i].p.p.data[0])
                 dav1d_thread_picture_unref(&c->refs[i].p);
-            dav1d_thread_picture_ref(&c->refs[i].p, &f->cur);
+            dav1d_thread_picture_ref(&c->refs[i].p, &f->sr_cur);
+            c->refs[i].coded_width = f->frame_hdr.width[0];
 
             if (c->cdf[i].cdf) dav1d_cdf_thread_unref(&c->cdf[i]);
             if (f->frame_hdr.refresh_context) {
@@ -3207,7 +3297,8 @@
         dav1d_ref_dec(&f->ref_mvs_ref[i]);
     }
     dav1d_picture_unref(&c->out);
-    dav1d_thread_picture_unref(&f->cur);
+    dav1d_picture_unref(&f->cur);
+    dav1d_thread_picture_unref(&f->sr_cur);
     dav1d_ref_dec(&f->mvs_ref);
 
     for (int i = 0; i < f->n_tile_data; i++)
--- a/src/internal.h
+++ b/src/internal.h
@@ -99,6 +99,7 @@
         Av1LoopfilterModeRefDeltas lf_mode_ref_deltas;
         Av1FilmGrainData film_grain;
         uint8_t qidx;
+        unsigned coded_width;
     } refs[8];
     CdfThreadContext cdf[8];
 
@@ -119,7 +120,9 @@
 struct Dav1dFrameContext {
     Av1SequenceHeader seq_hdr;
     Av1FrameHeader frame_hdr;
-    Dav1dThreadPicture refp[7], cur;
+    Dav1dThreadPicture refp[7];
+    Dav1dPicture cur; // during block coding / reconstruction
+    Dav1dThreadPicture sr_cur; // after super-resolution upscaling
     Dav1dRef *mvs_ref;
     refmvs *mvs, *ref_mvs[7];
     Dav1dRef *ref_mvs_ref[7];
@@ -127,6 +130,7 @@
     uint8_t *cur_segmap;
     const uint8_t *prev_segmap;
     unsigned refpoc[7], refrefpoc[7][7];
+    int ref_coded_width[7];
     CdfThreadContext in_cdf, out_cdf;
     struct {
         Dav1dData data;
@@ -139,6 +143,7 @@
         int scale; // if no scaling, this is 0
         int step;
     } svc[7][2 /* x, y */];
+    int resize_step[2 /* y, uv */], resize_start[2 /* y, uv */];
 
     const Dav1dContext *c;
     Dav1dTileContext *tc;
@@ -157,7 +162,7 @@
     int ipred_edge_sz;
     pixel *ipred_edge[3];
     ptrdiff_t b4_stride;
-    int w4, h4, bw, bh, sb128w, sb128h, sbh, sb_shift, sb_step;
+    int w4, h4, bw, bh, sb128w, sb128h, sbh, sb_shift, sb_step, sr_sb128w;
     uint16_t dq[NUM_SEGMENTS][3 /* plane */][2 /* dc/ac */];
     const uint8_t *qm[2 /* is_1d */][N_RECT_TX_SIZES][3 /* plane */];
     BlockContext *a;
@@ -188,8 +193,9 @@
     struct {
         uint8_t (*level)[4];
         Av1Filter *mask;
+        Av1Restoration *lr_mask;
         int top_pre_cdef_toggle;
-        int mask_sz /* w*h */, line_sz /* w */, re_sz /* h */;
+        int mask_sz /* w*h */, lr_mask_sz, line_sz /* w */, lr_line_sz, re_sz /* h */;
         Av1FilterLUT lim_lut;
         int last_sharpness;
         uint8_t lvl[8 /* seg_id */][4 /* dir */][8 /* ref */][2 /* is_gmv */];
@@ -201,7 +207,7 @@
 
         // in-loop filter per-frame state keeping
         int tile_row; // for carry-over at tile row edges
-        pixel *p[3];
+        pixel *p[3], *sr_p[3];
         Av1Filter *mask_ptr, *prev_mask_ptr;
     } lf;
 
--- a/src/levels.h
+++ b/src/levels.h
@@ -431,9 +431,12 @@
     } operating_points[32];
     int frame_offset;
     int refresh_frame_flags;
-    int width, height;
+    int width[2 /* { coded_width, superresolution_upscaled_width } */], height;
     int render_width, render_height;
-    int super_res;
+    struct {
+        int width_scale_denominator;
+        int enabled;
+    } super_res;
     int have_render_size;
     int allow_intrabc;
     int frame_ref_short_signaling;
--- a/src/lf_apply_tmpl.c
+++ b/src/lf_apply_tmpl.c
@@ -183,8 +183,8 @@
     const int sbsz = 32 >> is_sb64;
     const int sbl2 = 5 - is_sb64;
     const int halign = (f->bh + 31) & ~31;
-    const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-    const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+    const int ss_ver = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_hor = f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int vmask = 16 >> ss_ver, hmask = 16 >> ss_hor;
     const unsigned vmax = 1U << vmask, hmax = 1U << hmask;
     const unsigned endy4 = starty4 + imin(f->h4 - sby * sbsz, sbsz);
@@ -211,7 +211,7 @@
             y_hmask[imin(idx, lpf_y[y - starty4])][sidx] |= smask;
         }
 
-        if (f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I400) {
+        if (f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I400) {
             uint16_t (*const uv_hmask)[2] = lflvl[x].filter_uv[0][cbx4];
             for (unsigned y = starty4 >> ss_ver, uv_mask = 1 << y; y < uv_endy4;
                  y++, uv_mask <<= 1)
@@ -247,7 +247,7 @@
                 y_vmask[imin(idx, a->tx_lpf_y[i])][sidx] |= smask;
             }
 
-            if (f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I400) {
+            if (f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I400) {
                 const unsigned cw = (w + ss_hor) >> ss_hor;
                 uint16_t (*const uv_vmask)[2] = lflvl[x].filter_uv[1][starty4 >> ss_ver];
                 for (unsigned uv_mask = 1, i = 0; i < cw; uv_mask <<= 1, i++) {
@@ -268,7 +268,7 @@
          x++, have_left = 1, ptr += 128, level_ptr += 32)
     {
         filter_plane_cols_y(f, have_left, level_ptr, f->b4_stride,
-                            lflvl[x].filter_y[0], ptr, f->cur.p.stride[0],
+                            lflvl[x].filter_y[0], ptr, f->cur.stride[0],
                             imin(32, f->w4 - x * 32), starty4, endy4);
     }
 
@@ -275,7 +275,7 @@
     level_ptr = f->lf.level + f->b4_stride * sby * sbsz;
     for (ptr = p[0], x = 0; x < f->sb128w; x++, ptr += 128, level_ptr += 32) {
         filter_plane_rows_y(f, have_top, level_ptr, f->b4_stride,
-                            lflvl[x].filter_y[1], ptr, f->cur.p.stride[0],
+                            lflvl[x].filter_y[1], ptr, f->cur.stride[0],
                             imin(32, f->w4 - x * 32), starty4, endy4);
     }
 
@@ -289,7 +289,7 @@
     {
         filter_plane_cols_uv(f, have_left, level_ptr, f->b4_stride,
                              lflvl[x].filter_uv[0],
-                             &p[1][uv_off], &p[2][uv_off], f->cur.p.stride[1],
+                             &p[1][uv_off], &p[2][uv_off], f->cur.stride[1],
                              (imin(32, f->w4 - x * 32) + ss_hor) >> ss_hor,
                              starty4 >> ss_ver, uv_endy4, ss_ver);
     }
@@ -300,7 +300,7 @@
     {
         filter_plane_rows_uv(f, have_top, level_ptr, f->b4_stride,
                              lflvl[x].filter_uv[1],
-                             &p[1][uv_off], &p[2][uv_off], f->cur.p.stride[1],
+                             &p[1][uv_off], &p[2][uv_off], f->cur.stride[1],
                              (imin(32, f->w4 - x * 32) + ss_hor) >> ss_hor,
                              starty4 >> ss_ver, uv_endy4, ss_hor);
     }
--- a/src/lf_mask.h
+++ b/src/lf_mask.h
@@ -47,7 +47,7 @@
     int16_t sgr_weights[2];
 } Av1RestorationUnit;
 
-// each struct describes one 128x128 area (1 or 4 SBs)
+// each struct describes one 128x128 area (1 or 4 SBs), pre-superres-scaling
 typedef struct Av1Filter {
     // each bit is 1 col
     uint16_t filter_y[2 /* 0=col, 1=row */][32][3][2];
@@ -54,8 +54,12 @@
     uint16_t filter_uv[2 /* 0=col, 1=row */][32][2][2];
     int8_t cdef_idx[4]; // -1 means "unset"
     uint16_t noskip_mask[32][2];
-    Av1RestorationUnit lr[3][4];
 } Av1Filter;
+
+// each struct describes one 128x128 area (1 or 4 SBs), post-superres-scaling
+typedef struct Av1Restoration {
+    Av1RestorationUnit lr[3][4];
+} Av1Restoration;
 
 void dav1d_create_lf_mask_intra(Av1Filter *lflvl, uint8_t (*level_cache)[4],
                                 const ptrdiff_t b4_stride,
--- a/src/lib.c
+++ b/src/lib.c
@@ -266,7 +266,8 @@
                     dav1d_thread_picture_unref(&f->refp[i]);
                 dav1d_ref_dec(&f->ref_mvs_ref[i]);
             }
-            dav1d_thread_picture_unref(&f->cur);
+            dav1d_picture_unref(&f->cur);
+            dav1d_thread_picture_unref(&f->sr_cur);
             dav1d_cdf_thread_unref(&f->in_cdf);
             if (f->frame_hdr.refresh_context)
                 dav1d_cdf_thread_unref(&f->out_cdf);
@@ -324,6 +325,7 @@
         dav1d_free_aligned(f->ipred_edge[0]);
         free(f->a);
         free(f->lf.mask);
+        free(f->lf.lr_mask);
         free(f->lf.level);
         free(f->lf.tx_lpf_right_edge[0]);
         av1_free_ref_mv_common(f->libaom_cm);
--- a/src/lr_apply_tmpl.c
+++ b/src/lr_apply_tmpl.c
@@ -33,7 +33,6 @@
 
 #include "src/lr_apply.h"
 
-
 enum LrRestorePlanes {
     LR_RESTORE_Y = 1 << 0,
     LR_RESTORE_U = 1 << 1,
@@ -44,13 +43,14 @@
 // contain at most 2 stripes. Each stripe requires 4 rows pixels (2 above
 // and 2 below) the final 4 rows are used to swap the bottom of the last
 // stripe with the top of the next super block row.
-static void backup_lpf(pixel *dst, ptrdiff_t dst_stride,
-                       const pixel *src, ptrdiff_t src_stride,
+static void backup_lpf(const Dav1dFrameContext *const f,
+                       pixel *dst, const ptrdiff_t dst_stride,
+                       const pixel *src, const ptrdiff_t src_stride,
                        const int ss_ver, const int sb128,
-                       int row, const int row_h, const int w)
+                       int row, const int row_h, const int src_w, const int ss_hor)
 {
-    src_stride = PXSTRIDE(src_stride);
-    dst_stride = PXSTRIDE(dst_stride);
+    const int dst_w = f->frame_hdr.super_res.enabled ?
+                      (f->frame_hdr.width[1] + ss_hor) >> ss_hor : src_w;
 
     // The first stripe of the frame is shorter by 8 luma pixel rows.
     int stripe_h = (64 - 8 * !row) >> ss_ver;
@@ -59,23 +59,38 @@
         const int top = 4 << sb128;
         // Copy the top part of the stored loop filtered pixels from the
         // previous sb row needed above the first stripe of this sb row.
-        pixel_copy(&dst[dst_stride *  0], &dst[dst_stride *  top], w);
-        pixel_copy(&dst[dst_stride *  1], &dst[dst_stride * (top + 1)], w);
-        pixel_copy(&dst[dst_stride *  2], &dst[dst_stride * (top + 2)], w);
-        pixel_copy(&dst[dst_stride *  3], &dst[dst_stride * (top + 3)], w);
+        pixel_copy(&dst[PXSTRIDE(dst_stride) *  0],
+                   &dst[PXSTRIDE(dst_stride) *  top],      dst_w);
+        pixel_copy(&dst[PXSTRIDE(dst_stride) *  1],
+                   &dst[PXSTRIDE(dst_stride) * (top + 1)], dst_w);
+        pixel_copy(&dst[PXSTRIDE(dst_stride) *  2],
+                   &dst[PXSTRIDE(dst_stride) * (top + 2)], dst_w);
+        pixel_copy(&dst[PXSTRIDE(dst_stride) *  3],
+                   &dst[PXSTRIDE(dst_stride) * (top + 3)], dst_w);
     }
 
-    dst += 4 * dst_stride;
-    src += (stripe_h - 2) * src_stride;
+    dst += 4 * PXSTRIDE(dst_stride);
+    src += (stripe_h - 2) * PXSTRIDE(src_stride);
 
-    for (; row + stripe_h <= row_h; row += stripe_h) {
-        for (int i = 0; i < 4; i++) {
-            pixel_copy(dst, src, w);
-            dst += dst_stride;
-            src += src_stride;
+    if (f->frame_hdr.super_res.enabled) {
+        for (; row + stripe_h <= row_h; row += stripe_h) {
+            f->dsp->mc.resize(dst, dst_stride, src, src_stride,
+                              dst_w, src_w, 4, f->resize_step[ss_hor],
+                              f->resize_start[ss_hor]);
+            stripe_h = 64 >> ss_ver;
+            src += stripe_h * PXSTRIDE(src_stride);
+            dst += 4 * PXSTRIDE(dst_stride);
         }
-        stripe_h = 64 >> ss_ver;
-        src += (stripe_h - 4) * src_stride;
+    } else {
+        for (; row + stripe_h <= row_h; row += stripe_h) {
+            for (int i = 0; i < 4; i++) {
+                pixel_copy(dst, src, src_w);
+                dst += PXSTRIDE(dst_stride);
+                src += PXSTRIDE(src_stride);
+            }
+            stripe_h = 64 >> ss_ver;
+            src += (stripe_h - 4) * PXSTRIDE(src_stride);
+        }
     }
 }
 
@@ -83,7 +98,8 @@
                                /*const*/ pixel *const src[3], const int sby)
 {
     const ptrdiff_t offset = 8 * !!sby;
-    const ptrdiff_t *const src_stride = f->cur.p.stride;
+    const ptrdiff_t *const src_stride = f->cur.stride;
+    const ptrdiff_t lr_stride = ((f->sr_cur.p.p.w + 31) & ~31) * sizeof(pixel);
 
     // TODO Also check block level restore type to reduce copying.
     const int restore_planes =
@@ -96,13 +112,13 @@
         const int w = f->bw << 2;
         const int row_h = imin((sby + 1) << (6 + f->seq_hdr.sb128), h);
         const int y_stripe = (sby << (6 + f->seq_hdr.sb128)) - offset;
-        backup_lpf(f->lf.lr_lpf_line_ptr[0], sizeof(pixel) * f->b4_stride * 4,
+        backup_lpf(f, f->lf.lr_lpf_line_ptr[0], lr_stride,
                    src[0] - offset * PXSTRIDE(src_stride[0]), src_stride[0],
-                   0, f->seq_hdr.sb128, y_stripe, row_h, w);
+                   0, f->seq_hdr.sb128, y_stripe, row_h, w, 0);
     }
     if (restore_planes & (LR_RESTORE_U | LR_RESTORE_V)) {
-        const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-        const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+        const int ss_ver = f->sr_cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+        const int ss_hor = f->sr_cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
         const int h = f->bh << (2 - ss_ver);
         const int w = f->bw << (2 - ss_hor);
         const int row_h = imin((sby + 1) << ((6 - ss_ver) + f->seq_hdr.sb128), h);
@@ -111,19 +127,18 @@
             (sby << ((6 - ss_ver) + f->seq_hdr.sb128)) - offset_uv;
 
         if (restore_planes & LR_RESTORE_U) {
-            backup_lpf(f->lf.lr_lpf_line_ptr[1], sizeof(pixel) * f->b4_stride * 4,
+            backup_lpf(f, f->lf.lr_lpf_line_ptr[1], lr_stride,
                        src[1] - offset_uv * PXSTRIDE(src_stride[1]), src_stride[1],
-                       ss_ver, f->seq_hdr.sb128, y_stripe, row_h, w);
+                       ss_ver, f->seq_hdr.sb128, y_stripe, row_h, w, ss_hor);
         }
         if (restore_planes & LR_RESTORE_V) {
-            backup_lpf(f->lf.lr_lpf_line_ptr[2], sizeof(pixel) * f->b4_stride * 4,
+            backup_lpf(f, f->lf.lr_lpf_line_ptr[2], lr_stride,
                        src[2] - offset_uv * PXSTRIDE(src_stride[1]), src_stride[1],
-                       ss_ver, f->seq_hdr.sb128, y_stripe, row_h, w);
+                       ss_ver, f->seq_hdr.sb128, y_stripe, row_h, w, ss_hor);
         }
     }
 }
 
-
 static void lr_stripe(const Dav1dFrameContext *const f, pixel *p,
                       const pixel (*left)[4], int x, int y,
                       const int plane, const int unit_w, const int row_h,
@@ -131,11 +146,11 @@
 {
     const Dav1dDSPContext *const dsp = f->dsp;
     const int chroma = !!plane;
-    const int ss_ver = chroma & (f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420);
+    const int ss_ver = chroma & (f->sr_cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420);
     const int sbrow_has_bottom = (edges & LR_HAVE_BOTTOM);
     const pixel *lpf = f->lf.lr_lpf_line_ptr[plane] + x;
-    const ptrdiff_t p_stride = f->cur.p.stride[chroma];
-    const ptrdiff_t lpf_stride = sizeof(pixel) * f->b4_stride * 4;
+    const ptrdiff_t p_stride = f->sr_cur.p.stride[chroma];
+    const ptrdiff_t lpf_stride = sizeof(pixel) * ((f->sr_cur.p.p.w + 31) & ~31);
 
     // The first stripe of the frame is shorter by 8 luma pixel rows.
     int stripe_h = imin((64 - 8 * !y) >> ss_ver, row_h - y);
@@ -192,9 +207,9 @@
                      const int w, const int h, const int row_h, const int plane)
 {
     const int chroma = !!plane;
-    const int ss_ver = chroma & (f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420);
-    const int ss_hor = chroma & (f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444);
-    const ptrdiff_t p_stride = f->cur.p.stride[chroma];
+    const int ss_ver = chroma & (f->sr_cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420);
+    const int ss_hor = chroma & (f->sr_cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444);
+    const ptrdiff_t p_stride = f->sr_cur.p.stride[chroma];
 
     const int unit_size_log2 = f->frame_hdr.restoration.unit_size[!!plane];
     const int unit_size = 1 << unit_size_log2;
@@ -238,8 +253,8 @@
         // AV1Filter unit.
         const int unit_idx = ((ruy & 16) >> 3) + ((rux & 16) >> 4);
         const Av1RestorationUnit *const lr =
-            &f->lf.mask[(((ruy << (unit_size_log2)) >> shift_ver) * f->sb128w) +
-                        (x >> shift_hor)].lr[plane][unit_idx];
+            &f->lf.lr_mask[(((ruy << (unit_size_log2)) >> shift_ver) * f->sr_sb128w) +
+                           (x >> shift_hor)].lr[plane][unit_idx];
 
         // FIXME Don't backup if the next restoration unit is RESTORE_NONE
         // This also requires not restoring in the same conditions.
@@ -257,7 +272,7 @@
                             const int sby)
 {
     const ptrdiff_t offset_y = 8 * !!sby;
-    const ptrdiff_t *const dst_stride = f->cur.p.stride;
+    const ptrdiff_t *const dst_stride = f->sr_cur.p.stride;
 
     const int restore_planes =
         ((f->frame_hdr.restoration.type[0] != RESTORATION_NONE) << 0) +
@@ -265,8 +280,8 @@
         ((f->frame_hdr.restoration.type[2] != RESTORATION_NONE) << 2);
 
     if (restore_planes & LR_RESTORE_Y) {
-        const int h = f->cur.p.p.h;
-        const int w = f->cur.p.p.w;
+        const int h = f->sr_cur.p.p.h;
+        const int w = f->sr_cur.p.p.w;
         const int row_h = imin((sby + 1) << (6 + f->seq_hdr.sb128), h);
         const int y_stripe = (sby << (6 + f->seq_hdr.sb128)) - offset_y;
         lr_sbrow(f, dst[0] - offset_y * PXSTRIDE(dst_stride[0]), y_stripe, w,
@@ -273,10 +288,10 @@
                  h, row_h, 0);
     }
     if (restore_planes & (LR_RESTORE_U | LR_RESTORE_V)) {
-        const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-        const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
-        const int h = (f->cur.p.p.h + ss_ver) >> ss_ver;
-        const int w = (f->cur.p.p.w + ss_hor) >> ss_hor;
+        const int ss_ver = f->sr_cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+        const int ss_hor = f->sr_cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+        const int h = (f->sr_cur.p.p.h + ss_ver) >> ss_ver;
+        const int w = (f->sr_cur.p.p.w + ss_hor) >> ss_hor;
         const int row_h = imin((sby + 1) << ((6 - ss_ver) + f->seq_hdr.sb128), h);
         const ptrdiff_t offset_uv = offset_y >> ss_ver;
         const int y_stripe =
--- a/src/mc.h
+++ b/src/mc.h
@@ -105,6 +105,12 @@
             pixel *dst, ptrdiff_t dst_stride, const pixel *src, ptrdiff_t src_stride)
 typedef decl_emu_edge_fn(*emu_edge_fn);
 
+#define decl_resize_fn(name) \
+void (name)(pixel *dst, ptrdiff_t dst_stride, \
+            const pixel *src, ptrdiff_t src_stride, \
+            int dst_w, int src_w, int h, int dx, int mx)
+typedef decl_resize_fn(*resize_fn);
+
 typedef struct Dav1dMCDSPContext {
     mc_fn mc[N_2D_FILTERS];
     mc_scaled_fn mc_scaled[N_2D_FILTERS];
@@ -120,6 +126,7 @@
     warp8x8_fn warp8x8;
     warp8x8t_fn warp8x8t;
     emu_edge_fn emu_edge;
+    resize_fn resize;
 } Dav1dMCDSPContext;
 
 void dav1d_mc_dsp_init_8bpc(Dav1dMCDSPContext *c);
--- a/src/mc_tmpl.c
+++ b/src/mc_tmpl.c
@@ -782,6 +782,34 @@
     }
 }
 
+static void resize_c(pixel *dst, const ptrdiff_t dst_stride,
+                     const pixel *src, const ptrdiff_t src_stride,
+                     const int dst_w, const int src_w, int h,
+                     const int dx, const int mx0)
+{
+    do {
+        int mx = mx0, src_x = -1;
+        for (int x = 0; x < dst_w; x++) {
+            const int16_t *const F = dav1d_resize_filter[mx >> 8];
+            dst[x] = iclip_pixel((F[0] * src[iclip(src_x - 3, 0, src_w - 1)] +
+                                  F[1] * src[iclip(src_x - 2, 0, src_w - 1)] +
+                                  F[2] * src[iclip(src_x - 1, 0, src_w - 1)] +
+                                  F[3] * src[iclip(src_x + 0, 0, src_w - 1)] +
+                                  F[4] * src[iclip(src_x + 1, 0, src_w - 1)] +
+                                  F[5] * src[iclip(src_x + 2, 0, src_w - 1)] +
+                                  F[6] * src[iclip(src_x + 3, 0, src_w - 1)] +
+                                  F[7] * src[iclip(src_x + 4, 0, src_w - 1)] +
+                                  64) >> 7);
+            mx += dx;
+            src_x += mx >> 14;
+            mx &= 0x3fff;
+        }
+
+        dst += PXSTRIDE(dst_stride);
+        src += PXSTRIDE(src_stride);
+    } while (--h);
+}
+
 void bitfn(dav1d_mc_dsp_init)(Dav1dMCDSPContext *const c) {
 #define init_mc_fns(type, name) do { \
     c->mc        [type] = put_##name##_c; \
@@ -813,6 +841,7 @@
     c->warp8x8  = warp_affine_8x8_c;
     c->warp8x8t = warp_affine_8x8t_c;
     c->emu_edge = emu_edge_c;
+    c->resize   = resize_c;
 
 #if HAVE_ASM
 #if ARCH_AARCH64 || ARCH_ARM
--- a/src/obu.c
+++ b/src/obu.c
@@ -280,9 +280,18 @@
                     &c->refs[c->frame_hdr.refidx[i]].p;
                 if (!ref->p.data[0]) return -1;
                 // FIXME render_* may be wrong
-                hdr->render_width = hdr->width = ref->p.p.w;
+                hdr->render_width = hdr->width[1] = ref->p.p.w;
                 hdr->render_height = hdr->height = ref->p.p.h;
-                hdr->super_res = 0; // FIXME probably wrong
+                hdr->super_res.enabled = seqhdr->super_res && dav1d_get_bits(gb, 1);
+                if (hdr->super_res.enabled) {
+                    const int d = hdr->super_res.width_scale_denominator =
+                        9 + dav1d_get_bits(gb, 3);
+                    hdr->width[0] = imax((hdr->width[1] * 8 + (d >> 1)) / d,
+                                         imin(16, hdr->width[1]));
+                } else {
+                    hdr->super_res.width_scale_denominator = 8;
+                    hdr->width[0] = hdr->width[1];
+                }
                 return 0;
             }
         }
@@ -289,20 +298,26 @@
     }
 
     if (hdr->frame_size_override) {
-        hdr->width = dav1d_get_bits(gb, seqhdr->width_n_bits) + 1;
+        hdr->width[1] = dav1d_get_bits(gb, seqhdr->width_n_bits) + 1;
         hdr->height = dav1d_get_bits(gb, seqhdr->height_n_bits) + 1;
     } else {
-        hdr->width = seqhdr->max_width;
+        hdr->width[1] = seqhdr->max_width;
         hdr->height = seqhdr->max_height;
     }
-    hdr->super_res = seqhdr->super_res && dav1d_get_bits(gb, 1);
-    if (hdr->super_res) return -1; // FIXME
+    hdr->super_res.enabled = seqhdr->super_res && dav1d_get_bits(gb, 1);
+    if (hdr->super_res.enabled) {
+        const int d = hdr->super_res.width_scale_denominator = 9 + dav1d_get_bits(gb, 3);
+        hdr->width[0] = imax((hdr->width[1] * 8 + (d >> 1)) / d, imin(16, hdr->width[1]));
+    } else {
+        hdr->super_res.width_scale_denominator = 8;
+        hdr->width[0] = hdr->width[1];
+    }
     hdr->have_render_size = dav1d_get_bits(gb, 1);
     if (hdr->have_render_size) {
         hdr->render_width = dav1d_get_bits(gb, 16) + 1;
         hdr->render_height = dav1d_get_bits(gb, 16) + 1;
     } else {
-        hdr->render_width = hdr->width;
+        hdr->render_width = hdr->width[1];
         hdr->render_height = hdr->height;
     }
     return 0;
@@ -411,7 +426,7 @@
                 dav1d_get_bits(gb, seqhdr->order_hint_n_bits);
         if ((res = read_frame_size(c, gb, 0)) < 0) goto error;
         hdr->allow_intrabc = hdr->allow_screen_content_tools &&
-                             /* FIXME: no superres scaling && */ dav1d_get_bits(gb, 1);
+                             !hdr->super_res.enabled && dav1d_get_bits(gb, 1);
         hdr->use_ref_frame_mvs = 0;
     } else {
         hdr->allow_intrabc = 0;
@@ -455,7 +470,7 @@
     hdr->tiling.uniform = dav1d_get_bits(gb, 1);
     const int sbsz_min1 = (64 << seqhdr->sb128) - 1;
     int sbsz_log2 = 6 + seqhdr->sb128;
-    int sbw = (hdr->width + sbsz_min1) >> sbsz_log2;
+    int sbw = (hdr->width[0] + sbsz_min1) >> sbsz_log2;
     int sbh = (hdr->height + sbsz_min1) >> sbsz_log2;
     int max_tile_width_sb = 4096 >> sbsz_log2;
     int max_tile_area_sb = 4096 * 2304 >> (2 * sbsz_log2);
@@ -733,7 +748,9 @@
 #endif
 
     // restoration
-    if (!hdr->all_lossless && seqhdr->restoration && !hdr->allow_intrabc) {
+    if ((!hdr->all_lossless || hdr->super_res.enabled) &&
+        seqhdr->restoration && !hdr->allow_intrabc)
+    {
         hdr->restoration.type[0] = dav1d_get_bits(gb, 2);
         if (seqhdr->layout != DAV1D_PIXEL_LAYOUT_I400) {
             hdr->restoration.type[1] = dav1d_get_bits(gb, 2);
--- a/src/picture.c
+++ b/src/picture.c
@@ -91,8 +91,7 @@
     void *extra_ptr; /* MUST BE AT THE END */
 };
 
-static void free_buffer(const uint8_t *data, void *user_data)
-{
+static void free_buffer(const uint8_t *const data, void *const user_data) {
     struct pic_ctx_context *pic_ctx = user_data;
 
     pic_ctx->allocator.release_picture_callback(pic_ctx->data,
@@ -148,6 +147,13 @@
         *extra_ptr = &pic_ctx->extra_ptr;
 
     return 0;
+}
+
+int dav1d_picture_alloc(Dav1dPicture *const p, const int w, const int h,
+                        const enum Dav1dPixelLayout layout, const int bpc,
+                        Dav1dPicAllocator *const p_allocator)
+{
+    return picture_alloc_with_edges(p, w, h, layout, bpc, p_allocator, 0, NULL);
 }
 
 int dav1d_thread_picture_alloc(Dav1dThreadPicture *const p,
--- a/src/picture.h
+++ b/src/picture.h
@@ -54,6 +54,10 @@
 /*
  * Allocate a picture with custom border size.
  */
+int dav1d_picture_alloc(Dav1dPicture *p, int w, int h,
+                        enum Dav1dPixelLayout layout, int bpc,
+                        Dav1dPicAllocator *);
+
 int dav1d_thread_picture_alloc(Dav1dThreadPicture *p, int w, int h,
                                enum Dav1dPixelLayout layout, int bpc,
                                struct thread_data *t, int visible,
--- a/src/recon_tmpl.c
+++ b/src/recon_tmpl.c
@@ -72,7 +72,7 @@
     if (dbg) printf("Start: r=%d\n", ts->msac.rng);
 
     // does this block have any non-zero coefficients
-    const int sctx = get_coef_skip_ctx(t_dim, bs, a, l, chroma, f->cur.p.p.layout);
+    const int sctx = get_coef_skip_ctx(t_dim, bs, a, l, chroma, f->cur.p.layout);
     const int all_skip =
         msac_decode_bool_adapt(&ts->msac, ts->cdf.coef.skip[t_dim->ctx][sctx]);
     if (dbg)
@@ -289,7 +289,7 @@
         t->by += txsh;
         if (txh >= txw && t->by < f->bh) {
             if (dst)
-                dst += 4 * txsh * PXSTRIDE(f->cur.p.stride[0]);
+                dst += 4 * txsh * PXSTRIDE(f->cur.stride[0]);
             read_coef_tree(t, bs, b, sub, depth + 1, tx_split,
                            x_off * 2 + 0, y_off * 2 + 1, dst);
             t->bx += txsw;
@@ -349,9 +349,9 @@
             if (eob >= 0) {
                 if (DEBUG_BLOCK_INFO && DEBUG_B_PIXELS)
                     coef_dump(cf, imin(t_dim->h, 8) * 4, imin(t_dim->w, 8) * 4, 3, "dq");
-                dsp->itx.itxfm_add[ytx][txtp](dst, f->cur.p.stride[0], cf, eob);
+                dsp->itx.itxfm_add[ytx][txtp](dst, f->cur.stride[0], cf, eob);
                 if (DEBUG_BLOCK_INFO && DEBUG_B_PIXELS)
-                    hex_dump(dst, f->cur.p.stride[0], t_dim->w * 4, t_dim->h * 4, "recon");
+                    hex_dump(dst, f->cur.stride[0], t_dim->w * 4, t_dim->h * 4, "recon");
             }
         }
     }
@@ -361,8 +361,8 @@
                                     const enum BlockSize bs, const Av1Block *const b)
 {
     const Dav1dFrameContext *const f = t->f;
-    const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-    const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+    const int ss_ver = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_hor = f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int bx4 = t->bx & 31, by4 = t->by & 31;
     const int cbx4 = bx4 >> ss_hor, cby4 = by4 >> ss_ver;
     const uint8_t *const b_dim = dav1d_block_dimensions[bs];
@@ -501,8 +501,8 @@
 {
     assert((dst8 != NULL) ^ (dst16 != NULL));
     const Dav1dFrameContext *const f = t->f;
-    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 ss_ver = !!pl && f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_hor = !!pl && f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int h_mul = 4 >> ss_hor, v_mul = 4 >> ss_ver;
     const int mvx = mv.x, mvy = mv.y;
     const int mx = mvx & (15 >> !ss_hor), my = mvy & (15 >> !ss_ver);
@@ -509,19 +509,19 @@
     ptrdiff_t ref_stride = refp->p.stride[!!pl];
     const pixel *ref;
 
-    if (refp->p.p.w == f->cur.p.p.w && refp->p.p.h == f->cur.p.p.h) {
+    if (refp->p.p.w == f->cur.p.w && refp->p.p.h == f->cur.p.h) {
         const int dx = bx * h_mul + (mvx >> (3 + ss_hor));
         const int dy = by * v_mul + (mvy >> (3 + ss_ver));
         int w, h;
 
-        if (refp != &f->cur) { // i.e. not for intrabc
+        if (refp->p.data[0] != f->cur.data[0]) { // i.e. not for intrabc
             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;
+            w = (f->cur.p.w + ss_hor) >> ss_hor;
+            h = (f->cur.p.h + ss_ver) >> ss_ver;
         } else {
             w = f->bw * 4 >> ss_hor;
             h = f->bh * 4 >> ss_ver;
@@ -548,7 +548,7 @@
                                       bh4 * v_mul, mx << !ss_hor, my << !ss_ver);
         }
     } else {
-        assert(refp != &f->cur);
+        assert(refp != &f->sr_cur);
 
         int orig_pos_y = (by * v_mul << 4) + mvy * (1 << !ss_ver);
         int orig_pos_x = (bx * h_mul << 4) + mvx * (1 << !ss_hor);
@@ -569,6 +569,11 @@
 
         if (dav1d_thread_picture_wait(refp, bottom, PLANE_TYPE_Y + !!pl))
             return -1;
+        if (DEBUG_BLOCK_INFO)
+            printf("Off %dx%d [%d,%d,%d], size %dx%d [%d,%d]\n",
+                   left, top, orig_pos_x, f->svc[refidx][0].scale, refidx,
+                   right-left, bottom-top,
+                   f->svc[refidx][0].step, f->svc[refidx][1].step);
 
         const int w = (refp->p.p.w + ss_hor) >> ss_hor;
         const int h = (refp->p.p.h + ss_ver) >> ss_ver;
@@ -579,6 +584,7 @@
                                 refp->p.data[pl], ref_stride);
             ref = &t->emu_edge[320 * 3 + 3];
             ref_stride = 320 * sizeof(pixel);
+            if (DEBUG_BLOCK_INFO) printf("Emu\n");
         } else {
             ref = ((pixel *) refp->p.data[pl]) + PXSTRIDE(ref_stride) * top + left;
         }
@@ -610,8 +616,8 @@
     const Dav1dFrameContext *const f = t->f;
     const refmvs *const r = &f->mvs[t->by * f->b4_stride + t->bx];
     pixel *const lap = t->scratch.lap;
-    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 ss_ver = !!pl && f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_hor = !!pl && f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int h_mul = 4 >> ss_hor, v_mul = 4 >> ss_ver;
     int res;
 
@@ -673,8 +679,8 @@
     assert((dst8 != NULL) ^ (dst16 != NULL));
     const Dav1dFrameContext *const f = t->f;
     const Dav1dDSPContext *const dsp = f->dsp;
-    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 ss_ver = !!pl && f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_hor = !!pl && f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int h_mul = 4 >> ss_hor, v_mul = 4 >> ss_ver;
     assert(!((b_dim[0] * h_mul) & 7) && !((b_dim[1] * v_mul) & 7));
     const int32_t *const mat = wmp->matrix;
@@ -735,8 +741,8 @@
     const Dav1dFrameContext *const f = t->f;
     const Dav1dDSPContext *const dsp = f->dsp;
     const int bx4 = t->bx & 31, by4 = t->by & 31;
-    const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-    const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+    const int ss_ver = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_hor = f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int cbx4 = bx4 >> ss_hor, cby4 = by4 >> ss_ver;
     const uint8_t *const b_dim = dav1d_block_dimensions[bs];
     const int bw4 = b_dim[0], bh4 = b_dim[1];
@@ -758,8 +764,8 @@
     for (int init_y = 0; init_y < h4; init_y += 16) {
         for (int init_x = 0; init_x < w4; init_x += 16) {
             if (b->pal_sz[0]) {
-                pixel *dst = ((pixel *) f->cur.p.data[0]) +
-                             4 * (t->by * PXSTRIDE(f->cur.p.stride[0]) + t->bx);
+                pixel *dst = ((pixel *) f->cur.data[0]) +
+                             4 * (t->by * PXSTRIDE(f->cur.stride[0]) + t->bx);
                 const uint8_t *pal_idx;
                 if (f->frame_thread.pass) {
                     pal_idx = ts->frame_thread.pal_idx;
@@ -770,10 +776,10 @@
                 const uint16_t *const pal = f->frame_thread.pass ?
                     f->frame_thread.pal[((t->by >> 1) + (t->bx & 1)) * (f->b4_stride >> 1) +
                                         ((t->bx >> 1) + (t->by & 1))][0] : t->pal[0];
-                f->dsp->ipred.pal_pred(dst, f->cur.p.stride[0], pal,
+                f->dsp->ipred.pal_pred(dst, f->cur.stride[0], pal,
                                        pal_idx, bw4 * 4, bh4 * 4);
                 if (DEBUG_BLOCK_INFO && DEBUG_B_PIXELS)
-                    hex_dump(dst, PXSTRIDE(f->cur.p.stride[0]),
+                    hex_dump(dst, PXSTRIDE(f->cur.stride[0]),
                              bw4 * 4, bh4 * 4, "y-pal-pred");
             }
 
@@ -790,8 +796,8 @@
             for (y = init_y, t->by += init_y; y < sub_h4;
                  y += t_dim->h, t->by += t_dim->h)
             {
-                pixel *dst = ((pixel *) f->cur.p.data[0]) +
-                               4 * (t->by * PXSTRIDE(f->cur.p.stride[0]) +
+                pixel *dst = ((pixel *) f->cur.data[0]) +
+                               4 * (t->by * PXSTRIDE(f->cur.stride[0]) +
                                     t->bx + init_x);
                 for (x = init_x, t->bx += init_x; x < sub_w4;
                      x += t_dim->w, t->bx += t_dim->w)
@@ -818,10 +824,10 @@
                                                           ts->tiling.col_end,
                                                           ts->tiling.row_end,
                                                           edge_flags, dst,
-                                                          f->cur.p.stride[0], top_sb_edge,
+                                                          f->cur.stride[0], top_sb_edge,
                                                           b->y_mode, &angle,
                                                           t_dim->w, t_dim->h, edge);
-                    dsp->ipred.intra_pred[m](dst, f->cur.p.stride[0], edge,
+                    dsp->ipred.intra_pred[m](dst, f->cur.stride[0], edge,
                                              t_dim->w * 4, t_dim->h * 4,
                                              angle | intra_flags,
                                              4 * f->bw - 4 * t->bx,
@@ -833,7 +839,7 @@
                         hex_dump(edge, 0, 1, 1, "tl");
                         hex_dump(edge + 1, t_dim->w * 4,
                                  t_dim->w * 4, 2, "t");
-                        hex_dump(dst, f->cur.p.stride[0],
+                        hex_dump(dst, f->cur.stride[0],
                                  t_dim->w * 4, t_dim->h * 4, "y-intra-pred");
                     }
 
@@ -875,10 +881,10 @@
                                           imin(t_dim->w, 8) * 4, 3, "dq");
                             dsp->itx.itxfm_add[b->tx]
                                               [txtp](dst,
-                                                     f->cur.p.stride[0],
+                                                     f->cur.stride[0],
                                                      cf, eob);
                             if (DEBUG_BLOCK_INFO && DEBUG_B_PIXELS)
-                                hex_dump(dst, f->cur.p.stride[0],
+                                hex_dump(dst, f->cur.stride[0],
                                          t_dim->w * 4, t_dim->h * 4, "recon");
                         }
                     } else if (!f->frame_thread.pass) {
@@ -896,24 +902,24 @@
 
             if (!has_chroma) continue;
 
-            const ptrdiff_t stride = f->cur.p.stride[1];
+            const ptrdiff_t stride = f->cur.stride[1];
 
             if (b->uv_mode == CFL_PRED) {
                 assert(!init_x && !init_y);
 
                 int16_t *const ac = t->scratch.ac;
-                pixel *y_src = ((pixel *) f->cur.p.data[0]) + 4 * (t->bx & ~ss_hor) +
-                                 4 * (t->by & ~ss_ver) * PXSTRIDE(f->cur.p.stride[0]);
+                pixel *y_src = ((pixel *) f->cur.data[0]) + 4 * (t->bx & ~ss_hor) +
+                                 4 * (t->by & ~ss_ver) * PXSTRIDE(f->cur.stride[0]);
                 const ptrdiff_t uv_off = 4 * ((t->bx >> ss_hor) +
                                               (t->by >> ss_ver) * PXSTRIDE(stride));
-                pixel *const uv_dst[2] = { ((pixel *) f->cur.p.data[1]) + uv_off,
-                                           ((pixel *) f->cur.p.data[2]) + uv_off };
+                pixel *const uv_dst[2] = { ((pixel *) f->cur.data[1]) + uv_off,
+                                           ((pixel *) f->cur.data[2]) + uv_off };
 
                 const int furthest_r =
                     ((cw4 << ss_hor) + t_dim->w - 1) & ~(t_dim->w - 1);
                 const int furthest_b =
                     ((ch4 << ss_ver) + t_dim->h - 1) & ~(t_dim->h - 1);
-                dsp->ipred.cfl_ac[f->cur.p.p.layout - 1](ac, y_src, f->cur.p.stride[0],
+                dsp->ipred.cfl_ac[f->cur.p.layout - 1](ac, y_src, f->cur.stride[0],
                                                          cbw4 - (furthest_r >> ss_hor),
                                                          cbh4 - (furthest_b >> ss_ver),
                                                          cbw4 * 4, cbh4 * 4);
@@ -950,7 +956,7 @@
                 }
             } else if (b->pal_sz[1]) {
                 ptrdiff_t uv_dstoff = 4 * ((t->bx >> ss_hor) +
-                                           (t->by >> ss_ver) * PXSTRIDE(f->cur.p.stride[1]));
+                                           (t->by >> ss_ver) * PXSTRIDE(f->cur.stride[1]));
                 const uint8_t *pal_idx;
                 if (f->frame_thread.pass) {
                     pal_idx = ts->frame_thread.pal_idx;
@@ -961,21 +967,21 @@
                 const uint16_t *const pal_u = f->frame_thread.pass ?
                     f->frame_thread.pal[((t->by >> 1) + (t->bx & 1)) * (f->b4_stride >> 1) +
                                         ((t->bx >> 1) + (t->by & 1))][1] : t->pal[1];
-                f->dsp->ipred.pal_pred(((pixel *) f->cur.p.data[1]) + uv_dstoff,
-                                       f->cur.p.stride[1], pal_u,
+                f->dsp->ipred.pal_pred(((pixel *) f->cur.data[1]) + uv_dstoff,
+                                       f->cur.stride[1], pal_u,
                                        pal_idx, cbw4 * 4, cbh4 * 4);
                 const uint16_t *const pal_v = f->frame_thread.pass ?
                     f->frame_thread.pal[((t->by >> 1) + (t->bx & 1)) * (f->b4_stride >> 1) +
                                         ((t->bx >> 1) + (t->by & 1))][2] : t->pal[2];
-                f->dsp->ipred.pal_pred(((pixel *) f->cur.p.data[2]) + uv_dstoff,
-                                       f->cur.p.stride[1], pal_v,
+                f->dsp->ipred.pal_pred(((pixel *) f->cur.data[2]) + uv_dstoff,
+                                       f->cur.stride[1], pal_v,
                                        pal_idx, cbw4 * 4, cbh4 * 4);
                 if (DEBUG_BLOCK_INFO && DEBUG_B_PIXELS) {
-                    hex_dump(((pixel *) f->cur.p.data[1]) + uv_dstoff,
-                             PXSTRIDE(f->cur.p.stride[1]),
+                    hex_dump(((pixel *) f->cur.data[1]) + uv_dstoff,
+                             PXSTRIDE(f->cur.stride[1]),
                              cbw4 * 4, cbh4 * 4, "u-pal-pred");
-                    hex_dump(((pixel *) f->cur.p.data[2]) + uv_dstoff,
-                             PXSTRIDE(f->cur.p.stride[1]),
+                    hex_dump(((pixel *) f->cur.data[2]) + uv_dstoff,
+                             PXSTRIDE(f->cur.stride[1]),
                              cbw4 * 4, cbh4 * 4, "v-pal-pred");
                 }
             }
@@ -984,10 +990,10 @@
                                  sm_uv_flag(&t->l, cby4);
             const int uv_sb_has_tr =
                 ((init_x + 16) >> ss_hor) < cw4 ? 1 : init_y ? 0 :
-                intra_edge_flags & (EDGE_I420_TOP_HAS_RIGHT >> (f->cur.p.p.layout - 1));
+                intra_edge_flags & (EDGE_I420_TOP_HAS_RIGHT >> (f->cur.p.layout - 1));
             const int uv_sb_has_bl =
                 init_x ? 0 : ((init_y + 16) >> ss_ver) < ch4 ? 1 :
-                intra_edge_flags & (EDGE_I420_LEFT_HAS_BOTTOM >> (f->cur.p.p.layout - 1));
+                intra_edge_flags & (EDGE_I420_LEFT_HAS_BOTTOM >> (f->cur.p.layout - 1));
             const int sub_ch4 = imin(ch4, (init_y + 16) >> ss_ver);
             const int sub_cw4 = imin(cw4, (init_x + 16) >> ss_hor);
             for (int pl = 0; pl < 2; pl++) {
@@ -994,7 +1000,7 @@
                 for (y = init_y >> ss_ver, t->by += init_y; y < sub_ch4;
                      y += uv_t_dim->h, t->by += uv_t_dim->h << ss_ver)
                 {
-                    pixel *dst = ((pixel *) f->cur.p.data[1 + pl]) +
+                    pixel *dst = ((pixel *) f->cur.data[1 + pl]) +
                                    4 * ((t->by >> ss_ver) * PXSTRIDE(stride) +
                                         ((t->bx + init_x) >> ss_hor));
                     for (x = init_x >> ss_hor, t->bx += init_x; x < sub_cw4;
@@ -1127,8 +1133,8 @@
     const Dav1dFrameContext *const f = t->f;
     const Dav1dDSPContext *const dsp = f->dsp;
     const int bx4 = t->bx & 31, by4 = t->by & 31;
-    const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-    const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+    const int ss_ver = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_hor = f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
     const int cbx4 = bx4 >> ss_hor, cby4 = by4 >> ss_ver;
     const uint8_t *const b_dim = dav1d_block_dimensions[bs];
     const int bw4 = b_dim[0], bh4 = b_dim[1];
@@ -1136,26 +1142,27 @@
     const int has_chroma = f->seq_hdr.layout != DAV1D_PIXEL_LAYOUT_I400 &&
                            (bw4 > ss_hor || t->bx & 1) &&
                            (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;
+    const int chr_layout_idx = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I400 ? 0 :
+                               DAV1D_PIXEL_LAYOUT_I444 - f->cur.p.layout;
     int res;
 
     // prediction
     const int cbh4 = (bh4 + ss_ver) >> ss_ver, cbw4 = (bw4 + ss_hor) >> ss_hor;
-    pixel *dst = ((pixel *) f->cur.p.data[0]) +
-        4 * (t->by * PXSTRIDE(f->cur.p.stride[0]) + t->bx);
+    pixel *dst = ((pixel *) f->cur.data[0]) +
+        4 * (t->by * PXSTRIDE(f->cur.stride[0]) + t->bx);
     const ptrdiff_t uvdstoff =
-        4 * ((t->bx >> ss_hor) + (t->by >> ss_ver) * PXSTRIDE(f->cur.p.stride[1]));
+        4 * ((t->bx >> ss_hor) + (t->by >> ss_ver) * PXSTRIDE(f->cur.stride[1]));
     if (!(f->frame_hdr.frame_type & 1)) {
         // intrabc
-        res = mc(t, dst, NULL, f->cur.p.stride[0],
-                 bw4, bh4, t->bx, t->by, 0, b->mv[0], &f->cur, -1, FILTER_2D_BILINEAR);
+        assert(!f->frame_hdr.super_res.enabled);
+        res = mc(t, dst, NULL, f->cur.stride[0], bw4, bh4, t->bx, t->by, 0,
+                 b->mv[0], &f->sr_cur, 0 /* unused */, 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],
+            res = mc(t, ((pixel *)f->cur.data[pl]) + uvdstoff, NULL, f->cur.stride[1],
                      bw4 << (bw4 == ss_hor), bh4 << (bh4 == ss_ver),
-                     t->bx & ~ss_hor, t->by & ~ss_ver,
-                     pl, b->mv[0], &f->cur, -1, FILTER_2D_BILINEAR);
+                     t->bx & ~ss_hor, t->by & ~ss_ver, pl, b->mv[0],
+                     &f->sr_cur, 0 /* unused */, FILTER_2D_BILINEAR);
             if (res) return res;
         }
     } else if (b->comp_type == COMP_INTER_NONE) {
@@ -1168,16 +1175,16 @@
              (b->motion_mode == MM_WARP &&
               t->warpmv.type > WM_TYPE_TRANSLATION)))
         {
-            res = warp_affine(t, dst, NULL, f->cur.p.stride[0], b_dim, 0, refp,
+            res = warp_affine(t, dst, NULL, f->cur.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 {
-            res = mc(t, dst, NULL, f->cur.p.stride[0],
+            res = mc(t, dst, NULL, f->cur.stride[0],
                      bw4, bh4, t->bx, t->by, 0, b->mv[0], refp, b->ref[0], 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);
+                res = obmc(t, dst, f->cur.stride[0], b_dim, 0, bx4, by4, w4, h4);
                 if (res) return res;
             }
         }
@@ -1197,7 +1204,7 @@
             m = bytefn(dav1d_prepare_intra_edges)(t->bx, t->bx > ts->tiling.col_start,
                                                   t->by, t->by > ts->tiling.row_start,
                                                   ts->tiling.col_end, ts->tiling.row_end,
-                                                  0, dst, f->cur.p.stride[0], top_sb_edge,
+                                                  0, dst, f->cur.stride[0], top_sb_edge,
                                                   m, &angle, bw4, bh4, tl_edge);
             dsp->ipred.intra_pred[m](tmp, 4 * bw4 * sizeof(pixel),
                                      tl_edge, bw4 * 4, bh4 * 4, 0, 0, 0);
@@ -1205,7 +1212,7 @@
                 b->interintra_type == INTER_INTRA_BLEND ?
                      dav1d_ii_masks[bs][0][b->interintra_mode] :
                      dav1d_wedge_masks[bs][0][0][b->wedge_idx];
-            dsp->mc.blend(dst, f->cur.p.stride[0], tmp,
+            dsp->mc.blend(dst, f->cur.stride[0], tmp,
                           bw4 * 4, bh4 * 4, ii_mask);
         }
 
@@ -1229,8 +1236,8 @@
             int h_off = 0, v_off = 0;
             if (bw4 == 1 && bh4 == ss_ver) {
                 for (int pl = 0; pl < 2; pl++) {
-                    res = mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff,
-                             NULL, f->cur.p.stride[1],
+                    res = mc(t, ((pixel *) f->cur.data[1 + pl]) + uvdstoff,
+                             NULL, f->cur.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],
@@ -1239,7 +1246,7 @@
                                  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]);
+                v_off = 2 * PXSTRIDE(f->cur.stride[1]);
                 h_off = 2;
             }
             if (bw4 == 1) {
@@ -1246,8 +1253,8 @@
                 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++) {
-                    res = mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff + v_off, NULL,
-                             f->cur.p.stride[1], bw4, bh4, t->bx - 1,
+                    res = mc(t, ((pixel *) f->cur.data[1 + pl]) + uvdstoff + v_off, NULL,
+                             f->cur.stride[1], bw4, bh4, t->bx - 1,
                              t->by, 1 + pl, r[-1].mv[0], &f->refp[r[-1].ref[0] - 1],
                              r[-1].ref[0] - 1,
                              f->frame_thread.pass != 2 ? left_filter_2d :
@@ -1260,8 +1267,8 @@
                 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++) {
-                    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,
+                    res = mc(t, ((pixel *) f->cur.data[1 + pl]) + uvdstoff + h_off, NULL,
+                             f->cur.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],
                              r[-f->b4_stride].ref[0] - 1,
@@ -1269,10 +1276,10 @@
                                  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]);
+                v_off = 2 * PXSTRIDE(f->cur.stride[1]);
             }
             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],
+                res = mc(t, ((pixel *) f->cur.data[1 + pl]) + uvdstoff + h_off + v_off, NULL, f->cur.stride[1],
                          bw4, bh4, t->bx, t->by, 1 + pl, b->mv[0],
                          refp, b->ref[0], filter_2d);
                 if (res) return res;
@@ -1285,8 +1292,8 @@
                   t->warpmv.type > WM_TYPE_TRANSLATION)))
             {
                 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,
+                    res = warp_affine(t, ((pixel *) f->cur.data[1 + pl]) + uvdstoff, NULL,
+                                      f->cur.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;
@@ -1293,15 +1300,15 @@
                 }
             } else {
                 for (int pl = 0; pl < 2; pl++) {
-                    res = mc(t, ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff,
-                             NULL, f->cur.p.stride[1],
+                    res = mc(t, ((pixel *) f->cur.data[1 + pl]) + uvdstoff,
+                             NULL, f->cur.stride[1],
                              bw4 << (bw4 == ss_hor), bh4 << (bh4 == ss_ver),
                              t->bx & ~ss_hor, t->by & ~ss_ver,
                              1 + pl, b->mv[0], refp, b->ref[0], 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);
+                        res = obmc(t, ((pixel *) f->cur.data[1 + pl]) + uvdstoff,
+                                   f->cur.stride[1], b_dim, 1 + pl, bx4, by4, w4, h4);
                         if (res) return res;
                     }
                 }
@@ -1322,7 +1329,7 @@
                         b->interintra_mode == II_SMOOTH_PRED ?
                         SMOOTH_PRED : b->interintra_mode;
                     int angle = 0;
-                    pixel *const uvdst = ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff;
+                    pixel *const uvdst = ((pixel *) f->cur.data[1 + pl]) + uvdstoff;
                     const pixel *top_sb_edge = NULL;
                     if (!(t->by & (f->sb_step - 1))) {
                         top_sb_edge = f->ipred_edge[pl + 1];
@@ -1337,12 +1344,12 @@
                                                               (ts->tiling.row_start >> ss_ver),
                                                           ts->tiling.col_end >> ss_hor,
                                                           ts->tiling.row_end >> ss_ver,
-                                                          0, uvdst, f->cur.p.stride[1],
+                                                          0, uvdst, f->cur.stride[1],
                                                           top_sb_edge, m,
                                                           &angle, cbw4, cbh4, tl_edge);
                     dsp->ipred.intra_pred[m](tmp, cbw4 * 4 * sizeof(pixel),
                                              tl_edge, cbw4 * 4, cbh4 * 4, 0, 0, 0);
-                    dsp->mc.blend(uvdst, f->cur.p.stride[1], tmp,
+                    dsp->mc.blend(uvdst, f->cur.stride[1], tmp,
                                   cbw4 * 4, cbh4 * 4, ii_mask);
                 }
             }
@@ -1370,21 +1377,23 @@
             } else {
                 res = mc(t, NULL, tmp[i], 0, bw4, bh4, t->bx, t->by, 0,
                          b->mv[i], refp, b->ref[i], filter_2d);
+                if (DEBUG_BLOCK_INFO)
+                    coef_dump(tmp[i], bw4*4, bh4*4, 3, "med");
                 if (res) return res;
             }
         }
         switch (b->comp_type) {
         case COMP_INTER_AVG:
-            dsp->mc.avg(dst, f->cur.p.stride[0], tmp[0], tmp[1],
+            dsp->mc.avg(dst, f->cur.stride[0], tmp[0], tmp[1],
                         bw4 * 4, bh4 * 4);
             break;
         case COMP_INTER_WEIGHTED_AVG:
             jnt_weight = f->jnt_weights[b->ref[0]][b->ref[1]];
-            dsp->mc.w_avg(dst, f->cur.p.stride[0], tmp[0], tmp[1],
+            dsp->mc.w_avg(dst, f->cur.stride[0], tmp[0], tmp[1],
                           bw4 * 4, bh4 * 4, jnt_weight);
             break;
         case COMP_INTER_SEG:
-            dsp->mc.w_mask[chr_layout_idx](dst, f->cur.p.stride[0],
+            dsp->mc.w_mask[chr_layout_idx](dst, f->cur.stride[0],
                                            tmp[b->mask_sign], tmp[!b->mask_sign],
                                            bw4 * 4, bh4 * 4, seg_mask, b->mask_sign);
             mask = seg_mask;
@@ -1391,7 +1400,7 @@
             break;
         case COMP_INTER_WEDGE:
             mask = dav1d_wedge_masks[bs][0][0][b->wedge_idx];
-            dsp->mc.mask(dst, f->cur.p.stride[0],
+            dsp->mc.mask(dst, f->cur.stride[0],
                          tmp[b->mask_sign], tmp[!b->mask_sign],
                          bw4 * 4, bh4 * 4, mask);
             if (has_chroma)
@@ -1416,19 +1425,19 @@
                     if (res) return res;
                 }
             }
-            pixel *const uvdst = ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff;
+            pixel *const uvdst = ((pixel *) f->cur.data[1 + pl]) + uvdstoff;
             switch (b->comp_type) {
             case COMP_INTER_AVG:
-                dsp->mc.avg(uvdst, f->cur.p.stride[1], tmp[0], tmp[1],
+                dsp->mc.avg(uvdst, f->cur.stride[1], tmp[0], tmp[1],
                             bw4 * 4 >> ss_hor, bh4 * 4 >> ss_ver);
                 break;
             case COMP_INTER_WEIGHTED_AVG:
-                dsp->mc.w_avg(uvdst, f->cur.p.stride[1], tmp[0], tmp[1],
+                dsp->mc.w_avg(uvdst, f->cur.stride[1], tmp[0], tmp[1],
                               bw4 * 4 >> ss_hor, bh4 * 4 >> ss_ver, jnt_weight);
                 break;
             case COMP_INTER_WEDGE:
             case COMP_INTER_SEG:
-                dsp->mc.mask(uvdst, f->cur.p.stride[1],
+                dsp->mc.mask(uvdst, f->cur.stride[1],
                              tmp[b->mask_sign], tmp[!b->mask_sign],
                              bw4 * 4 >> ss_hor, bh4 * 4 >> ss_ver, mask);
                 break;
@@ -1437,11 +1446,11 @@
     }
 
     if (DEBUG_BLOCK_INFO && DEBUG_B_PIXELS) {
-        hex_dump(dst, f->cur.p.stride[0], b_dim[0] * 4, b_dim[1] * 4, "y-pred");
+        hex_dump(dst, f->cur.stride[0], b_dim[0] * 4, b_dim[1] * 4, "y-pred");
         if (has_chroma) {
-            hex_dump(&((pixel *) f->cur.p.data[1])[uvdstoff], f->cur.p.stride[1],
+            hex_dump(&((pixel *) f->cur.data[1])[uvdstoff], f->cur.stride[1],
                      cbw4 * 4, cbh4 * 4, "u-pred");
-            hex_dump(&((pixel *) f->cur.p.data[2])[uvdstoff], f->cur.p.stride[1],
+            hex_dump(&((pixel *) f->cur.data[2])[uvdstoff], f->cur.stride[1],
                      cbw4 * 4, cbh4 * 4, "v-pred");
         }
     }
@@ -1473,7 +1482,7 @@
         for (int init_x = 0; init_x < bw4; init_x += 16) {
             // coefficient coding & inverse transforms
             int y_off = !!init_y, y;
-            dst += PXSTRIDE(f->cur.p.stride[0]) * 4 * init_y;
+            dst += PXSTRIDE(f->cur.stride[0]) * 4 * init_y;
             for (y = init_y, t->by += init_y; y < imin(h4, init_y + 16);
                  y += ytx->h, y_off++)
             {
@@ -1485,17 +1494,17 @@
                                    x_off, y_off, &dst[x * 4]);
                     t->bx += ytx->w;
                 }
-                dst += PXSTRIDE(f->cur.p.stride[0]) * 4 * ytx->h;
+                dst += PXSTRIDE(f->cur.stride[0]) * 4 * ytx->h;
                 t->bx -= x;
                 t->by += ytx->h;
             }
-            dst -= PXSTRIDE(f->cur.p.stride[0]) * 4 * y;
+            dst -= PXSTRIDE(f->cur.stride[0]) * 4 * y;
             t->by -= y;
 
             // chroma coefs and inverse transform
             if (has_chroma) for (int pl = 0; pl < 2; pl++) {
-                pixel *uvdst = ((pixel *) f->cur.p.data[1 + pl]) + uvdstoff +
-                    (PXSTRIDE(f->cur.p.stride[1]) * init_y * 4 >> ss_ver);
+                pixel *uvdst = ((pixel *) f->cur.data[1 + pl]) + uvdstoff +
+                    (PXSTRIDE(f->cur.stride[1]) * init_y * 4 >> ss_ver);
                 for (y = init_y >> ss_ver, t->by += init_y;
                      y < imin(ch4, (init_y + 16) >> ss_ver); y += uvtx->h)
                 {
@@ -1544,15 +1553,15 @@
                                 coef_dump(cf, uvtx->h * 4, uvtx->w * 4, 3, "dq");
                             dsp->itx.itxfm_add[b->uvtx]
                                               [txtp](&uvdst[4 * x],
-                                                     f->cur.p.stride[1],
+                                                     f->cur.stride[1],
                                                      cf, eob);
                             if (DEBUG_BLOCK_INFO && DEBUG_B_PIXELS)
-                                hex_dump(&uvdst[4 * x], f->cur.p.stride[1],
+                                hex_dump(&uvdst[4 * x], f->cur.stride[1],
                                          uvtx->w * 4, uvtx->h * 4, "recon");
                         }
                         t->bx += uvtx->w << ss_hor;
                     }
-                    uvdst += PXSTRIDE(f->cur.p.stride[1]) * 4 * uvtx->h;
+                    uvdst += PXSTRIDE(f->cur.stride[1]) * 4 * uvtx->h;
                     t->bx -= x << ss_hor;
                     t->by += uvtx->h << ss_ver;
                 }
@@ -1564,7 +1573,7 @@
 }
 
 void bytefn(dav1d_filter_sbrow)(Dav1dFrameContext *const f, const int sby) {
-    const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+    const int ss_ver = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
     const int sbsz = f->sb_step, sbh = f->sbh;
 
     if (f->frame_hdr.loopfilter.level_y[0] ||
@@ -1584,9 +1593,9 @@
     if (f->seq_hdr.cdef) {
         if (sby) {
             pixel *p_up[3] = {
-                f->lf.p[0] - 8 * PXSTRIDE(f->cur.p.stride[0]),
-                f->lf.p[1] - (8 * PXSTRIDE(f->cur.p.stride[1]) >> ss_ver),
-                f->lf.p[2] - (8 * PXSTRIDE(f->cur.p.stride[1]) >> ss_ver),
+                f->lf.p[0] - 8 * PXSTRIDE(f->cur.stride[0]),
+                f->lf.p[1] - (8 * PXSTRIDE(f->cur.stride[1]) >> ss_ver),
+                f->lf.p[2] - (8 * PXSTRIDE(f->cur.stride[1]) >> ss_ver),
             };
             bytefn(dav1d_cdef_brow)(f, p_up, f->lf.prev_mask_ptr,
                                     sby * sbsz - 2, sby * sbsz);
@@ -1595,13 +1604,34 @@
         bytefn(dav1d_cdef_brow)(f, f->lf.p, f->lf.mask_ptr, sby * sbsz,
                                 imin(sby * sbsz + n_blks, f->bh));
     }
+    if (f->frame_hdr.super_res.enabled) {
+        const int has_chroma = f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I400;
+        for (int pl = 0; pl < 1 + 2 * has_chroma; pl++) {
+            const int h_start = 8 * !!sby >> (ss_ver & !!pl);
+            const ptrdiff_t dst_stride = f->sr_cur.p.stride[!!pl];
+            pixel *dst = f->lf.sr_p[pl] - h_start * PXSTRIDE(dst_stride);
+            const ptrdiff_t src_stride = f->cur.stride[!!pl];
+            const pixel *src = f->lf.p[pl] - h_start * PXSTRIDE(src_stride);
+            const int h_end = 4 * (sbsz - 2 * (sby + 1 < sbh)) >> (ss_ver & !!pl);
+            const int ss_hor = pl && f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+            const int dst_w = (f->sr_cur.p.p.w + ss_hor) >> ss_hor;
+            const int src_w = (4 * f->bw + ss_hor) >> ss_hor;
+
+            f->dsp->mc.resize(dst, dst_stride, src, src_stride, dst_w, src_w,
+                              h_end + h_start, f->resize_step[!!pl],
+                              f->resize_start[!!pl]);
+        }
+    }
     if (f->seq_hdr.restoration) {
-        bytefn(dav1d_lr_sbrow)(f, f->lf.p, sby);
+        bytefn(dav1d_lr_sbrow)(f, f->lf.sr_p, sby);
     }
 
-    f->lf.p[0] += sbsz * 4 * PXSTRIDE(f->cur.p.stride[0]);
-    f->lf.p[1] += sbsz * 4 * PXSTRIDE(f->cur.p.stride[1]) >> ss_ver;
-    f->lf.p[2] += sbsz * 4 * PXSTRIDE(f->cur.p.stride[1]) >> ss_ver;
+    f->lf.p[0] += sbsz * 4 * PXSTRIDE(f->cur.stride[0]);
+    f->lf.p[1] += sbsz * 4 * PXSTRIDE(f->cur.stride[1]) >> ss_ver;
+    f->lf.p[2] += sbsz * 4 * PXSTRIDE(f->cur.stride[1]) >> ss_ver;
+    f->lf.sr_p[0] += sbsz * 4 * PXSTRIDE(f->sr_cur.p.stride[0]);
+    f->lf.sr_p[1] += sbsz * 4 * PXSTRIDE(f->sr_cur.p.stride[1]) >> ss_ver;
+    f->lf.sr_p[2] += sbsz * 4 * PXSTRIDE(f->sr_cur.p.stride[1]) >> ss_ver;
     f->lf.prev_mask_ptr = f->lf.mask_ptr;
     if ((sby & 1) || f->seq_hdr.sb128) {
         f->lf.mask_ptr += f->sb128w;
@@ -1616,20 +1646,20 @@
     const int x_off = ts->tiling.col_start;
 
     const pixel *const y =
-        ((const pixel *) f->cur.p.data[0]) + x_off * 4 +
-                    ((t->by + f->sb_step) * 4 - 1) * PXSTRIDE(f->cur.p.stride[0]);
+        ((const pixel *) f->cur.data[0]) + x_off * 4 +
+                    ((t->by + f->sb_step) * 4 - 1) * PXSTRIDE(f->cur.stride[0]);
     pixel_copy(&f->ipred_edge[0][sby_off + x_off * 4], y,
                4 * (ts->tiling.col_end - x_off));
 
-    if (f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I400) {
-        const int ss_ver = f->cur.p.p.layout == DAV1D_PIXEL_LAYOUT_I420;
-        const int ss_hor = f->cur.p.p.layout != DAV1D_PIXEL_LAYOUT_I444;
+    if (f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I400) {
+        const int ss_ver = f->cur.p.layout == DAV1D_PIXEL_LAYOUT_I420;
+        const int ss_hor = f->cur.p.layout != DAV1D_PIXEL_LAYOUT_I444;
 
         const ptrdiff_t uv_off = (x_off * 4 >> ss_hor) +
-            (((t->by + f->sb_step) * 4 >> ss_ver) - 1) * PXSTRIDE(f->cur.p.stride[1]);
+            (((t->by + f->sb_step) * 4 >> ss_ver) - 1) * PXSTRIDE(f->cur.stride[1]);
         for (int pl = 1; pl <= 2; pl++)
             pixel_copy(&f->ipred_edge[pl][sby_off + (x_off * 4 >> ss_hor)],
-                       &((const pixel *) f->cur.p.data[pl])[uv_off],
+                       &((const pixel *) f->cur.data[pl])[uv_off],
                        4 * (ts->tiling.col_end - x_off) >> ss_hor);
     }
 }
--- a/src/tables.c
+++ b/src/tables.c
@@ -712,6 +712,41 @@
     { 0, 0,   2,  -1, 0,   0, 127, 0 }
 };
 
+const int16_t dav1d_resize_filter[64][8] = {
+    {  0, 0,   0, 128,   0,   0, 0,  0 }, {  0, 0,  -1, 128,   2,  -1, 0,  0 },
+    {  0, 1,  -3, 127,   4,  -2, 1,  0 }, {  0, 1,  -4, 127,   6,  -3, 1,  0 },
+    {  0, 2,  -6, 126,   8,  -3, 1,  0 }, {  0, 2,  -7, 125,  11,  -4, 1,  0 },
+    { -1, 2,  -8, 125,  13,  -5, 2,  0 }, { -1, 3,  -9, 124,  15,  -6, 2,  0 },
+    { -1, 3, -10, 123,  18,  -6, 2, -1 }, { -1, 3, -11, 122,  20,  -7, 3, -1 },
+    { -1, 4, -12, 121,  22,  -8, 3, -1 }, { -1, 4, -13, 120,  25,  -9, 3, -1 },
+    { -1, 4, -14, 118,  28,  -9, 3, -1 }, { -1, 4, -15, 117,  30, -10, 4, -1 },
+    { -1, 5, -16, 116,  32, -11, 4, -1 }, { -1, 5, -16, 114,  35, -12, 4, -1 },
+    { -1, 5, -17, 112,  38, -12, 4, -1 }, { -1, 5, -18, 111,  40, -13, 5, -1 },
+    { -1, 5, -18, 109,  43, -14, 5, -1 }, { -1, 6, -19, 107,  45, -14, 5, -1 },
+    { -1, 6, -19, 105,  48, -15, 5, -1 }, { -1, 6, -19, 103,  51, -16, 5, -1 },
+    { -1, 6, -20, 101,  53, -16, 6, -1 }, { -1, 6, -20,  99,  56, -17, 6, -1 },
+    { -1, 6, -20,  97,  58, -17, 6, -1 }, { -1, 6, -20,  95,  61, -18, 6, -1 },
+    { -2, 7, -20,  93,  64, -18, 6, -2 }, { -2, 7, -20,  91,  66, -19, 6, -1 },
+    { -2, 7, -20,  88,  69, -19, 6, -1 }, { -2, 7, -20,  86,  71, -19, 6, -1 },
+    { -2, 7, -20,  84,  74, -20, 7, -2 }, { -2, 7, -20,  81,  76, -20, 7, -1 },
+    { -2, 7, -20,  79,  79, -20, 7, -2 }, { -1, 7, -20,  76,  81, -20, 7, -2 },
+    { -2, 7, -20,  74,  84, -20, 7, -2 }, { -1, 6, -19,  71,  86, -20, 7, -2 },
+    { -1, 6, -19,  69,  88, -20, 7, -2 }, { -1, 6, -19,  66,  91, -20, 7, -2 },
+    { -2, 6, -18,  64,  93, -20, 7, -2 }, { -1, 6, -18,  61,  95, -20, 6, -1 },
+    { -1, 6, -17,  58,  97, -20, 6, -1 }, { -1, 6, -17,  56,  99, -20, 6, -1 },
+    { -1, 6, -16,  53, 101, -20, 6, -1 }, { -1, 5, -16,  51, 103, -19, 6, -1 },
+    { -1, 5, -15,  48, 105, -19, 6, -1 }, { -1, 5, -14,  45, 107, -19, 6, -1 },
+    { -1, 5, -14,  43, 109, -18, 5, -1 }, { -1, 5, -13,  40, 111, -18, 5, -1 },
+    { -1, 4, -12,  38, 112, -17, 5, -1 }, { -1, 4, -12,  35, 114, -16, 5, -1 },
+    { -1, 4, -11,  32, 116, -16, 5, -1 }, { -1, 4, -10,  30, 117, -15, 4, -1 },
+    { -1, 3,  -9,  28, 118, -14, 4, -1 }, { -1, 3,  -9,  25, 120, -13, 4, -1 },
+    { -1, 3,  -8,  22, 121, -12, 4, -1 }, { -1, 3,  -7,  20, 122, -11, 3, -1 },
+    { -1, 2,  -6,  18, 123, -10, 3, -1 }, {  0, 2,  -6,  15, 124,  -9, 3, -1 },
+    {  0, 2,  -5,  13, 125,  -8, 2, -1 }, {  0, 1,  -4,  11, 125,  -7, 2,  0 },
+    {  0, 1,  -3,   8, 126,  -6, 2,  0 }, {  0, 1,  -3,   6, 127,  -4, 1,  0 },
+    {  0, 1,  -2,   4, 127,  -3, 1,  0 }, {  0, 0,  -1,   2, 128,  -1, 0,  0 },
+};
+
 const uint8_t dav1d_sm_weights[128] = {
     // Unused, because we always offset by bs, which is at least 2.
       0,   0,
--- a/src/tables.h
+++ b/src/tables.h
@@ -111,6 +111,7 @@
 
 extern const int8_t dav1d_mc_subpel_filters[5][15][8];
 extern const int8_t dav1d_mc_warp_filter[193][8];
+extern const int16_t dav1d_resize_filter[64][8];
 
 extern const uint8_t dav1d_sm_weights[128];
 extern const int16_t dav1d_dr_intra_derivative[90];