blob: 7b253ffedcac8bb244b221855d4d26b2468a196d [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/super_res.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "gtest/gtest.h"
#include "src/dsp/dsp.h"
#include "src/utils/common.h"
#include "src/utils/constants.h"
#include "src/utils/cpu.h"
#include "src/utils/memory.h"
#include "tests/third_party/libvpx/acm_random.h"
#include "tests/utils.h"
namespace libgav1 {
namespace dsp {
namespace {
constexpr int kNumSpeedTests = 5e5;
const char* GetDigest8bpp(int id) {
static const char* const kDigestSuperRes[] = {
"52eb4eac1df0c51599d57696405b69d0", "ccb07cc8295fd1440ff2e3b9199ec4f9",
"baef34cca795b95f3d1fd81d609da679", "03f1579c2773c8ba9c867316a22b94a3"};
return kDigestSuperRes[id];
}
#if LIBGAV1_MAX_BITDEPTH >= 10
const char* GetDigest10bpp(int id) {
static const char* const kDigestSuperRes[] = {
"8fd78e05d944aeb11fac278b47ee60ba", "948eaecb70fa5614ce1c1c95e9942dc3",
"126cd7727e787e0625ec3f5ce97f8fa0", "85c806c41d40b841764bcb54f6d3a712"};
return kDigestSuperRes[id];
}
#endif // LIBGAV1_MAX_BITDEPTH >= 10
#if LIBGAV1_MAX_BITDEPTH == 12
const char* GetDigest12bpp(int id) {
static const char* const kDigestSuperRes[] = {
"9a08983d82df4983700976f18919201b", "6e5edbafcb6c38db37258bf79c00ea32",
"f5c57e6d3b518f9585f768ed19b91568", "b5de9b93c8a1a50580e7c7c9456fb615"};
return kDigestSuperRes[id];
}
#endif // LIBGAV1_MAX_BITDEPTH == 12
struct SuperResTestParam {
SuperResTestParam(int downscaled_width, int upscaled_width)
: downscaled_width(downscaled_width), upscaled_width(upscaled_width) {}
int downscaled_width;
int upscaled_width;
};
template <int bitdepth, typename Pixel, typename Coefficient>
class SuperResTest : public testing::TestWithParam<SuperResTestParam>,
public test_utils::MaxAlignedAllocable {
public:
static_assert(bitdepth >= kBitdepth8 && bitdepth <= LIBGAV1_MAX_BITDEPTH, "");
SuperResTest() = default;
void SetUp() override {
test_utils::ResetDspTable(bitdepth);
SuperResInit_C();
const dsp::Dsp* const dsp = dsp::GetDspTable(bitdepth);
ASSERT_NE(dsp, nullptr);
const testing::TestInfo* const test_info =
testing::UnitTest::GetInstance()->current_test_info();
const std::vector<std::string> split_test_name =
absl::StrSplit(test_info->name(), '/');
ASSERT_TRUE(absl::SimpleAtoi(split_test_name[1], &test_id_));
const absl::string_view test_case = test_info->test_suite_name();
if (absl::StartsWith(test_case, "C/")) {
} else if (absl::StartsWith(test_case, "NEON/")) {
SuperResInit_NEON();
} else if (absl::StartsWith(test_case, "SSE41/")) {
SuperResInit_SSE4_1();
} else {
FAIL() << "Unrecognized architecture prefix in test case name: "
<< test_case;
}
super_res_coefficients_ = dsp->super_res_coefficients;
func_ = dsp->super_res;
}
void TestComputeSuperRes(int fixed_value, int num_runs);
private:
static constexpr int kHeight = 127;
// The maximum width that must be allocated.
static constexpr int kUpscaledBufferWidth = 192;
// Allow room for the filter taps.
static constexpr int kStride =
((kUpscaledBufferWidth + 2 * kSuperResHorizontalBorder + 15) & ~15);
const int kDownscaledWidth = GetParam().downscaled_width;
const int kUpscaledWidth = GetParam().upscaled_width;
int test_id_;
SuperResCoefficientsFunc super_res_coefficients_;
SuperResFunc func_;
Pixel source_buffer_[kHeight][kStride];
alignas(kMaxAlignment) Pixel dest_buffer_[kHeight][kStride];
alignas(kMaxAlignment) Coefficient
superres_coefficients_[kSuperResFilterTaps * kUpscaledBufferWidth];
};
template <int bitdepth, typename Pixel, typename Coefficient>
void SuperResTest<bitdepth, Pixel, Coefficient>::TestComputeSuperRes(
int fixed_value, int num_runs) {
if (func_ == nullptr) return;
const int superres_width = kDownscaledWidth << kSuperResScaleBits;
const int step = (superres_width + kUpscaledWidth / 2) / kUpscaledWidth;
const int error = step * kUpscaledWidth - superres_width;
const int initial_subpixel_x =
((-((kUpscaledWidth - kDownscaledWidth) << (kSuperResScaleBits - 1)) +
DivideBy2(kUpscaledWidth)) /
kUpscaledWidth +
(1 << (kSuperResExtraBits - 1)) - error / 2) &
kSuperResScaleMask;
if (super_res_coefficients_ != nullptr) {
super_res_coefficients_(kUpscaledWidth, initial_subpixel_x, step,
superres_coefficients_);
}
memset(dest_buffer_, 0, sizeof(dest_buffer_));
if (fixed_value != 0) {
SetBlock<Pixel>(kHeight, kStride, fixed_value, source_buffer_[0], kStride);
} else {
// Random values.
libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed());
const int bitdepth_mask = (1 << bitdepth) - 1;
for (int y = 0; y < kHeight; ++y) {
for (int x = 0; x < kStride; ++x) {
source_buffer_[y][x] = rnd.Rand16() & bitdepth_mask;
}
}
}
// Offset starting point in the buffer to accommodate line extension.
Pixel* src_ptr = source_buffer_[0] + kSuperResHorizontalBorder;
const absl::Time start = absl::Now();
for (int i = 0; i < num_runs; ++i) {
func_(superres_coefficients_, src_ptr, kStride, kHeight, kDownscaledWidth,
kUpscaledWidth, initial_subpixel_x, step, dest_buffer_, kStride);
}
const absl::Duration elapsed_time = absl::Now() - start;
if (fixed_value != 0) {
for (int y = 0; y < kHeight; ++y) {
for (int x = 0; x < kUpscaledWidth; ++x) {
EXPECT_TRUE(dest_buffer_[y][x] == fixed_value)
<< "At location [" << y << ", " << x
<< "]\nexpected: " << fixed_value
<< "\nactual: " << dest_buffer_[y][x];
}
}
} else if (num_runs == 1) {
// Random values.
if ((kUpscaledWidth & 15) != 0) {
// The SIMD functions overwrite up to 15 pixels in each row. Reset them.
for (int y = 0; y < kHeight; ++y) {
for (int x = kUpscaledWidth; x < Align(kUpscaledWidth, 16); ++x) {
dest_buffer_[y][x] = 0;
}
}
}
const char* expected_digest = nullptr;
switch (bitdepth) {
case 8:
expected_digest = GetDigest8bpp(test_id_);
break;
#if LIBGAV1_MAX_BITDEPTH >= 10
case 10:
expected_digest = GetDigest10bpp(test_id_);
break;
#endif
#if LIBGAV1_MAX_BITDEPTH == 12
case 12:
expected_digest = GetDigest12bpp(test_id_);
break;
#endif
}
ASSERT_NE(expected_digest, nullptr);
test_utils::CheckMd5Digest(
"SuperRes",
absl::StrFormat("width %d, step %d, start %d", kUpscaledWidth, step,
initial_subpixel_x)
.c_str(),
expected_digest, dest_buffer_, sizeof(dest_buffer_), elapsed_time);
} else {
// Speed test.
printf("Mode SuperRes [width %d, step %d, start %d]: %d us\n",
kUpscaledWidth, step, initial_subpixel_x,
static_cast<int>(absl::ToInt64Microseconds(elapsed_time)));
}
}
using SuperResTest8bpp = SuperResTest<8, uint8_t, int8_t>;
TEST_P(SuperResTest8bpp, FixedValues) {
TestComputeSuperRes(100, 1);
TestComputeSuperRes(255, 1);
TestComputeSuperRes(1, 1);
}
TEST_P(SuperResTest8bpp, RandomValues) { TestComputeSuperRes(0, 1); }
TEST_P(SuperResTest8bpp, DISABLED_Speed) {
TestComputeSuperRes(0, kNumSpeedTests);
}
const SuperResTestParam kSuperResTestParams[] = {
SuperResTestParam(96, 192),
SuperResTestParam(171, 192),
SuperResTestParam(102, 128),
SuperResTestParam(61, 121),
};
INSTANTIATE_TEST_SUITE_P(C, SuperResTest8bpp,
testing::ValuesIn(kSuperResTestParams));
#if LIBGAV1_ENABLE_NEON
INSTANTIATE_TEST_SUITE_P(NEON, SuperResTest8bpp,
testing::ValuesIn(kSuperResTestParams));
#endif
#if LIBGAV1_ENABLE_SSE4_1
INSTANTIATE_TEST_SUITE_P(SSE41, SuperResTest8bpp,
testing::ValuesIn(kSuperResTestParams));
#endif
#if LIBGAV1_MAX_BITDEPTH >= 10
using SuperResTest10bpp = SuperResTest<10, uint16_t, int16_t>;
TEST_P(SuperResTest10bpp, FixedValues) {
TestComputeSuperRes(100, 1);
TestComputeSuperRes(511, 1);
TestComputeSuperRes(1, 1);
}
TEST_P(SuperResTest10bpp, RandomValues) { TestComputeSuperRes(0, 1); }
TEST_P(SuperResTest10bpp, DISABLED_Speed) {
TestComputeSuperRes(0, kNumSpeedTests);
}
INSTANTIATE_TEST_SUITE_P(C, SuperResTest10bpp,
testing::ValuesIn(kSuperResTestParams));
#if LIBGAV1_ENABLE_SSE4_1
INSTANTIATE_TEST_SUITE_P(SSE41, SuperResTest10bpp,
testing::ValuesIn(kSuperResTestParams));
#endif
#if LIBGAV1_ENABLE_NEON
INSTANTIATE_TEST_SUITE_P(NEON, SuperResTest10bpp,
testing::ValuesIn(kSuperResTestParams));
#endif
#endif // LIBGAV1_MAX_BITDEPTH >= 10
#if LIBGAV1_MAX_BITDEPTH == 12
using SuperResTest12bpp = SuperResTest<12, uint16_t, int16_t>;
TEST_P(SuperResTest12bpp, FixedValues) {
TestComputeSuperRes(100, 1);
TestComputeSuperRes(2047, 1);
TestComputeSuperRes(1, 1);
}
TEST_P(SuperResTest12bpp, RandomValues) { TestComputeSuperRes(0, 1); }
TEST_P(SuperResTest12bpp, DISABLED_Speed) {
TestComputeSuperRes(0, kNumSpeedTests);
}
INSTANTIATE_TEST_SUITE_P(C, SuperResTest12bpp,
testing::ValuesIn(kSuperResTestParams));
#endif // LIBGAV1_MAX_BITDEPTH == 12
} // namespace
} // namespace dsp
} // namespace libgav1