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