blob: 2612466f0095726db94d2825a4a3ea43e29d171a [file] [log] [blame]
// Copyright 2019 The libgav1 Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/dsp/film_grain.h"
#include "src/utils/cpu.h"
#if LIBGAV1_ENABLE_NEON
#include <arm_neon.h>
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <new>
#include "src/dsp/arm/common_neon.h"
#include "src/dsp/arm/film_grain_neon.h"
#include "src/dsp/common.h"
#include "src/dsp/constants.h"
#include "src/dsp/dsp.h"
#include "src/dsp/film_grain_common.h"
#include "src/utils/common.h"
#include "src/utils/compiler_attributes.h"
#include "src/utils/logging.h"
namespace libgav1 {
namespace dsp {
namespace film_grain {
namespace {
// These functions are overloaded for both possible sizes in order to simplify
// loading and storing to and from intermediate value types from within a
// template function.
inline int16x8_t GetSignedSource8(const int8_t* src) {
return vmovl_s8(vld1_s8(src));
}
inline int16x8_t GetSignedSource8(const uint8_t* src) {
return ZeroExtend(vld1_u8(src));
}
inline void StoreUnsigned8(uint8_t* dest, const uint16x8_t data) {
vst1_u8(dest, vmovn_u16(data));
}
#if LIBGAV1_MAX_BITDEPTH >= 10
inline int16x8_t GetSignedSource8(const int16_t* src) { return vld1q_s16(src); }
inline int16x8_t GetSignedSource8(const uint16_t* src) {
return vreinterpretq_s16_u16(vld1q_u16(src));
}
inline void StoreUnsigned8(uint16_t* dest, const uint16x8_t data) {
vst1q_u16(dest, data);
}
#endif // LIBGAV1_MAX_BITDEPTH >= 10
// Each element in |sum| represents one destination value's running
// autoregression formula. The fixed source values in |grain_lo| and |grain_hi|
// allow for a sliding window in successive calls to this function.
template <int position_offset>
inline int32x4x2_t AccumulateWeightedGrain(const int16x8_t grain_lo,
const int16x8_t grain_hi,
int16_t coeff, int32x4x2_t sum) {
const int16x8_t grain = vextq_s16(grain_lo, grain_hi, position_offset);
sum.val[0] = vmlal_n_s16(sum.val[0], vget_low_s16(grain), coeff);
sum.val[1] = vmlal_n_s16(sum.val[1], vget_high_s16(grain), coeff);
return sum;
}
// Because the autoregressive filter requires the output of each pixel to
// compute pixels that come after in the row, we have to finish the calculations
// one at a time.
template <int bitdepth, int auto_regression_coeff_lag, int lane>
inline void WriteFinalAutoRegression(int8_t* grain_cursor, int32x4x2_t sum,
const int8_t* coeffs, int pos, int shift) {
int32_t result = vgetq_lane_s32(sum.val[lane >> 2], lane & 3);
for (int delta_col = -auto_regression_coeff_lag; delta_col < 0; ++delta_col) {
result += grain_cursor[lane + delta_col] * coeffs[pos];
++pos;
}
grain_cursor[lane] =
Clip3(grain_cursor[lane] + RightShiftWithRounding(result, shift),
GetGrainMin<bitdepth>(), GetGrainMax<bitdepth>());
}
#if LIBGAV1_MAX_BITDEPTH >= 10
template <int bitdepth, int auto_regression_coeff_lag, int lane>
inline void WriteFinalAutoRegression(int16_t* grain_cursor, int32x4x2_t sum,
const int8_t* coeffs, int pos, int shift) {
int32_t result = vgetq_lane_s32(sum.val[lane >> 2], lane & 3);
for (int delta_col = -auto_regression_coeff_lag; delta_col < 0; ++delta_col) {
result += grain_cursor[lane + delta_col] * coeffs[pos];
++pos;
}
grain_cursor[lane] =
Clip3(grain_cursor[lane] + RightShiftWithRounding(result, shift),
GetGrainMin<bitdepth>(), GetGrainMax<bitdepth>());
}
#endif // LIBGAV1_MAX_BITDEPTH >= 10
// Because the autoregressive filter requires the output of each pixel to
// compute pixels that come after in the row, we have to finish the calculations
// one at a time.
template <int bitdepth, int auto_regression_coeff_lag, int lane>
inline void WriteFinalAutoRegressionChroma(int8_t* u_grain_cursor,
int8_t* v_grain_cursor,
int32x4x2_t sum_u, int32x4x2_t sum_v,
const int8_t* coeffs_u,
const int8_t* coeffs_v, int pos,
int shift) {
WriteFinalAutoRegression<bitdepth, auto_regression_coeff_lag, lane>(
u_grain_cursor, sum_u, coeffs_u, pos, shift);
WriteFinalAutoRegression<bitdepth, auto_regression_coeff_lag, lane>(
v_grain_cursor, sum_v, coeffs_v, pos, shift);
}
#if LIBGAV1_MAX_BITDEPTH >= 10
template <int bitdepth, int auto_regression_coeff_lag, int lane>
inline void WriteFinalAutoRegressionChroma(int16_t* u_grain_cursor,
int16_t* v_grain_cursor,
int32x4x2_t sum_u, int32x4x2_t sum_v,
const int8_t* coeffs_u,
const int8_t* coeffs_v, int pos,
int shift) {
WriteFinalAutoRegression<bitdepth, auto_regression_coeff_lag, lane>(
u_grain_cursor, sum_u, coeffs_u, pos, shift);
WriteFinalAutoRegression<bitdepth, auto_regression_coeff_lag, lane>(
v_grain_cursor, sum_v, coeffs_v, pos, shift);
}
#endif // LIBGAV1_MAX_BITDEPTH >= 10
inline void SetZero(int32x4x2_t* v) {
v->val[0] = vdupq_n_s32(0);
v->val[1] = vdupq_n_s32(0);
}
// Computes subsampled luma for use with chroma, by averaging in the x direction
// or y direction when applicable.
int16x8_t GetSubsampledLuma(const int8_t* const luma, int subsampling_x,
int subsampling_y, ptrdiff_t stride) {
if (subsampling_y != 0) {
assert(subsampling_x != 0);
const int8x16_t src0 = vld1q_s8(luma);
const int8x16_t src1 = vld1q_s8(luma + stride);
const int16x8_t ret0 = vcombine_s16(vpaddl_s8(vget_low_s8(src0)),
vpaddl_s8(vget_high_s8(src0)));
const int16x8_t ret1 = vcombine_s16(vpaddl_s8(vget_low_s8(src1)),
vpaddl_s8(vget_high_s8(src1)));
return vrshrq_n_s16(vaddq_s16(ret0, ret1), 2);
}
if (subsampling_x != 0) {
const int8x16_t src = vld1q_s8(luma);
return vrshrq_n_s16(
vcombine_s16(vpaddl_s8(vget_low_s8(src)), vpaddl_s8(vget_high_s8(src))),
1);
}
return vmovl_s8(vld1_s8(luma));
}
// For BlendNoiseWithImageChromaWithCfl, only |subsampling_x| is needed.
inline uint16x8_t GetAverageLuma(const uint8_t* const luma, int subsampling_x) {
if (subsampling_x != 0) {
const uint8x16_t src = vld1q_u8(luma);
return vrshrq_n_u16(vpaddlq_u8(src), 1);
}
return vmovl_u8(vld1_u8(luma));
}
#if LIBGAV1_MAX_BITDEPTH >= 10
// Computes subsampled luma for use with chroma, by averaging in the x direction
// or y direction when applicable.
int16x8_t GetSubsampledLuma(const int16_t* const luma, int subsampling_x,
int subsampling_y, ptrdiff_t stride) {
if (subsampling_y != 0) {
assert(subsampling_x != 0);
int16x8_t src0_lo = vld1q_s16(luma);
int16x8_t src0_hi = vld1q_s16(luma + 8);
const int16x8_t src1_lo = vld1q_s16(luma + stride);
const int16x8_t src1_hi = vld1q_s16(luma + stride + 8);
const int16x8_t src0 =
vcombine_s16(vpadd_s16(vget_low_s16(src0_lo), vget_high_s16(src0_lo)),
vpadd_s16(vget_low_s16(src0_hi), vget_high_s16(src0_hi)));
const int16x8_t src1 =
vcombine_s16(vpadd_s16(vget_low_s16(src1_lo), vget_high_s16(src1_lo)),
vpadd_s16(vget_low_s16(src1_hi), vget_high_s16(src1_hi)));
return vrshrq_n_s16(vaddq_s16(src0, src1), 2);
}
if (subsampling_x != 0) {
const int16x8_t src_lo = vld1q_s16(luma);
const int16x8_t src_hi = vld1q_s16(luma + 8);
const int16x8_t ret =
vcombine_s16(vpadd_s16(vget_low_s16(src_lo), vget_high_s16(src_lo)),
vpadd_s16(vget_low_s16(src_hi), vget_high_s16(src_hi)));
return vrshrq_n_s16(ret, 1);
}
return vld1q_s16(luma);
}
// For BlendNoiseWithImageChromaWithCfl, only |subsampling_x| is needed.
inline uint16x8_t GetAverageLuma(const uint16_t* const luma,
int subsampling_x) {
if (subsampling_x != 0) {
const uint16x8x2_t src = vld2q_u16(luma);
return vrhaddq_u16(src.val[0], src.val[1]);
}
return vld1q_u16(luma);
}
#endif // LIBGAV1_MAX_BITDEPTH >= 10
template <int bitdepth, typename GrainType, int auto_regression_coeff_lag,
bool use_luma>
void ApplyAutoRegressiveFilterToChromaGrains_NEON(const FilmGrainParams& params,
const void* luma_grain_buffer,
int subsampling_x,
int subsampling_y,
void* u_grain_buffer,
void* v_grain_buffer) {
static_assert(auto_regression_coeff_lag <= 3, "Invalid autoregression lag.");
const auto* luma_grain = static_cast<const GrainType*>(luma_grain_buffer);
auto* u_grain = static_cast<GrainType*>(u_grain_buffer);
auto* v_grain = static_cast<GrainType*>(v_grain_buffer);
const int auto_regression_shift = params.auto_regression_shift;
const int chroma_width =
(subsampling_x == 0) ? kMaxChromaWidth : kMinChromaWidth;
const int chroma_height =
(subsampling_y == 0) ? kMaxChromaHeight : kMinChromaHeight;
// When |chroma_width| == 44, we write 8 at a time from x in [3, 34],
// leaving [35, 40] to write at the end.
const int chroma_width_remainder =
(chroma_width - 2 * kAutoRegressionBorder) & 7;
int y = kAutoRegressionBorder;
luma_grain += kLumaWidth * y;
u_grain += chroma_width * y;
v_grain += chroma_width * y;
do {
// Each row is computed 8 values at a time in the following loop. At the
// end of the loop, 4 values remain to write. They are given a special
// reduced iteration at the end.
int x = kAutoRegressionBorder;
int luma_x = kAutoRegressionBorder;
do {
int pos = 0;
int32x4x2_t sum_u;
int32x4x2_t sum_v;
SetZero(&sum_u);
SetZero(&sum_v);
if (auto_regression_coeff_lag > 0) {
for (int delta_row = -auto_regression_coeff_lag; delta_row < 0;
++delta_row) {
// These loads may overflow to the next row, but they are never called
// on the final row of a grain block. Therefore, they will never
// exceed the block boundaries.
// Note: this could be slightly optimized to a single load in 8bpp,
// but requires making a special first iteration and accumulate
// function that takes an int8x16_t.
const int16x8_t u_grain_lo =
GetSignedSource8(u_grain + x + delta_row * chroma_width -
auto_regression_coeff_lag);
const int16x8_t u_grain_hi =
GetSignedSource8(u_grain + x + delta_row * chroma_width -
auto_regression_coeff_lag + 8);
const int16x8_t v_grain_lo =
GetSignedSource8(v_grain + x + delta_row * chroma_width -
auto_regression_coeff_lag);
const int16x8_t v_grain_hi =
GetSignedSource8(v_grain + x + delta_row * chroma_width -
auto_regression_coeff_lag + 8);
#define ACCUMULATE_WEIGHTED_GRAIN(offset) \
sum_u = AccumulateWeightedGrain<offset>( \
u_grain_lo, u_grain_hi, params.auto_regression_coeff_u[pos], sum_u); \
sum_v = AccumulateWeightedGrain<offset>( \
v_grain_lo, v_grain_hi, params.auto_regression_coeff_v[pos++], sum_v)
ACCUMULATE_WEIGHTED_GRAIN(0);
ACCUMULATE_WEIGHTED_GRAIN(1);
ACCUMULATE_WEIGHTED_GRAIN(2);
// The horizontal |auto_regression_coeff_lag| loop is replaced with
// if-statements to give vextq_s16 an immediate param.
if (auto_regression_coeff_lag > 1) {
ACCUMULATE_WEIGHTED_GRAIN(3);
ACCUMULATE_WEIGHTED_GRAIN(4);
}
if (auto_regression_coeff_lag > 2) {
assert(auto_regression_coeff_lag == 3);
ACCUMULATE_WEIGHTED_GRAIN(5);
ACCUMULATE_WEIGHTED_GRAIN(6);
}
}
}
if (use_luma) {
const int16x8_t luma = GetSubsampledLuma(
luma_grain + luma_x, subsampling_x, subsampling_y, kLumaWidth);
// Luma samples get the final coefficient in the formula, but are best
// computed all at once before the final row.
const int coeff_u =
params.auto_regression_coeff_u[pos + auto_regression_coeff_lag];
const int coeff_v =
params.auto_regression_coeff_v[pos + auto_regression_coeff_lag];
sum_u.val[0] = vmlal_n_s16(sum_u.val[0], vget_low_s16(luma), coeff_u);
sum_u.val[1] = vmlal_n_s16(sum_u.val[1], vget_high_s16(luma), coeff_u);
sum_v.val[0] = vmlal_n_s16(sum_v.val[0], vget_low_s16(luma), coeff_v);
sum_v.val[1] = vmlal_n_s16(sum_v.val[1], vget_high_s16(luma), coeff_v);
}
// At this point in the filter, the source addresses and destination
// addresses overlap. Because this is an auto-regressive filter, the
// higher lanes cannot be computed without the results of the lower lanes.
// Each call to WriteFinalAutoRegression incorporates preceding values
// on the final row, and writes a single sample. This allows the next
// pixel's value to be computed in the next call.
#define WRITE_AUTO_REGRESSION_RESULT(lane) \
WriteFinalAutoRegressionChroma<bitdepth, auto_regression_coeff_lag, lane>( \
u_grain + x, v_grain + x, sum_u, sum_v, params.auto_regression_coeff_u, \
params.auto_regression_coeff_v, pos, auto_regression_shift)
WRITE_AUTO_REGRESSION_RESULT(0);
WRITE_AUTO_REGRESSION_RESULT(1);
WRITE_AUTO_REGRESSION_RESULT(2);
WRITE_AUTO_REGRESSION_RESULT(3);
WRITE_AUTO_REGRESSION_RESULT(4);
WRITE_AUTO_REGRESSION_RESULT(5);
WRITE_AUTO_REGRESSION_RESULT(6);
WRITE_AUTO_REGRESSION_RESULT(7);
x += 8;
luma_x += 8 << subsampling_x;
} while (x < chroma_width - kAutoRegressionBorder - chroma_width_remainder);
// This is the "final iteration" of the above loop over width. We fill in
// the remainder of the width, which is less than 8.
int pos = 0;
int32x4x2_t sum_u;
int32x4x2_t sum_v;
SetZero(&sum_u);
SetZero(&sum_v);
for (int delta_row = -auto_regression_coeff_lag; delta_row < 0;
++delta_row) {
// These loads may overflow to the next row, but they are never called on
// the final row of a grain block. Therefore, they will never exceed the
// block boundaries.
const int16x8_t u_grain_lo = GetSignedSource8(
u_grain + x + delta_row * chroma_width - auto_regression_coeff_lag);
const int16x8_t u_grain_hi =
GetSignedSource8(u_grain + x + delta_row * chroma_width -
auto_regression_coeff_lag + 8);
const int16x8_t v_grain_lo = GetSignedSource8(
v_grain + x + delta_row * chroma_width - auto_regression_coeff_lag);
const int16x8_t v_grain_hi =
GetSignedSource8(v_grain + x + delta_row * chroma_width -
auto_regression_coeff_lag + 8);
ACCUMULATE_WEIGHTED_GRAIN(0);
ACCUMULATE_WEIGHTED_GRAIN(1);
ACCUMULATE_WEIGHTED_GRAIN(2);
// The horizontal |auto_regression_coeff_lag| loop is replaced with
// if-statements to give vextq_s16 an immediate param.
if (auto_regression_coeff_lag > 1) {
ACCUMULATE_WEIGHTED_GRAIN(3);
ACCUMULATE_WEIGHTED_GRAIN(4);
}
if (auto_regression_coeff_lag > 2) {
assert(auto_regression_coeff_lag == 3);
ACCUMULATE_WEIGHTED_GRAIN(5);
ACCUMULATE_WEIGHTED_GRAIN(6);
}
}
if (use_luma) {
const int16x8_t luma = GetSubsampledLuma(
luma_grain + luma_x, subsampling_x, subsampling_y, kLumaWidth);
// Luma samples get the final coefficient in the formula, but are best
// computed all at once before the final row.
const int coeff_u =
params.auto_regression_coeff_u[pos + auto_regression_coeff_lag];
const int coeff_v =
params.auto_regression_coeff_v[pos + auto_regression_coeff_lag];
sum_u.val[0] = vmlal_n_s16(sum_u.val[0], vget_low_s16(luma), coeff_u);
sum_u.val[1] = vmlal_n_s16(sum_u.val[1], vget_high_s16(luma), coeff_u);
sum_v.val[0] = vmlal_n_s16(sum_v.val[0], vget_low_s16(luma), coeff_v);
sum_v.val[1] = vmlal_n_s16(sum_v.val[1], vget_high_s16(luma), coeff_v);
}
WRITE_AUTO_REGRESSION_RESULT(0);
WRITE_AUTO_REGRESSION_RESULT(1);
WRITE_AUTO_REGRESSION_RESULT(2);
WRITE_AUTO_REGRESSION_RESULT(3);
if (chroma_width_remainder == 6) {
WRITE_AUTO_REGRESSION_RESULT(4);
WRITE_AUTO_REGRESSION_RESULT(5);
}
luma_grain += kLumaWidth << subsampling_y;
u_grain += chroma_width;
v_grain += chroma_width;
} while (++y < chroma_height);
#undef ACCUMULATE_WEIGHTED_GRAIN
#undef WRITE_AUTO_REGRESSION_RESULT
}
// Applies an auto-regressive filter to the white noise in luma_grain.
template <int bitdepth, typename GrainType, int auto_regression_coeff_lag>
void ApplyAutoRegressiveFilterToLumaGrain_NEON(const FilmGrainParams& params,
void* luma_grain_buffer) {
static_assert(auto_regression_coeff_lag > 0, "");
const int8_t* const auto_regression_coeff_y = params.auto_regression_coeff_y;
const uint8_t auto_regression_shift = params.auto_regression_shift;
int y = kAutoRegressionBorder;
auto* luma_grain =
static_cast<GrainType*>(luma_grain_buffer) + kLumaWidth * y;
do {
// Each row is computed 8 values at a time in the following loop. At the
// end of the loop, 4 values remain to write. They are given a special
// reduced iteration at the end.
int x = kAutoRegressionBorder;
do {
int pos = 0;
int32x4x2_t sum;
SetZero(&sum);
for (int delta_row = -auto_regression_coeff_lag; delta_row < 0;
++delta_row) {
// These loads may overflow to the next row, but they are never called
// on the final row of a grain block. Therefore, they will never exceed
// the block boundaries.
const int16x8_t src_grain_lo =
GetSignedSource8(luma_grain + x + delta_row * kLumaWidth -
auto_regression_coeff_lag);
const int16x8_t src_grain_hi =
GetSignedSource8(luma_grain + x + delta_row * kLumaWidth -
auto_regression_coeff_lag + 8);
// A pictorial representation of the auto-regressive filter for
// various values of params.auto_regression_coeff_lag. The letter 'O'
// represents the current sample. (The filter always operates on the
// current sample with filter coefficient 1.) The letters 'X'
// represent the neighboring samples that the filter operates on, below
// their corresponding "offset" number.
//
// params.auto_regression_coeff_lag == 3:
// 0 1 2 3 4 5 6
// X X X X X X X
// X X X X X X X
// X X X X X X X
// X X X O
// params.auto_regression_coeff_lag == 2:
// 0 1 2 3 4
// X X X X X
// X X X X X
// X X O
// params.auto_regression_coeff_lag == 1:
// 0 1 2
// X X X
// X O
// params.auto_regression_coeff_lag == 0:
// O
// The function relies on the caller to skip the call in the 0 lag
// case.
#define ACCUMULATE_WEIGHTED_GRAIN(offset) \
sum = AccumulateWeightedGrain<offset>(src_grain_lo, src_grain_hi, \
auto_regression_coeff_y[pos++], sum)
ACCUMULATE_WEIGHTED_GRAIN(0);
ACCUMULATE_WEIGHTED_GRAIN(1);
ACCUMULATE_WEIGHTED_GRAIN(2);
// The horizontal |auto_regression_coeff_lag| loop is replaced with
// if-statements to give vextq_s16 an immediate param.
if (auto_regression_coeff_lag > 1) {
ACCUMULATE_WEIGHTED_GRAIN(3);
ACCUMULATE_WEIGHTED_GRAIN(4);
}
if (auto_regression_coeff_lag > 2) {
assert(auto_regression_coeff_lag == 3);
ACCUMULATE_WEIGHTED_GRAIN(5);
ACCUMULATE_WEIGHTED_GRAIN(6);
}
}
// At this point in the filter, the source addresses and destination
// addresses overlap. Because this is an auto-regressive filter, the
// higher lanes cannot be computed without the results of the lower lanes.
// Each call to WriteFinalAutoRegression incorporates preceding values
// on the final row, and writes a single sample. This allows the next
// pixel's value to be computed in the next call.
#define WRITE_AUTO_REGRESSION_RESULT(lane) \
WriteFinalAutoRegression<bitdepth, auto_regression_coeff_lag, lane>( \
luma_grain + x, sum, auto_regression_coeff_y, pos, \
auto_regression_shift)
WRITE_AUTO_REGRESSION_RESULT(0);
WRITE_AUTO_REGRESSION_RESULT(1);
WRITE_AUTO_REGRESSION_RESULT(2);
WRITE_AUTO_REGRESSION_RESULT(3);
WRITE_AUTO_REGRESSION_RESULT(4);
WRITE_AUTO_REGRESSION_RESULT(5);
WRITE_AUTO_REGRESSION_RESULT(6);
WRITE_AUTO_REGRESSION_RESULT(7);
x += 8;
// Leave the final four pixels for the special iteration below.
} while (x < kLumaWidth - kAutoRegressionBorder - 4);
// Final 4 pixels in the row.
int pos = 0;
int32x4x2_t sum;
SetZero(&sum);
for (int delta_row = -auto_regression_coeff_lag; delta_row < 0;
++delta_row) {
const int16x8_t src_grain_lo = GetSignedSource8(
luma_grain + x + delta_row * kLumaWidth - auto_regression_coeff_lag);
const int16x8_t src_grain_hi =
GetSignedSource8(luma_grain + x + delta_row * kLumaWidth -
auto_regression_coeff_lag + 8);
ACCUMULATE_WEIGHTED_GRAIN(0);
ACCUMULATE_WEIGHTED_GRAIN(1);
ACCUMULATE_WEIGHTED_GRAIN(2);
// The horizontal |auto_regression_coeff_lag| loop is replaced with
// if-statements to give vextq_s16 an immediate param.
if (auto_regression_coeff_lag > 1) {
ACCUMULATE_WEIGHTED_GRAIN(3);
ACCUMULATE_WEIGHTED_GRAIN(4);
}
if (auto_regression_coeff_lag > 2) {
assert(auto_regression_coeff_lag == 3);
ACCUMULATE_WEIGHTED_GRAIN(5);
ACCUMULATE_WEIGHTED_GRAIN(6);
}
}
// delta_row == 0
WRITE_AUTO_REGRESSION_RESULT(0);
WRITE_AUTO_REGRESSION_RESULT(1);
WRITE_AUTO_REGRESSION_RESULT(2);
WRITE_AUTO_REGRESSION_RESULT(3);
luma_grain += kLumaWidth;
} while (++y < kLumaHeight);
#undef WRITE_AUTO_REGRESSION_RESULT
#undef ACCUMULATE_WEIGHTED_GRAIN
}
void InitializeScalingLookupTable_NEON(
int num_points, const uint8_t point_value[], const uint8_t point_scaling[],
uint8_t scaling_lut[kScalingLookupTableSize]) {
if (num_points == 0) {
memset(scaling_lut, 0, sizeof(scaling_lut[0]) * kScalingLookupTableSize);
return;
}
static_assert(sizeof(scaling_lut[0]) == 1, "");
memset(scaling_lut, point_scaling[0], point_value[0]);
const uint32x4_t steps = vmovl_u16(vcreate_u16(0x0003000200010000));
const uint32x4_t offset = vdupq_n_u32(32768);
for (int i = 0; i < num_points - 1; ++i) {
const int delta_y = point_scaling[i + 1] - point_scaling[i];
const int delta_x = point_value[i + 1] - point_value[i];
const int delta = delta_y * ((65536 + (delta_x >> 1)) / delta_x);
const int delta4 = delta << 2;
const uint8x8_t base_point = vdup_n_u8(point_scaling[i]);
uint32x4_t upscaled_points0 = vmlaq_n_u32(offset, steps, delta);
const uint32x4_t line_increment4 = vdupq_n_u32(delta4);
// Get the second set of 4 points by adding 4 steps to the first set.
uint32x4_t upscaled_points1 = vaddq_u32(upscaled_points0, line_increment4);
// We obtain the next set of 8 points by adding 8 steps to each of the
// current 8 points.
const uint32x4_t line_increment8 = vshlq_n_u32(line_increment4, 1);
int x = 0;
do {
const uint16x4_t interp_points0 = vshrn_n_u32(upscaled_points0, 16);
const uint16x4_t interp_points1 = vshrn_n_u32(upscaled_points1, 16);
const uint8x8_t interp_points =
vmovn_u16(vcombine_u16(interp_points0, interp_points1));
// The spec guarantees that the max value of |point_value[i]| + x is 255.
// Writing 8 bytes starting at the final table byte, leaves 7 bytes of
// required padding.
vst1_u8(&scaling_lut[point_value[i] + x],
vadd_u8(interp_points, base_point));
upscaled_points0 = vaddq_u32(upscaled_points0, line_increment8);
upscaled_points1 = vaddq_u32(upscaled_points1, line_increment8);
x += 8;
} while (x < delta_x);
}
const uint8_t last_point_value = point_value[num_points - 1];
memset(&scaling_lut[last_point_value], point_scaling[num_points - 1],
kScalingLookupTableSize - last_point_value);
}
inline int16x8_t Clip3(const int16x8_t value, const int16x8_t low,
const int16x8_t high) {
const int16x8_t clipped_to_ceiling = vminq_s16(high, value);
return vmaxq_s16(low, clipped_to_ceiling);
}
template <int bitdepth, typename Pixel>
inline int16x8_t GetScalingFactors(
const uint8_t scaling_lut[kScalingLookupTableSize], const Pixel* source) {
int16_t start_vals[8];
if (bitdepth == 8) {
start_vals[0] = scaling_lut[source[0]];
start_vals[1] = scaling_lut[source[1]];
start_vals[2] = scaling_lut[source[2]];
start_vals[3] = scaling_lut[source[3]];
start_vals[4] = scaling_lut[source[4]];
start_vals[5] = scaling_lut[source[5]];
start_vals[6] = scaling_lut[source[6]];
start_vals[7] = scaling_lut[source[7]];
return vld1q_s16(start_vals);
}
int16_t end_vals[8];
// TODO(petersonab): Precompute this into a larger table for direct lookups.
int index = source[0] >> 2;
start_vals[0] = scaling_lut[index];
end_vals[0] = scaling_lut[index + 1];
index = source[1] >> 2;
start_vals[1] = scaling_lut[index];
end_vals[1] = scaling_lut[index + 1];
index = source[2] >> 2;
start_vals[2] = scaling_lut[index];
end_vals[2] = scaling_lut[index + 1];
index = source[3] >> 2;
start_vals[3] = scaling_lut[index];
end_vals[3] = scaling_lut[index + 1];
index = source[4] >> 2;
start_vals[4] = scaling_lut[index];
end_vals[4] = scaling_lut[index + 1];
index = source[5] >> 2;
start_vals[5] = scaling_lut[index];
end_vals[5] = scaling_lut[index + 1];
index = source[6] >> 2;
start_vals[6] = scaling_lut[index];
end_vals[6] = scaling_lut[index + 1];
index = source[7] >> 2;
start_vals[7] = scaling_lut[index];
end_vals[7] = scaling_lut[index + 1];
const int16x8_t start = vld1q_s16(start_vals);
const int16x8_t end = vld1q_s16(end_vals);
int16x8_t remainder = GetSignedSource8(source);
remainder = vandq_s16(remainder, vdupq_n_s16(3));
const int16x8_t delta = vmulq_s16(vsubq_s16(end, start), remainder);
return vaddq_s16(start, vrshrq_n_s16(delta, 2));
}
inline int16x8_t ScaleNoise(const int16x8_t noise, const int16x8_t scaling,
const int16x8_t scaling_shift_vect) {
const int16x8_t upscaled_noise = vmulq_s16(noise, scaling);
return vrshlq_s16(upscaled_noise, scaling_shift_vect);
}
#if LIBGAV1_MAX_BITDEPTH >= 10
inline int16x8_t ScaleNoise(const int16x8_t noise, const int16x8_t scaling,
const int32x4_t scaling_shift_vect) {
// TODO(petersonab): Try refactoring scaling lookup table to int16_t and
// upscaling by 7 bits to permit high half multiply. This would eliminate
// the intermediate 32x4 registers. Also write the averaged values directly
// into the table so it doesn't have to be done for every pixel in
// the frame.
const int32x4_t upscaled_noise_lo =
vmull_s16(vget_low_s16(noise), vget_low_s16(scaling));
const int32x4_t upscaled_noise_hi =
vmull_s16(vget_high_s16(noise), vget_high_s16(scaling));
const int16x4_t noise_lo =
vmovn_s32(vrshlq_s32(upscaled_noise_lo, scaling_shift_vect));
const int16x4_t noise_hi =
vmovn_s32(vrshlq_s32(upscaled_noise_hi, scaling_shift_vect));
return vcombine_s16(noise_lo, noise_hi);
}
#endif // LIBGAV1_MAX_BITDEPTH >= 10
template <int bitdepth, typename GrainType, typename Pixel>
void BlendNoiseWithImageLuma_NEON(
const void* noise_image_ptr, int min_value, int max_luma, int scaling_shift,
int width, int height, int start_height,
const uint8_t scaling_lut_y[kScalingLookupTableSize],
const void* source_plane_y, ptrdiff_t source_stride_y, void* dest_plane_y,
ptrdiff_t dest_stride_y) {
const auto* noise_image =
static_cast<const Array2D<GrainType>*>(noise_image_ptr);
const auto* in_y_row = static_cast<const Pixel*>(source_plane_y);
source_stride_y /= sizeof(Pixel);
auto* out_y_row = static_cast<Pixel*>(dest_plane_y);
dest_stride_y /= sizeof(Pixel);
const int16x8_t floor = vdupq_n_s16(min_value);
const int16x8_t ceiling = vdupq_n_s16(max_luma);
// In 8bpp, the maximum upscaled noise is 127*255 = 0x7E81, which is safe
// for 16 bit signed integers. In higher bitdepths, however, we have to
// expand to 32 to protect the sign bit.
const int16x8_t scaling_shift_vect16 = vdupq_n_s16(-scaling_shift);
#if LIBGAV1_MAX_BITDEPTH >= 10
const int32x4_t scaling_shift_vect32 = vdupq_n_s32(-scaling_shift);
#endif // LIBGAV1_MAX_BITDEPTH >= 10
int y = 0;
do {
int x = 0;
do {
// This operation on the unsigned input is safe in 8bpp because the vector
// is widened before it is reinterpreted.
const int16x8_t orig = GetSignedSource8(&in_y_row[x]);
const int16x8_t scaling =
GetScalingFactors<bitdepth, Pixel>(scaling_lut_y, &in_y_row[x]);
int16x8_t noise =
GetSignedSource8(&(noise_image[kPlaneY][y + start_height][x]));
if (bitdepth == 8) {
noise = ScaleNoise(noise, scaling, scaling_shift_vect16);
} else {
#if LIBGAV1_MAX_BITDEPTH >= 10
noise = ScaleNoise(noise, scaling, scaling_shift_vect32);
#endif // LIBGAV1_MAX_BITDEPTH >= 10
}
const int16x8_t combined = vaddq_s16(orig, noise);
// In 8bpp, when params_.clip_to_restricted_range == false, we can replace
// clipping with vqmovun_s16, but it's not likely to be worth copying the
// function for just that case, though the gain would be very small.
StoreUnsigned8(&out_y_row[x],
vreinterpretq_u16_s16(Clip3(combined, floor, ceiling)));
x += 8;
} while (x < width);
in_y_row += source_stride_y;
out_y_row += dest_stride_y;
} while (++y < height);
}
template <int bitdepth, typename GrainType, typename Pixel>
inline int16x8_t BlendChromaValsWithCfl(
const Pixel* average_luma_buffer,
const uint8_t scaling_lut[kScalingLookupTableSize],
const Pixel* chroma_cursor, const GrainType* noise_image_cursor,
const int16x8_t scaling_shift_vect16,
const int32x4_t scaling_shift_vect32) {
const int16x8_t scaling =
GetScalingFactors<bitdepth, Pixel>(scaling_lut, average_luma_buffer);
const int16x8_t orig = GetSignedSource8(chroma_cursor);
int16x8_t noise = GetSignedSource8(noise_image_cursor);
if (bitdepth == 8) {
noise = ScaleNoise(noise, scaling, scaling_shift_vect16);
} else {
noise = ScaleNoise(noise, scaling, scaling_shift_vect32);
}
return vaddq_s16(orig, noise);
}
template <int bitdepth, typename GrainType, typename Pixel>
LIBGAV1_ALWAYS_INLINE void BlendChromaPlaneWithCfl_NEON(
const Array2D<GrainType>& noise_image, int min_value, int max_chroma,
int width, int height, int start_height, int subsampling_x,
int subsampling_y, int scaling_shift,
const uint8_t scaling_lut[kScalingLookupTableSize], const Pixel* in_y_row,
ptrdiff_t source_stride_y, const Pixel* in_chroma_row,
ptrdiff_t source_stride_chroma, Pixel* out_chroma_row,
ptrdiff_t dest_stride) {
const int16x8_t floor = vdupq_n_s16(min_value);
const int16x8_t ceiling = vdupq_n_s16(max_chroma);
Pixel luma_buffer[16];
memset(luma_buffer, 0, sizeof(luma_buffer));
// In 8bpp, the maximum upscaled noise is 127*255 = 0x7E81, which is safe
// for 16 bit signed integers. In higher bitdepths, however, we have to
// expand to 32 to protect the sign bit.
const int16x8_t scaling_shift_vect16 = vdupq_n_s16(-scaling_shift);
const int32x4_t scaling_shift_vect32 = vdupq_n_s32(-scaling_shift);
const int chroma_height = (height + subsampling_y) >> subsampling_y;
const int chroma_width = (width + subsampling_x) >> subsampling_x;
const int safe_chroma_width = chroma_width & ~7;
// Writing to this buffer avoids the cost of doing 8 lane lookups in a row
// in GetScalingFactors.
Pixel average_luma_buffer[8];
assert(start_height % 2 == 0);
start_height >>= subsampling_y;
int y = 0;
do {
int x = 0;
do {
const int luma_x = x << subsampling_x;
// TODO(petersonab): Consider specializing by subsampling_x. In the 444
// case &in_y_row[x] can be passed to GetScalingFactors directly.
const uint16x8_t average_luma =
GetAverageLuma(&in_y_row[luma_x], subsampling_x);
StoreUnsigned8(average_luma_buffer, average_luma);
const int16x8_t blended =
BlendChromaValsWithCfl<bitdepth, GrainType, Pixel>(
average_luma_buffer, scaling_lut, &in_chroma_row[x],
&(noise_image[y + start_height][x]), scaling_shift_vect16,
scaling_shift_vect32);
// In 8bpp, when params_.clip_to_restricted_range == false, we can replace
// clipping with vqmovun_s16, but it's not likely to be worth copying the
// function for just that case.
StoreUnsigned8(&out_chroma_row[x],
vreinterpretq_u16_s16(Clip3(blended, floor, ceiling)));
x += 8;
} while (x < safe_chroma_width);
if (x < chroma_width) {
const int luma_x = x << subsampling_x;
const int valid_range = width - luma_x;
memcpy(luma_buffer, &in_y_row[luma_x], valid_range * sizeof(in_y_row[0]));
luma_buffer[valid_range] = in_y_row[width - 1];
const uint16x8_t average_luma =
GetAverageLuma(luma_buffer, subsampling_x);
StoreUnsigned8(average_luma_buffer, average_luma);
const int16x8_t blended =
BlendChromaValsWithCfl<bitdepth, GrainType, Pixel>(
average_luma_buffer, scaling_lut, &in_chroma_row[x],
&(noise_image[y + start_height][x]), scaling_shift_vect16,
scaling_shift_vect32);
// In 8bpp, when params_.clip_to_restricted_range == false, we can replace
// clipping with vqmovun_s16, but it's not likely to be worth copying the
// function for just that case.
StoreUnsigned8(&out_chroma_row[x],
vreinterpretq_u16_s16(Clip3(blended, floor, ceiling)));
}
in_y_row += source_stride_y << subsampling_y;
in_chroma_row += source_stride_chroma;
out_chroma_row += dest_stride;
} while (++y < chroma_height);
}
// This function is for the case params_.chroma_scaling_from_luma == true.
// This further implies that scaling_lut_u == scaling_lut_v == scaling_lut_y.
template <int bitdepth, typename GrainType, typename Pixel>
void BlendNoiseWithImageChromaWithCfl_NEON(
Plane plane, const FilmGrainParams& params, const void* noise_image_ptr,
int min_value, int max_chroma, int width, int height, int start_height,
int subsampling_x, int subsampling_y,
const uint8_t scaling_lut[kScalingLookupTableSize],
const void* source_plane_y, ptrdiff_t source_stride_y,
const void* source_plane_uv, ptrdiff_t source_stride_uv,
void* dest_plane_uv, ptrdiff_t dest_stride_uv) {
const auto* noise_image =
static_cast<const Array2D<GrainType>*>(noise_image_ptr);
const auto* in_y = static_cast<const Pixel*>(source_plane_y);
source_stride_y /= sizeof(Pixel);
const auto* in_uv = static_cast<const Pixel*>(source_plane_uv);
source_stride_uv /= sizeof(Pixel);
auto* out_uv = static_cast<Pixel*>(dest_plane_uv);
dest_stride_uv /= sizeof(Pixel);
// Looping over one plane at a time is faster in higher resolutions, despite
// re-computing luma.
BlendChromaPlaneWithCfl_NEON<bitdepth, GrainType, Pixel>(
noise_image[plane], min_value, max_chroma, width, height, start_height,
subsampling_x, subsampling_y, params.chroma_scaling, scaling_lut, in_y,
source_stride_y, in_uv, source_stride_uv, out_uv, dest_stride_uv);
}
} // namespace
namespace low_bitdepth {
namespace {
inline int16x8_t BlendChromaValsNoCfl(
const uint8_t scaling_lut[kScalingLookupTableSize],
const uint8_t* chroma_cursor, const int8_t* noise_image_cursor,
const int16x8_t& average_luma, const int16x8_t& scaling_shift_vect,
const int16x8_t& offset, int luma_multiplier, int chroma_multiplier) {
uint8_t merged_buffer[8];
const int16x8_t orig = GetSignedSource8(chroma_cursor);
const int16x8_t weighted_luma = vmulq_n_s16(average_luma, luma_multiplier);
const int16x8_t weighted_chroma = vmulq_n_s16(orig, chroma_multiplier);
// Maximum value of |combined_u| is 127*255 = 0x7E81.
const int16x8_t combined = vhaddq_s16(weighted_luma, weighted_chroma);
// Maximum value of u_offset is (255 << 5) = 0x1FE0.
// 0x7E81 + 0x1FE0 = 0x9E61, therefore another halving add is required.
const uint8x8_t merged = vqshrun_n_s16(vhaddq_s16(offset, combined), 4);
vst1_u8(merged_buffer, merged);
const int16x8_t scaling =
GetScalingFactors<8, uint8_t>(scaling_lut, merged_buffer);
int16x8_t noise = GetSignedSource8(noise_image_cursor);
noise = ScaleNoise(noise, scaling, scaling_shift_vect);
return vaddq_s16(orig, noise);
}
LIBGAV1_ALWAYS_INLINE void BlendChromaPlane8bpp_NEON(
const Array2D<int8_t>& noise_image, int min_value, int max_chroma,
int width, int height, int start_height, int subsampling_x,
int subsampling_y, int scaling_shift, int chroma_offset,
int chroma_multiplier, int luma_multiplier,
const uint8_t scaling_lut[kScalingLookupTableSize], const uint8_t* in_y_row,
ptrdiff_t source_stride_y, const uint8_t* in_chroma_row,
ptrdiff_t source_stride_chroma, uint8_t* out_chroma_row,
ptrdiff_t dest_stride) {
const int16x8_t floor = vdupq_n_s16(min_value);
const int16x8_t ceiling = vdupq_n_s16(max_chroma);
// In 8bpp, the maximum upscaled noise is 127*255 = 0x7E81, which is safe
// for 16 bit signed integers. In higher bitdepths, however, we have to
// expand to 32 to protect the sign bit.
const int16x8_t scaling_shift_vect = vdupq_n_s16(-scaling_shift);
const int chroma_height = (height + subsampling_y) >> subsampling_y;
const int chroma_width = (width + subsampling_x) >> subsampling_x;
const int safe_chroma_width = chroma_width & ~7;
uint8_t luma_buffer[16];
const int16x8_t offset = vdupq_n_s16(chroma_offset << 5);
start_height >>= subsampling_y;
int y = 0;
do {
int x = 0;
do {
const int luma_x = x << subsampling_x;
const int16x8_t average_luma = vreinterpretq_s16_u16(
GetAverageLuma(&in_y_row[luma_x], subsampling_x));
const int16x8_t blended = BlendChromaValsNoCfl(
scaling_lut, &in_chroma_row[x], &(noise_image[y + start_height][x]),
average_luma, scaling_shift_vect, offset, luma_multiplier,
chroma_multiplier);
// In 8bpp, when params_.clip_to_restricted_range == false, we can
// replace clipping with vqmovun_s16, but the gain would be small.
StoreUnsigned8(&out_chroma_row[x],
vreinterpretq_u16_s16(Clip3(blended, floor, ceiling)));
x += 8;
} while (x < safe_chroma_width);
if (x < chroma_width) {
// Begin right edge iteration. Same as the normal iterations, but the
// |average_luma| computation requires a duplicated luma value at the
// end.
const int luma_x = x << subsampling_x;
const int valid_range = width - luma_x;
memcpy(luma_buffer, &in_y_row[luma_x], valid_range * sizeof(in_y_row[0]));
luma_buffer[valid_range] = in_y_row[width - 1];
const int16x8_t average_luma =
vreinterpretq_s16_u16(GetAverageLuma(luma_buffer, subsampling_x));
const int16x8_t blended = BlendChromaValsNoCfl(
scaling_lut, &in_chroma_row[x], &(noise_image[y + start_height][x]),
average_luma, scaling_shift_vect, offset, luma_multiplier,
chroma_multiplier);
StoreUnsigned8(&out_chroma_row[x],
vreinterpretq_u16_s16(Clip3(blended, floor, ceiling)));
// End of right edge iteration.
}
in_y_row += source_stride_y << subsampling_y;
in_chroma_row += source_stride_chroma;
out_chroma_row += dest_stride;
} while (++y < chroma_height);
}
// This function is for the case params_.chroma_scaling_from_luma == false.
void BlendNoiseWithImageChroma8bpp_NEON(
Plane plane, const FilmGrainParams& params, const void* noise_image_ptr,
int min_value, int max_chroma, int width, int height, int start_height,
int subsampling_x, int subsampling_y,
const uint8_t scaling_lut[kScalingLookupTableSize],
const void* source_plane_y, ptrdiff_t source_stride_y,
const void* source_plane_uv, ptrdiff_t source_stride_uv,
void* dest_plane_uv, ptrdiff_t dest_stride_uv) {
assert(plane == kPlaneU || plane == kPlaneV);
const auto* noise_image =
static_cast<const Array2D<int8_t>*>(noise_image_ptr);
const auto* in_y = static_cast<const uint8_t*>(source_plane_y);
const auto* in_uv = static_cast<const uint8_t*>(source_plane_uv);
auto* out_uv = static_cast<uint8_t*>(dest_plane_uv);
const int offset = (plane == kPlaneU) ? params.u_offset : params.v_offset;
const int luma_multiplier =
(plane == kPlaneU) ? params.u_luma_multiplier : params.v_luma_multiplier;
const int multiplier =
(plane == kPlaneU) ? params.u_multiplier : params.v_multiplier;
BlendChromaPlane8bpp_NEON(noise_image[plane], min_value, max_chroma, width,
height, start_height, subsampling_x, subsampling_y,
params.chroma_scaling, offset, multiplier,
luma_multiplier, scaling_lut, in_y, source_stride_y,
in_uv, source_stride_uv, out_uv, dest_stride_uv);
}
inline void WriteOverlapLine8bpp_NEON(const int8_t* noise_stripe_row,
const int8_t* noise_stripe_row_prev,
int plane_width,
const int8x8_t grain_coeff,
const int8x8_t old_coeff,
int8_t* noise_image_row) {
int x = 0;
do {
// Note that these reads may exceed noise_stripe_row's width by up to 7
// bytes.
const int8x8_t source_grain = vld1_s8(noise_stripe_row + x);
const int8x8_t source_old = vld1_s8(noise_stripe_row_prev + x);
const int16x8_t weighted_grain = vmull_s8(grain_coeff, source_grain);
const int16x8_t grain = vmlal_s8(weighted_grain, old_coeff, source_old);
// Note that this write may exceed noise_image_row's width by up to 7 bytes.
vst1_s8(noise_image_row + x, vqrshrn_n_s16(grain, 5));
x += 8;
} while (x < plane_width);
}
void ConstructNoiseImageOverlap8bpp_NEON(const void* noise_stripes_buffer,
int width, int height,
int subsampling_x, int subsampling_y,
void* noise_image_buffer) {
const auto* noise_stripes =
static_cast<const Array2DView<int8_t>*>(noise_stripes_buffer);
auto* noise_image = static_cast<Array2D<int8_t>*>(noise_image_buffer);
const int plane_width = (width + subsampling_x) >> subsampling_x;
const int plane_height = (height + subsampling_y) >> subsampling_y;
const int stripe_height = 32 >> subsampling_y;
const int stripe_mask = stripe_height - 1;
int y = stripe_height;
int luma_num = 1;
if (subsampling_y == 0) {
const int8x8_t first_row_grain_coeff = vdup_n_s8(17);
const int8x8_t first_row_old_coeff = vdup_n_s8(27);
const int8x8_t second_row_grain_coeff = first_row_old_coeff;
const int8x8_t second_row_old_coeff = first_row_grain_coeff;
for (; y < (plane_height & ~stripe_mask); ++luma_num, y += stripe_height) {
const int8_t* noise_stripe = (*noise_stripes)[luma_num];
const int8_t* noise_stripe_prev = (*noise_stripes)[luma_num - 1];
WriteOverlapLine8bpp_NEON(
noise_stripe, &noise_stripe_prev[32 * plane_width], plane_width,
first_row_grain_coeff, first_row_old_coeff, (*noise_image)[y]);
WriteOverlapLine8bpp_NEON(&noise_stripe[plane_width],
&noise_stripe_prev[(32 + 1) * plane_width],
plane_width, second_row_grain_coeff,
second_row_old_coeff, (*noise_image)[y + 1]);
}
// Either one partial stripe remains (remaining_height > 0),
// OR image is less than one stripe high (remaining_height < 0),
// OR all stripes are completed (remaining_height == 0).
const int remaining_height = plane_height - y;
if (remaining_height <= 0) {
return;
}
const int8_t* noise_stripe = (*noise_stripes)[luma_num];
const int8_t* noise_stripe_prev = (*noise_stripes)[luma_num - 1];
WriteOverlapLine8bpp_NEON(
noise_stripe, &noise_stripe_prev[32 * plane_width], plane_width,
first_row_grain_coeff, first_row_old_coeff, (*noise_image)[y]);
if (remaining_height > 1) {
WriteOverlapLine8bpp_NEON(&noise_stripe[plane_width],
&noise_stripe_prev[(32 + 1) * plane_width],
plane_width, second_row_grain_coeff,
second_row_old_coeff, (*noise_image)[y + 1]);
}
} else { // subsampling_y == 1
const int8x8_t first_row_grain_coeff = vdup_n_s8(22);
const int8x8_t first_row_old_coeff = vdup_n_s8(23);
for (; y < plane_height; ++luma_num, y += stripe_height) {
const int8_t* noise_stripe = (*noise_stripes)[luma_num];
const int8_t* noise_stripe_prev = (*noise_stripes)[luma_num - 1];
WriteOverlapLine8bpp_NEON(
noise_stripe, &noise_stripe_prev[16 * plane_width], plane_width,
first_row_grain_coeff, first_row_old_coeff, (*noise_image)[y]);
}
}
}
void Init8bpp() {
Dsp* const dsp = dsp_internal::GetWritableDspTable(kBitdepth8);
assert(dsp != nullptr);
// LumaAutoRegressionFunc
dsp->film_grain.luma_auto_regression[0] =
ApplyAutoRegressiveFilterToLumaGrain_NEON<8, int8_t, 1>;
dsp->film_grain.luma_auto_regression[1] =
ApplyAutoRegressiveFilterToLumaGrain_NEON<8, int8_t, 2>;
dsp->film_grain.luma_auto_regression[2] =
ApplyAutoRegressiveFilterToLumaGrain_NEON<8, int8_t, 3>;
// ChromaAutoRegressionFunc[use_luma][auto_regression_coeff_lag]
// Chroma autoregression should never be called when lag is 0 and use_luma
// is false.
dsp->film_grain.chroma_auto_regression[0][0] = nullptr;
dsp->film_grain.chroma_auto_regression[0][1] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<8, int8_t, 1, false>;
dsp->film_grain.chroma_auto_regression[0][2] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<8, int8_t, 2, false>;
dsp->film_grain.chroma_auto_regression[0][3] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<8, int8_t, 3, false>;
dsp->film_grain.chroma_auto_regression[1][0] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<8, int8_t, 0, true>;
dsp->film_grain.chroma_auto_regression[1][1] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<8, int8_t, 1, true>;
dsp->film_grain.chroma_auto_regression[1][2] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<8, int8_t, 2, true>;
dsp->film_grain.chroma_auto_regression[1][3] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<8, int8_t, 3, true>;
dsp->film_grain.construct_noise_image_overlap =
ConstructNoiseImageOverlap8bpp_NEON;
dsp->film_grain.initialize_scaling_lut = InitializeScalingLookupTable_NEON;
dsp->film_grain.blend_noise_luma =
BlendNoiseWithImageLuma_NEON<8, int8_t, uint8_t>;
dsp->film_grain.blend_noise_chroma[0] = BlendNoiseWithImageChroma8bpp_NEON;
dsp->film_grain.blend_noise_chroma[1] =
BlendNoiseWithImageChromaWithCfl_NEON<8, int8_t, uint8_t>;
}
} // namespace
} // namespace low_bitdepth
#if LIBGAV1_MAX_BITDEPTH >= 10
namespace high_bitdepth {
namespace {
void Init10bpp() {
Dsp* const dsp = dsp_internal::GetWritableDspTable(kBitdepth10);
assert(dsp != nullptr);
// LumaAutoRegressionFunc
dsp->film_grain.luma_auto_regression[0] =
ApplyAutoRegressiveFilterToLumaGrain_NEON<10, int16_t, 1>;
dsp->film_grain.luma_auto_regression[1] =
ApplyAutoRegressiveFilterToLumaGrain_NEON<10, int16_t, 2>;
dsp->film_grain.luma_auto_regression[2] =
ApplyAutoRegressiveFilterToLumaGrain_NEON<10, int16_t, 3>;
// ChromaAutoRegressionFunc[use_luma][auto_regression_coeff_lag][subsampling]
// Chroma autoregression should never be called when lag is 0 and use_luma
// is false.
dsp->film_grain.chroma_auto_regression[0][0] = nullptr;
dsp->film_grain.chroma_auto_regression[0][1] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<10, int16_t, 1, false>;
dsp->film_grain.chroma_auto_regression[0][2] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<10, int16_t, 2, false>;
dsp->film_grain.chroma_auto_regression[0][3] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<10, int16_t, 3, false>;
dsp->film_grain.chroma_auto_regression[1][0] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<10, int16_t, 0, true>;
dsp->film_grain.chroma_auto_regression[1][1] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<10, int16_t, 1, true>;
dsp->film_grain.chroma_auto_regression[1][2] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<10, int16_t, 2, true>;
dsp->film_grain.chroma_auto_regression[1][3] =
ApplyAutoRegressiveFilterToChromaGrains_NEON<10, int16_t, 3, true>;
dsp->film_grain.initialize_scaling_lut = InitializeScalingLookupTable_NEON;
dsp->film_grain.blend_noise_luma =
BlendNoiseWithImageLuma_NEON<10, int16_t, uint16_t>;
dsp->film_grain.blend_noise_chroma[1] =
BlendNoiseWithImageChromaWithCfl_NEON<10, int16_t, uint16_t>;
}
} // namespace
} // namespace high_bitdepth
#endif // LIBGAV1_MAX_BITDEPTH >= 10
} // namespace film_grain
void FilmGrainInit_NEON() {
film_grain::low_bitdepth::Init8bpp();
#if LIBGAV1_MAX_BITDEPTH >= 10
film_grain::high_bitdepth::Init10bpp();
#endif // LIBGAV1_MAX_BITDEPTH >= 10
}
} // namespace dsp
} // namespace libgav1
#else // !LIBGAV1_ENABLE_NEON
namespace libgav1 {
namespace dsp {
void FilmGrainInit_NEON() {}
} // namespace dsp
} // namespace libgav1
#endif // LIBGAV1_ENABLE_NEON