// Copyright 2021 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/post_filter.h" #include #include #include #include #include #include #include #include #include #include "absl/time/clock.h" #include "absl/time/time.h" #include "gtest/gtest.h" #include "src/dsp/cdef.h" #include "src/dsp/dsp.h" #include "src/dsp/super_res.h" #include "src/frame_scratch_buffer.h" #include "src/obu_parser.h" #include "src/threading_strategy.h" #include "src/utils/array_2d.h" #include "src/utils/common.h" #include "src/utils/constants.h" #include "src/utils/memory.h" #include "src/utils/types.h" #include "src/yuv_buffer.h" #include "tests/block_utils.h" #include "tests/third_party/libvpx/acm_random.h" #include "tests/utils.h" namespace libgav1 { namespace { constexpr char kCdef[] = "Cdef"; constexpr char kApplyCdefName[] = "ApplyCdef"; constexpr int kMaxBlockWidth4x4 = 32; constexpr int kMaxBlockHeight4x4 = 32; constexpr int kMaxTestFrameSize = 1920 * 1080; int GetIdFromInputParam(int subsampling_x, int subsampling_y, int height) { int id = subsampling_x * 8 + subsampling_y * 4; if (height == 288) { id += 0; } else if (height == 480) { id += 1; } else if (height == 1080) { id += 2; } else { id += 3; } return id; } const char* GetSuperResDigest8bpp(int id, int plane) { static const char* const kDigestSuperRes[][kMaxPlanes] = { { // all input is 0. "ff5f7a63d3b1f9176e216eb01a0387ad", // kPlaneY. "38b6551d7ac3e86c8af407d5a1aa36dc", // kPlaneU. "38b6551d7ac3e86c8af407d5a1aa36dc", // kPlaneV. }, { // all input is 1. "819f21dcce0e779180bbd613a9e3543c", // kPlaneY. "e784bfa8f517d83b014c3dcd45b780a5", // kPlaneU. "e784bfa8f517d83b014c3dcd45b780a5", // kPlaneV. }, { // all input is 128. "2d6ea5b39f9168d56c2e2b8846d208ec", // kPlaneY. "8030b6e70f1544efbc37b902d3f88bd3", // kPlaneU. "8030b6e70f1544efbc37b902d3f88bd3", // kPlaneV. }, { // all input is 255. "5c0b4bc50e0980dc6ba7c042d3b50a5e", // kPlaneY. "3c566ef847c45be09ddac297123a3bad", // kPlaneU. "3c566ef847c45be09ddac297123a3bad", // kPlaneV. }, { // random input. "50514467dd6a5c3a8268eddaa542c41f", // kPlaneY. "3ce720c2b5b44928e1477b11040e5c00", // kPlaneU. "3ce720c2b5b44928e1477b11040e5c00", // kPlaneV. }, }; return kDigestSuperRes[id][plane]; } #if LIBGAV1_MAX_BITDEPTH >= 10 const char* GetSuperResDigest10bpp(int id, int plane) { // Digests are in Y/U/V order. static const char* const kDigestSuperRes[][kMaxPlanes] = { { // all input is 0. "fccb1f57b252b1a86d335aea929d1d58", "2f244a56091c9705794e92e6bcc38058", "2f244a56091c9705794e92e6bcc38058", }, { // all input is 1. "de8556204999d6e4bf74cfdde61a095b", "e7d0f4ce6df81c46de95da7790a67384", "e7d0f4ce6df81c46de95da7790a67384", }, { // all input is 512. "d3b6980363eb9b808885537b3485af87", "bcffddb26210da6861e7b31414e58b77", "bcffddb26210da6861e7b31414e58b77", }, { // all input is 1023. "ce0762aeee1cdef1db101e4ca39bcbd6", "33aeaa7f5d7c032e3dfda43925c3dcb2", "33aeaa7f5d7c032e3dfda43925c3dcb2", }, { // random input. "63c701bceb187ffa535be15ae58f8171", "f570e30e9ea8d2a1e6d99202cd2f8994", "f570e30e9ea8d2a1e6d99202cd2f8994", }, }; return kDigestSuperRes[id][plane]; } #endif // LIBGAV1_MAX_BITDEPTH >= 10 #if LIBGAV1_MAX_BITDEPTH == 12 const char* GetSuperResDigest12bpp(int id, int plane) { // Digests are in Y/U/V order. static const char* const kDigestSuperRes[][kMaxPlanes] = { { // all input is 0. "fccb1f57b252b1a86d335aea929d1d58", "2f244a56091c9705794e92e6bcc38058", "2f244a56091c9705794e92e6bcc38058", }, { // all input is 1. "de8556204999d6e4bf74cfdde61a095b", "e7d0f4ce6df81c46de95da7790a67384", "e7d0f4ce6df81c46de95da7790a67384", }, { // all input is 2048. "83d600a7b3dc9bc3f710668ee2244e6b", "468eec1453edc1befeb8a346f61950a7", "468eec1453edc1befeb8a346f61950a7", }, { // all input is 4095. "30bdb1dfee2b02b12b38e6b9f6287e27", "34d673f075d2caa93a2f648ee3569e20", "34d673f075d2caa93a2f648ee3569e20", }, { // random input. "f10f21f5322231d991550fce7ef9787d", "a2d8b6140bd5002e86644ef433b8eb42", "a2d8b6140bd5002e86644ef433b8eb42", }, }; return kDigestSuperRes[id][plane]; } #endif // LIBGAV1_MAX_BITDEPTH == 12 } // namespace // This type is used to parameterize the tests so is defined outside the // anonymous namespace to avoid the GCC -Wsubobject-linkage warning. struct FrameSizeParam { FrameSizeParam(uint32_t width, uint32_t upscaled_width, uint32_t height, int8_t ss_x, int8_t ss_y) : width(width), upscaled_width(upscaled_width), height(height), subsampling_x(ss_x), subsampling_y(ss_y) {} uint32_t width; uint32_t upscaled_width; uint32_t height; int8_t subsampling_x; int8_t subsampling_y; }; // Print operators must be defined in the same namespace as the type for the // lookup to work correctly. static std::ostream& operator<<(std::ostream& os, const FrameSizeParam& param) { return os << param.width << "x" << param.height << ", upscaled_width: " << param.upscaled_width << ", subsampling(x/y): " << static_cast(param.subsampling_x) << "/" << static_cast(param.subsampling_y); } // Note the following test classes access private functions/members of // PostFilter. To be declared friends of PostFilter they must not have internal // linkage (they must be outside the anonymous namespace). template class PostFilterTestBase : public testing::TestWithParam { public: static_assert(bitdepth >= kBitdepth8 && bitdepth <= LIBGAV1_MAX_BITDEPTH, ""); PostFilterTestBase() = default; PostFilterTestBase(const PostFilterTestBase&) = delete; PostFilterTestBase& operator=(const PostFilterTestBase&) = delete; ~PostFilterTestBase() override = default; void SetUp() override { // Allocate buffer_ with a border size of kBorderPixels (which is // subsampled for chroma planes). Some tests (for loop restoration) only use // the nearest 2 or 3 pixels (for both luma and chroma planes) in the // border. ASSERT_TRUE(buffer_.Realloc( bitdepth, /*is_monochrome=*/false, frame_size_.upscaled_width, frame_size_.height, frame_size_.subsampling_x, frame_size_.subsampling_y, kBorderPixels, kBorderPixels, kBorderPixels, kBorderPixels, nullptr, nullptr, nullptr)); ASSERT_TRUE(loop_restoration_border_.Realloc( bitdepth, /*is_monochrome=*/false, frame_size_.upscaled_width, frame_size_.height, frame_size_.subsampling_x, frame_size_.subsampling_y, kBorderPixels, kBorderPixels, kBorderPixels, kBorderPixels, nullptr, nullptr, nullptr)); for (int plane = kPlaneY; plane < kMaxPlanes; ++plane) { const int8_t subsampling_x = (plane == kPlaneY) ? 0 : frame_size_.subsampling_x; const int8_t subsampling_y = (plane == kPlaneY) ? 0 : frame_size_.subsampling_y; width_[plane] = frame_size_.width >> subsampling_x; upscaled_width_[plane] = frame_size_.upscaled_width >> subsampling_x; stride_[plane] = (frame_size_.upscaled_width + 2 * kBorderPixels) >> subsampling_x; height_[plane] = (frame_size_.height + 2 * kBorderPixels) >> subsampling_y; reference_buffer_[plane].reserve(stride_[plane] * height_[plane]); reference_buffer_[plane].resize(stride_[plane] * height_[plane]); std::fill(reference_buffer_[plane].begin(), reference_buffer_[plane].end(), 0); } } protected: YuvBuffer buffer_; YuvBuffer cdef_border_; YuvBuffer loop_restoration_border_; uint32_t width_[kMaxPlanes]; uint32_t upscaled_width_[kMaxPlanes]; uint32_t stride_[kMaxPlanes]; uint32_t height_[kMaxPlanes]; std::vector reference_buffer_[kMaxPlanes]; const FrameSizeParam frame_size_ = GetParam(); }; template class PostFilterHelperFuncTest : public PostFilterTestBase { public: static_assert(bitdepth >= kBitdepth8 && bitdepth <= LIBGAV1_MAX_BITDEPTH, ""); PostFilterHelperFuncTest() = default; PostFilterHelperFuncTest(const PostFilterHelperFuncTest&) = delete; PostFilterHelperFuncTest& operator=(const PostFilterHelperFuncTest&) = delete; ~PostFilterHelperFuncTest() override = default; protected: using PostFilterTestBase::buffer_; using PostFilterTestBase::cdef_border_; using PostFilterTestBase::loop_restoration_border_; using PostFilterTestBase::width_; using PostFilterTestBase::upscaled_width_; using PostFilterTestBase::stride_; using PostFilterTestBase::height_; using PostFilterTestBase::reference_buffer_; using PostFilterTestBase::frame_size_; void SetUp() override { PostFilterTestBase::SetUp(); for (int plane = kPlaneY; plane < kMaxPlanes; ++plane) { const int8_t subsampling_x = (plane == kPlaneY) ? 0 : frame_size_.subsampling_x; const int8_t subsampling_y = (plane == kPlaneY) ? 0 : frame_size_.subsampling_y; width_[plane] = frame_size_.width >> subsampling_x; upscaled_width_[plane] = frame_size_.upscaled_width >> subsampling_x; stride_[plane] = (frame_size_.upscaled_width >> subsampling_x) + 2 * kRestorationHorizontalBorder; height_[plane] = (frame_size_.height >> subsampling_y) + 2 * kRestorationVerticalBorder; reference_buffer_[plane].reserve(stride_[plane] * height_[plane]); reference_buffer_[plane].resize(stride_[plane] * height_[plane]); std::fill(reference_buffer_[plane].begin(), reference_buffer_[plane].end(), 0); buffer_border_corner_[plane] = reinterpret_cast(buffer_.data(plane)) - buffer_.stride(plane) / sizeof(Pixel) * kRestorationVerticalBorder - kRestorationHorizontalBorder; loop_restoration_border_corner_[plane] = reinterpret_cast(loop_restoration_border_.data(plane)) - loop_restoration_border_.stride(plane) / sizeof(Pixel) * kRestorationVerticalBorder - kRestorationHorizontalBorder; } } void TestExtendFrame(bool use_fixed_values, Pixel value); void TestAdjustFrameBufferPointer(); void TestPrepareLoopRestorationBlock(); // Fill the frame buffer with either a fixed value, or random values. // If fill in with random values, make special operations at buffer // boundaries. Make the outer most 3 pixel wide borders the same value // as their immediate inner neighbor. For example: // 4 4 4 4 5 6 6 6 6 // 4 4 4 4 5 6 6 6 6 // 4 4 4 4 5 6 6 6 6 // --------- // 4 4 4 | 4 5 6 | 6 6 6 // 1 1 1 | 1 0 1 | 1 1 1 // 0 0 0 | 0 1 0 | 0 0 0 // 1 1 1 | 1 0 1 | 1 1 1 // 0 0 0 | 0 1 0 | 0 0 0 // 6 6 6 | 6 5 4 | 4 4 4 // ------- // 6 6 6 6 5 4 4 4 4 // 6 6 6 6 5 4 4 4 4 // 6 6 6 6 5 4 4 4 4 // Pixels within box is the current block. Outside is extended area from it. void FillBuffer(bool use_fixed_values, Pixel value); // Points to the upper left corner of the restoration border in buffer_. Pixel* buffer_border_corner_[kMaxPlanes]; // Points to the upper left corner of the restoration border in // loop_restoration_border_. Pixel* loop_restoration_border_corner_[kMaxPlanes]; }; template void PostFilterHelperFuncTest::FillBuffer( bool use_fixed_values, Pixel value) { if (use_fixed_values) { for (int plane = kPlaneY; plane < kMaxPlanes; ++plane) { // Fill buffer with a fixed value. std::fill(reference_buffer_[plane].begin(), reference_buffer_[plane].end(), value); // Fill frame buffer. Note that the border is not filled. auto* row = reinterpret_cast(buffer_.data(plane)); for (int i = 0; i < buffer_.height(plane); ++i) { std::fill(row, row + width_[plane], value); row += buffer_.stride(plane) / sizeof(Pixel); } } } else { // Random value. libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed()); const int mask = (1 << bitdepth) - 1; for (int plane = kPlaneY; plane < kMaxPlanes; ++plane) { // Fill buffer with random values. std::vector line_buffer(stride_[plane]); std::fill(line_buffer.begin(), line_buffer.end(), 0); for (int i = kRestorationHorizontalBorder; i < stride_[plane] - kRestorationHorizontalBorder; ++i) { line_buffer[i] = rnd.Rand16() & mask; } // Copy boundary values to extended border. for (int i = 0; i < kRestorationHorizontalBorder; ++i) { line_buffer[i] = line_buffer[kRestorationHorizontalBorder]; line_buffer[stride_[plane] - i - 1] = line_buffer[stride_[plane] - 1 - kRestorationHorizontalBorder]; } // The first three rows are the same as the line_buffer. for (int i = 0; i < kRestorationVerticalBorder + 1; ++i) { std::copy(line_buffer.begin(), line_buffer.end(), reference_buffer_[plane].begin() + i * stride_[plane]); } for (int i = kRestorationVerticalBorder + 1; i < height_[plane] - kRestorationVerticalBorder; ++i) { for (int j = kRestorationHorizontalBorder; j < stride_[plane] - kRestorationHorizontalBorder; ++j) { line_buffer[j] = rnd.Rand16() & mask; } for (int j = 0; j < kRestorationHorizontalBorder; ++j) { line_buffer[j] = line_buffer[kRestorationHorizontalBorder]; line_buffer[stride_[plane] - j - 1] = line_buffer[stride_[plane] - 1 - kRestorationHorizontalBorder]; } std::copy(line_buffer.begin(), line_buffer.end(), reference_buffer_[plane].begin() + i * stride_[plane]); } // The extended border are the same as the line_buffer. for (int i = 0; i < kRestorationVerticalBorder; ++i) { std::copy(line_buffer.begin(), line_buffer.end(), reference_buffer_[plane].begin() + (height_[plane] - kRestorationVerticalBorder + i) * stride_[plane]); } // Fill frame buffer. Note that the border is not filled. for (int i = 0; i < buffer_.height(plane); ++i) { memcpy(buffer_.data(plane) + i * buffer_.stride(plane), reference_buffer_[plane].data() + kRestorationHorizontalBorder + (i + kRestorationVerticalBorder) * stride_[plane], sizeof(Pixel) * width_[plane]); } } } } template void PostFilterHelperFuncTest::TestExtendFrame( bool use_fixed_values, Pixel value) { ObuFrameHeader frame_header = {}; frame_header.upscaled_width = frame_size_.upscaled_width; frame_header.width = frame_size_.width; frame_header.height = frame_size_.height; ObuSequenceHeader sequence_header; sequence_header.color_config.bitdepth = bitdepth; sequence_header.color_config.is_monochrome = false; sequence_header.color_config.subsampling_x = frame_size_.subsampling_x; sequence_header.color_config.subsampling_y = frame_size_.subsampling_y; const dsp::Dsp* const dsp = dsp::GetDspTable(bitdepth); ASSERT_NE(dsp, nullptr); FrameScratchBuffer frame_scratch_buffer; PostFilter post_filter(frame_header, sequence_header, &frame_scratch_buffer, &buffer_, dsp, /*do_post_filter_mask=*/0x00); FillBuffer(use_fixed_values, value); for (int plane = kPlaneY; plane < kMaxPlanes; ++plane) { const int plane_width = plane == kPlaneY ? frame_header.upscaled_width : frame_header.upscaled_width >> sequence_header.color_config.subsampling_x; const int plane_height = plane == kPlaneY ? frame_header.height : frame_header.height >> sequence_header.color_config.subsampling_y; PostFilter::ExtendFrame( reinterpret_cast(buffer_.data(plane)), plane_width, plane_height, buffer_.stride(plane) / sizeof(Pixel), kRestorationHorizontalBorder, kRestorationHorizontalBorder, kRestorationVerticalBorder, kRestorationVerticalBorder); const bool success = test_utils::CompareBlocks( buffer_border_corner_[plane], reference_buffer_[plane].data(), stride_[plane], height_[plane], buffer_.stride(plane) / sizeof(Pixel), stride_[plane], /*check_padding=*/false, /*print_diff=*/false); ASSERT_TRUE(success) << "Failure of extend frame at plane: " << plane; } } template class PostFilterSuperResTest : public PostFilterTestBase { public: static_assert(bitdepth >= kBitdepth8 && bitdepth <= LIBGAV1_MAX_BITDEPTH, ""); PostFilterSuperResTest() { test_utils::ResetDspTable(bitdepth); dsp::SuperResInit_C(); dsp::SuperResInit_SSE4_1(); dsp::SuperResInit_NEON(); } PostFilterSuperResTest(const PostFilterSuperResTest&) = delete; PostFilterSuperResTest& operator=(const PostFilterSuperResTest&) = delete; ~PostFilterSuperResTest() override = default; protected: using PostFilterTestBase::buffer_; using PostFilterTestBase::width_; using PostFilterTestBase::upscaled_width_; using PostFilterTestBase::stride_; using PostFilterTestBase::height_; using PostFilterTestBase::reference_buffer_; using PostFilterTestBase::frame_size_; void TestApplySuperRes(bool use_fixed_values, Pixel value, int id, bool multi_threaded); }; // This class must be in namespace libgav1 to access private member function // of class PostFilter in src/post_filter.h. template void PostFilterSuperResTest::TestApplySuperRes( bool use_fixed_values, Pixel value, int id, bool multi_threaded) { ObuFrameHeader frame_header = {}; frame_header.width = frame_size_.width; frame_header.upscaled_width = frame_size_.upscaled_width; frame_header.height = frame_size_.height; frame_header.rows4x4 = DivideBy4(frame_size_.height); frame_header.columns4x4 = DivideBy4(frame_size_.width); frame_header.tile_info.tile_count = 1; ObuSequenceHeader sequence_header; sequence_header.color_config.bitdepth = bitdepth; sequence_header.color_config.is_monochrome = false; sequence_header.color_config.subsampling_x = frame_size_.subsampling_x; sequence_header.color_config.subsampling_y = frame_size_.subsampling_y; // Apply SuperRes. Array2D cdef_index; Array2D inter_transform_sizes; const dsp::Dsp* const dsp = dsp::GetDspTable(bitdepth); ASSERT_NE(dsp, nullptr); constexpr int kNumThreads = 4; FrameScratchBuffer frame_scratch_buffer; if (multi_threaded) { ASSERT_TRUE(frame_scratch_buffer.threading_strategy.Reset(frame_header, kNumThreads)); } const int pixel_size = sequence_header.color_config.bitdepth == 8 ? sizeof(uint8_t) : sizeof(uint16_t); ASSERT_TRUE(frame_scratch_buffer.superres_coefficients[kPlaneTypeY].Resize( kSuperResFilterTaps * Align(frame_header.upscaled_width, 16) * pixel_size)); if (!sequence_header.color_config.is_monochrome && sequence_header.color_config.subsampling_x != 0) { ASSERT_TRUE(frame_scratch_buffer.superres_coefficients[kPlaneTypeUV].Resize( kSuperResFilterTaps * Align(SubsampledValue(frame_header.upscaled_width, 1), 16) * pixel_size)); } ASSERT_TRUE(frame_scratch_buffer.superres_line_buffer.Realloc( sequence_header.color_config.bitdepth, sequence_header.color_config.is_monochrome, MultiplyBy4(frame_header.columns4x4), (multi_threaded ? kNumThreads : 1), sequence_header.color_config.subsampling_x, /*subsampling_y=*/0, 2 * kSuperResHorizontalBorder, 2 * (kSuperResHorizontalBorder + kSuperResHorizontalPadding), 0, 0, nullptr, nullptr, nullptr)); PostFilter post_filter(frame_header, sequence_header, &frame_scratch_buffer, &buffer_, dsp, /*do_post_filter_mask=*/0x04); const int num_planes = sequence_header.color_config.is_monochrome ? kMaxPlanesMonochrome : kMaxPlanes; int width[kMaxPlanes]; int upscaled_width[kMaxPlanes]; int height[kMaxPlanes]; for (int plane = kPlaneY; plane < num_planes; ++plane) { const int8_t subsampling_x = (plane == kPlaneY) ? 0 : frame_size_.subsampling_x; const int8_t subsampling_y = (plane == kPlaneY) ? 0 : frame_size_.subsampling_y; width[plane] = frame_size_.width >> subsampling_x; upscaled_width[plane] = frame_size_.upscaled_width >> subsampling_x; height[plane] = frame_size_.height >> subsampling_y; if (use_fixed_values) { auto* src = reinterpret_cast(post_filter.cdef_buffer_[plane]); for (int y = 0; y < height[plane]; ++y) { for (int x = 0; x < width[plane]; ++x) { src[x] = value; } src += buffer_.stride(plane) / sizeof(Pixel); } } else { // Random input. const int mask = (1 << bitdepth) - 1; libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed()); auto* src = reinterpret_cast(post_filter.cdef_buffer_[plane]); for (int y = 0; y < height[plane]; ++y) { for (int x = 0; x < width[plane]; ++x) { src[x] = rnd.Rand16() & mask; } src += buffer_.stride(plane) / sizeof(Pixel); } } } if (multi_threaded) { post_filter.ApplySuperResThreaded(); } else { std::array buffers = { post_filter.cdef_buffer_[kPlaneY], post_filter.cdef_buffer_[kPlaneU], post_filter.cdef_buffer_[kPlaneV]}; std::array dst = { post_filter.GetSuperResBuffer(static_cast(kPlaneY), 0, 0), post_filter.GetSuperResBuffer(static_cast(kPlaneU), 0, 0), post_filter.GetSuperResBuffer(static_cast(kPlaneV), 0, 0)}; std::array rows = { frame_header.rows4x4 * 4, (frame_header.rows4x4 * 4) >> frame_size_.subsampling_y, (frame_header.rows4x4 * 4) >> frame_size_.subsampling_y}; post_filter.ApplySuperRes(buffers, rows, /*line_buffer_row=*/-1, dst); } // Check md5. std::vector output; for (int plane = kPlaneY; plane < num_planes; ++plane) { output.reserve(upscaled_width[plane] * height[plane]); output.resize(upscaled_width[plane] * height[plane]); auto* dst = reinterpret_cast( post_filter.GetSuperResBuffer(static_cast(plane), 0, 0)); for (int y = 0; y < height[plane]; ++y) { for (int x = 0; x < upscaled_width[plane]; ++x) { output[y * upscaled_width[plane] + x] = dst[x]; } dst += buffer_.stride(plane) / sizeof(Pixel); } const std::string digest = test_utils::GetMd5Sum( output.data(), upscaled_width[plane] * height[plane] * sizeof(Pixel)); printf("MD5: %s\n", digest.c_str()); const char* expected_digest = nullptr; switch (bitdepth) { case 8: expected_digest = GetSuperResDigest8bpp(id, plane); break; #if LIBGAV1_MAX_BITDEPTH >= 10 case 10: expected_digest = GetSuperResDigest10bpp(id, plane); break; #endif #if LIBGAV1_MAX_BITDEPTH == 12 case 12: expected_digest = GetSuperResDigest12bpp(id, plane); break; #endif } ASSERT_NE(expected_digest, nullptr); EXPECT_STREQ(digest.c_str(), expected_digest); } } using PostFilterSuperResTest8bpp = PostFilterSuperResTest<8, uint8_t>; const FrameSizeParam kTestParamSuperRes[] = { FrameSizeParam(176, 352, 288, 1, 1)}; TEST_P(PostFilterSuperResTest8bpp, ApplySuperRes) { TestApplySuperRes(true, 0, 0, false); TestApplySuperRes(true, 1, 1, false); TestApplySuperRes(true, 128, 2, false); TestApplySuperRes(true, 255, 3, false); TestApplySuperRes(false, 0, 4, false); } TEST_P(PostFilterSuperResTest8bpp, ApplySuperResThreaded) { TestApplySuperRes(true, 0, 0, true); TestApplySuperRes(true, 1, 1, true); TestApplySuperRes(true, 128, 2, true); TestApplySuperRes(true, 255, 3, true); TestApplySuperRes(false, 0, 4, true); } INSTANTIATE_TEST_SUITE_P(PostFilterSuperResTestInstance, PostFilterSuperResTest8bpp, testing::ValuesIn(kTestParamSuperRes)); using PostFilterHelperFuncTest8bpp = PostFilterHelperFuncTest<8, uint8_t>; const FrameSizeParam kTestParamExtendFrame[] = { FrameSizeParam(16, 16, 16, 1, 1), FrameSizeParam(64, 64, 64, 1, 1), FrameSizeParam(128, 128, 64, 1, 1), FrameSizeParam(64, 64, 128, 1, 1), FrameSizeParam(352, 352, 288, 1, 1), FrameSizeParam(720, 720, 480, 1, 1), FrameSizeParam(1080, 1080, 720, 1, 1), FrameSizeParam(16, 16, 16, 0, 0), FrameSizeParam(64, 64, 64, 0, 0), FrameSizeParam(128, 128, 64, 0, 0), FrameSizeParam(64, 64, 128, 0, 0), FrameSizeParam(352, 352, 288, 0, 0), FrameSizeParam(720, 720, 480, 0, 0), FrameSizeParam(1080, 1080, 720, 0, 0)}; TEST_P(PostFilterHelperFuncTest8bpp, ExtendFrame) { TestExtendFrame(true, 0); TestExtendFrame(true, 1); TestExtendFrame(true, 128); TestExtendFrame(true, 255); TestExtendFrame(false, 0); } INSTANTIATE_TEST_SUITE_P(PostFilterHelperFuncTestInstance, PostFilterHelperFuncTest8bpp, testing::ValuesIn(kTestParamExtendFrame)); #if LIBGAV1_MAX_BITDEPTH >= 10 using PostFilterSuperResTest10bpp = PostFilterSuperResTest<10, uint16_t>; TEST_P(PostFilterSuperResTest10bpp, ApplySuperRes) { TestApplySuperRes(true, 0, 0, false); TestApplySuperRes(true, 1, 1, false); TestApplySuperRes(true, 1 << 9, 2, false); TestApplySuperRes(true, (1 << 10) - 1, 3, false); TestApplySuperRes(false, 0, 4, false); } TEST_P(PostFilterSuperResTest10bpp, ApplySuperResThreaded) { TestApplySuperRes(true, 0, 0, true); TestApplySuperRes(true, 1, 1, true); TestApplySuperRes(true, 1 << 9, 2, true); TestApplySuperRes(true, (1 << 10) - 1, 3, true); TestApplySuperRes(false, 0, 4, true); } INSTANTIATE_TEST_SUITE_P(PostFilterSuperResTestInstance, PostFilterSuperResTest10bpp, testing::ValuesIn(kTestParamSuperRes)); using PostFilterHelperFuncTest10bpp = PostFilterHelperFuncTest<10, uint16_t>; TEST_P(PostFilterHelperFuncTest10bpp, ExtendFrame) { TestExtendFrame(true, 0); TestExtendFrame(true, 1); TestExtendFrame(true, 255); TestExtendFrame(true, (1 << 10) - 1); TestExtendFrame(false, 0); } INSTANTIATE_TEST_SUITE_P(PostFilterHelperFuncTestInstance, PostFilterHelperFuncTest10bpp, testing::ValuesIn(kTestParamExtendFrame)); #endif // LIBGAV1_MAX_BITDEPTH >= 10 #if LIBGAV1_MAX_BITDEPTH == 12 using PostFilterSuperResTest12bpp = PostFilterSuperResTest<12, uint16_t>; TEST_P(PostFilterSuperResTest12bpp, ApplySuperRes) { TestApplySuperRes(true, 0, 0, false); TestApplySuperRes(true, 1, 1, false); TestApplySuperRes(true, 1 << 11, 2, false); TestApplySuperRes(true, (1 << 12) - 1, 3, false); TestApplySuperRes(false, 0, 4, false); } TEST_P(PostFilterSuperResTest12bpp, ApplySuperResThreaded) { TestApplySuperRes(true, 0, 0, true); TestApplySuperRes(true, 1, 1, true); TestApplySuperRes(true, 1 << 11, 2, true); TestApplySuperRes(true, (1 << 12) - 1, 3, true); TestApplySuperRes(false, 0, 4, true); } INSTANTIATE_TEST_SUITE_P(PostFilterSuperResTestInstance, PostFilterSuperResTest12bpp, testing::ValuesIn(kTestParamSuperRes)); using PostFilterHelperFuncTest12bpp = PostFilterHelperFuncTest<12, uint16_t>; TEST_P(PostFilterHelperFuncTest12bpp, ExtendFrame) { TestExtendFrame(true, 0); TestExtendFrame(true, 1); TestExtendFrame(true, 255); TestExtendFrame(true, (1 << 12) - 1); TestExtendFrame(false, 0); } INSTANTIATE_TEST_SUITE_P(PostFilterHelperFuncTestInstance, PostFilterHelperFuncTest12bpp, testing::ValuesIn(kTestParamExtendFrame)); #endif // LIBGAV1_MAX_BITDEPTH == 12 namespace { const char* GetDigestApplyCdef8bpp(int id) { static const char* const kDigest[] = { "9593af24f9c6faecce53437f6e128edf", "ecb633cc2ecd6e7e0cf39d4439f4a6ea", "9ec4cb4124f0a686a7bda72b447f5b8e", "7ebd859a23162bc864a69dbea60bc687", "de7a15fc00664692a794aa68cf695980", "cf3fc8fe041f68d31ab4e34ad3643541", "94c116b191b0268cf7ab4a0e6996e1ec", "1ad60c943a5a914aba7bc26706620a05", "ce33c6f80e3608c4d18c49be2e393c20", "e140586ffc663798b74b8f6fb5b44736", "b7379bba8bcb97f09a74655f4e0eee91", "02ce174061c98babd3987461b3984e47", "64655dd1dfba8317e27d2fdcb211b7b4", "eeb6a61c70c5ee75a4c31dc5099b4dfb", "ee944b31148fa2e30938084f7c046464", "db7b63497750fa4c51cf45c56a2da01c", }; return kDigest[id]; } #if LIBGAV1_MAX_BITDEPTH >= 10 const char* GetDigestApplyCdef10bpp(int id) { static const char* const kDigest[] = { "53f8d68ac7f3aea65151b2066f8501c9", "021e70d5406fa182dd9713380eb66d1d", "bab1c84e7f06b87d81617d2d0a194b89", "58e302ff0522f64901909fb97535b270", "5ff95a6a798eadc7207793c03d898ce4", "1483d28cc0f1bfffedd1128966719aa0", "6af5a36890b465ae962c2878af874f70", "bd1ed4a2ff09d323ab98190d1805a010", "5ff95a6a798eadc7207793c03d898ce4", "1483d28cc0f1bfffedd1128966719aa0", "6af5a36890b465ae962c2878af874f70", "bd1ed4a2ff09d323ab98190d1805a010", "6f0299645cd6f0655fd26044cd43a37c", "56d7febf5bbebdc82e8f157ab926a0bb", "f54654f11006453f496be5883216a3bb", "9abc6e3230792ba78bcc65504a62075e", }; return kDigest[id]; } #endif // LIBGAV1_MAX_BITDEPTH >= 10 #if LIBGAV1_MAX_BITDEPTH == 12 const char* GetDigestApplyCdef12bpp(int id) { static const char* const kDigest[] = { "06e2d09b6ce3924f3b5d4c00ab76eea5", "287240e4b13cb75e17932a3dd7ba3b3c", "265da123e3347c4fb3e434f26a3949e7", "e032ce6eb76242df6894482ac6688406", "f648328221f0f02a5b7fc3d55a66271a", "8f759aa84a110902025dacf8062d2f6a", "592b49e4b993d6b4634d8eb1ee3bba54", "29a3e8e329ec70d06910e982ea763e6b", "f648328221f0f02a5b7fc3d55a66271a", "8f759aa84a110902025dacf8062d2f6a", "592b49e4b993d6b4634d8eb1ee3bba54", "29a3e8e329ec70d06910e982ea763e6b", "155dd4283f8037f86cce34b6cfe67a7e", "0a022c70ead199517af9bad2002d70cd", "a966dfea52a7a2084545f68b2c9e1735", "e098438a23a7c9f276e594b98b2db922", }; return kDigest[id]; } #endif // LIBGAV1_MAX_BITDEPTH == 12 } // namespace template class PostFilterApplyCdefTest : public testing::TestWithParam, public test_utils::MaxAlignedAllocable { public: static_assert(bitdepth >= kBitdepth8 && bitdepth <= LIBGAV1_MAX_BITDEPTH, ""); PostFilterApplyCdefTest() = default; PostFilterApplyCdefTest(const PostFilterApplyCdefTest&) = delete; PostFilterApplyCdefTest& operator=(const PostFilterApplyCdefTest&) = delete; ~PostFilterApplyCdefTest() override = default; protected: void SetUp() override { test_utils::ResetDspTable(bitdepth); dsp::CdefInit_C(); dsp::CdefInit_SSE4_1(); dsp::CdefInit_NEON(); dsp_ = dsp::GetDspTable(bitdepth); ASSERT_NE(dsp_, nullptr); } // Sets sequence_header_, frame_header_, cdef_index_ and cdef_skip_. // Allocates yuv_buffer_ but does not set it. void SetInput(libvpx_test::ACMRandom* rnd); // Sets yuv_buffer_. void SetInputBuffer(libvpx_test::ACMRandom* rnd, PostFilter* post_filter); void CopyFilterOutputToDestBuffer(); void TestMultiThread(int num_threads); ObuSequenceHeader sequence_header_; ObuFrameHeader frame_header_ = {}; FrameScratchBuffer frame_scratch_buffer_; YuvBuffer yuv_buffer_; const dsp::Dsp* dsp_; FrameSizeParam param_ = GetParam(); Pixel dest_[kMaxTestFrameSize * kMaxPlanes]; const size_t y_size_ = param_.width * param_.height; const size_t uv_size_ = y_size_ >> (param_.subsampling_x + param_.subsampling_y); const size_t size_ = y_size_ + uv_size_ * 2; }; template void PostFilterApplyCdefTest::SetInput( libvpx_test::ACMRandom* rnd) { sequence_header_.color_config.bitdepth = bitdepth; sequence_header_.color_config.subsampling_x = param_.subsampling_x; sequence_header_.color_config.subsampling_y = param_.subsampling_y; sequence_header_.color_config.is_monochrome = false; sequence_header_.use_128x128_superblock = static_cast(rnd->Rand16() & 1); ASSERT_TRUE(param_.width <= param_.upscaled_width); ASSERT_TRUE(param_.upscaled_width * param_.height <= kMaxTestFrameSize) << "Please adjust the max frame size."; frame_header_.width = param_.width; frame_header_.upscaled_width = param_.upscaled_width; frame_header_.height = param_.height; frame_header_.columns4x4 = DivideBy4(Align(frame_header_.width, 8)); frame_header_.rows4x4 = DivideBy4(Align(frame_header_.height, 8)); frame_header_.tile_info.tile_count = 1; frame_header_.refresh_frame_flags = 0; Cdef* const cdef = &frame_header_.cdef; const int coeff_shift = bitdepth - 8; do { cdef->damping = (rnd->Rand16() & 3) + 3 + coeff_shift; cdef->bits = rnd->Rand16() & 3; } while (cdef->bits <= 0); for (int i = 0; i < (1 << cdef->bits); ++i) { cdef->y_primary_strength[i] = (rnd->Rand16() & 15) << coeff_shift; cdef->y_secondary_strength[i] = rnd->Rand16() & 3; if (cdef->y_secondary_strength[i] == 3) { ++cdef->y_secondary_strength[i]; } cdef->y_secondary_strength[i] <<= coeff_shift; cdef->uv_primary_strength[i] = (rnd->Rand16() & 15) << coeff_shift; cdef->uv_secondary_strength[i] = rnd->Rand16() & 3; if (cdef->uv_secondary_strength[i] == 3) { ++cdef->uv_secondary_strength[i]; } cdef->uv_secondary_strength[i] <<= coeff_shift; } const int rows64x64 = DivideBy16(frame_header_.rows4x4 + kMaxBlockHeight4x4); const int columns64x64 = DivideBy16(frame_header_.columns4x4 + kMaxBlockWidth4x4); ASSERT_TRUE(frame_scratch_buffer_.cdef_index.Reset(rows64x64, columns64x64)); for (int row = 0; row < rows64x64; ++row) { for (int column = 0; column < columns64x64; ++column) { frame_scratch_buffer_.cdef_index[row][column] = rnd->Rand16() & ((1 << cdef->bits) - 1); } } const int skip_rows = DivideBy2(frame_header_.rows4x4 + kMaxBlockHeight4x4); const int skip_columns = DivideBy16(frame_header_.columns4x4 + kMaxBlockWidth4x4); ASSERT_TRUE(frame_scratch_buffer_.cdef_skip.Reset(skip_rows, skip_columns)); for (int row = 0; row < skip_rows; ++row) { memset(frame_scratch_buffer_.cdef_skip[row], 0xFF, skip_columns); } ASSERT_TRUE(yuv_buffer_.Realloc( sequence_header_.color_config.bitdepth, sequence_header_.color_config.is_monochrome, frame_header_.upscaled_width, frame_header_.height, sequence_header_.color_config.subsampling_x, sequence_header_.color_config.subsampling_y, kBorderPixels, kBorderPixels, kBorderPixels, kBorderPixels, nullptr, nullptr, nullptr)) << "Failed to allocate source buffer."; } template void PostFilterApplyCdefTest::SetInputBuffer( libvpx_test::ACMRandom* rnd, PostFilter* post_filter) { for (int plane = kPlaneY; plane < kMaxPlanes; ++plane) { const int subsampling_x = (plane == 0) ? 0 : param_.subsampling_x; const int subsampling_y = (plane == 0) ? 0 : param_.subsampling_y; const int plane_width = MultiplyBy4(frame_header_.columns4x4) >> subsampling_x; const int plane_height = MultiplyBy4(frame_header_.rows4x4) >> subsampling_y; auto* src = reinterpret_cast(post_filter->GetUnfilteredBuffer(plane)); const int src_stride = yuv_buffer_.stride(plane) / sizeof(src[0]); for (int y = 0; y < plane_height; ++y) { for (int x = 0; x < plane_width; ++x) { src[x] = rnd->Rand16() & ((1 << bitdepth) - 1); } src += src_stride; } } } template void PostFilterApplyCdefTest::CopyFilterOutputToDestBuffer() { for (int plane = kPlaneY; plane < kMaxPlanes; ++plane) { const int subsampling_x = (plane == 0) ? 0 : param_.subsampling_x; const int subsampling_y = (plane == 0) ? 0 : param_.subsampling_y; const int plane_width = SubsampledValue(param_.width, subsampling_x); const int plane_height = SubsampledValue(param_.height, subsampling_y); auto* src = reinterpret_cast(yuv_buffer_.data(plane)); const int src_stride = yuv_buffer_.stride(plane) / sizeof(src[0]); Pixel* dest_plane = dest_ + ((plane == 0) ? 0 : ((plane == 1) ? y_size_ : y_size_ + uv_size_)); for (int y = 0; y < plane_height; ++y) { for (int x = 0; x < plane_width; ++x) { dest_plane[y * plane_width + x] = src[x]; } src += src_stride; } } } template void PostFilterApplyCdefTest::TestMultiThread( int num_threads) { libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed()); SetInput(&rnd); ASSERT_TRUE(frame_scratch_buffer_.threading_strategy.Reset(frame_header_, num_threads)); if (num_threads > 1) { const int num_units = MultiplyBy4(RightShiftWithCeiling(frame_header_.rows4x4, 4)); ASSERT_TRUE(frame_scratch_buffer_.cdef_border.Realloc( bitdepth, /*is_monochrome=*/false, MultiplyBy4(frame_header_.columns4x4), num_units, sequence_header_.color_config.subsampling_x, /*subsampling_y=*/0, kBorderPixels, kBorderPixels, kBorderPixels, kBorderPixels, nullptr, nullptr, nullptr)); } PostFilter post_filter(frame_header_, sequence_header_, &frame_scratch_buffer_, &yuv_buffer_, dsp_, /*do_post_filter_mask=*/0x02); SetInputBuffer(&rnd, &post_filter); const int id = GetIdFromInputParam(param_.subsampling_x, param_.subsampling_y, param_.height); absl::Duration elapsed_time; const absl::Time start = absl::Now(); // Only ApplyCdef() and frame copy inside ApplyFilteringThreaded() are // triggered, since we set the filter mask to 0x02. post_filter.ApplyFilteringThreaded(); elapsed_time += absl::Now() - start; CopyFilterOutputToDestBuffer(); const char* expected_digest = nullptr; switch (bitdepth) { case 8: expected_digest = GetDigestApplyCdef8bpp(id); break; #if LIBGAV1_MAX_BITDEPTH >= 10 case 10: expected_digest = GetDigestApplyCdef10bpp(id); break; #endif #if LIBGAV1_MAX_BITDEPTH == 12 case 12: expected_digest = GetDigestApplyCdef12bpp(id); break; #endif } ASSERT_NE(expected_digest, nullptr); test_utils::CheckMd5Digest(kCdef, kApplyCdefName, expected_digest, dest_, size_, elapsed_time); } const FrameSizeParam kTestParamApplyCdef[] = { FrameSizeParam(352, 352, 288, 0, 0), FrameSizeParam(720, 720, 480, 0, 0), FrameSizeParam(1920, 1920, 1080, 0, 0), FrameSizeParam(251, 251, 187, 0, 0), FrameSizeParam(352, 352, 288, 0, 1), FrameSizeParam(720, 720, 480, 0, 1), FrameSizeParam(1920, 1920, 1080, 0, 1), FrameSizeParam(251, 251, 187, 0, 1), FrameSizeParam(352, 352, 288, 1, 0), FrameSizeParam(720, 720, 480, 1, 0), FrameSizeParam(1920, 1920, 1080, 1, 0), FrameSizeParam(251, 251, 187, 1, 0), FrameSizeParam(352, 352, 288, 1, 1), FrameSizeParam(720, 720, 480, 1, 1), FrameSizeParam(1920, 1920, 1080, 1, 1), FrameSizeParam(251, 251, 187, 1, 1), }; using PostFilterApplyCdefTest8bpp = PostFilterApplyCdefTest<8, uint8_t>; TEST_P(PostFilterApplyCdefTest8bpp, ApplyCdef) { TestMultiThread(2); TestMultiThread(4); TestMultiThread(8); } INSTANTIATE_TEST_SUITE_P(PostFilterApplyCdefTestInstance, PostFilterApplyCdefTest8bpp, testing::ValuesIn(kTestParamApplyCdef)); #if LIBGAV1_MAX_BITDEPTH >= 10 using PostFilterApplyCdefTest10bpp = PostFilterApplyCdefTest<10, uint16_t>; TEST_P(PostFilterApplyCdefTest10bpp, ApplyCdef) { TestMultiThread(2); TestMultiThread(4); TestMultiThread(8); } INSTANTIATE_TEST_SUITE_P(PostFilterApplyCdefTestInstance, PostFilterApplyCdefTest10bpp, testing::ValuesIn(kTestParamApplyCdef)); #endif // LIBGAV1_MAX_BITDEPTH >= 10 #if LIBGAV1_MAX_BITDEPTH == 12 using PostFilterApplyCdefTest12bpp = PostFilterApplyCdefTest<12, uint16_t>; TEST_P(PostFilterApplyCdefTest12bpp, ApplyCdef) { TestMultiThread(2); TestMultiThread(4); TestMultiThread(8); } INSTANTIATE_TEST_SUITE_P(PostFilterApplyCdefTestInstance, PostFilterApplyCdefTest12bpp, testing::ValuesIn(kTestParamApplyCdef)); #endif // LIBGAV1_MAX_BITDEPTH == 12 } // namespace libgav1