Coverage Report

Created: 2026-10-17 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/dav1d/src/refmvs.c
Line
Count
Source
1
/*
2
 * Copyright © 2020, VideoLAN and dav1d authors
3
 * Copyright © 2020, Two Orioles, LLC
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * 1. Redistributions of source code must retain the above copyright notice, this
10
 *    list of conditions and the following disclaimer.
11
 *
12
 * 2. Redistributions in binary form must reproduce the above copyright notice,
13
 *    this list of conditions and the following disclaimer in the documentation
14
 *    and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
#include "config.h"
29
30
#include <limits.h>
31
#include <stdlib.h>
32
#include <string.h>
33
34
#include "dav1d/common.h"
35
36
#include "common/intops.h"
37
38
#include "src/env.h"
39
#include "src/mem.h"
40
#include "src/refmvs.h"
41
42
static void add_spatial_candidate(refmvs_candidate *const mvstack, int *const cnt,
43
                                  const int weight, const refmvs_block *const b,
44
                                  const union refmvs_refpair ref, const mv gmv[2],
45
                                  int *const have_newmv_match,
46
                                  int *const have_refmv_match)
47
0
{
48
0
    if (b->mv.mv[0].n == INVALID_MV) return; // intra block, no intrabc
49
50
0
    if (ref.ref[1] == -1) {
51
0
        for (int n = 0; n < 2; n++) {
52
0
            if (b->ref.ref[n] == ref.ref[0]) {
53
0
                const mv cand_mv = ((b->mf & 1) && gmv[0].n != INVALID_MV) ?
54
0
                                   gmv[0] : b->mv.mv[n];
55
56
0
                *have_refmv_match = 1;
57
0
                *have_newmv_match |= b->mf >> 1;
58
59
0
                const int last = *cnt;
60
0
                for (int m = 0; m < last; m++)
61
0
                    if (mvstack[m].mv.mv[0].n == cand_mv.n) {
62
0
                        mvstack[m].weight += weight;
63
0
                        return;
64
0
                    }
65
66
0
                if (last < 8) {
67
0
                    mvstack[last].mv.mv[0] = cand_mv;
68
0
                    mvstack[last].weight = weight;
69
0
                    *cnt = last + 1;
70
0
                }
71
0
                return;
72
0
            }
73
0
        }
74
0
    } else if (b->ref.pair == ref.pair) {
75
0
        const refmvs_mvpair cand_mv = { .mv = {
76
0
            [0] = ((b->mf & 1) && gmv[0].n != INVALID_MV) ? gmv[0] : b->mv.mv[0],
77
0
            [1] = ((b->mf & 1) && gmv[1].n != INVALID_MV) ? gmv[1] : b->mv.mv[1],
78
0
        }};
79
80
0
        *have_refmv_match = 1;
81
0
        *have_newmv_match |= b->mf >> 1;
82
83
0
        const int last = *cnt;
84
0
        for (int n = 0; n < last; n++)
85
0
            if (mvstack[n].mv.n == cand_mv.n) {
86
0
                mvstack[n].weight += weight;
87
0
                return;
88
0
            }
89
90
0
        if (last < 8) {
91
0
            mvstack[last].mv = cand_mv;
92
0
            mvstack[last].weight = weight;
93
0
            *cnt = last + 1;
94
0
        }
95
0
    }
96
0
}
97
98
static int scan_row(refmvs_candidate *const mvstack, int *const cnt,
99
                    const union refmvs_refpair ref, const mv gmv[2],
100
                    const refmvs_block *b, const int bw4, const int w4,
101
                    const int max_rows, const int step,
102
                    int *const have_newmv_match, int *const have_refmv_match)
103
0
{
104
0
    const refmvs_block *cand_b = b;
105
0
    const enum BlockSize first_cand_bs = cand_b->bs;
106
0
    const uint8_t *const first_cand_b_dim = dav1d_block_dimensions[first_cand_bs];
107
0
    int cand_bw4 = first_cand_b_dim[0];
108
0
    int len = imax(step, imin(bw4, cand_bw4));
109
110
0
    if (bw4 <= cand_bw4) {
111
        // FIXME weight can be higher for odd blocks (bx4 & 1), but then the
112
        // position of the first block has to be odd already, i.e. not just
113
        // for row_offset=-3/-5
114
        // FIXME why can this not be cand_bw4?
115
0
        const int weight = bw4 == 1 ? 2 :
116
0
                           imax(2, imin(2 * max_rows, first_cand_b_dim[1]));
117
0
        add_spatial_candidate(mvstack, cnt, len * weight, cand_b, ref, gmv,
118
0
                              have_newmv_match, have_refmv_match);
119
0
        return weight >> 1;
120
0
    }
121
122
0
    for (int x = 0;;) {
123
        // FIXME if we overhang above, we could fill a bitmask so we don't have
124
        // to repeat the add_spatial_candidate() for the next row, but just increase
125
        // the weight here
126
0
        add_spatial_candidate(mvstack, cnt, len * 2, cand_b, ref, gmv,
127
0
                              have_newmv_match, have_refmv_match);
128
0
        x += len;
129
0
        if (x >= w4) return 1;
130
0
        cand_b = &b[x];
131
0
        cand_bw4 = dav1d_block_dimensions[cand_b->bs][0];
132
0
        assert(cand_bw4 < bw4);
133
0
        len = imax(step, cand_bw4);
134
0
    }
135
0
}
136
137
static int scan_col(refmvs_candidate *const mvstack, int *const cnt,
138
                    const union refmvs_refpair ref, const mv gmv[2],
139
                    /*const*/ refmvs_block *const *b, const int bh4, const int h4,
140
                    const int bx4, const int max_cols, const int step,
141
                    int *const have_newmv_match, int *const have_refmv_match)
142
0
{
143
0
    const refmvs_block *cand_b = &b[0][bx4];
144
0
    const enum BlockSize first_cand_bs = cand_b->bs;
145
0
    const uint8_t *const first_cand_b_dim = dav1d_block_dimensions[first_cand_bs];
146
0
    int cand_bh4 = first_cand_b_dim[1];
147
0
    int len = imax(step, imin(bh4, cand_bh4));
148
149
0
    if (bh4 <= cand_bh4) {
150
        // FIXME weight can be higher for odd blocks (by4 & 1), but then the
151
        // position of the first block has to be odd already, i.e. not just
152
        // for col_offset=-3/-5
153
        // FIXME why can this not be cand_bh4?
154
0
        const int weight = bh4 == 1 ? 2 :
155
0
                           imax(2, imin(2 * max_cols, first_cand_b_dim[0]));
156
0
        add_spatial_candidate(mvstack, cnt, len * weight, cand_b, ref, gmv,
157
0
                            have_newmv_match, have_refmv_match);
158
0
        return weight >> 1;
159
0
    }
160
161
0
    for (int y = 0;;) {
162
        // FIXME if we overhang above, we could fill a bitmask so we don't have
163
        // to repeat the add_spatial_candidate() for the next row, but just increase
164
        // the weight here
165
0
        add_spatial_candidate(mvstack, cnt, len * 2, cand_b, ref, gmv,
166
0
                              have_newmv_match, have_refmv_match);
167
0
        y += len;
168
0
        if (y >= h4) return 1;
169
0
        cand_b = &b[y][bx4];
170
0
        cand_bh4 = dav1d_block_dimensions[cand_b->bs][1];
171
0
        assert(cand_bh4 < bh4);
172
0
        len = imax(step, cand_bh4);
173
0
    }
174
0
}
175
176
0
static inline union mv mv_projection(const union mv mv, const int num, const int den) {
177
0
    static const uint16_t div_mult[32] = {
178
0
           0, 16384, 8192, 5461, 4096, 3276, 2730, 2340,
179
0
        2048,  1820, 1638, 1489, 1365, 1260, 1170, 1092,
180
0
        1024,   963,  910,  862,  819,  780,  744,  712,
181
0
         682,   655,  630,  606,  585,  564,  546,  528
182
0
    };
183
0
    assert(den > 0 && den < 32);
184
0
    assert(num > -32 && num < 32);
185
0
    const int frac = num * div_mult[den];
186
0
    const int y = mv.y * frac, x = mv.x * frac;
187
    // Round and clip according to AV1 spec section 7.9.3
188
0
    return (union mv) { // 0x3fff == (1 << 14) - 1
189
0
        .y = iclip((y + 8192 + (y >> 31)) >> 14, -0x3fff, 0x3fff),
190
0
        .x = iclip((x + 8192 + (x >> 31)) >> 14, -0x3fff, 0x3fff)
191
0
    };
192
0
}
193
194
static void add_temporal_candidate(const refmvs_frame *const rf,
195
                                   refmvs_candidate *const mvstack, int *const cnt,
196
                                   const refmvs_temporal_block *const rb,
197
                                   const union refmvs_refpair ref, int *const globalmv_ctx,
198
                                   const union mv gmv[], const int weight)
199
0
{
200
0
    if (rb->mv.n == INVALID_MV) return;
201
202
0
    union mv mv = mv_projection(rb->mv, rf->pocdiff[ref.ref[0] - 1], rb->ref);
203
0
    fix_mv_precision(rf->frm_hdr, &mv);
204
205
0
    const int last = *cnt;
206
0
    if (ref.ref[1] == -1) {
207
0
        if (globalmv_ctx)
208
0
            *globalmv_ctx = (abs(mv.x - gmv[0].x) | abs(mv.y - gmv[0].y)) >= 16;
209
210
0
        for (int n = 0; n < last; n++)
211
0
            if (mvstack[n].mv.mv[0].n == mv.n) {
212
0
                mvstack[n].weight += weight;
213
0
                return;
214
0
            }
215
0
        if (last < 8) {
216
0
            mvstack[last].mv.mv[0] = mv;
217
0
            mvstack[last].weight = weight;
218
0
            *cnt = last + 1;
219
0
        }
220
0
    } else {
221
0
        refmvs_mvpair mvp = { .mv = {
222
0
            [0] = mv,
223
0
            [1] = mv_projection(rb->mv, rf->pocdiff[ref.ref[1] - 1], rb->ref),
224
0
        }};
225
0
        fix_mv_precision(rf->frm_hdr, &mvp.mv[1]);
226
227
0
        for (int n = 0; n < last; n++)
228
0
            if (mvstack[n].mv.n == mvp.n) {
229
0
                mvstack[n].weight += weight;
230
0
                return;
231
0
            }
232
0
        if (last < 8) {
233
0
            mvstack[last].mv = mvp;
234
0
            mvstack[last].weight = weight;
235
0
            *cnt = last + 1;
236
0
        }
237
0
    }
238
0
}
239
240
static void add_compound_extended_candidate(refmvs_candidate *const same,
241
                                            int *const same_count,
242
                                            const refmvs_block *const cand_b,
243
                                            const int sign0, const int sign1,
244
                                            const union refmvs_refpair ref,
245
                                            const uint8_t *const sign_bias)
246
0
{
247
0
    refmvs_candidate *const diff = &same[2];
248
0
    int *const diff_count = &same_count[2];
249
250
0
    for (int n = 0; n < 2; n++) {
251
0
        const int cand_ref = cand_b->ref.ref[n];
252
253
0
        if (cand_ref <= 0) break;
254
255
0
        mv cand_mv = cand_b->mv.mv[n];
256
0
        if (cand_ref == ref.ref[0]) {
257
0
            if (same_count[0] < 2)
258
0
                same[same_count[0]++].mv.mv[0] = cand_mv;
259
0
            if (diff_count[1] < 2) {
260
0
                if (sign1 ^ sign_bias[cand_ref - 1]) {
261
0
                    cand_mv.y = -cand_mv.y;
262
0
                    cand_mv.x = -cand_mv.x;
263
0
                }
264
0
                diff[diff_count[1]++].mv.mv[1] = cand_mv;
265
0
            }
266
0
        } else if (cand_ref == ref.ref[1]) {
267
0
            if (same_count[1] < 2)
268
0
                same[same_count[1]++].mv.mv[1] = cand_mv;
269
0
            if (diff_count[0] < 2) {
270
0
                if (sign0 ^ sign_bias[cand_ref - 1]) {
271
0
                    cand_mv.y = -cand_mv.y;
272
0
                    cand_mv.x = -cand_mv.x;
273
0
                }
274
0
                diff[diff_count[0]++].mv.mv[0] = cand_mv;
275
0
            }
276
0
        } else {
277
0
            mv i_cand_mv = (union mv) {
278
0
                .x = -cand_mv.x,
279
0
                .y = -cand_mv.y
280
0
            };
281
282
0
            if (diff_count[0] < 2) {
283
0
                diff[diff_count[0]++].mv.mv[0] =
284
0
                    sign0 ^ sign_bias[cand_ref - 1] ?
285
0
                    i_cand_mv : cand_mv;
286
0
            }
287
288
0
            if (diff_count[1] < 2) {
289
0
                diff[diff_count[1]++].mv.mv[1] =
290
0
                    sign1 ^ sign_bias[cand_ref - 1] ?
291
0
                    i_cand_mv : cand_mv;
292
0
            }
293
0
        }
294
0
    }
295
0
}
296
297
static void add_single_extended_candidate(refmvs_candidate mvstack[8], int *const cnt,
298
                                          const refmvs_block *const cand_b,
299
                                          const int sign, const uint8_t *const sign_bias)
300
0
{
301
0
    for (int n = 0; n < 2; n++) {
302
0
        const int cand_ref = cand_b->ref.ref[n];
303
304
0
        if (cand_ref <= 0) break;
305
        // we need to continue even if cand_ref == ref.ref[0], since
306
        // the candidate could have been added as a globalmv variant,
307
        // which changes the value
308
        // FIXME if scan_{row,col}() returned a mask for the nearest
309
        // edge, we could skip the appropriate ones here
310
311
0
        mv cand_mv = cand_b->mv.mv[n];
312
0
        if (sign ^ sign_bias[cand_ref - 1]) {
313
0
            cand_mv.y = -cand_mv.y;
314
0
            cand_mv.x = -cand_mv.x;
315
0
        }
316
317
0
        int m;
318
0
        const int last = *cnt;
319
0
        for (m = 0; m < last; m++)
320
0
            if (cand_mv.n == mvstack[m].mv.mv[0].n)
321
0
                break;
322
0
        if (m == last) {
323
0
            mvstack[m].mv.mv[0] = cand_mv;
324
0
            mvstack[m].weight = 2; // "minimal"
325
0
            *cnt = last + 1;
326
0
        }
327
0
    }
328
0
}
329
330
/*
331
 * refmvs_frame allocates memory for one sbrow (32 blocks high, whole frame
332
 * wide) of 4x4-resolution refmvs_block entries for spatial MV referencing.
333
 * mvrefs_tile[] keeps a list of 35 (32 + 3 above) pointers into this memory,
334
 * and each sbrow, the bottom entries (y=27/29/31) are exchanged with the top
335
 * (-5/-3/-1) pointers by calling dav1d_refmvs_tile_sbrow_init() at the start
336
 * of each tile/sbrow.
337
 *
338
 * For temporal MV referencing, we call dav1d_refmvs_save_tmvs() at the end of
339
 * each tile/sbrow (when tile column threading is enabled), or at the start of
340
 * each interleaved sbrow (i.e. once for all tile columns together, when tile
341
 * column threading is disabled). This will copy the 4x4-resolution spatial MVs
342
 * into 8x8-resolution refmvs_temporal_block structures. Then, for subsequent
343
 * frames, at the start of each tile/sbrow (when tile column threading is
344
 * enabled) or at the start of each interleaved sbrow (when tile column
345
 * threading is disabled), we call load_tmvs(), which will project the MVs to
346
 * their respective position in the current frame.
347
 */
348
349
void dav1d_refmvs_find(const refmvs_tile *const rt,
350
                       refmvs_candidate mvstack[8], int *const cnt,
351
                       int *const ctx,
352
                       const union refmvs_refpair ref, const enum BlockSize bs,
353
                       const enum EdgeFlags edge_flags,
354
                       const int by4, const int bx4)
355
0
{
356
0
    const refmvs_frame *const rf = rt->rf;
357
0
    const uint8_t *const b_dim = dav1d_block_dimensions[bs];
358
0
    const int bw4 = b_dim[0], w4 = imin(imin(bw4, 16), rt->tile_col.end - bx4);
359
0
    const int bh4 = b_dim[1], h4 = imin(imin(bh4, 16), rt->tile_row.end - by4);
360
0
    mv gmv[2], tgmv[2];
361
362
0
    *cnt = 0;
363
0
    assert(ref.ref[0] >=  0 && ref.ref[0] <= 8 &&
364
0
           ref.ref[1] >= -1 && ref.ref[1] <= 8);
365
0
    if (ref.ref[0] > 0) {
366
0
        tgmv[0] = get_gmv_2d(&rf->frm_hdr->gmv[ref.ref[0] - 1],
367
0
                             bx4, by4, bw4, bh4, rf->frm_hdr);
368
0
        gmv[0] = rf->frm_hdr->gmv[ref.ref[0] - 1].type > DAV1D_WM_TYPE_TRANSLATION ?
369
0
                 tgmv[0] : (mv) { .n = INVALID_MV };
370
0
    } else {
371
0
        tgmv[0] = (mv) { .n = 0 };
372
0
        gmv[0] = (mv) { .n = INVALID_MV };
373
0
    }
374
0
    if (ref.ref[1] > 0) {
375
0
        tgmv[1] = get_gmv_2d(&rf->frm_hdr->gmv[ref.ref[1] - 1],
376
0
                             bx4, by4, bw4, bh4, rf->frm_hdr);
377
0
        gmv[1] = rf->frm_hdr->gmv[ref.ref[1] - 1].type > DAV1D_WM_TYPE_TRANSLATION ?
378
0
                 tgmv[1] : (mv) { .n = INVALID_MV };
379
0
    }
380
381
    // top
382
0
    int have_newmv = 0, have_col_mvs = 0, have_row_mvs = 0;
383
0
    unsigned max_rows = 0, n_rows = ~0;
384
0
    const refmvs_block *b_top;
385
0
    if (by4 > rt->tile_row.start) {
386
0
        max_rows = imin((by4 - rt->tile_row.start + 1) >> 1, 2 + (bh4 > 1));
387
0
        b_top = &rt->r[(by4 & 31) - 1 + 5][bx4];
388
0
        n_rows = scan_row(mvstack, cnt, ref, gmv, b_top,
389
0
                          bw4, w4, max_rows, bw4 >= 16 ? 4 : 1,
390
0
                          &have_newmv, &have_row_mvs);
391
0
    }
392
393
    // left
394
0
    unsigned max_cols = 0, n_cols = ~0U;
395
0
    refmvs_block *const *b_left;
396
0
    if (bx4 > rt->tile_col.start) {
397
0
        max_cols = imin((bx4 - rt->tile_col.start + 1) >> 1, 2 + (bw4 > 1));
398
0
        b_left = &rt->r[(by4 & 31) + 5];
399
0
        n_cols = scan_col(mvstack, cnt, ref, gmv, b_left,
400
0
                          bh4, h4, bx4 - 1, max_cols, bh4 >= 16 ? 4 : 1,
401
0
                          &have_newmv, &have_col_mvs);
402
0
    }
403
404
    // top/right
405
0
    if (n_rows != ~0U && edge_flags & EDGE_I444_TOP_HAS_RIGHT &&
406
0
        imax(bw4, bh4) <= 16 && bw4 + bx4 < rt->tile_col.end)
407
0
    {
408
0
        add_spatial_candidate(mvstack, cnt, 4, &b_top[bw4], ref, gmv,
409
0
                              &have_newmv, &have_row_mvs);
410
0
    }
411
412
0
    const int nearest_match = have_col_mvs + have_row_mvs;
413
0
    const int nearest_cnt = *cnt;
414
0
    for (int n = 0; n < nearest_cnt; n++)
415
0
        mvstack[n].weight += 640;
416
417
    // temporal
418
0
    int globalmv_ctx = rf->frm_hdr->use_ref_frame_mvs;
419
0
    if (rf->use_ref_frame_mvs) {
420
0
        const ptrdiff_t stride = rf->rp_stride;
421
0
        const int by8 = by4 >> 1, bx8 = bx4 >> 1;
422
0
        const refmvs_temporal_block *const rbi = &rt->rp_proj[(by8 & 15) * stride + bx8];
423
0
        const refmvs_temporal_block *rb = rbi;
424
0
        const int step_h = bw4 >= 16 ? 2 : 1, step_v = bh4 >= 16 ? 2 : 1;
425
0
        const int w8 = imin((w4 + 1) >> 1, 8), h8 = imin((h4 + 1) >> 1, 8);
426
        // save_tmvs() writes one record per 8x8 cell of a coded block, so the
427
        // scan walks runs of bit-identical records. add_temporal_candidate()
428
        // is a pure function of (rb->mv, rb->ref) plus loop invariants, and on
429
        // a repeat its only effect is `weight += 2` on the same stack entry
430
        // the first call of the run inserted or merged into -- the first match
431
        // is stable because a repeat never inserts. So a run of `n` identical
432
        // records is exactly one call carrying weight 2*n.
433
        //
434
        // Two records with mv.n == INVALID_MV belong to the same run whatever
435
        // their .ref holds: add_temporal_candidate() returns before reading
436
        // .ref, so both are no-ops. The check must be written that way round
437
        // rather than merely being allowed to be, because load_tmvs() marks a
438
        // cell unprojected by writing .mv.n alone and leaves .ref uninitialized.
439
0
        const refmvs_temporal_block *run = &rb[0];
440
0
        int run_len = 1;
441
0
        int *run_gmv_ctx = &globalmv_ctx;
442
0
        for (int y = 0; y < h8; y += step_v) {
443
0
            for (int x = y ? 0 : step_h; x < w8; x += step_h) {
444
0
                const unsigned mvn = rb[x].mv.n;
445
0
                if (mvn == run->mv.n &&
446
0
                    (mvn == INVALID_MV || rb[x].ref == run->ref))
447
0
                {
448
0
                    run_len++;
449
0
                    continue;
450
0
                }
451
0
                add_temporal_candidate(rf, mvstack, cnt, run, ref,
452
0
                                       run_gmv_ctx, tgmv, 2 * run_len);
453
0
                run_gmv_ctx = NULL;
454
0
                run = &rb[x];
455
0
                run_len = 1;
456
0
            }
457
0
            rb += stride * step_v;
458
0
        }
459
0
        add_temporal_candidate(rf, mvstack, cnt, run, ref,
460
0
                               run_gmv_ctx, tgmv, 2 * run_len);
461
0
        if (imin(bw4, bh4) >= 2 && imax(bw4, bh4) < 16) {
462
0
            const int bh8 = bh4 >> 1, bw8 = bw4 >> 1;
463
0
            rb = &rbi[bh8 * stride];
464
0
            const int has_bottom = by8 + bh8 < imin(rt->tile_row.end >> 1,
465
0
                                                    (by8 & ~7) + 8);
466
0
            if (has_bottom && bx8 - 1 >= imax(rt->tile_col.start >> 1, bx8 & ~7)) {
467
0
                add_temporal_candidate(rf, mvstack, cnt, &rb[-1], ref,
468
0
                                       NULL, NULL, 2);
469
0
            }
470
0
            if (bx8 + bw8 < imin(rt->tile_col.end >> 1, (bx8 & ~7) + 8)) {
471
0
                if (has_bottom) {
472
0
                    add_temporal_candidate(rf, mvstack, cnt, &rb[bw8], ref,
473
0
                                           NULL, NULL, 2);
474
0
                }
475
0
                if (by8 + bh8 - 1 < imin(rt->tile_row.end >> 1, (by8 & ~7) + 8)) {
476
0
                    add_temporal_candidate(rf, mvstack, cnt, &rb[bw8 - stride],
477
0
                                           ref, NULL, NULL, 2);
478
0
                }
479
0
            }
480
0
        }
481
0
    }
482
0
    assert(*cnt <= 8);
483
484
    // top/left (which, confusingly, is part of "secondary" references)
485
0
    int have_dummy_newmv_match;
486
0
    if ((n_rows | n_cols) != ~0U) {
487
0
        add_spatial_candidate(mvstack, cnt, 4, &b_top[-1], ref, gmv,
488
0
                              &have_dummy_newmv_match, &have_row_mvs);
489
0
    }
490
491
    // "secondary" (non-direct neighbour) top & left edges
492
    // what is different about secondary is that everything is now in 8x8 resolution
493
0
    for (int n = 2; n <= 3; n++) {
494
0
        if ((unsigned) n > n_rows && (unsigned) n <= max_rows) {
495
0
            n_rows += scan_row(mvstack, cnt, ref, gmv,
496
0
                               &rt->r[(((by4 & 31) - 2 * n + 1) | 1) + 5][bx4 | 1],
497
0
                               bw4, w4, 1 + max_rows - n, bw4 >= 16 ? 4 : 2,
498
0
                               &have_dummy_newmv_match, &have_row_mvs);
499
0
        }
500
501
0
        if ((unsigned) n > n_cols && (unsigned) n <= max_cols) {
502
0
            n_cols += scan_col(mvstack, cnt, ref, gmv, &rt->r[((by4 & 31) | 1) + 5],
503
0
                               bh4, h4, (bx4 - n * 2 + 1) | 1,
504
0
                               1 + max_cols - n, bh4 >= 16 ? 4 : 2,
505
0
                               &have_dummy_newmv_match, &have_col_mvs);
506
0
        }
507
0
    }
508
0
    assert(*cnt <= 8);
509
510
0
    const int ref_match_count = have_col_mvs + have_row_mvs;
511
512
    // context build-up
513
0
    int refmv_ctx, newmv_ctx;
514
0
    switch (nearest_match) {
515
0
    case 0:
516
0
        refmv_ctx = imin(2, ref_match_count);
517
0
        newmv_ctx = ref_match_count > 0;
518
0
        break;
519
0
    case 1:
520
0
        refmv_ctx = imin(ref_match_count * 3, 4);
521
0
        newmv_ctx = 3 - have_newmv;
522
0
        break;
523
0
    case 2:
524
0
        refmv_ctx = 5;
525
0
        newmv_ctx = 5 - have_newmv;
526
0
        break;
527
0
    }
528
529
    // sorting (nearest, then "secondary")
530
0
    int len = nearest_cnt;
531
0
    while (len) {
532
0
        int last = 0;
533
0
        for (int n = 1; n < len; n++) {
534
0
            if (mvstack[n - 1].weight < mvstack[n].weight) {
535
0
#define EXCHANGE(a, b) do { refmvs_candidate tmp = a; a = b; b = tmp; } while (0)
536
0
                EXCHANGE(mvstack[n - 1], mvstack[n]);
537
0
                last = n;
538
0
            }
539
0
        }
540
0
        len = last;
541
0
    }
542
0
    len = *cnt;
543
0
    while (len > nearest_cnt) {
544
0
        int last = nearest_cnt;
545
0
        for (int n = nearest_cnt + 1; n < len; n++) {
546
0
            if (mvstack[n - 1].weight < mvstack[n].weight) {
547
0
                EXCHANGE(mvstack[n - 1], mvstack[n]);
548
0
#undef EXCHANGE
549
0
                last = n;
550
0
            }
551
0
        }
552
0
        len = last;
553
0
    }
554
555
0
    if (ref.ref[1] > 0) {
556
0
        if (*cnt < 2) {
557
0
            const int sign0 = rf->sign_bias[ref.ref[0] - 1];
558
0
            const int sign1 = rf->sign_bias[ref.ref[1] - 1];
559
0
            const int sz4 = imin(w4, h4);
560
0
            refmvs_candidate *const same = &mvstack[*cnt];
561
0
            int same_count[4] = { 0 };
562
563
            // non-self references in top
564
0
            if (n_rows != ~0U) for (int x = 0; x < sz4;) {
565
0
                const refmvs_block *const cand_b = &b_top[x];
566
0
                add_compound_extended_candidate(same, same_count, cand_b,
567
0
                                                sign0, sign1, ref, rf->sign_bias);
568
0
                x += dav1d_block_dimensions[cand_b->bs][0];
569
0
            }
570
571
            // non-self references in left
572
0
            if (n_cols != ~0U) for (int y = 0; y < sz4;) {
573
0
                const refmvs_block *const cand_b = &b_left[y][bx4 - 1];
574
0
                add_compound_extended_candidate(same, same_count, cand_b,
575
0
                                                sign0, sign1, ref, rf->sign_bias);
576
0
                y += dav1d_block_dimensions[cand_b->bs][1];
577
0
            }
578
579
0
            refmvs_candidate *const diff = &same[2];
580
0
            const int *const diff_count = &same_count[2];
581
582
            // merge together
583
0
            for (int n = 0; n < 2; n++) {
584
0
                int m = same_count[n];
585
586
0
                if (m >= 2) continue;
587
588
0
                const int l = diff_count[n];
589
0
                if (l) {
590
0
                    same[m].mv.mv[n] = diff[0].mv.mv[n];
591
0
                    if (++m == 2) continue;
592
0
                    if (l == 2) {
593
0
                        same[1].mv.mv[n] = diff[1].mv.mv[n];
594
0
                        continue;
595
0
                    }
596
0
                }
597
0
                do {
598
0
                    same[m].mv.mv[n] = tgmv[n];
599
0
                } while (++m < 2);
600
0
            }
601
602
            // if the first extended was the same as the non-extended one,
603
            // then replace it with the second extended one
604
0
            int n = *cnt;
605
0
            if (n == 1 && mvstack[0].mv.n == same[0].mv.n)
606
0
                mvstack[1].mv = mvstack[2].mv;
607
0
            do {
608
0
                mvstack[n].weight = 2;
609
0
            } while (++n < 2);
610
0
            *cnt = 2;
611
0
        }
612
613
        // clamping
614
0
        const int left = -(bx4 + bw4 + 4) * 4 * 8;
615
0
        const int right = (rf->iw4 - bx4 + 4) * 4 * 8;
616
0
        const int top = -(by4 + bh4 + 4) * 4 * 8;
617
0
        const int bottom = (rf->ih4 - by4 + 4) * 4 * 8;
618
619
0
        const int n_refmvs = *cnt;
620
0
        int n = 0;
621
0
        do {
622
0
            mvstack[n].mv.mv[0].x = iclip(mvstack[n].mv.mv[0].x, left, right);
623
0
            mvstack[n].mv.mv[0].y = iclip(mvstack[n].mv.mv[0].y, top, bottom);
624
0
            mvstack[n].mv.mv[1].x = iclip(mvstack[n].mv.mv[1].x, left, right);
625
0
            mvstack[n].mv.mv[1].y = iclip(mvstack[n].mv.mv[1].y, top, bottom);
626
0
        } while (++n < n_refmvs);
627
628
0
        switch (refmv_ctx >> 1) {
629
0
        case 0:
630
0
            *ctx = imin(newmv_ctx, 1);
631
0
            break;
632
0
        case 1:
633
0
            *ctx = 1 + imin(newmv_ctx, 3);
634
0
            break;
635
0
        case 2:
636
0
            *ctx = iclip(3 + newmv_ctx, 4, 7);
637
0
            break;
638
0
        }
639
640
0
        return;
641
0
    } else if (*cnt < 2 && ref.ref[0] > 0) {
642
0
        const int sign = rf->sign_bias[ref.ref[0] - 1];
643
0
        const int sz4 = imin(w4, h4);
644
645
        // non-self references in top
646
0
        if (n_rows != ~0U) for (int x = 0; x < sz4 && *cnt < 2;) {
647
0
            const refmvs_block *const cand_b = &b_top[x];
648
0
            add_single_extended_candidate(mvstack, cnt, cand_b, sign, rf->sign_bias);
649
0
            x += dav1d_block_dimensions[cand_b->bs][0];
650
0
        }
651
652
        // non-self references in left
653
0
        if (n_cols != ~0U) for (int y = 0; y < sz4 && *cnt < 2;) {
654
0
            const refmvs_block *const cand_b = &b_left[y][bx4 - 1];
655
0
            add_single_extended_candidate(mvstack, cnt, cand_b, sign, rf->sign_bias);
656
0
            y += dav1d_block_dimensions[cand_b->bs][1];
657
0
        }
658
0
    }
659
0
    assert(*cnt <= 8);
660
661
    // clamping
662
0
    int n_refmvs = *cnt;
663
0
    if (n_refmvs) {
664
0
        const int left = -(bx4 + bw4 + 4) * 4 * 8;
665
0
        const int right = (rf->iw4 - bx4 + 4) * 4 * 8;
666
0
        const int top = -(by4 + bh4 + 4) * 4 * 8;
667
0
        const int bottom = (rf->ih4 - by4 + 4) * 4 * 8;
668
669
0
        int n = 0;
670
0
        do {
671
0
            mvstack[n].mv.mv[0].x = iclip(mvstack[n].mv.mv[0].x, left, right);
672
0
            mvstack[n].mv.mv[0].y = iclip(mvstack[n].mv.mv[0].y, top, bottom);
673
0
        } while (++n < n_refmvs);
674
0
    }
675
676
0
    for (int n = *cnt; n < 2; n++)
677
0
        mvstack[n].mv.mv[0] = tgmv[0];
678
679
0
    *ctx = (refmv_ctx << 4) | (globalmv_ctx << 3) | newmv_ctx;
680
0
}
681
682
void dav1d_refmvs_tile_sbrow_init(refmvs_tile *const rt, const refmvs_frame *const rf,
683
                                  const int tile_col_start4, const int tile_col_end4,
684
                                  const int tile_row_start4, const int tile_row_end4,
685
                                  const int sby, int tile_row_idx, const int pass)
686
0
{
687
0
    if (rf->n_tile_threads == 1) tile_row_idx = 0;
688
0
    rt->rp_proj = &rf->rp_proj[16 * rf->rp_stride * tile_row_idx];
689
0
    const ptrdiff_t r_stride = rf->rp_stride * 2;
690
0
    const ptrdiff_t pass_off = (rf->n_frame_threads > 1 && pass == 2) ?
691
0
        35 * 2 * rf->n_blocks : 0;
692
0
    refmvs_block *r = &rf->r[35 * r_stride * tile_row_idx + pass_off];
693
0
    const int sbsz = rf->sbsz;
694
0
    const int off = (sbsz * sby) & 16;
695
0
    for (int i = 0; i < sbsz; i++, r += r_stride)
696
0
        rt->r[off + 5 + i] = r;
697
0
    rt->r[off + 0] = r;
698
0
    r += r_stride;
699
0
    rt->r[off + 1] = NULL;
700
0
    rt->r[off + 2] = r;
701
0
    r += r_stride;
702
0
    rt->r[off + 3] = NULL;
703
0
    rt->r[off + 4] = r;
704
0
    if (sby & 1) {
705
0
#define EXCHANGE(a, b) do { void *const tmp = a; a = b; b = tmp; } while (0)
706
0
        EXCHANGE(rt->r[off + 0], rt->r[off + sbsz + 0]);
707
0
        EXCHANGE(rt->r[off + 2], rt->r[off + sbsz + 2]);
708
0
        EXCHANGE(rt->r[off + 4], rt->r[off + sbsz + 4]);
709
0
#undef EXCHANGE
710
0
    }
711
712
0
    rt->rf = rf;
713
0
    rt->tile_row.start = tile_row_start4;
714
0
    rt->tile_row.end = imin(tile_row_end4, rf->ih4);
715
0
    rt->tile_col.start = tile_col_start4;
716
0
    rt->tile_col.end = imin(tile_col_end4, rf->iw4);
717
0
}
718
719
static void load_tmvs_c(const refmvs_frame *const rf, int tile_row_idx,
720
                        const int col_start8, const int col_end8,
721
                        const int row_start8, int row_end8)
722
0
{
723
0
    if (rf->n_tile_threads == 1) tile_row_idx = 0;
724
0
    assert(row_start8 >= 0);
725
0
    assert((unsigned) (row_end8 - row_start8) <= 16U);
726
0
    row_end8 = imin(row_end8, rf->ih8);
727
0
    const int col_start8i = imax(col_start8 - 8, 0);
728
0
    const int col_end8i = imin(col_end8 + 8, rf->iw8);
729
730
0
    const ptrdiff_t stride = rf->rp_stride;
731
0
    refmvs_temporal_block *rp_proj =
732
0
        &rf->rp_proj[16 * stride * tile_row_idx + (row_start8 & 15) * stride];
733
0
    for (int y = row_start8; y < row_end8; y++) {
734
0
        for (int x = col_start8; x < col_end8; x++)
735
0
            rp_proj[x].mv.n = INVALID_MV;
736
0
        rp_proj += stride;
737
0
    }
738
739
0
    rp_proj = &rf->rp_proj[16 * stride * tile_row_idx];
740
0
    for (int n = 0; n < rf->n_mfmvs; n++) {
741
0
        const int ref2cur = rf->mfmv_ref2cur[n];
742
0
        if (ref2cur == INVALID_REF2CUR) continue;
743
744
0
        const int ref = rf->mfmv_ref[n];
745
0
        const int ref_sign = ref - 4;
746
0
        const refmvs_temporal_block *r = &rf->rp_ref[ref][row_start8 * stride];
747
0
        for (int y = row_start8; y < row_end8; y++) {
748
0
            const int y_sb_align = y & ~7;
749
0
            const int y_proj_start = imax(y_sb_align, row_start8);
750
0
            const int y_proj_end = imin(y_sb_align + 8, row_end8);
751
0
            for (int x = col_start8i; x < col_end8i; x++) {
752
0
                const refmvs_temporal_block *rb = &r[x];
753
0
                const int b_ref = rb->ref;
754
0
                if (!b_ref) continue;
755
0
                const int ref2ref = rf->mfmv_ref2ref[n][b_ref - 1];
756
0
                if (!ref2ref) continue;
757
0
                const mv b_mv = rb->mv;
758
0
                const mv offset = mv_projection(b_mv, ref2cur, ref2ref);
759
0
                int pos_x = x + apply_sign(abs(offset.x) >> 6,
760
0
                                           offset.x ^ ref_sign);
761
0
                const int pos_y = y + apply_sign(abs(offset.y) >> 6,
762
0
                                                 offset.y ^ ref_sign);
763
0
                if (pos_y >= y_proj_start && pos_y < y_proj_end) {
764
0
                    const ptrdiff_t pos = (pos_y & 15) * stride;
765
0
                    for (;;) {
766
0
                        const int x_sb_align = x & ~7;
767
0
                        if (pos_x >= imax(x_sb_align - 8, col_start8) &&
768
0
                            pos_x < imin(x_sb_align + 16, col_end8))
769
0
                        {
770
0
                            rp_proj[pos + pos_x].mv = rb->mv;
771
0
                            rp_proj[pos + pos_x].ref = ref2ref;
772
0
                        }
773
0
                        if (++x >= col_end8i) break;
774
0
                        rb++;
775
0
                        if (rb->ref != b_ref || rb->mv.n != b_mv.n) break;
776
0
                        pos_x++;
777
0
                    }
778
0
                } else {
779
0
                    for (;;) {
780
0
                        if (++x >= col_end8i) break;
781
0
                        rb++;
782
0
                        if (rb->ref != b_ref || rb->mv.n != b_mv.n) break;
783
0
                    }
784
0
                }
785
0
                x--;
786
0
            }
787
0
            r += stride;
788
0
        }
789
0
    }
790
0
}
791
792
static void save_tmvs_c(refmvs_temporal_block *rp, const ptrdiff_t stride,
793
                        refmvs_block *const *const rr,
794
                        const uint8_t *const ref_sign,
795
                        const int col_end8, const int row_end8,
796
                        const int col_start8, const int row_start8)
797
0
{
798
0
    for (int y = row_start8; y < row_end8; y++) {
799
0
        const refmvs_block *const b = rr[(y & 15) * 2];
800
801
0
        for (int x = col_start8; x < col_end8;) {
802
0
            const refmvs_block *const cand_b = &b[x * 2 + 1];
803
0
            const int bw8 = (dav1d_block_dimensions[cand_b->bs][0] + 1) >> 1;
804
805
0
            if (cand_b->ref.ref[1] > 0 && ref_sign[cand_b->ref.ref[1] - 1] &&
806
0
                (abs(cand_b->mv.mv[1].y) | abs(cand_b->mv.mv[1].x)) < 4096)
807
0
            {
808
0
                const refmvs_temporal_block tmv = {
809
0
                    .mv = cand_b->mv.mv[1],
810
0
                    .ref = cand_b->ref.ref[1],
811
0
                };
812
0
                for (int n = 0; n < bw8; n++, x++)
813
0
                    rp[x] = tmv;
814
0
            } else if (cand_b->ref.ref[0] > 0 && ref_sign[cand_b->ref.ref[0] - 1] &&
815
0
                       (abs(cand_b->mv.mv[0].y) | abs(cand_b->mv.mv[0].x)) < 4096)
816
0
            {
817
0
                const refmvs_temporal_block tmv = {
818
0
                    .mv = cand_b->mv.mv[0],
819
0
                    .ref = cand_b->ref.ref[0],
820
0
                };
821
0
                for (int n = 0; n < bw8; n++, x++)
822
0
                    rp[x] = tmv;
823
0
            } else {
824
0
                memset(&rp[x], 0, bw8 * sizeof(*rp));
825
0
                x += bw8;
826
0
            }
827
0
        }
828
0
        rp += stride;
829
0
    }
830
0
}
831
832
int dav1d_refmvs_init_frame(refmvs_frame *const rf,
833
                            const Dav1dSequenceHeader *const seq_hdr,
834
                            const Dav1dFrameHeader *const frm_hdr,
835
                            const uint8_t ref_poc[7],
836
                            refmvs_temporal_block *const rp,
837
                            const uint8_t ref_ref_poc[7][7],
838
                            /*const*/ refmvs_temporal_block *const rp_ref[7],
839
                            const int n_tile_threads, const int n_frame_threads)
840
0
{
841
0
    const int rp_stride = ((frm_hdr->width[0] + 127) & ~127) >> 3;
842
0
    const int n_tile_rows = n_tile_threads > 1 ? frm_hdr->tiling.rows : 1;
843
0
    const int n_blocks = rp_stride * n_tile_rows;
844
845
0
    rf->sbsz = 16 << seq_hdr->sb128;
846
0
    rf->frm_hdr = frm_hdr;
847
0
    rf->iw8 = (frm_hdr->width[0] + 7) >> 3;
848
0
    rf->ih8 = (frm_hdr->height + 7) >> 3;
849
0
    rf->iw4 = rf->iw8 << 1;
850
0
    rf->ih4 = rf->ih8 << 1;
851
0
    rf->rp = rp;
852
0
    rf->rp_stride = rp_stride;
853
0
    rf->n_tile_threads = n_tile_threads;
854
0
    rf->n_frame_threads = n_frame_threads;
855
856
0
    if (n_blocks != rf->n_blocks) {
857
0
        const size_t r_sz = sizeof(*rf->r) * 35 * 2 * n_blocks * (1 + (n_frame_threads > 1));
858
0
        const size_t rp_proj_sz = sizeof(*rf->rp_proj) * 16 * n_blocks;
859
        /* Note that sizeof(*rf->r) == 12, but it's accessed using 16-byte unaligned
860
         * loads in save_tmvs() asm which can overread 4 bytes into rp_proj. */
861
0
        dav1d_free_aligned(rf->r);
862
0
        rf->r = dav1d_alloc_aligned(ALLOC_REFMVS, r_sz + rp_proj_sz, 64);
863
0
        if (!rf->r) {
864
0
            rf->n_blocks = 0;
865
0
            return DAV1D_ERR(ENOMEM);
866
0
        }
867
868
0
        rf->rp_proj = (refmvs_temporal_block*)((uintptr_t)rf->r + r_sz);
869
0
        rf->n_blocks = n_blocks;
870
0
    }
871
872
0
    const int poc = frm_hdr->frame_offset;
873
0
    for (int i = 0; i < 7; i++) {
874
0
        const int poc_diff = get_poc_diff(seq_hdr->order_hint_n_bits,
875
0
                                          ref_poc[i], poc);
876
0
        rf->sign_bias[i] = poc_diff > 0;
877
0
        rf->mfmv_sign[i] = poc_diff < 0;
878
0
        rf->pocdiff[i] = iclip(get_poc_diff(seq_hdr->order_hint_n_bits,
879
0
                                            poc, ref_poc[i]), -31, 31);
880
0
    }
881
882
    // temporal MV setup
883
0
    rf->n_mfmvs = 0;
884
0
    rf->rp_ref = rp_ref;
885
0
    if (frm_hdr->use_ref_frame_mvs && seq_hdr->order_hint_n_bits) {
886
0
        int total = 2;
887
0
        if (rp_ref[0] && ref_ref_poc[0][6] != ref_poc[3] /* alt-of-last != gold */) {
888
0
            rf->mfmv_ref[rf->n_mfmvs++] = 0; // last
889
0
            total = 3;
890
0
        }
891
0
        if (rp_ref[4] && get_poc_diff(seq_hdr->order_hint_n_bits, ref_poc[4],
892
0
                                      frm_hdr->frame_offset) > 0)
893
0
        {
894
0
            rf->mfmv_ref[rf->n_mfmvs++] = 4; // bwd
895
0
        }
896
0
        if (rp_ref[5] && get_poc_diff(seq_hdr->order_hint_n_bits, ref_poc[5],
897
0
                                      frm_hdr->frame_offset) > 0)
898
0
        {
899
0
            rf->mfmv_ref[rf->n_mfmvs++] = 5; // altref2
900
0
        }
901
0
        if (rf->n_mfmvs < total && rp_ref[6] &&
902
0
            get_poc_diff(seq_hdr->order_hint_n_bits, ref_poc[6],
903
0
                         frm_hdr->frame_offset) > 0)
904
0
        {
905
0
            rf->mfmv_ref[rf->n_mfmvs++] = 6; // altref
906
0
        }
907
0
        if (rf->n_mfmvs < total && rp_ref[1])
908
0
            rf->mfmv_ref[rf->n_mfmvs++] = 1; // last2
909
910
0
        for (int n = 0; n < rf->n_mfmvs; n++) {
911
0
            const int rpoc = ref_poc[rf->mfmv_ref[n]];
912
0
            const int diff1 = get_poc_diff(seq_hdr->order_hint_n_bits,
913
0
                                           rpoc, frm_hdr->frame_offset);
914
0
            if (abs(diff1) > 31) {
915
0
                rf->mfmv_ref2cur[n] = INVALID_REF2CUR;
916
0
            } else {
917
0
                rf->mfmv_ref2cur[n] = rf->mfmv_ref[n] < 4 ? -diff1 : diff1;
918
0
                for (int m = 0; m < 7; m++) {
919
0
                    const int rrpoc = ref_ref_poc[rf->mfmv_ref[n]][m];
920
0
                    const int diff2 = get_poc_diff(seq_hdr->order_hint_n_bits,
921
0
                                                   rpoc, rrpoc);
922
                    // unsigned comparison also catches the < 0 case
923
0
                    rf->mfmv_ref2ref[n][m] = (unsigned) diff2 > 31U ? 0 : diff2;
924
0
                }
925
0
            }
926
0
        }
927
0
    }
928
0
    rf->use_ref_frame_mvs = rf->n_mfmvs > 0;
929
930
0
    return 0;
931
0
}
932
933
static void splat_mv_c(refmvs_block **rr, const refmvs_block *const rmv,
934
                       const int bx4, const int bw4, int bh4)
935
0
{
936
0
    do {
937
0
        refmvs_block *const r = *rr++ + bx4;
938
0
        for (int x = 0; x < bw4; x++)
939
0
            r[x] = *rmv;
940
0
    } while (--bh4);
941
0
}
942
943
#if HAVE_ASM
944
#if ARCH_AARCH64 || ARCH_ARM
945
#include "src/arm/refmvs.h"
946
#elif ARCH_LOONGARCH64
947
#include "src/loongarch/refmvs.h"
948
#elif ARCH_X86
949
#include "src/x86/refmvs.h"
950
#endif
951
#endif
952
953
COLD void dav1d_refmvs_dsp_init(Dav1dRefmvsDSPContext *const c)
954
0
{
955
0
    c->load_tmvs = load_tmvs_c;
956
0
    c->save_tmvs = save_tmvs_c;
957
0
    c->splat_mv = splat_mv_c;
958
959
#if HAVE_ASM
960
#if ARCH_AARCH64 || ARCH_ARM
961
    refmvs_dsp_init_arm(c);
962
#elif ARCH_LOONGARCH64
963
    refmvs_dsp_init_loongarch(c);
964
#elif ARCH_X86
965
    refmvs_dsp_init_x86(c);
966
#endif
967
#endif
968
0
}