blob: fa6b4e0ec9ec16319c1c9a36abdb0aef3cfd30a6 [file] [log] [blame]
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include "src/buffer_pool.h"
#include "src/dsp/constants.h"
#include "src/dsp/dsp.h"
#include "src/motion_vector.h"
#include "src/obu_parser.h"
#include "src/prediction_mask.h"
#include "src/tile.h"
#include "src/utils/array_2d.h"
#include "src/utils/bit_mask_set.h"
#include "src/utils/block_parameters_holder.h"
#include "src/utils/common.h"
#include "src/utils/constants.h"
#include "src/utils/logging.h"
#include "src/utils/memory.h"
#include "src/utils/types.h"
#include "src/warp_prediction.h"
#include "src/yuv_buffer.h"
namespace libgav1 {
namespace {
constexpr int kObmcBufferSize = 4096; // 64x64
constexpr int kAngleStep = 3;
constexpr int kPredictionModeToAngle[kIntraPredictionModesUV] = {
0, 90, 180, 45, 135, 113, 157, 203, 67, 0, 0, 0, 0};
enum : uint8_t {
kNeedsLeft = 1,
kNeedsTop = 2,
};
// The values for directional and dc modes are not used since the left/top
// requirement for those modes depend on the prediction angle and the type of dc
// mode.
constexpr BitMaskSet kPredictionModeNeedsMask[kIntraPredictionModesY] = {
BitMaskSet(0), // kPredictionModeDc
BitMaskSet(kNeedsTop), // kPredictionModeVertical
BitMaskSet(kNeedsLeft), // kPredictionModeHorizontal
BitMaskSet(kNeedsTop), // kPredictionModeD45
BitMaskSet(kNeedsLeft, kNeedsTop), // kPredictionModeD135
BitMaskSet(kNeedsLeft, kNeedsTop), // kPredictionModeD113
BitMaskSet(kNeedsLeft, kNeedsTop), // kPredictionModeD157
BitMaskSet(kNeedsLeft), // kPredictionModeD203
BitMaskSet(kNeedsTop), // kPredictionModeD67
BitMaskSet(kNeedsLeft, kNeedsTop), // kPredictionModeSmooth
BitMaskSet(kNeedsLeft, kNeedsTop), // kPredictionModeSmoothVertical
BitMaskSet(kNeedsLeft, kNeedsTop), // kPredictionModeSmoothHorizontal
BitMaskSet(kNeedsLeft, kNeedsTop) // kPredictionModePaeth
};
int16_t GetDirectionalIntraPredictorDerivative(const int angle) {
assert(angle >= 3);
assert(angle <= 87);
return kDirectionalIntraPredictorDerivative[DivideBy2(angle) - 1];
}
// Maps the block_size to an index as follows:
// kBlock8x8 => 0.
// kBlock8x16 => 1.
// kBlock8x32 => 2.
// kBlock16x8 => 3.
// kBlock16x16 => 4.
// kBlock16x32 => 5.
// kBlock32x8 => 6.
// kBlock32x16 => 7.
// kBlock32x32 => 8.
int GetWedgeBlockSizeIndex(BlockSize block_size) {
assert(block_size >= kBlock8x8);
return block_size - kBlock8x8 - static_cast<int>(block_size >= kBlock16x8) -
static_cast<int>(block_size >= kBlock32x8);
}
// 7.11.2.9.
int GetIntraEdgeFilterStrength(int width, int height, int filter_type,
int delta) {
const int sum = width + height;
delta = std::abs(delta);
if (filter_type == 0) {
if (sum <= 8) {
if (delta >= 56) return 1;
} else if (sum <= 16) {
if (delta >= 40) return 1;
} else if (sum <= 24) {
if (delta >= 32) return 3;
if (delta >= 16) return 2;
if (delta >= 8) return 1;
} else if (sum <= 32) {
if (delta >= 32) return 3;
if (delta >= 4) return 2;
return 1;
} else {
return 3;
}
} else {
if (sum <= 8) {
if (delta >= 64) return 2;
if (delta >= 40) return 1;
} else if (sum <= 16) {
if (delta >= 48) return 2;
if (delta >= 20) return 1;
} else if (sum <= 24) {
if (delta >= 4) return 3;
} else {
return 3;
}
}
return 0;
}
// 7.11.2.10.
bool DoIntraEdgeUpsampling(int width, int height, int filter_type, int delta) {
const int sum = width + height;
delta = std::abs(delta);
if (delta == 0 || delta >= 40) return false;
return (filter_type == 1) ? sum <= 8 : sum <= 16;
}
constexpr uint8_t kQuantizedDistanceWeight[4][2] = {
{2, 3}, {2, 5}, {2, 7}, {1, kMaxFrameDistance}};
constexpr uint8_t kQuantizedDistanceLookup[4][2] = {
{9, 7}, {11, 5}, {12, 4}, {13, 3}};
void GetDistanceWeights(const int distance[2], int weight[2]) {
// Note: distance[0] and distance[1] correspond to relative distance
// between current frame and reference frame [1] and [0], respectively.
const int order = static_cast<int>(distance[0] <= distance[1]);
if (distance[0] == 0 || distance[1] == 0) {
weight[0] = kQuantizedDistanceLookup[3][order];
weight[1] = kQuantizedDistanceLookup[3][1 - order];
} else {
int i;
for (i = 0; i < 3; ++i) {
const int weight_0 = kQuantizedDistanceWeight[i][order];
const int weight_1 = kQuantizedDistanceWeight[i][1 - order];
if (order == 0) {
if (distance[0] * weight_0 < distance[1] * weight_1) break;
} else {
if (distance[0] * weight_0 > distance[1] * weight_1) break;
}
}
weight[0] = kQuantizedDistanceLookup[i][order];
weight[1] = kQuantizedDistanceLookup[i][1 - order];
}
}
template <int bitdepth, typename Pixel>
void ClipPrediction(const uint16_t* prediction,
const ptrdiff_t prediction_stride, const int width,
const int height, uint8_t* clipped_prediction,
ptrdiff_t clipped_prediction_stride) {
// An offset to cancel offsets used in compound predictor generation that
// make intermediate computations non negative.
const int single_round_offset = (1 << bitdepth) + (1 << (bitdepth - 1));
auto* clipped_pred = reinterpret_cast<Pixel*>(clipped_prediction);
clipped_prediction_stride /= sizeof(Pixel);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
clipped_pred[x] = static_cast<Pixel>(
Clip3(prediction[x] - single_round_offset, 0, (1 << bitdepth) - 1));
}
prediction += prediction_stride;
clipped_pred += clipped_prediction_stride;
}
}
dsp::IntraPredictor GetIntraPredictor(PredictionMode mode, bool has_left,
bool has_top) {
if (mode == kPredictionModeDc) {
if (has_left && has_top) {
return dsp::kIntraPredictorDc;
}
if (has_left) {
return dsp::kIntraPredictorDcLeft;
}
if (has_top) {
return dsp::kIntraPredictorDcTop;
}
return dsp::kIntraPredictorDcFill;
}
switch (mode) {
case kPredictionModePaeth:
return dsp::kIntraPredictorPaeth;
case kPredictionModeSmooth:
return dsp::kIntraPredictorSmooth;
case kPredictionModeSmoothVertical:
return dsp::kIntraPredictorSmoothVertical;
case kPredictionModeSmoothHorizontal:
return dsp::kIntraPredictorSmoothHorizontal;
default:
return dsp::kNumIntraPredictors;
}
}
// 7.11.3.2. Note InterRoundBits0 is derived in the dsp layer.
int GetInterRoundingBits(const bool is_compound, const int bitdepth) {
if (is_compound) return 7;
#if LIBGAV1_MAX_BITDEPTH == 12
if (bitdepth == 12) return 9;
#else
static_cast<void>(bitdepth);
#endif
return 11;
}
uint8_t* GetStartPoint(Array2DView<uint8_t>* const buffer, const int plane,
const int x, const int y, const int bitdepth) {
#if LIBGAV1_MAX_BITDEPTH >= 10
if (bitdepth > 8) {
Array2DView<uint16_t> buffer16(
buffer[plane].rows(), buffer[plane].columns() / sizeof(uint16_t),
reinterpret_cast<uint16_t*>(&buffer[plane][0][0]));
return reinterpret_cast<uint8_t*>(&buffer16[y][x]);
}
#endif // LIBGAV1_MAX_BITDEPTH >= 10
static_cast<void>(bitdepth);
return &buffer[plane][y][x];
}
int GetPixelPositionFromHighScale(int start, int step, int offset) {
return (start + step * offset) >> kScaleSubPixelBits;
}
dsp::MaskBlendFunc GetMaskBlendFunc(const dsp::Dsp& dsp,
InterIntraMode inter_intra_mode,
bool is_wedge_inter_intra,
int subsampling_x, int subsampling_y) {
const int is_inter_intra =
static_cast<int>(inter_intra_mode != kNumInterIntraModes);
return (is_inter_intra == 1 && !is_wedge_inter_intra)
? dsp.mask_blend[0][is_inter_intra]
: dsp.mask_blend[subsampling_x + subsampling_y][is_inter_intra];
}
void PopulatePredictionMaskFromWedgeMask(const uint8_t* wedge_mask,
int wedge_mask_stride,
int prediction_width,
int prediction_height,
uint8_t* prediction_mask,
int prediction_mask_stride) {
for (int y = 0; y < prediction_height; ++y) {
memcpy(prediction_mask, wedge_mask, prediction_width);
prediction_mask += prediction_mask_stride;
wedge_mask += wedge_mask_stride;
}
}
} // namespace
template <typename Pixel>
void Tile::IntraPrediction(const Block& block, Plane plane, int x, int y,
bool has_left, bool has_top, bool has_top_right,
bool has_bottom_left, PredictionMode mode,
TransformSize tx_size) {
const int width = 1 << kTransformWidthLog2[tx_size];
const int height = 1 << kTransformHeightLog2[tx_size];
const int x_shift = subsampling_x_[plane];
const int y_shift = subsampling_y_[plane];
const int max_x = (MultiplyBy4(frame_header_.columns4x4) >> x_shift) - 1;
const int max_y = (MultiplyBy4(frame_header_.rows4x4) >> y_shift) - 1;
alignas(kMaxAlignment) Pixel top_row_data[160] = {};
alignas(kMaxAlignment) Pixel left_column_data[160] = {};
// Some predictors use |top_row_data| and |left_column_data| with a negative
// offset to access pixels to the top-left of the current block. So have some
// space before the arrays to allow populating those without having to move
// the rest of the array.
Pixel* const top_row = top_row_data + 16;
Pixel* const left_column = left_column_data + 16;
const int bitdepth = sequence_header_.color_config.bitdepth;
const int top_and_left_size = width + height;
const bool is_directional_mode = IsDirectionalMode(mode);
const PredictionParameters& prediction_parameters =
*block.bp->prediction_parameters;
const bool use_filter_intra =
(plane == kPlaneY && prediction_parameters.use_filter_intra);
const int prediction_angle =
is_directional_mode
? kPredictionModeToAngle[mode] +
prediction_parameters.angle_delta[GetPlaneType(plane)] *
kAngleStep
: 0;
const bool needs_top = use_filter_intra ||
kPredictionModeNeedsMask[mode].Contains(kNeedsTop) ||
(is_directional_mode && prediction_angle < 180) ||
(mode == kPredictionModeDc && has_top);
Array2DView<Pixel> buffer(buffer_[plane].rows(),
buffer_[plane].columns() / sizeof(Pixel),
reinterpret_cast<Pixel*>(&buffer_[plane][0][0]));
if (needs_top) {
// Compute top_row.
top_row[-1] = (has_top || has_left)
? buffer[has_top ? y - 1 : y][has_left ? x - 1 : x]
: (1 << (bitdepth - 1));
if (!has_top && has_left) {
Memset(top_row, buffer[y][x - 1], top_and_left_size);
} else if (!has_top && !has_left) {
Memset(top_row, (1 << (bitdepth - 1)) - 1, top_and_left_size);
} else {
const int top_limit =
std::min(max_x, x - 1 + ((has_top_right ? 2 : 1) * width));
for (int i = 0; i < top_and_left_size; ++i) {
top_row[i] = buffer[y - 1][std::min(top_limit, x + i)];
}
}
}
const bool needs_left = use_filter_intra ||
kPredictionModeNeedsMask[mode].Contains(kNeedsLeft) ||
(is_directional_mode && prediction_angle > 90) ||
(mode == kPredictionModeDc && has_left);
if (needs_left) {
// Compute left_column.
left_column[-1] = (has_top || has_left)
? buffer[has_top ? y - 1 : y][has_left ? x - 1 : x]
: (1 << (bitdepth - 1));
if (!has_left && has_top) {
Memset(left_column, buffer[y - 1][x], top_and_left_size);
} else if (!has_left && !has_top) {
Memset(left_column, (1 << (bitdepth - 1)) + 1, top_and_left_size);
} else {
const int left_limit =
std::min(max_y, y - 1 + ((has_bottom_left ? 2 : 1) * height));
for (int i = 0; i < top_and_left_size; ++i) {
left_column[i] = buffer[std::min(left_limit, y + i)][x - 1];
}
}
}
Pixel* const dest = &buffer[y][x];
const ptrdiff_t dest_stride = buffer_[plane].columns();
if (use_filter_intra) {
dsp_.filter_intra_predictor(reinterpret_cast<uint8_t*>(dest), dest_stride,
reinterpret_cast<uint8_t*>(top_row),
reinterpret_cast<uint8_t*>(left_column),
prediction_parameters.filter_intra_mode, width,
height);
} else if (is_directional_mode) {
DirectionalPrediction(block, plane, x, y, has_left, has_top,
prediction_angle, width, height, max_x, max_y,
tx_size, top_row, left_column);
} else {
const dsp::IntraPredictor predictor =
GetIntraPredictor(mode, has_left, has_top);
assert(predictor != dsp::kNumIntraPredictors);
dsp_.intra_predictors[tx_size][predictor](
reinterpret_cast<uint8_t*>(dest), dest_stride,
reinterpret_cast<uint8_t*>(top_row),
reinterpret_cast<uint8_t*>(left_column));
}
}
template void Tile::IntraPrediction<uint8_t>(const Block& block, Plane plane,
int x, int y, bool has_left,
bool has_top, bool has_top_right,
bool has_bottom_left,
PredictionMode mode,
TransformSize tx_size);
#if LIBGAV1_MAX_BITDEPTH >= 10
template void Tile::IntraPrediction<uint16_t>(const Block& block, Plane plane,
int x, int y, bool has_left,
bool has_top, bool has_top_right,
bool has_bottom_left,
PredictionMode mode,
TransformSize tx_size);
#endif
constexpr BitMaskSet kPredictionModeSmoothMask(kPredictionModeSmooth,
kPredictionModeSmoothHorizontal,
kPredictionModeSmoothVertical);
bool Tile::IsSmoothPrediction(int row, int column, Plane plane) const {
const BlockParameters& bp = *block_parameters_holder_.Find(row, column);
PredictionMode mode;
if (plane == kPlaneY) {
mode = bp.y_mode;
} else {
if (bp.reference_frame[0] > kReferenceFrameIntra) return false;
mode = bp.uv_mode;
}
return kPredictionModeSmoothMask.Contains(mode);
}
int Tile::GetIntraEdgeFilterType(const Block& block, Plane plane) const {
const int subsampling_x = subsampling_x_[plane];
const int subsampling_y = subsampling_y_[plane];
if ((plane == kPlaneY && block.top_available) ||
(plane != kPlaneY && block.TopAvailableChroma())) {
const int row =
block.row4x4 - 1 -
static_cast<int>(subsampling_y != 0 && (block.row4x4 & 1) != 0);
const int column =
block.column4x4 +
static_cast<int>(subsampling_x != 0 && (block.column4x4 & 1) == 0);
if (IsSmoothPrediction(row, column, plane)) return 1;
}
if ((plane == kPlaneY && block.left_available) ||
(plane != kPlaneY && block.LeftAvailableChroma())) {
const int row = block.row4x4 + static_cast<int>(subsampling_y != 0 &&
(block.row4x4 & 1) == 0);
const int column =
block.column4x4 - 1 -
static_cast<int>(subsampling_x != 0 && (block.column4x4 & 1) != 0);
if (IsSmoothPrediction(row, column, plane)) return 1;
}
return 0;
}
template <typename Pixel>
void Tile::DirectionalPrediction(const Block& block, Plane plane, int x, int y,
bool has_left, bool has_top,
int prediction_angle, int width, int height,
int max_x, int max_y, TransformSize tx_size,
Pixel* const top_row,
Pixel* const left_column) {
bool upsampled_top = false;
bool upsampled_left = false;
if (sequence_header_.enable_intra_edge_filter) {
const int filter_type = GetIntraEdgeFilterType(block, plane);
if (prediction_angle != 90 && prediction_angle != 180) {
if (prediction_angle > 90 && prediction_angle < 180 &&
(width + height) >= 24) {
// 7.11.2.7.
left_column[-1] = top_row[-1] = RightShiftWithRounding(
left_column[0] * 5 + top_row[-1] * 6 + top_row[0] * 5, 4);
}
if (has_top) {
const int strength = GetIntraEdgeFilterStrength(
width, height, filter_type, prediction_angle - 90);
if (strength > 0) {
const int num_pixels = std::min(width, max_x - x + 1) +
((prediction_angle < 90) ? height : 0) + 1;
dsp_.intra_edge_filter(top_row - 1, num_pixels, strength);
}
}
if (has_left) {
const int strength = GetIntraEdgeFilterStrength(
width, height, filter_type, prediction_angle - 180);
if (strength > 0) {
const int num_pixels = std::min(height, max_y - y + 1) +
((prediction_angle > 180) ? width : 0) + 1;
dsp_.intra_edge_filter(left_column - 1, num_pixels, strength);
}
}
}
upsampled_top = DoIntraEdgeUpsampling(width, height, filter_type,
prediction_angle - 90);
if (upsampled_top) {
const int num_pixels = width + ((prediction_angle < 90) ? height : 0);
dsp_.intra_edge_upsampler(top_row, num_pixels);
}
upsampled_left = DoIntraEdgeUpsampling(width, height, filter_type,
prediction_angle - 180);
if (upsampled_left) {
const int num_pixels = height + ((prediction_angle > 180) ? width : 0);
dsp_.intra_edge_upsampler(left_column, num_pixels);
}
}
Array2DView<Pixel> buffer(buffer_[plane].rows(),
buffer_[plane].columns() / sizeof(Pixel),
reinterpret_cast<Pixel*>(&buffer_[plane][0][0]));
auto* const dest = reinterpret_cast<uint8_t* const>(&buffer[y][x]);
const ptrdiff_t stride = buffer_[plane].columns();
if (prediction_angle == 90) {
dsp_.intra_predictors[tx_size][dsp::kIntraPredictorVertical](
dest, stride, reinterpret_cast<uint8_t*>(top_row),
reinterpret_cast<uint8_t*>(left_column));
} else if (prediction_angle == 180) {
dsp_.intra_predictors[tx_size][dsp::kIntraPredictorHorizontal](
dest, stride, reinterpret_cast<uint8_t*>(top_row),
reinterpret_cast<uint8_t*>(left_column));
} else if (prediction_angle < 90) {
const int dx = GetDirectionalIntraPredictorDerivative(prediction_angle);
dsp_.directional_intra_predictor_zone1(dest, stride,
reinterpret_cast<uint8_t*>(top_row),
width, height, dx, upsampled_top);
} else if (prediction_angle < 180) {
const int dx =
GetDirectionalIntraPredictorDerivative(180 - prediction_angle);
const int dy =
GetDirectionalIntraPredictorDerivative(prediction_angle - 90);
dsp_.directional_intra_predictor_zone2(
dest, stride, reinterpret_cast<uint8_t*>(top_row),
reinterpret_cast<uint8_t*>(left_column), width, height, dx, dy,
upsampled_top, upsampled_left);
} else {
assert(prediction_angle < 270);
const int dy =
GetDirectionalIntraPredictorDerivative(270 - prediction_angle);
dsp_.directional_intra_predictor_zone3(
dest, stride, reinterpret_cast<uint8_t*>(left_column), width, height,
dy, upsampled_left);
}
}
template <typename Pixel>
void Tile::PalettePrediction(const Block& block, const Plane plane,
const int start_x, const int start_y, const int x,
const int y, const TransformSize tx_size) {
const int tx_width = kTransformWidth[tx_size];
const int tx_height = kTransformHeight[tx_size];
const uint16_t* const palette = block.bp->palette_mode_info.color[plane];
const PlaneType plane_type = GetPlaneType(plane);
const int x4 = MultiplyBy4(x);
const int y4 = MultiplyBy4(y);
Array2DView<Pixel> buffer(buffer_[plane].rows(),
buffer_[plane].columns() / sizeof(Pixel),
reinterpret_cast<Pixel*>(&buffer_[plane][0][0]));
for (int row = 0; row < tx_height; ++row) {
assert(block.bp->prediction_parameters
->color_index_map[plane_type][y4 + row] != nullptr);
for (int column = 0; column < tx_width; ++column) {
buffer[start_y + row][start_x + column] =
palette[block.bp->prediction_parameters
->color_index_map[plane_type][y4 + row][x4 + column]];
}
}
}
template void Tile::PalettePrediction<uint8_t>(
const Block& block, const Plane plane, const int start_x, const int start_y,
const int x, const int y, const TransformSize tx_size);
#if LIBGAV1_MAX_BITDEPTH >= 10
template void Tile::PalettePrediction<uint16_t>(
const Block& block, const Plane plane, const int start_x, const int start_y,
const int x, const int y, const TransformSize tx_size);
#endif
template <typename Pixel>
void Tile::ChromaFromLumaPrediction(const Block& block, const Plane plane,
const int start_x, const int start_y,
const TransformSize tx_size) {
const int subsampling_x = subsampling_x_[plane];
const int subsampling_y = subsampling_y_[plane];
const PredictionParameters& prediction_parameters =
*block.bp->prediction_parameters;
Array2DView<Pixel> y_buffer(
buffer_[kPlaneY].rows(), buffer_[kPlaneY].columns() / sizeof(Pixel),
reinterpret_cast<Pixel*>(&buffer_[kPlaneY][0][0]));
if (!block.scratch_buffer->cfl_luma_buffer_valid) {
const int luma_x = start_x << subsampling_x;
const int luma_y = start_y << subsampling_y;
dsp_.cfl_subsamplers[tx_size][subsampling_x + subsampling_y](
block.scratch_buffer->cfl_luma_buffer,
prediction_parameters.max_luma_width - luma_x,
prediction_parameters.max_luma_height - luma_y,
reinterpret_cast<uint8_t*>(&y_buffer[luma_y][luma_x]),
buffer_[kPlaneY].columns());
block.scratch_buffer->cfl_luma_buffer_valid = true;
}
Array2DView<Pixel> buffer(buffer_[plane].rows(),
buffer_[plane].columns() / sizeof(Pixel),
reinterpret_cast<Pixel*>(&buffer_[plane][0][0]));
dsp_.cfl_intra_predictors[tx_size](
reinterpret_cast<uint8_t*>(&buffer[start_y][start_x]),
buffer_[plane].columns(), block.scratch_buffer->cfl_luma_buffer,
(plane == kPlaneU) ? prediction_parameters.cfl_alpha_u
: prediction_parameters.cfl_alpha_v);
}
template void Tile::ChromaFromLumaPrediction<uint8_t>(
const Block& block, const Plane plane, const int start_x, const int start_y,
const TransformSize tx_size);
#if LIBGAV1_MAX_BITDEPTH >= 10
template void Tile::ChromaFromLumaPrediction<uint16_t>(
const Block& block, const Plane plane, const int start_x, const int start_y,
const TransformSize tx_size);
#endif
void Tile::InterIntraPrediction(
uint16_t* prediction[2], const ptrdiff_t prediction_stride,
const uint8_t* const prediction_mask,
const ptrdiff_t prediction_mask_stride,
const PredictionParameters& prediction_parameters,
const int prediction_width, const int prediction_height,
const int subsampling_x, const int subsampling_y, uint8_t* const dest,
const ptrdiff_t dest_stride) {
assert(prediction_mask != nullptr);
assert(prediction_parameters.compound_prediction_type ==
kCompoundPredictionTypeIntra ||
prediction_parameters.compound_prediction_type ==
kCompoundPredictionTypeWedge);
// The first buffer of InterIntra is from inter prediction.
// The second buffer is from intra prediction.
ptrdiff_t intra_stride;
const int bitdepth = sequence_header_.color_config.bitdepth;
if (bitdepth == 8) {
// Both the input predictors must be of type uint16_t. For bitdepth ==
// 8, |buffer_| is uint8_t and hence a copy has to be made. For higher
// bitdepths, the |buffer_| itself can act as an uint16_t buffer so no
// copy is necessary.
uint8_t* dest_ptr = dest;
Array2DView<uint16_t> intra_prediction(
kMaxSuperBlockSizeInPixels, kMaxSuperBlockSizeInPixels, prediction[1]);
for (int r = 0; r < prediction_height; ++r) {
for (int c = 0; c < prediction_width; ++c) {
intra_prediction[r][c] = dest_ptr[c];
}
dest_ptr += dest_stride;
}
intra_stride = kMaxSuperBlockSizeInPixels;
} else {
prediction[1] = reinterpret_cast<uint16_t*>(dest);
intra_stride = dest_stride / sizeof(uint16_t);
}
GetMaskBlendFunc(dsp_, prediction_parameters.inter_intra_mode,
prediction_parameters.is_wedge_inter_intra, subsampling_x,
subsampling_y)(prediction[0], prediction_stride,
prediction[1], intra_stride, prediction_mask,
prediction_mask_stride, prediction_width,
prediction_height, dest, dest_stride);
}
void Tile::CompoundInterPrediction(
const Block& block, const ptrdiff_t prediction_stride,
const ptrdiff_t prediction_mask_stride, const int prediction_width,
const int prediction_height, const Plane plane, const int subsampling_x,
const int subsampling_y, const int bitdepth, const int candidate_row,
const int candidate_column, uint8_t* dest, const ptrdiff_t dest_stride) {
const PredictionParameters& prediction_parameters =
*block.bp->prediction_parameters;
uint16_t* prediction[2] = {block.scratch_buffer->prediction_buffer[0],
block.scratch_buffer->prediction_buffer[1]};
switch (prediction_parameters.compound_prediction_type) {
case kCompoundPredictionTypeWedge:
GetMaskBlendFunc(dsp_, prediction_parameters.inter_intra_mode,
prediction_parameters.is_wedge_inter_intra,
subsampling_x, subsampling_y)(
prediction[0], prediction_stride, prediction[1], prediction_stride,
block.scratch_buffer->prediction_mask, prediction_mask_stride,
prediction_width, prediction_height, dest, dest_stride);
break;
case kCompoundPredictionTypeDiffWeighted:
if (plane == kPlaneY) {
GenerateWeightMask(
prediction[0], prediction_stride, prediction[1], prediction_stride,
prediction_parameters.mask_is_inverse, prediction_width,
prediction_height, bitdepth, block.scratch_buffer->prediction_mask,
prediction_mask_stride);
}
GetMaskBlendFunc(dsp_, prediction_parameters.inter_intra_mode,
prediction_parameters.is_wedge_inter_intra,
subsampling_x, subsampling_y)(
prediction[0], prediction_stride, prediction[1], prediction_stride,
block.scratch_buffer->prediction_mask, prediction_mask_stride,
prediction_width, prediction_height, dest, dest_stride);
break;
case kCompoundPredictionTypeDistance:
DistanceWeightedPrediction(
prediction[0], prediction_stride, prediction[1], prediction_stride,
prediction_width, prediction_height, candidate_row, candidate_column,
dest, dest_stride);
break;
default:
assert(prediction_parameters.compound_prediction_type ==
kCompoundPredictionTypeAverage);
dsp_.average_blend(prediction[0], prediction_stride, prediction[1],
prediction_stride, prediction_width, prediction_height,
dest, dest_stride);
break;
}
}
GlobalMotion* Tile::GetWarpParams(
const Block& block, const Plane plane, const int prediction_width,
const int prediction_height,
const PredictionParameters& prediction_parameters,
const ReferenceFrameType reference_type, bool* const is_local_valid,
GlobalMotion* const global_motion_params,
GlobalMotion* const local_warp_params) const {
if (prediction_width < 8 || prediction_height < 8 ||
frame_header_.force_integer_mv == 1) {
return nullptr;
}
if (plane == kPlaneY) {
*is_local_valid =
prediction_parameters.motion_mode == kMotionModeLocalWarp &&
WarpEstimation(
prediction_parameters.num_warp_samples, DivideBy4(prediction_width),
DivideBy4(prediction_height), block.row4x4, block.column4x4,
block.bp->mv[0], prediction_parameters.warp_estimate_candidates,
local_warp_params) &&
SetupShear(local_warp_params);
}
if (prediction_parameters.motion_mode == kMotionModeLocalWarp &&
*is_local_valid) {
return local_warp_params;
}
if (!IsScaled(reference_type)) {
GlobalMotionTransformationType global_motion_type =
(reference_type != kReferenceFrameIntra)
? global_motion_params->type
: kNumGlobalMotionTransformationTypes;
const bool is_global_valid =
IsGlobalMvBlock(block.bp->y_mode, global_motion_type, block.size) &&
SetupShear(global_motion_params);
// Valid global motion type implies reference type can't be intra.
assert(!is_global_valid || reference_type != kReferenceFrameIntra);
if (is_global_valid) return global_motion_params;
}
return nullptr;
}
void Tile::InterPrediction(const Block& block, const Plane plane, const int x,
const int y, const int prediction_width,
const int prediction_height, int candidate_row,
int candidate_column, bool* const is_local_valid,
GlobalMotion* const local_warp_params) {
const int bitdepth = sequence_header_.color_config.bitdepth;
const BlockParameters& bp = *block.bp;
const BlockParameters& bp_reference =
*block_parameters_holder_.Find(candidate_row, candidate_column);
const bool is_compound =
bp_reference.reference_frame[1] > kReferenceFrameIntra;
const bool is_inter_intra =
bp.is_inter && bp.reference_frame[1] == kReferenceFrameIntra;
const ptrdiff_t prediction_stride = prediction_width;
const PredictionParameters& prediction_parameters =
*block.bp->prediction_parameters;
uint8_t* const dest = GetStartPoint(buffer_, plane, x, y, bitdepth);
const ptrdiff_t dest_stride = buffer_[plane].columns(); // In bytes.
const int round_bits =
GetInterRoundingBits(is_compound, sequence_header_.color_config.bitdepth);
for (int index = 0; index < 1 + static_cast<int>(is_compound); ++index) {
const ReferenceFrameType reference_type =
bp_reference.reference_frame[index];
GlobalMotion global_motion_params =
frame_header_.global_motion[reference_type];
GlobalMotion* warp_params =
GetWarpParams(block, plane, prediction_width, prediction_height,
prediction_parameters, reference_type, is_local_valid,
&global_motion_params, local_warp_params);
if (warp_params != nullptr) {
BlockWarpProcess(block, plane, index, x, y, prediction_width,
prediction_height, prediction_stride, warp_params,
round_bits, is_compound, is_inter_intra, dest,
dest_stride);
} else {
const int reference_index =
prediction_parameters.use_intra_block_copy
? -1
: frame_header_.reference_frame_index[reference_type -
kReferenceFrameLast];
BlockInterPrediction(
block, plane, reference_index, bp_reference.mv[index], x, y,
prediction_width, prediction_height, candidate_row, candidate_column,
block.scratch_buffer->prediction_buffer[index], prediction_stride,
round_bits, is_compound, is_inter_intra, dest, dest_stride);
}
}
const ptrdiff_t prediction_mask_stride = kMaxSuperBlockSizeInPixels;
const int subsampling_x = subsampling_x_[plane];
const int subsampling_y = subsampling_y_[plane];
if (prediction_parameters.compound_prediction_type ==
kCompoundPredictionTypeWedge &&
plane == kPlaneY) {
// Wedge masks are generated only once per decoder. We only need to
// populate wedge masks to prediction_mask_.
const int wedge_mask_stride_1 = kMaxMaskBlockSize;
const int wedge_mask_stride_2 = wedge_mask_stride_1 * 16;
const int wedge_mask_stride_3 = wedge_mask_stride_2 * 2;
const int block_size_index = GetWedgeBlockSizeIndex(block.size);
assert(block_size_index >= 0);
const int offset = block_size_index * wedge_mask_stride_3 +
prediction_parameters.wedge_sign * wedge_mask_stride_2 +
prediction_parameters.wedge_index * wedge_mask_stride_1;
PopulatePredictionMaskFromWedgeMask(
&wedge_masks_[offset], kWedgeMaskMasterSize, prediction_width,
prediction_height, block.scratch_buffer->prediction_mask,
kMaxSuperBlockSizeInPixels);
} else if (prediction_parameters.compound_prediction_type ==
kCompoundPredictionTypeIntra) {
GenerateInterIntraMask(prediction_parameters.inter_intra_mode,
prediction_width, prediction_height,
block.scratch_buffer->prediction_mask,
prediction_mask_stride);
}
if (is_compound) {
CompoundInterPrediction(block, prediction_stride, prediction_mask_stride,
prediction_width, prediction_height, plane,
subsampling_x, subsampling_y, bitdepth,
candidate_row, candidate_column, dest, dest_stride);
} else {
if (prediction_parameters.motion_mode == kMotionModeObmc) {
// Obmc mode is allowed only for single reference (!is_compound).
ObmcPrediction(block, plane, prediction_width, prediction_height,
round_bits);
} else if (is_inter_intra) {
// InterIntra and obmc must be mutually exclusive.
uint16_t* prediction_ptr[2] = {
block.scratch_buffer->prediction_buffer[0],
block.scratch_buffer->prediction_buffer[1]};
InterIntraPrediction(prediction_ptr, prediction_stride,
block.scratch_buffer->prediction_mask,
prediction_mask_stride, prediction_parameters,
prediction_width, prediction_height, subsampling_x,
subsampling_y, dest, dest_stride);
}
}
}
void Tile::ObmcBlockPrediction(const Block& block, const MotionVector& mv,
const Plane plane,
const int reference_frame_index, const int width,
const int height, const int x, const int y,
const int candidate_row,
const int candidate_column,
const ObmcDirection blending_direction,
const int round_bits) {
const int bitdepth = sequence_header_.color_config.bitdepth;
// Obmc's prediction needs to be clipped before blending with above/left
// prediction blocks.
uint8_t obmc_clipped_prediction[kObmcBufferSize
#if LIBGAV1_MAX_BITDEPTH >= 10
* 2
#endif
];
const ptrdiff_t obmc_clipped_prediction_stride =
(bitdepth == 8) ? width : width * sizeof(uint16_t);
BlockInterPrediction(block, plane, reference_frame_index, mv, x, y, width,
height, candidate_row, candidate_column, nullptr, width,
round_bits, false, false, obmc_clipped_prediction,
obmc_clipped_prediction_stride);
uint8_t* const prediction = GetStartPoint(buffer_, plane, x, y, bitdepth);
const ptrdiff_t prediction_stride = buffer_[plane].columns();
dsp_.obmc_blend[blending_direction](prediction, prediction_stride, width,
height, obmc_clipped_prediction,
obmc_clipped_prediction_stride);
}
void Tile::ObmcPrediction(const Block& block, const Plane plane,
const int width, const int height,
const int round_bits) {
const int subsampling_x = subsampling_x_[plane];
const int subsampling_y = subsampling_y_[plane];
const int num4x4_wide = kNum4x4BlocksWide[block.size];
const int num4x4_high = kNum4x4BlocksHigh[block.size];
if (block.top_available &&
!IsBlockSmallerThan8x8(block.residual_size[GetPlaneType(plane)])) {
const int num_limit = std::min(uint8_t{4}, k4x4WidthLog2[block.size]);
const int column4x4_max =
std::min(block.column4x4 + num4x4_wide, frame_header_.columns4x4);
const int candidate_row = block.row4x4 - 1;
const int block_start_y = MultiplyBy4(block.row4x4) >> subsampling_y;
int column4x4 = block.column4x4;
const int prediction_height = std::min(height >> 1, 32 >> subsampling_y);
for (int i = 0, step; i < num_limit && column4x4 < column4x4_max;
column4x4 += step) {
const int candidate_column = column4x4 | 1;
const BlockParameters& bp_top =
*block_parameters_holder_.Find(candidate_row, candidate_column);
const int candidate_block_size = bp_top.size;
step = Clip3(kNum4x4BlocksWide[candidate_block_size], 2, 16);
if (bp_top.reference_frame[0] > kReferenceFrameIntra) {
i++;
const int candidate_reference_frame_index =
frame_header_.reference_frame_index[bp_top.reference_frame[0] -
kReferenceFrameLast];
const int prediction_width =
std::min(width, MultiplyBy4(step) >> subsampling_x);
ObmcBlockPrediction(block, bp_top.mv[0], plane,
candidate_reference_frame_index, prediction_width,
prediction_height,
MultiplyBy4(column4x4) >> subsampling_x,
block_start_y, candidate_row, candidate_column,
kObmcDirectionVertical, round_bits);
}
}
}
if (block.left_available) {
const int num_limit = std::min(uint8_t{4}, k4x4HeightLog2[block.size]);
const int row4x4_max =
std::min(block.row4x4 + num4x4_high, frame_header_.rows4x4);
const int candidate_column = block.column4x4 - 1;
int row4x4 = block.row4x4;
const int block_start_x = MultiplyBy4(block.column4x4) >> subsampling_x;
const int prediction_width = std::min(width >> 1, 32 >> subsampling_x);
for (int i = 0, step; i < num_limit && row4x4 < row4x4_max;
row4x4 += step) {
const int candidate_row = row4x4 | 1;
const BlockParameters& bp_left =
*block_parameters_holder_.Find(candidate_row, candidate_column);
const int candidate_block_size = bp_left.size;
step = Clip3(kNum4x4BlocksHigh[candidate_block_size], 2, 16);
if (bp_left.reference_frame[0] > kReferenceFrameIntra) {
i++;
const int candidate_reference_frame_index =
frame_header_.reference_frame_index[bp_left.reference_frame[0] -
kReferenceFrameLast];
const int prediction_height =
std::min(height, MultiplyBy4(step) >> subsampling_y);
ObmcBlockPrediction(
block, bp_left.mv[0], plane, candidate_reference_frame_index,
prediction_width, prediction_height, block_start_x,
MultiplyBy4(row4x4) >> subsampling_y, candidate_row,
candidate_column, kObmcDirectionHorizontal, round_bits);
}
}
}
}
void Tile::DistanceWeightedPrediction(
uint16_t* prediction_0, ptrdiff_t prediction_stride_0,
uint16_t* prediction_1, ptrdiff_t prediction_stride_1, const int width,
const int height, const int candidate_row, const int candidate_column,
uint8_t* dest, ptrdiff_t dest_stride) {
int distance[2];
int weight[2];
for (int reference = 0; reference < 2; ++reference) {
const BlockParameters& bp =
*block_parameters_holder_.Find(candidate_row, candidate_column);
const int reference_hint =
current_frame_.order_hint(bp.reference_frame[reference]);
// Note: distance[0] and distance[1] correspond to relative distance
// between current frame and reference frame [1] and [0], respectively.
distance[1 - reference] = Clip3(
std::abs(GetRelativeDistance(reference_hint, frame_header_.order_hint,
sequence_header_.enable_order_hint,
sequence_header_.order_hint_bits)),
0, kMaxFrameDistance);
}
GetDistanceWeights(distance, weight);
dsp_.distance_weighted_blend(prediction_0, prediction_stride_0, prediction_1,
prediction_stride_1, weight[0], weight[1], width,
height, dest, dest_stride);
}
bool Tile::GetReferenceBlockPosition(
const int reference_frame_index, const bool is_scaled, const int width,
const int height, const int ref_start_x, const int ref_last_x,
const int ref_start_y, const int ref_last_y, const int start_x,
const int start_y, const int step_x, const int step_y,
const int left_border, const int right_border, const int top_border,
const int bottom_border, int* ref_block_start_x, int* ref_block_start_y,
int* ref_block_end_x, int* ref_block_end_y) {
*ref_block_start_x = GetPixelPositionFromHighScale(start_x, 0, 0);
*ref_block_start_y = GetPixelPositionFromHighScale(start_y, 0, 0);
if (reference_frame_index == -1) {
return false;
}
*ref_block_start_x -= kConvolveBorderLeftTop;
*ref_block_start_y -= kConvolveBorderLeftTop;
*ref_block_end_x = GetPixelPositionFromHighScale(start_x, step_x, width - 1) +
kConvolveBorderRightBottom;
*ref_block_end_y =
GetPixelPositionFromHighScale(start_y, step_y, height - 1) +
kConvolveBorderRightBottom;
if (is_scaled) {
const int block_height =
(((height - 1) * step_y + (1 << kScaleSubPixelBits) - 1) >>
kScaleSubPixelBits) +
kSubPixelTaps;
*ref_block_end_y = *ref_block_start_y + block_height - 1;
}
// Determines if we need to extend beyond the left/right/top/bottom border.
return *ref_block_start_x < (ref_start_x - left_border) ||
*ref_block_end_x > (ref_last_x + right_border) ||
*ref_block_start_y < (ref_start_y - top_border) ||
*ref_block_end_y > (ref_last_y + bottom_border);
}
// Builds a block as the input for convolve, by copying the content of
// reference frame (either a decoded reference frame, or current frame).
template <typename Pixel>
void Tile::BuildConvolveBlock(const Plane plane,
const int reference_frame_index,
const bool is_scaled, const int height,
const int ref_start_x, const int ref_last_x,
const int ref_start_y, const int ref_last_y,
const int step_y, const int ref_block_start_x,
const int ref_block_end_x,
const int ref_block_start_y,
uint8_t* block_buffer, ptrdiff_t block_stride) {
const YuvBuffer* const reference_buffer =
(reference_frame_index == -1)
? current_frame_.buffer()
: reference_frames_[reference_frame_index]->buffer();
Array2DView<const Pixel> reference_block(
reference_buffer->height(plane),
reference_buffer->stride(plane) / sizeof(Pixel),
reinterpret_cast<const Pixel*>(reference_buffer->data(plane)));
auto* const block_head = reinterpret_cast<Pixel*>(block_buffer);
block_stride /= sizeof(Pixel);
int block_height =
height + kConvolveBorderLeftTop + kConvolveBorderRightBottom;
if (is_scaled) {
block_height = (((height - 1) * step_y + (1 << kScaleSubPixelBits) - 1) >>
kScaleSubPixelBits) +
kSubPixelTaps;
}
const int copy_start_x =
std::min(std::max(ref_block_start_x, ref_start_x), ref_last_x);
const int copy_end_x =
std::max(std::min(ref_block_end_x, ref_last_x), copy_start_x);
const int copy_start_y =
std::min(std::max(ref_block_start_y, ref_start_y), ref_last_y);
const int block_width = copy_end_x - copy_start_x + 1;
const bool extend_left = ref_block_start_x < ref_start_x;
const bool extend_right = ref_block_end_x > ref_last_x;
const bool out_of_left = copy_start_x > ref_block_end_x;
const bool out_of_right = copy_end_x < ref_block_start_x;
if (out_of_left || out_of_right) {
const int ref_x = out_of_left ? copy_start_x : copy_end_x;
Pixel* buf_ptr = block_head;
for (int y = 0, ref_y = copy_start_y; y < block_height; ++y) {
Memset(buf_ptr, reference_block[ref_y][ref_x], block_stride);
if (ref_block_start_y + y >= ref_start_y &&
ref_block_start_y + y < ref_last_y) {
++ref_y;
}
buf_ptr += block_stride;
}
} else {
Pixel* buf_ptr = block_head;
const int left_width = copy_start_x - ref_block_start_x;
for (int y = 0, ref_y = copy_start_y; y < block_height; ++y) {
if (extend_left) {
Memset(buf_ptr, reference_block[ref_y][copy_start_x], left_width);
}
memcpy(buf_ptr + left_width, &reference_block[ref_y][copy_start_x],
block_width * sizeof(Pixel));
if (extend_right) {
Memset(buf_ptr + left_width + block_width,
reference_block[ref_y][copy_end_x],
block_stride - left_width - block_width);
}
if (ref_block_start_y + y >= ref_start_y &&
ref_block_start_y + y < ref_last_y) {
++ref_y;
}
buf_ptr += block_stride;
}
}
}
void Tile::BlockInterPrediction(
const Block& block, const Plane plane, const int reference_frame_index,
const MotionVector& mv, const int x, const int y, const int width,
const int height, const int candidate_row, const int candidate_column,
uint16_t* const prediction, const ptrdiff_t prediction_stride,
const int round_bits, const bool is_compound, const bool is_inter_intra,
uint8_t* const dest, const ptrdiff_t dest_stride) {
const BlockParameters& bp =
*block_parameters_holder_.Find(candidate_row, candidate_column);
int start_x;
int start_y;
int step_x;
int step_y;
ScaleMotionVector(mv, plane, reference_frame_index, x, y, &start_x, &start_y,
&step_x, &step_y);
const int horizontal_filter_index = bp.interpolation_filter[1];
const int vertical_filter_index = bp.interpolation_filter[0];
const int subsampling_x = subsampling_x_[plane];
const int subsampling_y = subsampling_y_[plane];
// reference_frame_index equal to -1 indicates using current frame as
// reference.
const YuvBuffer* const reference_buffer =
(reference_frame_index == -1)
? current_frame_.buffer()
: reference_frames_[reference_frame_index]->buffer();
const int reference_upscaled_width =
(reference_frame_index == -1)
? MultiplyBy4(frame_header_.columns4x4)
: reference_frames_[reference_frame_index]->upscaled_width();
const int reference_height =
(reference_frame_index == -1)
? MultiplyBy4(frame_header_.rows4x4)
: reference_frames_[reference_frame_index]->frame_height();
const int ref_start_x = 0;
const int ref_last_x =
((reference_upscaled_width + subsampling_x) >> subsampling_x) - 1;
const int ref_start_y = 0;
const int ref_last_y =
((reference_height + subsampling_y) >> subsampling_y) - 1;
const bool is_scaled = (reference_frame_index != -1) &&
(frame_header_.width != reference_upscaled_width ||
frame_header_.height != reference_height);
const int bitdepth = sequence_header_.color_config.bitdepth;
const size_t pixel_size =
(bitdepth == 8) ? sizeof(uint8_t) : sizeof(uint16_t);
int ref_block_start_x;
int ref_block_start_y;
int ref_block_end_x;
int ref_block_end_y;
bool extend_block = GetReferenceBlockPosition(
reference_frame_index, is_scaled, width, height, ref_start_x, ref_last_x,
ref_start_y, ref_last_y, start_x, start_y, step_x, step_y,
reference_buffer->left_border(plane),
reference_buffer->right_border(plane),
reference_buffer->top_border(plane),
reference_buffer->bottom_border(plane), &ref_block_start_x,
&ref_block_start_y, &ref_block_end_x, &ref_block_end_y);
const uint8_t* block_start = nullptr;
ptrdiff_t block_stride;
if (!extend_block) {
const YuvBuffer* const reference_buffer =
(reference_frame_index == -1)
? current_frame_.buffer()
: reference_frames_[reference_frame_index]->buffer();
block_stride = reference_buffer->stride(plane);
if (reference_frame_index == -1 || is_scaled) {
block_start = reference_buffer->data(plane) +
ref_block_start_y * reference_buffer->stride(plane) +
ref_block_start_x * pixel_size;
} else {
block_start = reference_buffer->data(plane) +
(ref_block_start_y + kConvolveBorderLeftTop) *
reference_buffer->stride(plane) +
(ref_block_start_x + kConvolveBorderLeftTop) * pixel_size;
}
} else {
// The reference block width can be at most 2 times as much as current
// block's width because of scaling.
block_stride =
(2 * width + kConvolveBorderLeftTop + kConvolveBorderRightBottom) *
pixel_size;
if (bitdepth == 8) {
BuildConvolveBlock<uint8_t>(
plane, reference_frame_index, is_scaled, height, ref_start_x,
ref_last_x, ref_start_y, ref_last_y, step_y, ref_block_start_x,
ref_block_end_x, ref_block_start_y,
block.scratch_buffer->convolve_block_buffer, block_stride);
#if LIBGAV1_MAX_BITDEPTH >= 10
} else {
BuildConvolveBlock<uint16_t>(
plane, reference_frame_index, is_scaled, height, ref_start_x,
ref_last_x, ref_start_y, ref_last_y, step_y, ref_block_start_x,
ref_block_end_x, ref_block_start_y,
block.scratch_buffer->convolve_block_buffer, block_stride);
#endif
}
block_start = block.scratch_buffer->convolve_block_buffer +
(is_scaled ? 0
: kConvolveBorderLeftTop * block_stride +
kConvolveBorderLeftTop * pixel_size);
}
const int has_horizontal_filter = static_cast<int>(
((mv.mv[MotionVector::kColumn] * (1 << (1 - subsampling_x))) & 15) != 0);
const int has_vertical_filter = static_cast<int>(
((mv.mv[MotionVector::kRow] * (1 << (1 - subsampling_y))) & 15) != 0);
void* const output =
(is_compound || is_inter_intra) ? prediction : static_cast<void*>(dest);
const ptrdiff_t output_stride =
(is_compound || is_inter_intra) ? prediction_stride : dest_stride;
assert(output != nullptr);
dsp::ConvolveFunc convolve_func =
is_scaled ? dsp_.convolve_scale[is_compound || is_inter_intra]
: dsp_.convolve[reference_frame_index == -1][is_compound]
[has_vertical_filter][has_horizontal_filter];
assert(convolve_func != nullptr);
// TODO(b/127805357): Refactor is_inter_intra into single prediction.
if (is_inter_intra && !is_scaled) {
convolve_func = dsp_.convolve[0][1][1][1];
}
convolve_func(block_start, block_stride, horizontal_filter_index,
vertical_filter_index, round_bits, start_x, start_y, step_x,
step_y, width, height, output, output_stride);
}
void Tile::BlockWarpProcess(const Block& block, const Plane plane,
const int index, const int block_start_x,
const int block_start_y, const int width,
const int height, const ptrdiff_t prediction_stride,
GlobalMotion* const warp_params,
const int round_bits, const bool is_compound,
const bool is_inter_intra, uint8_t* const dest,
const ptrdiff_t dest_stride) {
assert(width >= 8 && height >= 8);
const BlockParameters& bp = *block.bp;
const int reference_frame_index =
frame_header_.reference_frame_index[bp.reference_frame[index] -
kReferenceFrameLast];
const uint8_t* const source =
reference_frames_[reference_frame_index]->buffer()->data(plane);
ptrdiff_t source_stride =
reference_frames_[reference_frame_index]->buffer()->stride(plane);
const int source_width =
reference_frames_[reference_frame_index]->buffer()->displayed_width(
plane);
const int source_height =
reference_frames_[reference_frame_index]->buffer()->displayed_height(
plane);
uint16_t* const prediction = block.scratch_buffer->prediction_buffer[index];
dsp_.warp(source, source_stride, source_width, source_height,
warp_params->params, subsampling_x_[plane], subsampling_y_[plane],
round_bits, block_start_x, block_start_y, width, height,
warp_params->alpha, warp_params->beta, warp_params->gamma,
warp_params->delta, prediction, prediction_stride);
if (!is_compound && !is_inter_intra) {
const int bitdepth = sequence_header_.color_config.bitdepth;
if (bitdepth == 8) {
ClipPrediction<8, uint8_t>(prediction, prediction_stride, width, height,
dest, dest_stride);
#if LIBGAV1_MAX_BITDEPTH >= 10
} else {
ClipPrediction<10, uint16_t>(prediction, prediction_stride, width, height,
dest, dest_stride);
#endif
}
}
}
} // namespace libgav1