• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The libgav1 Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/motion_vector.h"
16 
17 #include <algorithm>
18 #include <cassert>
19 #include <cstdint>
20 #include <cstdlib>
21 #include <memory>
22 
23 #include "src/dsp/dsp.h"
24 #include "src/utils/bit_mask_set.h"
25 #include "src/utils/common.h"
26 #include "src/utils/constants.h"
27 #include "src/utils/logging.h"
28 
29 namespace libgav1 {
30 namespace {
31 
32 // Entry at index i is computed as:
33 // Clip3(std::max(kBlockWidthPixels[i], kBlockHeightPixels[i], 16, 112)).
34 constexpr int kWarpValidThreshold[kMaxBlockSizes] = {
35     16, 16, 16, 16, 16, 16, 32, 16, 16,  16,  32,
36     64, 32, 32, 32, 64, 64, 64, 64, 112, 112, 112};
37 
38 // 7.10.2.10.
LowerMvPrecision(const ObuFrameHeader & frame_header,MotionVector * const mvs)39 void LowerMvPrecision(const ObuFrameHeader& frame_header,
40                       MotionVector* const mvs) {
41   if (frame_header.allow_high_precision_mv) return;
42   if (frame_header.force_integer_mv != 0) {
43     for (auto& mv : mvs->mv) {
44       // The next line is equivalent to:
45       // const int value = (std::abs(static_cast<int>(mv)) + 3) & ~7;
46       // const int sign = mv >> 15;
47       // mv = ApplySign(value, sign);
48       mv = (mv + 3 - (mv >> 15)) & ~7;
49     }
50   } else {
51     for (auto& mv : mvs->mv) {
52       // The next line is equivalent to:
53       // if ((mv & 1) != 0) mv += (mv > 0) ? -1 : 1;
54       mv = (mv - (mv >> 15)) & ~1;
55     }
56   }
57 }
58 
59 // 7.10.2.1.
SetupGlobalMv(const Tile::Block & block,int index,MotionVector * const mv)60 void SetupGlobalMv(const Tile::Block& block, int index,
61                    MotionVector* const mv) {
62   const BlockParameters& bp = *block.bp;
63   const ObuFrameHeader& frame_header = block.tile.frame_header();
64   ReferenceFrameType reference_type = bp.reference_frame[index];
65   const auto& gm = frame_header.global_motion[reference_type];
66   GlobalMotionTransformationType global_motion_type =
67       (reference_type != kReferenceFrameIntra)
68           ? gm.type
69           : kNumGlobalMotionTransformationTypes;
70   if (reference_type == kReferenceFrameIntra ||
71       global_motion_type == kGlobalMotionTransformationTypeIdentity) {
72     mv->mv32 = 0;
73     return;
74   }
75   if (global_motion_type == kGlobalMotionTransformationTypeTranslation) {
76     for (int i = 0; i < 2; ++i) {
77       mv->mv[i] = gm.params[i] >> (kWarpedModelPrecisionBits - 3);
78     }
79     LowerMvPrecision(frame_header, mv);
80     return;
81   }
82   const int x = MultiplyBy4(block.column4x4) + DivideBy2(block.width) - 1;
83   const int y = MultiplyBy4(block.row4x4) + DivideBy2(block.height) - 1;
84   const int xc = (gm.params[2] - (1 << kWarpedModelPrecisionBits)) * x +
85                  gm.params[3] * y + gm.params[0];
86   const int yc = gm.params[4] * x +
87                  (gm.params[5] - (1 << kWarpedModelPrecisionBits)) * y +
88                  gm.params[1];
89   if (frame_header.allow_high_precision_mv) {
90     mv->mv[MotionVector::kRow] =
91         RightShiftWithRoundingSigned(yc, kWarpedModelPrecisionBits - 3);
92     mv->mv[MotionVector::kColumn] =
93         RightShiftWithRoundingSigned(xc, kWarpedModelPrecisionBits - 3);
94   } else {
95     mv->mv[MotionVector::kRow] = MultiplyBy2(
96         RightShiftWithRoundingSigned(yc, kWarpedModelPrecisionBits - 2));
97     mv->mv[MotionVector::kColumn] = MultiplyBy2(
98         RightShiftWithRoundingSigned(xc, kWarpedModelPrecisionBits - 2));
99     LowerMvPrecision(frame_header, mv);
100   }
101 }
102 
103 constexpr BitMaskSet kPredictionModeNewMvMask(kPredictionModeNewMv,
104                                               kPredictionModeNewNewMv,
105                                               kPredictionModeNearNewMv,
106                                               kPredictionModeNewNearMv,
107                                               kPredictionModeNearestNewMv,
108                                               kPredictionModeNewNearestMv);
109 
110 // 7.10.2.8.
SearchStack(const Tile::Block & block,const BlockParameters & mv_bp,int index,int weight,bool * const found_new_mv,bool * const found_match,int * const num_mv_found)111 void SearchStack(const Tile::Block& block, const BlockParameters& mv_bp,
112                  int index, int weight, bool* const found_new_mv,
113                  bool* const found_match, int* const num_mv_found) {
114   const BlockParameters& bp = *block.bp;
115   const std::array<GlobalMotion, kNumReferenceFrameTypes>& global_motion =
116       block.tile.frame_header().global_motion;
117   PredictionParameters& prediction_parameters = *bp.prediction_parameters;
118   MotionVector candidate_mv;
119   // LowerMvPrecision() is not necessary, since the values in
120   // |prediction_parameters.global_mv| and |mv_bp.mv| were generated by it.
121   const auto global_motion_type = global_motion[bp.reference_frame[0]].type;
122   if (IsGlobalMvBlock(mv_bp.is_global_mv_block, global_motion_type)) {
123     candidate_mv = prediction_parameters.global_mv[0];
124   } else {
125     candidate_mv = mv_bp.mv.mv[index];
126   }
127   *found_new_mv |= kPredictionModeNewMvMask.Contains(mv_bp.y_mode);
128   *found_match = true;
129   MotionVector* const ref_mv_stack = prediction_parameters.ref_mv_stack;
130   const auto result = std::find_if(ref_mv_stack, ref_mv_stack + *num_mv_found,
131                                    [&candidate_mv](const MotionVector& ref_mv) {
132                                      return ref_mv == candidate_mv;
133                                    });
134   if (result != ref_mv_stack + *num_mv_found) {
135     prediction_parameters.IncreaseWeight(std::distance(ref_mv_stack, result),
136                                          weight);
137     return;
138   }
139   if (*num_mv_found >= kMaxRefMvStackSize) return;
140   ref_mv_stack[*num_mv_found] = candidate_mv;
141   prediction_parameters.SetWeightIndexStackEntry(*num_mv_found, weight);
142   ++*num_mv_found;
143 }
144 
145 // 7.10.2.9.
CompoundSearchStack(const Tile::Block & block,const BlockParameters & mv_bp,int weight,bool * const found_new_mv,bool * const found_match,int * const num_mv_found)146 void CompoundSearchStack(const Tile::Block& block, const BlockParameters& mv_bp,
147                          int weight, bool* const found_new_mv,
148                          bool* const found_match, int* const num_mv_found) {
149   const BlockParameters& bp = *block.bp;
150   const std::array<GlobalMotion, kNumReferenceFrameTypes>& global_motion =
151       block.tile.frame_header().global_motion;
152   PredictionParameters& prediction_parameters = *bp.prediction_parameters;
153   // LowerMvPrecision() is not necessary, since the values in
154   // |prediction_parameters.global_mv| and |mv_bp.mv| were generated by it.
155   CompoundMotionVector candidate_mv = mv_bp.mv;
156   for (int i = 0; i < 2; ++i) {
157     const auto global_motion_type = global_motion[bp.reference_frame[i]].type;
158     if (IsGlobalMvBlock(mv_bp.is_global_mv_block, global_motion_type)) {
159       candidate_mv.mv[i] = prediction_parameters.global_mv[i];
160     }
161   }
162   *found_new_mv |= kPredictionModeNewMvMask.Contains(mv_bp.y_mode);
163   *found_match = true;
164   CompoundMotionVector* const compound_ref_mv_stack =
165       prediction_parameters.compound_ref_mv_stack;
166   const auto result =
167       std::find_if(compound_ref_mv_stack, compound_ref_mv_stack + *num_mv_found,
168                    [&candidate_mv](const CompoundMotionVector& ref_mv) {
169                      return ref_mv == candidate_mv;
170                    });
171   if (result != compound_ref_mv_stack + *num_mv_found) {
172     prediction_parameters.IncreaseWeight(
173         std::distance(compound_ref_mv_stack, result), weight);
174     return;
175   }
176   if (*num_mv_found >= kMaxRefMvStackSize) return;
177   compound_ref_mv_stack[*num_mv_found] = candidate_mv;
178   prediction_parameters.SetWeightIndexStackEntry(*num_mv_found, weight);
179   ++*num_mv_found;
180 }
181 
182 // 7.10.2.7.
AddReferenceMvCandidate(const Tile::Block & block,const BlockParameters & mv_bp,bool is_compound,int weight,bool * const found_new_mv,bool * const found_match,int * const num_mv_found)183 void AddReferenceMvCandidate(const Tile::Block& block,
184                              const BlockParameters& mv_bp, bool is_compound,
185                              int weight, bool* const found_new_mv,
186                              bool* const found_match, int* const num_mv_found) {
187   if (!mv_bp.is_inter) return;
188   const BlockParameters& bp = *block.bp;
189   if (is_compound) {
190     if (mv_bp.reference_frame[0] == bp.reference_frame[0] &&
191         mv_bp.reference_frame[1] == bp.reference_frame[1]) {
192       CompoundSearchStack(block, mv_bp, weight, found_new_mv, found_match,
193                           num_mv_found);
194     }
195     return;
196   }
197   for (int i = 0; i < 2; ++i) {
198     if (mv_bp.reference_frame[i] == bp.reference_frame[0]) {
199       SearchStack(block, mv_bp, i, weight, found_new_mv, found_match,
200                   num_mv_found);
201     }
202   }
203 }
204 
GetMinimumStep(int block_width_or_height4x4,int delta_row_or_column)205 int GetMinimumStep(int block_width_or_height4x4, int delta_row_or_column) {
206   assert(delta_row_or_column < 0);
207   if (block_width_or_height4x4 >= 16) return 4;
208   if (delta_row_or_column < -1) return 2;
209   return 0;
210 }
211 
212 // 7.10.2.2.
ScanRow(const Tile::Block & block,int mv_column,int delta_row,bool is_compound,bool * const found_new_mv,bool * const found_match,int * const num_mv_found)213 void ScanRow(const Tile::Block& block, int mv_column, int delta_row,
214              bool is_compound, bool* const found_new_mv,
215              bool* const found_match, int* const num_mv_found) {
216   const int mv_row = block.row4x4 + delta_row;
217   const Tile& tile = block.tile;
218   if (!tile.IsTopInside(mv_row + 1)) return;
219   const int width4x4 = block.width4x4;
220   const int min_step = GetMinimumStep(width4x4, delta_row);
221   BlockParameters** bps = tile.BlockParametersAddress(mv_row, mv_column);
222   BlockParameters** const end_bps =
223       bps + std::min({static_cast<int>(width4x4),
224                       tile.frame_header().columns4x4 - block.column4x4, 16});
225   do {
226     const BlockParameters& mv_bp = **bps;
227     const int step = std::max(
228         std::min(width4x4, static_cast<int>(kNum4x4BlocksWide[mv_bp.size])),
229         min_step);
230     AddReferenceMvCandidate(block, mv_bp, is_compound, MultiplyBy2(step),
231                             found_new_mv, found_match, num_mv_found);
232     bps += step;
233   } while (bps < end_bps);
234 }
235 
236 // 7.10.2.3.
ScanColumn(const Tile::Block & block,int mv_row,int delta_column,bool is_compound,bool * const found_new_mv,bool * const found_match,int * const num_mv_found)237 void ScanColumn(const Tile::Block& block, int mv_row, int delta_column,
238                 bool is_compound, bool* const found_new_mv,
239                 bool* const found_match, int* const num_mv_found) {
240   const int mv_column = block.column4x4 + delta_column;
241   const Tile& tile = block.tile;
242   if (!tile.IsLeftInside(mv_column + 1)) return;
243   const int height4x4 = block.height4x4;
244   const int min_step = GetMinimumStep(height4x4, delta_column);
245   const ptrdiff_t stride = tile.BlockParametersStride();
246   BlockParameters** bps = tile.BlockParametersAddress(mv_row, mv_column);
247   BlockParameters** const end_bps =
248       bps + stride * std::min({static_cast<int>(height4x4),
249                                tile.frame_header().rows4x4 - block.row4x4, 16});
250   do {
251     const BlockParameters& mv_bp = **bps;
252     const int step = std::max(
253         std::min(height4x4, static_cast<int>(kNum4x4BlocksHigh[mv_bp.size])),
254         min_step);
255     AddReferenceMvCandidate(block, mv_bp, is_compound, MultiplyBy2(step),
256                             found_new_mv, found_match, num_mv_found);
257     bps += step * stride;
258   } while (bps < end_bps);
259 }
260 
261 // 7.10.2.4.
ScanPoint(const Tile::Block & block,int delta_row,int delta_column,bool is_compound,bool * const found_new_mv,bool * const found_match,int * const num_mv_found)262 void ScanPoint(const Tile::Block& block, int delta_row, int delta_column,
263                bool is_compound, bool* const found_new_mv,
264                bool* const found_match, int* const num_mv_found) {
265   const int mv_row = block.row4x4 + delta_row;
266   const int mv_column = block.column4x4 + delta_column;
267   const Tile& tile = block.tile;
268   if (!tile.IsInside(mv_row, mv_column) ||
269       !tile.HasParameters(mv_row, mv_column)) {
270     return;
271   }
272   const BlockParameters& mv_bp = tile.Parameters(mv_row, mv_column);
273   if (mv_bp.reference_frame[0] == kReferenceFrameNone) return;
274   AddReferenceMvCandidate(block, mv_bp, is_compound, 4, found_new_mv,
275                           found_match, num_mv_found);
276 }
277 
278 // 7.10.2.6.
AddTemporalReferenceMvCandidate(const ObuFrameHeader & frame_header,const int reference_offsets[2],const MotionVector * const temporal_mvs,const int8_t * const temporal_reference_offsets,int count,bool is_compound,int * const zero_mv_context,int * const num_mv_found,PredictionParameters * const prediction_parameters)279 void AddTemporalReferenceMvCandidate(
280     const ObuFrameHeader& frame_header, const int reference_offsets[2],
281     const MotionVector* const temporal_mvs,
282     const int8_t* const temporal_reference_offsets, int count, bool is_compound,
283     int* const zero_mv_context, int* const num_mv_found,
284     PredictionParameters* const prediction_parameters) {
285   const int mv_projection_function_index =
286       frame_header.allow_high_precision_mv ? 2 : frame_header.force_integer_mv;
287   const MotionVector* const global_mv = prediction_parameters->global_mv;
288   if (is_compound) {
289     CompoundMotionVector candidate_mvs[kMaxTemporalMvCandidatesWithPadding];
290     const dsp::Dsp& dsp = *dsp::GetDspTable(8);
291     dsp.mv_projection_compound[mv_projection_function_index](
292         temporal_mvs, temporal_reference_offsets, reference_offsets, count,
293         candidate_mvs);
294     if (*zero_mv_context == -1) {
295       int max_difference =
296           std::max(std::abs(candidate_mvs[0].mv[0].mv[0] - global_mv[0].mv[0]),
297                    std::abs(candidate_mvs[0].mv[0].mv[1] - global_mv[0].mv[1]));
298       max_difference =
299           std::max(max_difference,
300                    std::abs(candidate_mvs[0].mv[1].mv[0] - global_mv[1].mv[0]));
301       max_difference =
302           std::max(max_difference,
303                    std::abs(candidate_mvs[0].mv[1].mv[1] - global_mv[1].mv[1]));
304       *zero_mv_context = static_cast<int>(max_difference >= 16);
305     }
306     CompoundMotionVector* const compound_ref_mv_stack =
307         prediction_parameters->compound_ref_mv_stack;
308     int index = 0;
309     do {
310       const CompoundMotionVector& candidate_mv = candidate_mvs[index];
311       const auto result = std::find_if(
312           compound_ref_mv_stack, compound_ref_mv_stack + *num_mv_found,
313           [&candidate_mv](const CompoundMotionVector& ref_mv) {
314             return ref_mv == candidate_mv;
315           });
316       if (result != compound_ref_mv_stack + *num_mv_found) {
317         prediction_parameters->IncreaseWeight(
318             std::distance(compound_ref_mv_stack, result), 2);
319         continue;
320       }
321       if (*num_mv_found >= kMaxRefMvStackSize) continue;
322       compound_ref_mv_stack[*num_mv_found] = candidate_mv;
323       prediction_parameters->SetWeightIndexStackEntry(*num_mv_found, 2);
324       ++*num_mv_found;
325     } while (++index < count);
326     return;
327   }
328   MotionVector* const ref_mv_stack = prediction_parameters->ref_mv_stack;
329   if (reference_offsets[0] == 0) {
330     if (*zero_mv_context == -1) {
331       const int max_difference =
332           std::max(std::abs(global_mv[0].mv[0]), std::abs(global_mv[0].mv[1]));
333       *zero_mv_context = static_cast<int>(max_difference >= 16);
334     }
335     const MotionVector candidate_mv = {};
336     const auto result =
337         std::find_if(ref_mv_stack, ref_mv_stack + *num_mv_found,
338                      [&candidate_mv](const MotionVector& ref_mv) {
339                        return ref_mv == candidate_mv;
340                      });
341     if (result != ref_mv_stack + *num_mv_found) {
342       prediction_parameters->IncreaseWeight(std::distance(ref_mv_stack, result),
343                                             2 * count);
344       return;
345     }
346     if (*num_mv_found >= kMaxRefMvStackSize) return;
347     ref_mv_stack[*num_mv_found] = candidate_mv;
348     prediction_parameters->SetWeightIndexStackEntry(*num_mv_found, 2 * count);
349     ++*num_mv_found;
350     return;
351   }
352   alignas(kMaxAlignment)
353       MotionVector candidate_mvs[kMaxTemporalMvCandidatesWithPadding];
354   const dsp::Dsp& dsp = *dsp::GetDspTable(8);
355   dsp.mv_projection_single[mv_projection_function_index](
356       temporal_mvs, temporal_reference_offsets, reference_offsets[0], count,
357       candidate_mvs);
358   if (*zero_mv_context == -1) {
359     const int max_difference =
360         std::max(std::abs(candidate_mvs[0].mv[0] - global_mv[0].mv[0]),
361                  std::abs(candidate_mvs[0].mv[1] - global_mv[0].mv[1]));
362     *zero_mv_context = static_cast<int>(max_difference >= 16);
363   }
364   int index = 0;
365   do {
366     const MotionVector& candidate_mv = candidate_mvs[index];
367     const auto result =
368         std::find_if(ref_mv_stack, ref_mv_stack + *num_mv_found,
369                      [&candidate_mv](const MotionVector& ref_mv) {
370                        return ref_mv == candidate_mv;
371                      });
372     if (result != ref_mv_stack + *num_mv_found) {
373       prediction_parameters->IncreaseWeight(std::distance(ref_mv_stack, result),
374                                             2);
375       continue;
376     }
377     if (*num_mv_found >= kMaxRefMvStackSize) continue;
378     ref_mv_stack[*num_mv_found] = candidate_mv;
379     prediction_parameters->SetWeightIndexStackEntry(*num_mv_found, 2);
380     ++*num_mv_found;
381   } while (++index < count);
382 }
383 
384 // Part of 7.10.2.5.
IsWithinTheSame64x64Block(const Tile::Block & block,int delta_row,int delta_column)385 bool IsWithinTheSame64x64Block(const Tile::Block& block, int delta_row,
386                                int delta_column) {
387   const int row = (block.row4x4 & 15) + delta_row;
388   const int column = (block.column4x4 & 15) + delta_column;
389   // |block.height4x4| is at least 2 for all elements in |kTemporalScanMask|.
390   // So |row| are all non-negative.
391   assert(row >= 0);
392   return row < 16 && column >= 0 && column < 16;
393 }
394 
395 constexpr BitMaskSet kTemporalScanMask(kBlock8x8, kBlock8x16, kBlock8x32,
396                                        kBlock16x8, kBlock16x16, kBlock16x32,
397                                        kBlock32x8, kBlock32x16, kBlock32x32);
398 
399 // 7.10.2.5.
400 //
401 // The |zero_mv_context| output parameter may be null. If |zero_mv_context| is
402 // not null, the function may set |*zero_mv_context|.
TemporalScan(const Tile::Block & block,bool is_compound,int * const zero_mv_context,int * const num_mv_found)403 void TemporalScan(const Tile::Block& block, bool is_compound,
404                   int* const zero_mv_context, int* const num_mv_found) {
405   const int step_w = (block.width4x4 >= 16) ? 4 : 2;
406   const int step_h = (block.height4x4 >= 16) ? 4 : 2;
407   const int row_start = block.row4x4 | 1;
408   const int column_start = block.column4x4 | 1;
409   const int row_end =
410       row_start + std::min(static_cast<int>(block.height4x4), 16);
411   const int column_end =
412       column_start + std::min(static_cast<int>(block.width4x4), 16);
413   const Tile& tile = block.tile;
414   const TemporalMotionField& motion_field = tile.motion_field();
415   const int stride = motion_field.mv.columns();
416   const MotionVector* motion_field_mv = motion_field.mv[0];
417   const int8_t* motion_field_reference_offset =
418       motion_field.reference_offset[0];
419   alignas(kMaxAlignment)
420       MotionVector temporal_mvs[kMaxTemporalMvCandidatesWithPadding];
421   int8_t temporal_reference_offsets[kMaxTemporalMvCandidatesWithPadding];
422   int count = 0;
423   int offset = stride * (row_start >> 1);
424   int mv_row = row_start;
425   do {
426     int mv_column = column_start;
427     do {
428       // Both horizontal and vertical offsets are positive. Only bottom and
429       // right boundaries need to be checked.
430       if (tile.IsBottomRightInside(mv_row, mv_column)) {
431         const int x8 = mv_column >> 1;
432         const MotionVector temporal_mv = motion_field_mv[offset + x8];
433         if (temporal_mv.mv[0] == kInvalidMvValue) {
434           if (mv_row == row_start && mv_column == column_start) {
435             *zero_mv_context = 1;
436           }
437         } else {
438           temporal_mvs[count] = temporal_mv;
439           temporal_reference_offsets[count++] =
440               motion_field_reference_offset[offset + x8];
441         }
442       }
443       mv_column += step_w;
444     } while (mv_column < column_end);
445     offset += stride * step_h >> 1;
446     mv_row += step_h;
447   } while (mv_row < row_end);
448   if (kTemporalScanMask.Contains(block.size)) {
449     const int temporal_sample_positions[3][2] = {
450         {block.height4x4, -2},
451         {block.height4x4, block.width4x4},
452         {block.height4x4 - 2, block.width4x4}};
453     // Getting the address of an element in Array2D is slow. Precalculate the
454     // offsets.
455     int temporal_sample_offsets[3];
456     temporal_sample_offsets[0] = stride * ((row_start + block.height4x4) >> 1) +
457                                  ((column_start - 2) >> 1);
458     temporal_sample_offsets[1] =
459         temporal_sample_offsets[0] + ((block.width4x4 + 2) >> 1);
460     temporal_sample_offsets[2] = temporal_sample_offsets[1] - stride;
461     for (int i = 0; i < 3; i++) {
462       const int row = temporal_sample_positions[i][0];
463       const int column = temporal_sample_positions[i][1];
464       if (!IsWithinTheSame64x64Block(block, row, column)) continue;
465       const int mv_row = row_start + row;
466       const int mv_column = column_start + column;
467       // IsWithinTheSame64x64Block() guarantees the reference block is inside
468       // the top and left boundary.
469       if (!tile.IsBottomRightInside(mv_row, mv_column)) continue;
470       const MotionVector temporal_mv =
471           motion_field_mv[temporal_sample_offsets[i]];
472       if (temporal_mv.mv[0] != kInvalidMvValue) {
473         temporal_mvs[count] = temporal_mv;
474         temporal_reference_offsets[count++] =
475             motion_field_reference_offset[temporal_sample_offsets[i]];
476       }
477     }
478   }
479   if (count != 0) {
480     BlockParameters* const bp = block.bp;
481     int reference_offsets[2];
482     const int offset_0 = tile.current_frame()
483                              .reference_info()
484                              ->relative_distance_to[bp->reference_frame[0]];
485     reference_offsets[0] =
486         Clip3(offset_0, -kMaxFrameDistance, kMaxFrameDistance);
487     if (is_compound) {
488       const int offset_1 = tile.current_frame()
489                                .reference_info()
490                                ->relative_distance_to[bp->reference_frame[1]];
491       reference_offsets[1] =
492           Clip3(offset_1, -kMaxFrameDistance, kMaxFrameDistance);
493       // Pad so that SIMD implementations won't read uninitialized memory.
494       if ((count & 1) != 0) {
495         temporal_mvs[count].mv32 = 0;
496         temporal_reference_offsets[count] = 0;
497       }
498     } else {
499       // Pad so that SIMD implementations won't read uninitialized memory.
500       for (int i = count; i < ((count + 3) & ~3); ++i) {
501         temporal_mvs[i].mv32 = 0;
502         temporal_reference_offsets[i] = 0;
503       }
504     }
505     AddTemporalReferenceMvCandidate(
506         tile.frame_header(), reference_offsets, temporal_mvs,
507         temporal_reference_offsets, count, is_compound, zero_mv_context,
508         num_mv_found, &(*bp->prediction_parameters));
509   }
510 }
511 
512 // Part of 7.10.2.13.
AddExtraCompoundMvCandidate(const Tile::Block & block,int mv_row,int mv_column,int * const ref_id_count,MotionVector ref_id[2][2],int * const ref_diff_count,MotionVector ref_diff[2][2])513 void AddExtraCompoundMvCandidate(const Tile::Block& block, int mv_row,
514                                  int mv_column, int* const ref_id_count,
515                                  MotionVector ref_id[2][2],
516                                  int* const ref_diff_count,
517                                  MotionVector ref_diff[2][2]) {
518   const auto& bp = block.tile.Parameters(mv_row, mv_column);
519   const std::array<bool, kNumReferenceFrameTypes>& reference_frame_sign_bias =
520       block.tile.reference_frame_sign_bias();
521   for (int i = 0; i < 2; ++i) {
522     const ReferenceFrameType candidate_reference_frame = bp.reference_frame[i];
523     if (candidate_reference_frame <= kReferenceFrameIntra) continue;
524     for (int j = 0; j < 2; ++j) {
525       MotionVector candidate_mv = bp.mv.mv[i];
526       const ReferenceFrameType block_reference_frame =
527           block.bp->reference_frame[j];
528       if (candidate_reference_frame == block_reference_frame &&
529           ref_id_count[j] < 2) {
530         ref_id[j][ref_id_count[j]] = candidate_mv;
531         ++ref_id_count[j];
532       } else if (ref_diff_count[j] < 2) {
533         if (reference_frame_sign_bias[candidate_reference_frame] !=
534             reference_frame_sign_bias[block_reference_frame]) {
535           candidate_mv.mv[0] *= -1;
536           candidate_mv.mv[1] *= -1;
537         }
538         ref_diff[j][ref_diff_count[j]] = candidate_mv;
539         ++ref_diff_count[j];
540       }
541     }
542   }
543 }
544 
545 // Part of 7.10.2.13.
AddExtraSingleMvCandidate(const Tile::Block & block,int mv_row,int mv_column,int * const num_mv_found)546 void AddExtraSingleMvCandidate(const Tile::Block& block, int mv_row,
547                                int mv_column, int* const num_mv_found) {
548   const auto& bp = block.tile.Parameters(mv_row, mv_column);
549   const std::array<bool, kNumReferenceFrameTypes>& reference_frame_sign_bias =
550       block.tile.reference_frame_sign_bias();
551   const ReferenceFrameType block_reference_frame = block.bp->reference_frame[0];
552   PredictionParameters& prediction_parameters =
553       *block.bp->prediction_parameters;
554   MotionVector* const ref_mv_stack = prediction_parameters.ref_mv_stack;
555   for (int i = 0; i < 2; ++i) {
556     const ReferenceFrameType candidate_reference_frame = bp.reference_frame[i];
557     if (candidate_reference_frame <= kReferenceFrameIntra) continue;
558     MotionVector candidate_mv = bp.mv.mv[i];
559     if (reference_frame_sign_bias[candidate_reference_frame] !=
560         reference_frame_sign_bias[block_reference_frame]) {
561       candidate_mv.mv[0] *= -1;
562       candidate_mv.mv[1] *= -1;
563     }
564     assert(*num_mv_found <= 2);
565     if ((*num_mv_found != 0 && ref_mv_stack[0] == candidate_mv) ||
566         (*num_mv_found == 2 && ref_mv_stack[1] == candidate_mv)) {
567       continue;
568     }
569     ref_mv_stack[*num_mv_found] = candidate_mv;
570     prediction_parameters.SetWeightIndexStackEntry(*num_mv_found, 0);
571     ++*num_mv_found;
572   }
573 }
574 
575 // 7.10.2.12.
ExtraSearch(const Tile::Block & block,bool is_compound,int * const num_mv_found)576 void ExtraSearch(const Tile::Block& block, bool is_compound,
577                  int* const num_mv_found) {
578   const Tile& tile = block.tile;
579   const int num4x4 = std::min({static_cast<int>(block.width4x4),
580                                tile.frame_header().columns4x4 - block.column4x4,
581                                static_cast<int>(block.height4x4),
582                                tile.frame_header().rows4x4 - block.row4x4, 16});
583   int ref_id_count[2] = {};
584   MotionVector ref_id[2][2] = {};
585   int ref_diff_count[2] = {};
586   MotionVector ref_diff[2][2] = {};
587   PredictionParameters& prediction_parameters =
588       *block.bp->prediction_parameters;
589   for (int pass = 0; pass < 2 && *num_mv_found < 2; ++pass) {
590     for (int i = 0; i < num4x4;) {
591       const int mv_row = block.row4x4 + ((pass == 0) ? -1 : i);
592       const int mv_column = block.column4x4 + ((pass == 0) ? i : -1);
593       if (!tile.IsTopLeftInside(mv_row + 1, mv_column + 1)) break;
594       if (is_compound) {
595         AddExtraCompoundMvCandidate(block, mv_row, mv_column, ref_id_count,
596                                     ref_id, ref_diff_count, ref_diff);
597       } else {
598         AddExtraSingleMvCandidate(block, mv_row, mv_column, num_mv_found);
599         if (*num_mv_found >= 2) break;
600       }
601       const auto& bp = tile.Parameters(mv_row, mv_column);
602       i +=
603           (pass == 0) ? kNum4x4BlocksWide[bp.size] : kNum4x4BlocksHigh[bp.size];
604     }
605   }
606   if (is_compound) {
607     // Merge compound mode extra search into mv stack.
608     CompoundMotionVector* const compound_ref_mv_stack =
609         prediction_parameters.compound_ref_mv_stack;
610     CompoundMotionVector combined_mvs[2] = {};
611     for (int i = 0; i < 2; ++i) {
612       int count = 0;
613       assert(ref_id_count[i] <= 2);
614       for (int j = 0; j < ref_id_count[i]; ++j, ++count) {
615         combined_mvs[count].mv[i] = ref_id[i][j];
616       }
617       for (int j = 0; j < ref_diff_count[i] && count < 2; ++j, ++count) {
618         combined_mvs[count].mv[i] = ref_diff[i][j];
619       }
620       for (; count < 2; ++count) {
621         combined_mvs[count].mv[i] = prediction_parameters.global_mv[i];
622       }
623     }
624     if (*num_mv_found == 1) {
625       if (combined_mvs[0] == compound_ref_mv_stack[0]) {
626         compound_ref_mv_stack[1] = combined_mvs[1];
627       } else {
628         compound_ref_mv_stack[1] = combined_mvs[0];
629       }
630       prediction_parameters.SetWeightIndexStackEntry(1, 0);
631     } else {
632       assert(*num_mv_found == 0);
633       for (int i = 0; i < 2; ++i) {
634         compound_ref_mv_stack[i] = combined_mvs[i];
635         prediction_parameters.SetWeightIndexStackEntry(i, 0);
636       }
637     }
638     *num_mv_found = 2;
639   } else {
640     // single prediction mode
641     MotionVector* const ref_mv_stack = prediction_parameters.ref_mv_stack;
642     for (int i = *num_mv_found; i < 2; ++i) {
643       ref_mv_stack[i] = prediction_parameters.global_mv[0];
644       prediction_parameters.SetWeightIndexStackEntry(i, 0);
645     }
646   }
647 }
648 
DescendingOrderTwo(int * const a,int * const b)649 void DescendingOrderTwo(int* const a, int* const b) {
650   if (*a < *b) {
651     std::swap(*a, *b);
652   }
653 }
654 
655 // Comparator used for sorting candidate motion vectors in descending order of
656 // their weights (as specified in 7.10.2.11).
CompareCandidateMotionVectors(const int16_t & lhs,const int16_t & rhs)657 bool CompareCandidateMotionVectors(const int16_t& lhs, const int16_t& rhs) {
658   return lhs > rhs;
659 }
660 
SortWeightIndexStack(const int size,const int sort_to_n,int16_t * const weight_index_stack)661 void SortWeightIndexStack(const int size, const int sort_to_n,
662                           int16_t* const weight_index_stack) {
663   if (size <= 1) return;
664   if (size <= 3) {
665     // Specialize small sort sizes to speed up.
666     int weight_index_0 = weight_index_stack[0];
667     int weight_index_1 = weight_index_stack[1];
668     DescendingOrderTwo(&weight_index_0, &weight_index_1);
669     if (size == 3) {
670       int weight_index_2 = weight_index_stack[2];
671       DescendingOrderTwo(&weight_index_1, &weight_index_2);
672       DescendingOrderTwo(&weight_index_0, &weight_index_1);
673       weight_index_stack[2] = weight_index_2;
674     }
675     weight_index_stack[0] = weight_index_0;
676     weight_index_stack[1] = weight_index_1;
677     return;
678   }
679   if (sort_to_n == 1) {
680     // std::max_element() is not efficient. Find the max element in a loop.
681     int16_t max_element = weight_index_stack[0];
682     int i = 1;
683     do {
684       max_element = std::max(max_element, weight_index_stack[i]);
685     } while (++i < size);
686     weight_index_stack[0] = max_element;
687     return;
688   }
689   std::partial_sort(&weight_index_stack[0], &weight_index_stack[sort_to_n],
690                     &weight_index_stack[size], CompareCandidateMotionVectors);
691 }
692 
693 // 7.10.2.14 (part 2).
ComputeContexts(bool found_new_mv,int nearest_matches,int total_matches,int * new_mv_context,int * reference_mv_context)694 void ComputeContexts(bool found_new_mv, int nearest_matches, int total_matches,
695                      int* new_mv_context, int* reference_mv_context) {
696   switch (nearest_matches) {
697     case 0:
698       *new_mv_context = std::min(total_matches, 1);
699       *reference_mv_context = total_matches;
700       break;
701     case 1:
702       *new_mv_context = 3 - static_cast<int>(found_new_mv);
703       *reference_mv_context = 2 + total_matches;
704       break;
705     default:
706       *new_mv_context = 5 - static_cast<int>(found_new_mv);
707       *reference_mv_context = 5;
708       break;
709   }
710 }
711 
712 // 7.10.4.2.
AddSample(const Tile::Block & block,int delta_row,int delta_column,int * const num_warp_samples,int * const num_samples_scanned,int candidates[kMaxLeastSquaresSamples][4])713 void AddSample(const Tile::Block& block, int delta_row, int delta_column,
714                int* const num_warp_samples, int* const num_samples_scanned,
715                int candidates[kMaxLeastSquaresSamples][4]) {
716   if (*num_samples_scanned >= kMaxLeastSquaresSamples) return;
717   const int mv_row = block.row4x4 + delta_row;
718   const int mv_column = block.column4x4 + delta_column;
719   const Tile& tile = block.tile;
720   if (!tile.IsInside(mv_row, mv_column) ||
721       !tile.HasParameters(mv_row, mv_column)) {
722     return;
723   }
724   const BlockParameters& bp = *block.bp;
725   const BlockParameters& mv_bp = tile.Parameters(mv_row, mv_column);
726   if (mv_bp.reference_frame[0] != bp.reference_frame[0] ||
727       mv_bp.reference_frame[1] != kReferenceFrameNone) {
728     return;
729   }
730   ++*num_samples_scanned;
731   const int candidate_height4x4 = kNum4x4BlocksHigh[mv_bp.size];
732   const int candidate_row = mv_row & ~(candidate_height4x4 - 1);
733   const int candidate_width4x4 = kNum4x4BlocksWide[mv_bp.size];
734   const int candidate_column = mv_column & ~(candidate_width4x4 - 1);
735   const BlockParameters& candidate_bp =
736       tile.Parameters(candidate_row, candidate_column);
737   const int mv_diff_row =
738       std::abs(candidate_bp.mv.mv[0].mv[0] - bp.mv.mv[0].mv[0]);
739   const int mv_diff_column =
740       std::abs(candidate_bp.mv.mv[0].mv[1] - bp.mv.mv[0].mv[1]);
741   const bool is_valid =
742       mv_diff_row + mv_diff_column <= kWarpValidThreshold[block.size];
743   if (!is_valid && *num_samples_scanned > 1) {
744     return;
745   }
746   const int mid_y =
747       MultiplyBy4(candidate_row) + MultiplyBy2(candidate_height4x4) - 1;
748   const int mid_x =
749       MultiplyBy4(candidate_column) + MultiplyBy2(candidate_width4x4) - 1;
750   candidates[*num_warp_samples][0] = MultiplyBy8(mid_y);
751   candidates[*num_warp_samples][1] = MultiplyBy8(mid_x);
752   candidates[*num_warp_samples][2] =
753       MultiplyBy8(mid_y) + candidate_bp.mv.mv[0].mv[0];
754   candidates[*num_warp_samples][3] =
755       MultiplyBy8(mid_x) + candidate_bp.mv.mv[0].mv[1];
756   if (is_valid) ++*num_warp_samples;
757 }
758 
759 // 7.9.2.
760 // In the spec, |dst_sign| is either 1 or -1. Here we set |dst_sign| to either 0
761 // or -1 so that it can be XORed and subtracted directly in ApplySign() and
762 // corresponding SIMD implementations.
MotionFieldProjection(const ObuFrameHeader & frame_header,const std::array<RefCountedBufferPtr,kNumReferenceFrameTypes> & reference_frames,ReferenceFrameType source,int reference_to_current_with_sign,int dst_sign,int y8_start,int y8_end,int x8_start,int x8_end,TemporalMotionField * const motion_field)763 bool MotionFieldProjection(
764     const ObuFrameHeader& frame_header,
765     const std::array<RefCountedBufferPtr, kNumReferenceFrameTypes>&
766         reference_frames,
767     ReferenceFrameType source, int reference_to_current_with_sign, int dst_sign,
768     int y8_start, int y8_end, int x8_start, int x8_end,
769     TemporalMotionField* const motion_field) {
770   const int source_index =
771       frame_header.reference_frame_index[source - kReferenceFrameLast];
772   auto* const source_frame = reference_frames[source_index].get();
773   assert(source_frame != nullptr);
774   assert(dst_sign == 0 || dst_sign == -1);
775   if (source_frame->rows4x4() != frame_header.rows4x4 ||
776       source_frame->columns4x4() != frame_header.columns4x4 ||
777       IsIntraFrame(source_frame->frame_type())) {
778     return false;
779   }
780   assert(reference_to_current_with_sign >= -kMaxFrameDistance);
781   if (reference_to_current_with_sign > kMaxFrameDistance) return true;
782   const ReferenceInfo& reference_info = *source_frame->reference_info();
783   const dsp::Dsp& dsp = *dsp::GetDspTable(8);
784   dsp.motion_field_projection_kernel(
785       reference_info, reference_to_current_with_sign, dst_sign, y8_start,
786       y8_end, x8_start, x8_end, motion_field);
787   return true;
788 }
789 
790 }  // namespace
791 
FindMvStack(const Tile::Block & block,bool is_compound,MvContexts * const contexts)792 void FindMvStack(const Tile::Block& block, bool is_compound,
793                  MvContexts* const contexts) {
794   PredictionParameters& prediction_parameters =
795       *block.bp->prediction_parameters;
796   SetupGlobalMv(block, 0, &prediction_parameters.global_mv[0]);
797   if (is_compound) SetupGlobalMv(block, 1, &prediction_parameters.global_mv[1]);
798   bool found_new_mv = false;
799   bool found_row_match = false;
800   int num_mv_found = 0;
801   ScanRow(block, block.column4x4, -1, is_compound, &found_new_mv,
802           &found_row_match, &num_mv_found);
803   bool found_column_match = false;
804   ScanColumn(block, block.row4x4, -1, is_compound, &found_new_mv,
805              &found_column_match, &num_mv_found);
806   if (std::max(block.width4x4, block.height4x4) <= 16) {
807     ScanPoint(block, -1, block.width4x4, is_compound, &found_new_mv,
808               &found_row_match, &num_mv_found);
809   }
810   const int nearest_matches =
811       static_cast<int>(found_row_match) + static_cast<int>(found_column_match);
812   prediction_parameters.nearest_mv_count = num_mv_found;
813   if (block.tile.frame_header().use_ref_frame_mvs) {
814     // Initialize to invalid value, and it will be set when temporal mv is zero.
815     contexts->zero_mv = -1;
816     TemporalScan(block, is_compound, &contexts->zero_mv, &num_mv_found);
817   } else {
818     contexts->zero_mv = 0;
819   }
820   bool dummy_bool = false;
821   ScanPoint(block, -1, -1, is_compound, &dummy_bool, &found_row_match,
822             &num_mv_found);
823   static constexpr int deltas[2] = {-3, -5};
824   for (int i = 0; i < 2; ++i) {
825     if (i == 0 || block.height4x4 > 1) {
826       ScanRow(block, block.column4x4 | 1, deltas[i] + (block.row4x4 & 1),
827               is_compound, &dummy_bool, &found_row_match, &num_mv_found);
828     }
829     if (i == 0 || block.width4x4 > 1) {
830       ScanColumn(block, block.row4x4 | 1, deltas[i] + (block.column4x4 & 1),
831                  is_compound, &dummy_bool, &found_column_match, &num_mv_found);
832     }
833   }
834   if (num_mv_found < 2) {
835     ExtraSearch(block, is_compound, &num_mv_found);
836   } else {
837     // The sort of |weight_index_stack| could be moved to Tile::AssignIntraMv()
838     // and Tile::AssignInterMv(), and only do a partial sort to the max index we
839     // need. However, the speed gain is trivial.
840     // For intra case, only the first 1 or 2 mvs in the stack will be used.
841     // For inter case, |prediction_parameters.ref_mv_index| is at most 3.
842     // We only need to do the partial sort up to the first 4 mvs.
843     SortWeightIndexStack(prediction_parameters.nearest_mv_count, 4,
844                          prediction_parameters.weight_index_stack);
845     // When there are 4 or more nearest mvs, the other mvs will not be used.
846     if (prediction_parameters.nearest_mv_count < 4) {
847       SortWeightIndexStack(
848           num_mv_found - prediction_parameters.nearest_mv_count,
849           4 - prediction_parameters.nearest_mv_count,
850           prediction_parameters.weight_index_stack +
851               prediction_parameters.nearest_mv_count);
852     }
853   }
854   prediction_parameters.ref_mv_count = num_mv_found;
855   const int total_matches =
856       static_cast<int>(found_row_match) + static_cast<int>(found_column_match);
857   ComputeContexts(found_new_mv, nearest_matches, total_matches,
858                   &contexts->new_mv, &contexts->reference_mv);
859   // The mv stack clamping process is in Tile::AssignIntraMv() and
860   // Tile::AssignInterMv(), and only up to two mvs are clamped.
861 }
862 
FindWarpSamples(const Tile::Block & block,int * const num_warp_samples,int * const num_samples_scanned,int candidates[kMaxLeastSquaresSamples][4])863 void FindWarpSamples(const Tile::Block& block, int* const num_warp_samples,
864                      int* const num_samples_scanned,
865                      int candidates[kMaxLeastSquaresSamples][4]) {
866   const Tile& tile = block.tile;
867   bool top_left = true;
868   bool top_right = true;
869   int step = 1;
870   if (block.top_available[kPlaneY]) {
871     BlockSize source_size =
872         tile.Parameters(block.row4x4 - 1, block.column4x4).size;
873     const int source_width4x4 = kNum4x4BlocksWide[source_size];
874     if (block.width4x4 <= source_width4x4) {
875       // The & here is equivalent to % since source_width4x4 is a power of two.
876       const int column_offset = -(block.column4x4 & (source_width4x4 - 1));
877       if (column_offset < 0) top_left = false;
878       if (column_offset + source_width4x4 > block.width4x4) top_right = false;
879       AddSample(block, -1, 0, num_warp_samples, num_samples_scanned,
880                 candidates);
881     } else {
882       for (int i = 0;
883            i < std::min(static_cast<int>(block.width4x4),
884                         tile.frame_header().columns4x4 - block.column4x4);
885            i += step) {
886         source_size =
887             tile.Parameters(block.row4x4 - 1, block.column4x4 + i).size;
888         step = std::min(static_cast<int>(block.width4x4),
889                         static_cast<int>(kNum4x4BlocksWide[source_size]));
890         AddSample(block, -1, i, num_warp_samples, num_samples_scanned,
891                   candidates);
892       }
893     }
894   }
895   if (block.left_available[kPlaneY]) {
896     BlockSize source_size =
897         tile.Parameters(block.row4x4, block.column4x4 - 1).size;
898     const int source_height4x4 = kNum4x4BlocksHigh[source_size];
899     if (block.height4x4 <= source_height4x4) {
900       const int row_offset = -(block.row4x4 & (source_height4x4 - 1));
901       if (row_offset < 0) top_left = false;
902       AddSample(block, 0, -1, num_warp_samples, num_samples_scanned,
903                 candidates);
904     } else {
905       for (int i = 0; i < std::min(static_cast<int>(block.height4x4),
906                                    tile.frame_header().rows4x4 - block.row4x4);
907            i += step) {
908         source_size =
909             tile.Parameters(block.row4x4 + i, block.column4x4 - 1).size;
910         step = std::min(static_cast<int>(block.height4x4),
911                         static_cast<int>(kNum4x4BlocksHigh[source_size]));
912         AddSample(block, i, -1, num_warp_samples, num_samples_scanned,
913                   candidates);
914       }
915     }
916   }
917   if (top_left) {
918     AddSample(block, -1, -1, num_warp_samples, num_samples_scanned, candidates);
919   }
920   if (top_right && block.size <= kBlock64x64) {
921     AddSample(block, -1, block.width4x4, num_warp_samples, num_samples_scanned,
922               candidates);
923   }
924   if (*num_warp_samples == 0 && *num_samples_scanned > 0) *num_warp_samples = 1;
925 }
926 
SetupMotionField(const ObuFrameHeader & frame_header,const RefCountedBuffer & current_frame,const std::array<RefCountedBufferPtr,kNumReferenceFrameTypes> & reference_frames,int row4x4_start,int row4x4_end,int column4x4_start,int column4x4_end,TemporalMotionField * const motion_field)927 void SetupMotionField(
928     const ObuFrameHeader& frame_header, const RefCountedBuffer& current_frame,
929     const std::array<RefCountedBufferPtr, kNumReferenceFrameTypes>&
930         reference_frames,
931     int row4x4_start, int row4x4_end, int column4x4_start, int column4x4_end,
932     TemporalMotionField* const motion_field) {
933   assert(frame_header.use_ref_frame_mvs);
934   const int y8_start = DivideBy2(row4x4_start);
935   const int y8_end = DivideBy2(std::min(row4x4_end, frame_header.rows4x4));
936   const int x8_start = DivideBy2(column4x4_start);
937   const int x8_end =
938       DivideBy2(std::min(column4x4_end, frame_header.columns4x4));
939   const int last_index = frame_header.reference_frame_index[0];
940   const ReferenceInfo& reference_info = *current_frame.reference_info();
941   if (!IsIntraFrame(reference_frames[last_index]->frame_type())) {
942     const int last_alternate_order_hint =
943         reference_frames[last_index]
944             ->reference_info()
945             ->order_hint[kReferenceFrameAlternate];
946     const int current_gold_order_hint =
947         reference_info.order_hint[kReferenceFrameGolden];
948     if (last_alternate_order_hint != current_gold_order_hint) {
949       const int reference_offset_last =
950           -reference_info.relative_distance_from[kReferenceFrameLast];
951       if (std::abs(reference_offset_last) <= kMaxFrameDistance) {
952         MotionFieldProjection(frame_header, reference_frames,
953                               kReferenceFrameLast, reference_offset_last, -1,
954                               y8_start, y8_end, x8_start, x8_end, motion_field);
955       }
956     }
957   }
958   int ref_stamp = 1;
959   const int reference_offset_backward =
960       reference_info.relative_distance_from[kReferenceFrameBackward];
961   if (reference_offset_backward > 0 &&
962       MotionFieldProjection(frame_header, reference_frames,
963                             kReferenceFrameBackward, reference_offset_backward,
964                             0, y8_start, y8_end, x8_start, x8_end,
965                             motion_field)) {
966     --ref_stamp;
967   }
968   const int reference_offset_alternate2 =
969       reference_info.relative_distance_from[kReferenceFrameAlternate2];
970   if (reference_offset_alternate2 > 0 &&
971       MotionFieldProjection(frame_header, reference_frames,
972                             kReferenceFrameAlternate2,
973                             reference_offset_alternate2, 0, y8_start, y8_end,
974                             x8_start, x8_end, motion_field)) {
975     --ref_stamp;
976   }
977   if (ref_stamp >= 0) {
978     const int reference_offset_alternate =
979         reference_info.relative_distance_from[kReferenceFrameAlternate];
980     if (reference_offset_alternate > 0 &&
981         MotionFieldProjection(frame_header, reference_frames,
982                               kReferenceFrameAlternate,
983                               reference_offset_alternate, 0, y8_start, y8_end,
984                               x8_start, x8_end, motion_field)) {
985       --ref_stamp;
986     }
987   }
988   if (ref_stamp >= 0) {
989     const int reference_offset_last2 =
990         -reference_info.relative_distance_from[kReferenceFrameLast2];
991     if (std::abs(reference_offset_last2) <= kMaxFrameDistance) {
992       MotionFieldProjection(frame_header, reference_frames,
993                             kReferenceFrameLast2, reference_offset_last2, -1,
994                             y8_start, y8_end, x8_start, x8_end, motion_field);
995     }
996   }
997 }
998 
999 }  // namespace libgav1
1000