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