blob: a87924b0c7254fb4a5a7a0733ca93ec896bf3601 [file]
// Copyright 2019 Google LLC
//
// 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
//
// https://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.
// -----------------------------------------------------------------------------
//
// Speed-critical deblocking functions
//
// Author: Yannis Guyon (yguyon@google.com)
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include "src/dsp/dsp.h"
#include "src/dsp/math.h"
namespace WP2 {
namespace {
//------------------------------------------------------------------------------
constexpr uint32_t kWeightLog2 = 8;
constexpr uint32_t kStrengthShift = WP2Log2Ceil_k(kDblkMaxStrength);
constexpr uint32_t kBlurShift = (kWeightLog2 + kStrengthShift + 1); // 16bit
constexpr uint32_t kBlurUnit = 1u << kBlurShift;
static_assert(kDblkMaxHalf <= 8u, "kDblkMaxHalf should be adjusted");
// Weights representing how much to blend a pixel with the average.
// Per line length, per distance to edge. Fixed point precision is kWeightLog2.
// Values are chosen experimentally.
constexpr uint16_t kBlurWeights[kDblkMaxHalf][8] = {
{160},
{180, 70},
{190, 89, 22},
{200, 113, 50, 13},
{200, 128, 72, 32, 8},
{200, 139, 89, 50, 22, 6},
{200, 147, 102, 65, 37, 16, 4},
{200, 153, 113, 78, 50, 28, 13, 3},
};
constexpr uint16_t kWeightThreshold(int i) {
return kBlurUnit / (2 * kBlurWeights[i][0]);
}
constexpr uint16_t kBlurWeightsThreshold[kDblkMaxHalf] = {
kWeightThreshold(0), kWeightThreshold(1), kWeightThreshold(2),
kWeightThreshold(3), kWeightThreshold(4), kWeightThreshold(5),
kWeightThreshold(6), kWeightThreshold(7),
};
// Per sharpness. Threshold to decide whether to filter a line or not-at-all.
// Values are chosen experimentally with a fixed-point precision of 10 bits.
constexpr int32_t kSharpnessThreshold[kDblkMaxSharpness + 1] = {
220, 212, 204, 168, 108, 72, 40, 20};
// Per sharpness, per distance to edge. Threshold between this pixel and the
// previous one (hence index 0 is irrelevant). Values are chosen experimentally.
// TODO(yguyon): Take 'num_precision_bits' into account.
// clang-format off
constexpr int8_t kFlatnessThreshold[kDblkMaxSharpness + 1][kDblkMaxHalf] = {
// 0x7f is the max int8_t value for thresh, to guaranty that the comparison
// "abs_all_8b > thresh" will always be false (and result in a leading 0 bit).
{0x7f, 82, 15, 7, 4, 3, 2, 2},
{0x7f, 80, 10, 6, 4, 3, 2, 2},
{0x7f, 78, 7, 5, 4, 3, 2, 2},
{0x7f, 66, 6, 4, 4, 3, 2, 1},
{0x7f, 54, 6, 4, 3, 2, 1, 1},
{0x7f, 40, 6, 4, 3, 2, 1, 1},
{0x7f, 37, 4, 3, 2, 2, 1, 1},
{0x7f, 25, 3, 2, 2, 1, 1, 1}
};
// clang-format on
//------------------------------------------------------------------------------
// plain-C implementation
// returns ...p0|q0... edge difference
static int32_t EdgeDelta(const int16_t* const q0, uint32_t half) {
assert(half > 0 && half <= kDblkMaxHalf);
// Extrapolate the edge color from the two closest pixels (consider it to be
// between p0 and q0, at a distance of 0.5 pixels from each).
const int16_t* const p0 = q0 - 1;
int32_t diff = (q0[0] - p0[0]);
if (half > 1) {
diff += RightShift(diff - q0[1] + p0[-1], 1);
}
return diff;
}
uint8_t WouldDeblockLine_C(int32_t threshold, uint32_t half,
const int16_t q0[]) {
return (std::abs(EdgeDelta(q0, half)) <= threshold) ? 1 : 0;
}
void DeblockLine_C(uint32_t filter_strength, int32_t threshold, uint8_t half,
int32_t min, int32_t max, int16_t* q0) {
assert(filter_strength > 0 && half > 0);
int32_t delta = EdgeDelta(q0, half);
if (std::abs(delta) > threshold) return;
// Do not filter invisible block edges, consider them already deblocked.
delta *= (int32_t)filter_strength;
if (std::abs(delta) <= kBlurWeightsThreshold[half - 1]) return;
int16_t* p = q0 - 1;
int16_t* q = q0;
for (uint32_t i = 0; i < half; ++i, p -= 1, q += 1) {
// warning! -(b>>1) is not equal to (-b>>1), that's why we need two deltas
const int16_t p_weighted_delta =
RightShiftRound(+delta * kBlurWeights[half - 1][i], kBlurShift);
const int16_t m_weighted_delta =
RightShiftRound(-delta * kBlurWeights[half - 1][i], kBlurShift);
// since kBlurWeights[] is decreasing, check if we can early-out
if ((m_weighted_delta | p_weighted_delta) == 0) break;
*p = (int16_t)Clamp(*p + p_weighted_delta, min, max);
*q = (int16_t)Clamp(*q + m_weighted_delta, min, max);
}
}
uint32_t MeasureFlatLength_2(uint32_t filter_sharpness,
const int16_t* const q0) {
const int16_t* const p = q0 - 1;
const int16_t* const q = q0;
const int32_t diff0 = q[0] - p[0];
const int32_t diff_q = q[1] - q[0];
const int32_t diff_p = p[0] - p[-1];
const int32_t t = kFlatnessThreshold[filter_sharpness][1];
if (std::abs(diff_q - diff0) > t || std::abs(diff_p - diff0) > t) return 1;
return 2;
}
uint32_t MeasureFlatLength_C(uint32_t filter_sharpness, uint32_t half,
const int16_t* q0) {
assert(half > 0 && half <= kDblkMaxHalf);
if (half == 1) return 1;
if (half == 2) return MeasureFlatLength_2(filter_sharpness, q0);
const int16_t* p = q0 - 1;
const int16_t* q = q0;
int32_t previous_diff_p = *p - *q;
int32_t previous_diff_q = -previous_diff_p;
for (uint32_t i = 1; i < half; ++i) {
const int32_t previous_p = *p, previous_q = *q;
p -= 1;
q += 1;
const int32_t diff_p = *p - previous_p, diff_q = *q - previous_q;
const int32_t t = kFlatnessThreshold[filter_sharpness][i];
if (std::abs(diff_p - previous_diff_p) > t ||
std::abs(diff_q - previous_diff_q) > t) {
return i;
}
previous_diff_p = diff_p;
previous_diff_q = diff_q;
}
return half;
}
void MeasureFlatLengths_C(uint32_t filter_sharpness, uint32_t half,
const int16_t* q0, uint32_t step, uint8_t out[],
uint32_t size) {
for (uint32_t i = 0; i < size; ++i, q0 += step) {
out[i] = MeasureFlatLength_C(filter_sharpness, half, q0);
}
}
//------------------------------------------------------------------------------
// buffer I/O
void CopyIn_C(const int16_t* src, uint32_t src_step, int16_t* dst,
int32_t width, int32_t height) {
src -= height * src_step;
dst -= height;
for (int j = 0; j < 2 * height; ++j, src += src_step, dst += 1) {
for (int i = 0; i < width; ++i) dst[i * kDblCacheStep] = src[i];
}
}
void CopyOut_C(const int16_t* src, int16_t* dst, uint32_t dst_step,
int32_t width, int32_t height) {
src -= height;
dst -= height * dst_step;
for (int j = 0; j < 2 * height; ++j, src += 1, dst += dst_step) {
for (int i = 0; i < width; ++i) dst[i] = src[i * kDblCacheStep];
}
}
} // namespace
//------------------------------------------------------------------------------
// SSE4.1 implementation
#if defined(WP2_USE_SSE)
namespace {
// loads 8 int16's of input
#define LOAD_ROW8(ROW) \
_mm_loadu_si128((const __m128i*)(src + (ROW) * kDblCacheStep))
// stores 2x4 int16's of output
#define STORE_ROW2x4(REG, ROW, STEP) \
_mm_storel_epi64((__m128i*)(dst + ((ROW) + 0) * (STEP)), (REG)); \
_mm_storel_epi64((__m128i*)(dst + ((ROW) + 1) * (STEP)), \
_mm_srli_si128((REG), 8))
void Transpose8x4_SSE(const int16_t* src, int16_t* dst, uint32_t dst_step) {
const __m128i a0 = LOAD_ROW8(0); // a00 a01 ... a07
const __m128i a1 = LOAD_ROW8(1); // a10 a11 ... a17
const __m128i a2 = LOAD_ROW8(2); // a20 a21 ... a27
const __m128i a3 = LOAD_ROW8(3); // a30 a31 ... a37
const __m128i b0 = _mm_unpacklo_epi16(a0, a1); // a00 a10 a01 a11 ... a03 a13
const __m128i b1 = _mm_unpackhi_epi16(a0, a1); // a04 a14 a05 a15 ... a07 a17
const __m128i b2 = _mm_unpacklo_epi16(a2, a3); // a20 a30 a21 a31 ... a23 a33
const __m128i b3 = _mm_unpackhi_epi16(a2, a3); // a24 a34 a25 a35 ... a27 a37
const __m128i c0 = _mm_unpacklo_epi32(b0, b2); // a00 a10 a20 a30 ... a21 a31
const __m128i c1 = _mm_unpackhi_epi32(b0, b2); // a02 a12 a22 a32 ... a23 a33
const __m128i c2 = _mm_unpacklo_epi32(b1, b3); // a04 a14 a24 a34 ... a25 a35
const __m128i c3 = _mm_unpackhi_epi32(b1, b3); // a06 a16 a17 a18 ... a27 a37
STORE_ROW2x4(c0, 0, dst_step);
STORE_ROW2x4(c1, 2, dst_step);
STORE_ROW2x4(c2, 4, dst_step);
STORE_ROW2x4(c3, 6, dst_step);
}
#undef STORE_ROW2x4
#undef LOAD_ROW8
// loads 2x4 int16's of input
#define LOAD_ROW2x4(ROW, STEP) \
_mm_unpacklo_epi16( \
_mm_loadl_epi64((const __m128i*)(src + (ROW + 0) * (STEP))), \
_mm_loadl_epi64((const __m128i*)(src + (ROW + 1) * (STEP))))
// stores 8 int16's of output
#define STORE_ROW8(REG, ROW) \
_mm_storeu_si128((__m128i*)(dst + (ROW) * kDblCacheStep), REG)
void Transpose4x8_SSE(const int16_t* src, uint32_t src_step, int16_t* dst) {
const __m128i a0 = LOAD_ROW2x4(0, src_step); // a00 a10 a01 a11 ... a03 a13
const __m128i a1 = LOAD_ROW2x4(2, src_step); // a20 a30 a21 a31 ... a23 a33
const __m128i a2 = LOAD_ROW2x4(4, src_step); // a40 a50 a41 a51 ... a43 a53
const __m128i a3 = LOAD_ROW2x4(6, src_step); // a60 a70 a61 a71 ... a63 a73
const __m128i b0 = _mm_unpacklo_epi32(a0, a1); // a00 a10 a20 a30 ... a21 a31
const __m128i b1 = _mm_unpackhi_epi32(a0, a1); // a02 a12 a22 a32 ... a23 a33
const __m128i b2 = _mm_unpacklo_epi32(a2, a3); // a40 a50 a60 a70 ... a61 a71
const __m128i b3 = _mm_unpackhi_epi32(a2, a3); // a42 a52 a62 a72 ... a63 a73
const __m128i c0 = _mm_unpacklo_epi64(b0, b2); // a00 a10 ... a70
const __m128i c1 = _mm_unpackhi_epi64(b0, b2); // a01 a11 ... a71
const __m128i c2 = _mm_unpacklo_epi64(b1, b3); // a02 a12 ... a72
const __m128i c3 = _mm_unpackhi_epi64(b1, b3); // a03 a13 ... a73
STORE_ROW8(c0, 0);
STORE_ROW8(c1, 1);
STORE_ROW8(c2, 2);
STORE_ROW8(c3, 3);
}
#undef STORE_ROW8
#undef LOAD_ROW2x4
void CopyIn_SSE(const int16_t* src, uint32_t src_step, int16_t* dst,
int32_t width, int32_t height) {
for (int y = 0; y < width; y += 4) {
if (height <= 4) {
Transpose4x8_SSE(src - 4 * src_step, src_step, dst - 4);
} else {
Transpose4x8_SSE(src - 8 * src_step, src_step, dst - 8);
Transpose4x8_SSE(src - 0 * src_step, src_step, dst - 0);
}
src += 4;
dst += 4 * kDblCacheStep;
}
}
void CopyOut_SSE(const int16_t* src, int16_t* dst, uint32_t dst_step,
int32_t width, int32_t height) {
for (int y = 0; y < width; y += 4) {
if (height <= 4) {
Transpose8x4_SSE(src - 4, dst - 4 * dst_step, dst_step);
} else {
Transpose8x4_SSE(src - 8, dst - 8 * dst_step, dst_step);
Transpose8x4_SSE(src - 0, dst - 0 * dst_step, dst_step);
}
dst += 4;
src += 4 * kDblCacheStep;
}
}
//------------------------------------------------------------------------------
// Actual deblocking
// clang-format off
constexpr int16_t kBlurWeights_SSE[kDblkMaxHalf][16] = {
{ 0, 0, 0, 0, 0, 0, 0, 160, -160, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, 70, 180, -180, -70, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 22, 89, 190, -190, -89, -22, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 13, 50, 113, 200, -200, -113, -50, -13, 0, 0, 0, 0},
{ 0, 0, 0, 8, 32, 72, 128, 200, -200, -128, -72, -32, -8, 0, 0, 0},
{ 0, 0, 6, 22, 50, 89, 139, 200, -200, -139, -89, -50, -22, -6, 0, 0},
{ 0, 4, 16, 37, 65, 102, 147, 200, -200, -147, -102, -65, -37, -16, -4, 0},
{ 3, 13, 28, 50, 78, 113, 153, 200, -200, -153, -113, -78, -50, -28, -13, -3}
};
// clang-format on
static_assert(kBlurShift == 15, "_mm_mulhrs_epi16 needs to be changed.");
void DeblockLine_SSE(uint32_t filter_strength, int32_t threshold, uint8_t half,
int32_t min, int32_t max, int16_t* q0) {
assert(filter_strength > 0 && half > 0);
const __m128i m_min = _mm_set1_epi16(min);
const __m128i m_max = _mm_set1_epi16(max);
int32_t delta = EdgeDelta(q0, half);
if (std::abs(delta) > threshold) return;
// Do not filter invisible block edges, consider them already deblocked.
delta *= (int32_t)filter_strength;
if (std::abs(delta) <= kBlurWeightsThreshold[half - 1]) return;
assert(std::abs(delta) < 0x7fff);
const int16_t* const weights = kBlurWeights_SSE[half - 1];
const __m128i m_delta = _mm_set1_epi16(delta);
if (half <= 4) {
const __m128i a0 = _mm_loadu_si128((const __m128i*)(weights + 4));
const __m128i b0 = _mm_mulhrs_epi16(a0, m_delta);
const __m128i c0 = _mm_loadu_si128((const __m128i*)(q0 - 4));
const __m128i d0 = _mm_adds_epi16(c0, b0);
const __m128i e0 = _mm_min_epi16(_mm_max_epi16(d0, m_min), m_max);
_mm_storeu_si128((__m128i*)(q0 - 4), e0);
} else {
const __m128i a0 = _mm_loadu_si128((const __m128i*)(weights + 0));
const __m128i a1 = _mm_loadu_si128((const __m128i*)(weights + 8));
const __m128i b0 = _mm_mulhrs_epi16(a0, m_delta);
const __m128i b1 = _mm_mulhrs_epi16(a1, m_delta);
const __m128i c0 = _mm_loadu_si128((const __m128i*)(q0 - 8));
const __m128i c1 = _mm_loadu_si128((const __m128i*)(q0 - 0));
const __m128i d0 = _mm_adds_epi16(c0, b0);
const __m128i d1 = _mm_adds_epi16(c1, b1);
const __m128i e0 = _mm_min_epi16(_mm_max_epi16(d0, m_min), m_max);
const __m128i e1 = _mm_min_epi16(_mm_max_epi16(d1, m_min), m_max);
_mm_storeu_si128((__m128i*)(q0 - 8), e0);
_mm_storeu_si128((__m128i*)(q0 - 0), e1);
}
}
void MeasureFlatLengths_SSE(uint32_t filter_sharpness, uint32_t half,
const int16_t* q0, uint32_t step, uint8_t out[],
uint32_t size) {
assert(half > 0 && half <= kDblkMaxHalf);
if (size <= 0) return;
if (half <= 2) {
if (half < 2) {
for (uint32_t j = 0; j < size; ++j) out[j] = 1;
} else {
const int32_t t = kFlatnessThreshold[filter_sharpness][1];
for (uint32_t j = 0; j < size; ++j, q0 += step) {
const int32_t diff_p = q0[-1] - q0[-2];
const int32_t diff0 = q0[0] - q0[-1];
const int32_t diff_q = q0[1] - q0[0];
out[j] = (std::abs(diff_q - diff0) > t || std::abs(diff_p - diff0) > t)
? 1
: 2;
}
}
return;
}
static const uint8_t kThresh_SSE[kDblkMaxSharpness + 1][2 * kDblkMaxHalf] = {
{82, 15, 7, 4, 3, 2, 2, 0x7f, 0x7f, 2, 2, 3, 4, 7, 15, 82},
{80, 10, 6, 4, 3, 2, 2, 0x7f, 0x7f, 2, 2, 3, 4, 6, 10, 80},
{78, 7, 5, 4, 3, 2, 2, 0x7f, 0x7f, 2, 2, 3, 4, 5, 7, 78},
{66, 6, 4, 4, 3, 2, 1, 0x7f, 0x7f, 1, 2, 3, 4, 4, 6, 66},
{54, 6, 4, 3, 2, 1, 1, 0x7f, 0x7f, 1, 1, 2, 3, 4, 6, 54},
{40, 6, 4, 3, 2, 1, 1, 0x7f, 0x7f, 1, 1, 2, 3, 4, 6, 40},
{37, 4, 3, 2, 2, 1, 1, 0x7f, 0x7f, 1, 1, 2, 2, 3, 4, 37},
{25, 3, 2, 2, 1, 1, 1, 0x7f, 0x7f, 1, 1, 1, 2, 2, 3, 25}};
static const uint16_t limit[8] = {0xffff, 0x7ffe, 0x3ffc, 0x1ff8,
0x0ff0, 0x07e0, 0x03c0, 0x0180};
const __m128i thresh =
_mm_loadu_si128((const __m128i*)(kThresh_SSE[filter_sharpness]));
const uint32_t or_mask = limit[half - 1];
if (half <= 4) {
for (uint32_t j = 0; j < size; ++j, q0 += step) {
const __m128i Q = _mm_loadu_si128((const __m128i*)(q0 - 4));
const __m128i diff1 = _mm_subs_epi16(Q, _mm_srli_si128(Q, 2));
const __m128i diff2 = _mm_slli_si128(diff1, 2);
const __m128i abs16 = _mm_abs_epi16(_mm_subs_epi16(diff1, diff2));
const __m128i abs8 = _mm_packs_epi16(abs16, abs16);
const __m128i abs_dPQ = _mm_shuffle_epi32(abs8, 0x05);
const __m128i bits_PQ = _mm_cmpgt_epi8(abs_dPQ, thresh);
const uint32_t bits = _mm_movemask_epi8(bits_PQ) | or_mask;
#if defined(__GNUC__)
if (!bits) __builtin_unreachable();
#endif
out[j] = std::min(15 - WP2Log2Floor(bits), WP2Ctz(bits)) + 1;
}
} else {
for (uint32_t j = 0; j < size; ++j, q0 += step) {
const __m128i P0 = _mm_loadu_si128((const __m128i*)(q0 - 8));
const __m128i Q0 = _mm_loadu_si128((const __m128i*)(q0 + 0));
const __m128i P1 = _mm_alignr_epi8(Q0, P0, 2);
const __m128i Q1 = _mm_alignr_epi8(Q0, P0, 14);
const __m128i diff_dP = _mm_subs_epi16(P1, P0);
const __m128i diff_dQ = _mm_subs_epi16(Q1, Q0);
const __m128i diff_dP1 = _mm_slli_si128(diff_dP, 2);
const __m128i diff_dQ1 = _mm_srli_si128(diff_dQ, 2);
const __m128i abs_dP = _mm_abs_epi16(_mm_subs_epi16(diff_dP, diff_dP1));
const __m128i abs_dQ = _mm_abs_epi16(_mm_subs_epi16(diff_dQ, diff_dQ1));
const __m128i abs_dPQ = _mm_packs_epi16(abs_dQ, abs_dP);
const __m128i bits_PQ = _mm_cmpgt_epi8(abs_dPQ, thresh);
const uint32_t bits = _mm_movemask_epi8(bits_PQ) | or_mask;
#if defined(__GNUC__)
if (!bits) __builtin_unreachable();
#endif
out[j] = std::min(15 - WP2Log2Floor(bits), WP2Ctz(bits)) + 1;
}
}
}
WP2_TSAN_IGNORE_FUNCTION void DblkFilterInitSSE() {
DeblockLine = DeblockLine_SSE;
FilterCopyIn = CopyIn_SSE;
FilterCopyOut = CopyOut_SSE;
MeasureFlatLengths = MeasureFlatLengths_SSE;
}
#if defined(WP2_USE_AVX2)
void DeblockLine_AVX2(uint32_t filter_strength, int32_t threshold, uint8_t half,
int32_t min, int32_t max, int16_t* q0) {
if (half <= 4) {
DeblockLine_SSE(filter_strength, threshold, half, min, max, q0);
return;
}
assert(filter_strength > 0 && half > 0);
int32_t delta = EdgeDelta(q0, half);
if (std::abs(delta) > threshold) return;
// Do not filter invisible block edges, consider them already deblocked.
delta *= (int32_t)filter_strength;
if (std::abs(delta) <= kBlurWeightsThreshold[half - 1]) return;
assert(std::abs(delta) < 0x7fff);
const __m256i m_min = _mm256_set1_epi16(min);
const __m256i m_max = _mm256_set1_epi16(max);
const int16_t* const weights = kBlurWeights_SSE[half - 1];
const __m256i m_delta = _mm256_set1_epi16(delta);
const __m256i a = _mm256_lddqu_si256((const __m256i*)weights);
const __m256i b = _mm256_mulhrs_epi16(a, m_delta);
const __m256i c = _mm256_lddqu_si256((const __m256i*)(q0 - 8));
const __m256i d = _mm256_adds_epi16(b, c);
const __m256i e = _mm256_min_epi16(d, m_max);
const __m256i f = _mm256_max_epi16(e, m_min);
_mm256_storeu_si256((__m256i*)(q0 - 8), f);
}
WP2_TSAN_IGNORE_FUNCTION void DblkFilterInitAVX2() {
DeblockLine = DeblockLine_AVX2;
}
#endif
} // namespace
#endif // WP2_USE_SSE
//------------------------------------------------------------------------------
int32_t DeblockThresholdFromSharpness(uint32_t filter_sharpness,
uint32_t num_precision_bits) {
// This threshold is only used after calling EdgeDelta() which uses one extra
// bit of precision to avoid a ">>1". Hence the "2 * " here.
return ChangePrecision(kSharpnessThreshold[filter_sharpness], 10u,
num_precision_bits);
}
void (*DeblockLine)(uint32_t filter_strength, int32_t threshold, uint8_t half,
int32_t min, int32_t max, int16_t* q0) = nullptr;
uint8_t (*WouldDeblockLine)(int32_t threshold, uint32_t half,
const int16_t q0[]) = nullptr;
void (*MeasureFlatLengths)(uint32_t filter_sharpness, uint32_t half,
const int16_t* q0, uint32_t step, uint8_t out[],
uint32_t size) = nullptr;
void (*FilterCopyIn)(const int16_t* src, uint32_t src_step, int16_t* dst,
int32_t W, int32_t H) = nullptr;
void (*FilterCopyOut)(const int16_t* src, int16_t* dst, uint32_t dst_step,
int32_t W, int32_t H) = nullptr;
static volatile WP2CPUInfo dblk_filter_last_cpuinfo_used =
(WP2CPUInfo)&dblk_filter_last_cpuinfo_used;
WP2_TSAN_IGNORE_FUNCTION void DblkFilterInit() {
if (dblk_filter_last_cpuinfo_used == WP2GetCPUInfo) return;
DeblockLine = DeblockLine_C;
WouldDeblockLine = WouldDeblockLine_C;
MeasureFlatLengths = MeasureFlatLengths_C;
FilterCopyIn = CopyIn_C;
FilterCopyOut = CopyOut_C;
if (WP2GetCPUInfo != nullptr) {
#if defined(WP2_USE_SSE)
if (WP2GetCPUInfo(kSSE)) DblkFilterInitSSE();
#if defined(WP2_USE_AVX2)
if (WP2GetCPUInfo(kAVX2)) DblkFilterInitAVX2();
#endif // AVX2
#endif // SSE
}
dblk_filter_last_cpuinfo_used = WP2GetCPUInfo;
}
//------------------------------------------------------------------------------
} // namespace WP2