// 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/cdef.h" #include #include #include #include #include #include "src/dsp/constants.h" #include "src/dsp/dsp.h" #include "src/utils/common.h" #include "src/utils/constants.h" namespace libgav1 { namespace dsp { namespace { #include "src/dsp/cdef.inc" // Silence unused function warnings when CdefDirection_C is obviated. #if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS || \ !defined(LIBGAV1_Dsp8bpp_CdefDirection) || \ (LIBGAV1_MAX_BITDEPTH >= 10 && !defined(LIBGAV1_Dsp10bpp_CdefDirection)) constexpr int16_t kDivisionTable[] = {840, 420, 280, 210, 168, 140, 120, 105}; int32_t Square(int32_t x) { return x * x; } template void CdefDirection_C(const void* const source, ptrdiff_t stride, uint8_t* const direction, int* const variance) { assert(direction != nullptr); assert(variance != nullptr); const auto* src = static_cast(source); stride /= sizeof(Pixel); int32_t cost[8] = {}; // |partial| does not have to be int32_t for 8bpp. int16_t will suffice. We // use int32_t to keep it simple since |cost| will have to be int32_t. int32_t partial[8][15] = {}; for (int i = 0; i < 8; ++i) { for (int j = 0; j < 8; ++j) { const int x = (src[j] >> (bitdepth - 8)) - 128; partial[0][i + j] += x; partial[1][i + j / 2] += x; partial[2][i] += x; partial[3][3 + i - j / 2] += x; partial[4][7 + i - j] += x; partial[5][3 - i / 2 + j] += x; partial[6][j] += x; partial[7][i / 2 + j] += x; } src += stride; } for (int i = 0; i < 8; ++i) { cost[2] += Square(partial[2][i]); cost[6] += Square(partial[6][i]); } cost[2] *= kDivisionTable[7]; cost[6] *= kDivisionTable[7]; for (int i = 0; i < 7; ++i) { cost[0] += (Square(partial[0][i]) + Square(partial[0][14 - i])) * kDivisionTable[i]; cost[4] += (Square(partial[4][i]) + Square(partial[4][14 - i])) * kDivisionTable[i]; } cost[0] += Square(partial[0][7]) * kDivisionTable[7]; cost[4] += Square(partial[4][7]) * kDivisionTable[7]; for (int i = 1; i < 8; i += 2) { for (int j = 0; j < 5; ++j) { cost[i] += Square(partial[i][3 + j]); } cost[i] *= kDivisionTable[7]; for (int j = 0; j < 3; ++j) { cost[i] += (Square(partial[i][j]) + Square(partial[i][10 - j])) * kDivisionTable[2 * j + 1]; } } int32_t best_cost = 0; *direction = 0; for (int i = 0; i < 8; ++i) { if (cost[i] > best_cost) { best_cost = cost[i]; *direction = i; } } *variance = (best_cost - cost[(*direction + 4) & 7]) >> 10; } #endif // LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS || // !defined(LIBGAV1_Dsp8bpp_CdefDirection) || // (LIBGAV1_MAX_BITDEPTH >= 10 && // !defined(LIBGAV1_Dsp10bpp_CdefDirection)) // Silence unused function warnings when CdefFilter_C is obviated. #if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS || \ !defined(LIBGAV1_Dsp8bpp_CdefFilters) || \ (LIBGAV1_MAX_BITDEPTH >= 10 && !defined(LIBGAV1_Dsp10bpp_CdefFilters)) int Constrain(int diff, int threshold, int damping) { assert(threshold != 0); damping = std::max(0, damping - FloorLog2(threshold)); const int sign = (diff < 0) ? -1 : 1; return sign * Clip3(threshold - (std::abs(diff) >> damping), 0, std::abs(diff)); } // Filters the source block. It doesn't check whether the candidate pixel is // inside the frame. However it requires the source input to be padded with a // constant large value (kCdefLargeValue) if at the boundary. template void CdefFilter_C(const uint16_t* src, const ptrdiff_t src_stride, const int block_height, const int primary_strength, const int secondary_strength, const int damping, const int direction, void* const dest, const ptrdiff_t dest_stride) { static_assert(block_width == 4 || block_width == 8, "Invalid CDEF width."); static_assert(enable_primary || enable_secondary, ""); assert(block_height == 4 || block_height == 8); assert(direction >= 0 && direction <= 7); constexpr int coeff_shift = bitdepth - 8; // Section 5.9.19. CDEF params syntax. assert(primary_strength >= 0 && primary_strength <= 15 << coeff_shift); assert(secondary_strength >= 0 && secondary_strength <= 4 << coeff_shift && secondary_strength != 3 << coeff_shift); assert(primary_strength != 0 || secondary_strength != 0); // damping is decreased by 1 for chroma. assert((damping >= 3 && damping <= 6 + coeff_shift) || (damping >= 2 && damping <= 5 + coeff_shift)); // When only primary_strength or secondary_strength are non-zero the number // of pixels inspected (4 for primary_strength, 8 for secondary_strength) and // the taps used don't exceed the amount the sum is // descaled by (16) so we can skip tracking and clipping to the minimum and // maximum value observed. constexpr bool clipping_required = enable_primary && enable_secondary; static constexpr int kCdefSecondaryTaps[2] = {kCdefSecondaryTap0, kCdefSecondaryTap1}; auto* dst = static_cast(dest); const ptrdiff_t dst_stride = dest_stride / sizeof(Pixel); int y = block_height; do { int x = 0; do { int16_t sum = 0; const uint16_t pixel_value = src[x]; uint16_t max_value = pixel_value; uint16_t min_value = pixel_value; for (int k = 0; k < 2; ++k) { static constexpr int signs[] = {-1, 1}; for (const int& sign : signs) { if (enable_primary) { const int dy = sign * kCdefDirections[direction][k][0]; const int dx = sign * kCdefDirections[direction][k][1]; const uint16_t value = src[dy * src_stride + dx + x]; // Note: the summation can ignore the condition check in SIMD // implementation, because Constrain() will return 0 when // value == kCdefLargeValue. if (value != kCdefLargeValue) { sum += Constrain(value - pixel_value, primary_strength, damping) * kCdefPrimaryTaps[(primary_strength >> coeff_shift) & 1][k]; if (clipping_required) { max_value = std::max(value, max_value); min_value = std::min(value, min_value); } } } if (enable_secondary) { static constexpr int offsets[] = {-2, 2}; for (const int& offset : offsets) { const int dy = sign * kCdefDirections[direction + offset][k][0]; const int dx = sign * kCdefDirections[direction + offset][k][1]; const uint16_t value = src[dy * src_stride + dx + x]; // Note: the summation can ignore the condition check in SIMD // implementation. if (value != kCdefLargeValue) { sum += Constrain(value - pixel_value, secondary_strength, damping) * kCdefSecondaryTaps[k]; if (clipping_required) { max_value = std::max(value, max_value); min_value = std::min(value, min_value); } } } } } } const int offset = (8 + sum - (sum < 0)) >> 4; if (clipping_required) { dst[x] = static_cast( Clip3(pixel_value + offset, min_value, max_value)); } else { dst[x] = static_cast(pixel_value + offset); } } while (++x < block_width); src += src_stride; dst += dst_stride; } while (--y != 0); } #endif // LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS || // !defined(LIBGAV1_Dsp8bpp_CdefFilters) || // (LIBGAV1_MAX_BITDEPTH >= 10 && // !defined(LIBGAV1_Dsp10bpp_CdefFilters)) void Init8bpp() { Dsp* const dsp = dsp_internal::GetWritableDspTable(8); assert(dsp != nullptr); #if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS dsp->cdef_direction = CdefDirection_C<8, uint8_t>; dsp->cdef_filters[0][0] = CdefFilter_C<4, 8, uint8_t>; dsp->cdef_filters[0][1] = CdefFilter_C<4, 8, uint8_t, /*enable_primary=*/true, /*enable_secondary=*/false>; dsp->cdef_filters[0][2] = CdefFilter_C<4, 8, uint8_t, /*enable_primary=*/false>; dsp->cdef_filters[1][0] = CdefFilter_C<8, 8, uint8_t>; dsp->cdef_filters[1][1] = CdefFilter_C<8, 8, uint8_t, /*enable_primary=*/true, /*enable_secondary=*/false>; dsp->cdef_filters[1][2] = CdefFilter_C<8, 8, uint8_t, /*enable_primary=*/false>; #else // !LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS static_cast(dsp); #ifndef LIBGAV1_Dsp8bpp_CdefDirection dsp->cdef_direction = CdefDirection_C<8, uint8_t>; #endif #ifndef LIBGAV1_Dsp8bpp_CdefFilters dsp->cdef_filters[0][0] = CdefFilter_C<4, 8, uint8_t>; dsp->cdef_filters[0][1] = CdefFilter_C<4, 8, uint8_t, /*enable_primary=*/true, /*enable_secondary=*/false>; dsp->cdef_filters[0][2] = CdefFilter_C<4, 8, uint8_t, /*enable_primary=*/false>; dsp->cdef_filters[1][0] = CdefFilter_C<8, 8, uint8_t>; dsp->cdef_filters[1][1] = CdefFilter_C<8, 8, uint8_t, /*enable_primary=*/true, /*enable_secondary=*/false>; dsp->cdef_filters[1][2] = CdefFilter_C<8, 8, uint8_t, /*enable_primary=*/false>; #endif #endif // LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS } #if LIBGAV1_MAX_BITDEPTH >= 10 void Init10bpp() { Dsp* const dsp = dsp_internal::GetWritableDspTable(10); assert(dsp != nullptr); #if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS dsp->cdef_direction = CdefDirection_C<10, uint16_t>; dsp->cdef_filters[0][0] = CdefFilter_C<4, 10, uint16_t>; dsp->cdef_filters[0][1] = CdefFilter_C<4, 10, uint16_t, /*enable_primary=*/true, /*enable_secondary=*/false>; dsp->cdef_filters[0][2] = CdefFilter_C<4, 10, uint16_t, /*enable_primary=*/false>; dsp->cdef_filters[1][0] = CdefFilter_C<8, 10, uint16_t>; dsp->cdef_filters[1][1] = CdefFilter_C<8, 10, uint16_t, /*enable_primary=*/true, /*enable_secondary=*/false>; dsp->cdef_filters[1][2] = CdefFilter_C<8, 10, uint16_t, /*enable_primary=*/false>; #else // !LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS static_cast(dsp); #ifndef LIBGAV1_Dsp10bpp_CdefDirection dsp->cdef_direction = CdefDirection_C<10, uint16_t>; #endif #ifndef LIBGAV1_Dsp10bpp_CdefFilters dsp->cdef_filters[0][0] = CdefFilter_C<4, 10, uint16_t>; dsp->cdef_filters[0][1] = CdefFilter_C<4, 10, uint16_t, /*enable_primary=*/true, /*enable_secondary=*/false>; dsp->cdef_filters[0][2] = CdefFilter_C<4, 10, uint16_t, /*enable_primary=*/false>; dsp->cdef_filters[1][0] = CdefFilter_C<8, 10, uint16_t>; dsp->cdef_filters[1][1] = CdefFilter_C<8, 10, uint16_t, /*enable_primary=*/true, /*enable_secondary=*/false>; dsp->cdef_filters[1][2] = CdefFilter_C<8, 10, uint16_t, /*enable_primary=*/false>; #endif #endif // LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS } #endif } // namespace void CdefInit_C() { Init8bpp(); #if LIBGAV1_MAX_BITDEPTH >= 10 Init10bpp(); #endif } } // namespace dsp } // namespace libgav1