blob: 63ed530d1f2ccfe9647ea42cbfa2a833019acbcc [file] [log] [blame]
// 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/dsp/loop_filter.h"
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <ostream>
#include <string>
#include "absl/strings/match.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "gtest/gtest.h"
#include "src/dsp/dsp.h"
#include "src/utils/constants.h"
#include "src/utils/cpu.h"
#include "tests/block_utils.h"
#include "tests/third_party/libvpx/acm_random.h"
#include "tests/third_party/libvpx/md5_helper.h"
#include "tests/utils.h"
namespace libgav1 {
namespace dsp {
namespace {
// Horizontal and Vertical need 32x32: 8 pixels preceding filtered section
// 16 pixels within filtered section
// 8 pixels following filtered section
constexpr int kNumPixels = 1024;
constexpr int kBlockStride = 32;
constexpr int kNumTests = 50000;
constexpr int kNumSpeedTests = 500000;
template <typename Pixel>
void InitInput(Pixel* dst, const int stride, const int bitdepth,
libvpx_test::ACMRandom& rnd, const uint8_t inner_thresh,
const bool transpose) {
const int max_pixel = (1 << bitdepth) - 1;
const int pixel_range = max_pixel + 1;
Pixel tmp[kNumPixels];
auto clip_pixel = [max_pixel](int val) {
return static_cast<Pixel>(std::max(std::min(val, max_pixel), 0));
};
for (int i = 0; i < kNumPixels;) {
const uint8_t val = rnd.Rand8();
if (val & 0x80) { // 50% chance to choose a new value.
tmp[i++] = rnd(pixel_range);
} else { // 50% chance to repeat previous value in row X times.
int j = 0;
while (j++ < ((val & 0x1f) + 1) && i < kNumPixels) {
if (i < 1) {
tmp[i] = rnd(pixel_range);
} else if (val & 0x20) { // Increment by a value within the limit.
tmp[i] = clip_pixel(tmp[i - 1] + (inner_thresh - 1));
} else { // Decrement by a value within the limit.
tmp[i] = clip_pixel(tmp[i - 1] - (inner_thresh - 1));
}
++i;
}
}
}
for (int i = 0; i < kNumPixels;) {
const uint8_t val = rnd.Rand8();
if (val & 0x80) {
++i;
} else { // 50% chance to repeat previous value in column X times.
int j = 0;
while (j++ < ((val & 0x1f) + 1) && i < kNumPixels) {
if (i < 1) {
tmp[i] = rnd(pixel_range);
} else if (val & 0x20) { // Increment by a value within the limit.
tmp[(i % 32) * 32 + i / 32] = clip_pixel(
tmp[((i - 1) % 32) * 32 + (i - 1) / 32] + (inner_thresh - 1));
} else { // Decrement by a value within the inner_thresh.
tmp[(i % 32) * 32 + i / 32] = clip_pixel(
tmp[((i - 1) % 32) * 32 + (i - 1) / 32] - (inner_thresh - 1));
}
++i;
}
}
}
for (int i = 0; i < kNumPixels; ++i) {
const int offset = transpose ? stride * (i % stride) + i / stride : i;
dst[i] = tmp[offset];
}
}
template <int bitdepth, typename Pixel>
class LoopFilterTest : public testing::TestWithParam<LoopFilterSize> {
public:
static_assert(bitdepth >= kBitdepth8 && bitdepth <= LIBGAV1_MAX_BITDEPTH, "");
LoopFilterTest() = default;
LoopFilterTest(const LoopFilterTest&) = delete;
LoopFilterTest& operator=(const LoopFilterTest&) = delete;
~LoopFilterTest() override = default;
protected:
void SetUp() override {
test_utils::ResetDspTable(bitdepth);
LoopFilterInit_C();
const Dsp* const dsp = GetDspTable(bitdepth);
ASSERT_NE(dsp, nullptr);
memcpy(base_loop_filters_, dsp->loop_filters[size_],
sizeof(base_loop_filters_));
const testing::TestInfo* const test_info =
testing::UnitTest::GetInstance()->current_test_info();
const char* const test_case = test_info->test_suite_name();
if (absl::StartsWith(test_case, "C/")) {
memset(base_loop_filters_, 0, sizeof(base_loop_filters_));
} else if (absl::StartsWith(test_case, "SSE41/")) {
if ((GetCpuInfo() & kSSE4_1) != 0) {
LoopFilterInit_SSE4_1();
}
} else if (absl::StartsWith(test_case, "NEON/")) {
LoopFilterInit_NEON();
#if LIBGAV1_MAX_BITDEPTH >= 10
LoopFilterInit10bpp_NEON();
#endif
} else {
FAIL() << "Unrecognized architecture prefix in test case name: "
<< test_case;
}
memcpy(cur_loop_filters_, dsp->loop_filters[size_],
sizeof(cur_loop_filters_));
for (int i = 0; i < kNumLoopFilterTypes; ++i) {
// skip functions that haven't been specialized for this particular
// architecture.
if (cur_loop_filters_[i] == base_loop_filters_[i]) {
cur_loop_filters_[i] = nullptr;
}
}
}
// Check |digests| if non-NULL otherwise print the filter timing.
void TestRandomValues(const char* const digests[kNumLoopFilterTypes],
int num_runs) const;
void TestSaturatedValues() const;
const LoopFilterSize size_ = GetParam();
LoopFilterFunc base_loop_filters_[kNumLoopFilterTypes];
LoopFilterFunc cur_loop_filters_[kNumLoopFilterTypes];
};
template <int bitdepth, typename Pixel>
void LoopFilterTest<bitdepth, Pixel>::TestRandomValues(
const char* const digests[kNumLoopFilterTypes], const int num_runs) const {
for (int i = 0; i < kNumLoopFilterTypes; ++i) {
libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed());
if (cur_loop_filters_[i] == nullptr) continue;
libvpx_test::MD5 md5_digest;
absl::Duration elapsed_time;
for (int n = 0; n < num_runs; ++n) {
Pixel dst[kNumPixels];
const auto outer_thresh = static_cast<uint8_t>(
rnd(3 * kMaxLoopFilterValue - 2) + 7); // [7, 193].
const auto inner_thresh =
static_cast<uint8_t>(rnd(kMaxLoopFilterValue) + 1); // [1, 63].
const auto hev_thresh =
static_cast<uint8_t>(rnd(kMaxLoopFilterValue + 1) >> 4); // [0, 3].
InitInput(dst, kBlockStride, bitdepth, rnd, inner_thresh, (n & 1) == 0);
const absl::Time start = absl::Now();
cur_loop_filters_[i](dst + 8 + kBlockStride * 8, kBlockStride,
outer_thresh, inner_thresh, hev_thresh);
elapsed_time += absl::Now() - start;
md5_digest.Add(reinterpret_cast<const uint8_t*>(dst), sizeof(dst));
}
if (digests == nullptr) {
const auto elapsed_time_us =
static_cast<int>(absl::ToInt64Microseconds(elapsed_time));
printf("Mode %s[%25s]: %5d us\n",
ToString(static_cast<LoopFilterSize>(size_)),
ToString(static_cast<LoopFilterType>(i)), elapsed_time_us);
} else {
const std::string digest = md5_digest.Get();
printf("Mode %s[%25s]: MD5: %s\n",
ToString(static_cast<LoopFilterSize>(size_)),
ToString(static_cast<LoopFilterType>(i)), digest.c_str());
EXPECT_STREQ(digests[i], digest.c_str());
}
}
}
template <int bitdepth, typename Pixel>
void LoopFilterTest<bitdepth, Pixel>::TestSaturatedValues() const {
Pixel dst[kNumPixels], ref[kNumPixels];
const auto value = static_cast<Pixel>((1 << bitdepth) - 1);
for (auto& r : dst) r = value;
memcpy(ref, dst, sizeof(dst));
for (int i = 0; i < kNumLoopFilterTypes; ++i) {
if (cur_loop_filters_[i] == nullptr) return;
const int outer_thresh = 24;
const int inner_thresh = 8;
const int hev_thresh = 0;
cur_loop_filters_[i](dst + 8 + kBlockStride * 8, kBlockStride, outer_thresh,
inner_thresh, hev_thresh);
ASSERT_TRUE(test_utils::CompareBlocks(ref, dst, kBlockStride, kBlockStride,
kBlockStride, kBlockStride, true))
<< ToString(static_cast<LoopFilterType>(i))
<< " output doesn't match reference";
}
}
//------------------------------------------------------------------------------
using LoopFilterTest8bpp = LoopFilterTest<8, uint8_t>;
const char* const* GetDigests8bpp(LoopFilterSize size) {
static const char* const kDigestsSize4[kNumLoopFilterTypes] = {
"6ba725d697d6209cb36dd199b8ffb47a",
"7dbb20e456ed0501fb4e7954f49f5e18",
};
static const char* const kDigestsSize6[kNumLoopFilterTypes] = {
"89bb757faa44298b7f6e9c1a67f455a5",
"be75d5a2fcd83709ff0845f7d83f7006",
};
static const char* const kDigestsSize8[kNumLoopFilterTypes] = {
"b09137d68c7b4f8a8a15e33b4b69828f",
"ef8a7f1aa073805516d3518a82a5cfa4",
};
static const char* const kDigestsSize14[kNumLoopFilterTypes] = {
"6a7bc061ace0888275af88093f82ca08",
"a957ddae005839aa41ba7691788b01e4",
};
switch (size) {
case kLoopFilterSize4:
return kDigestsSize4;
case kLoopFilterSize6:
return kDigestsSize6;
case kLoopFilterSize8:
return kDigestsSize8;
case kLoopFilterSize14:
return kDigestsSize14;
default:
ADD_FAILURE() << "Unknown loop filter size" << size;
return nullptr;
}
}
TEST_P(LoopFilterTest8bpp, DISABLED_Speed) {
TestRandomValues(nullptr, kNumSpeedTests);
}
TEST_P(LoopFilterTest8bpp, FixedInput) {
TestRandomValues(GetDigests8bpp(size_), kNumTests);
}
TEST_P(LoopFilterTest8bpp, SaturatedValues) { TestSaturatedValues(); }
constexpr LoopFilterSize kLoopFilterSizes[] = {
kLoopFilterSize4, kLoopFilterSize6, kLoopFilterSize8, kLoopFilterSize14};
INSTANTIATE_TEST_SUITE_P(C, LoopFilterTest8bpp,
testing::ValuesIn(kLoopFilterSizes));
#if LIBGAV1_ENABLE_SSE4_1
INSTANTIATE_TEST_SUITE_P(SSE41, LoopFilterTest8bpp,
testing::ValuesIn(kLoopFilterSizes));
#endif
#if LIBGAV1_ENABLE_NEON
INSTANTIATE_TEST_SUITE_P(NEON, LoopFilterTest8bpp,
testing::ValuesIn(kLoopFilterSizes));
#endif
//------------------------------------------------------------------------------
#if LIBGAV1_MAX_BITDEPTH >= 10
using LoopFilterTest10bpp = LoopFilterTest<10, uint16_t>;
const char* const* GetDigests10bpp(LoopFilterSize size) {
static const char* const kDigestsSize4[kNumLoopFilterTypes] = {
"72e75c478bb130ff1ebfa75f3a70b1a2",
"f32d67b611080e0bf1a9d162ff47c133",
};
static const char* const kDigestsSize6[kNumLoopFilterTypes] = {
"8aec73c60c87ac7cc6bc9cc5157a2795",
"0e4385d3a0cbb2b1551e05ad2b0f07fb",
};
static const char* const kDigestsSize8[kNumLoopFilterTypes] = {
"85cb2928fae43e1a27b2fe1b78ba7534",
"d044fad9d7c64b93ecb60c88ac48e55f",
};
static const char* const kDigestsSize14[kNumLoopFilterTypes] = {
"ebca95ec0db6efbac7ff7cbeabc0e6d0",
"754ffaf0ac26a5953a029653bb5dd275",
};
switch (size) {
case kLoopFilterSize4:
return kDigestsSize4;
case kLoopFilterSize6:
return kDigestsSize6;
case kLoopFilterSize8:
return kDigestsSize8;
case kLoopFilterSize14:
return kDigestsSize14;
default:
ADD_FAILURE() << "Unknown loop filter size" << size;
return nullptr;
}
}
TEST_P(LoopFilterTest10bpp, DISABLED_Speed) {
TestRandomValues(nullptr, kNumSpeedTests);
}
TEST_P(LoopFilterTest10bpp, FixedInput) {
TestRandomValues(GetDigests10bpp(size_), kNumTests);
}
TEST_P(LoopFilterTest10bpp, SaturatedValues) { TestSaturatedValues(); }
INSTANTIATE_TEST_SUITE_P(C, LoopFilterTest10bpp,
testing::ValuesIn(kLoopFilterSizes));
#if LIBGAV1_ENABLE_SSE4_1
INSTANTIATE_TEST_SUITE_P(SSE41, LoopFilterTest10bpp,
testing::ValuesIn(kLoopFilterSizes));
#endif
#if LIBGAV1_ENABLE_NEON
INSTANTIATE_TEST_SUITE_P(NEON, LoopFilterTest10bpp,
testing::ValuesIn(kLoopFilterSizes));
#endif
#endif // LIBGAV1_MAX_BITDEPTH >= 10
//------------------------------------------------------------------------------
#if LIBGAV1_MAX_BITDEPTH == 12
using LoopFilterTest12bpp = LoopFilterTest<12, uint16_t>;
const char* const* GetDigests12bpp(LoopFilterSize size) {
static const char* const kDigestsSize4[kNumLoopFilterTypes] = {
"a14599cbfe2daee633d556a15c47b1f6",
"1f0a0794832de1012e2fed6b1cb02e69",
};
static const char* const kDigestsSize6[kNumLoopFilterTypes] = {
"c76b24a73139239db10f16f36e01a625",
"3f75d904e9dcb1886e84a0f03f60f31e",
};
static const char* const kDigestsSize8[kNumLoopFilterTypes] = {
"57c6f0efe2ab3957f5500ca2a9670f37",
"caa1f90c2eb2b65b280d678f8fcf6be8",
};
static const char* const kDigestsSize14[kNumLoopFilterTypes] = {
"0c58f7466c36c3f4a2c1b4aa1b80f0b3",
"63077978326e6dddb5b2c3bfe6d684f5",
};
switch (size) {
case kLoopFilterSize4:
return kDigestsSize4;
case kLoopFilterSize6:
return kDigestsSize6;
case kLoopFilterSize8:
return kDigestsSize8;
case kLoopFilterSize14:
return kDigestsSize14;
default:
ADD_FAILURE() << "Unknown loop filter size" << size;
return nullptr;
}
}
TEST_P(LoopFilterTest12bpp, DISABLED_Speed) {
TestRandomValues(nullptr, kNumSpeedTests);
}
TEST_P(LoopFilterTest12bpp, FixedInput) {
TestRandomValues(GetDigests12bpp(size_), kNumTests);
}
TEST_P(LoopFilterTest12bpp, SaturatedValues) { TestSaturatedValues(); }
INSTANTIATE_TEST_SUITE_P(C, LoopFilterTest12bpp,
testing::ValuesIn(kLoopFilterSizes));
#endif // LIBGAV1_MAX_BITDEPTH == 12
} // namespace
static std::ostream& operator<<(std::ostream& os, const LoopFilterSize size) {
return os << ToString(size);
}
} // namespace dsp
} // namespace libgav1