blob: 3a47cc7f74ef201d9b8950b3185d84379ab1dfcb [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/motion_field_projection.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <string>
#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "gtest/gtest.h"
#include "src/dsp/dsp.h"
#include "src/utils/array_2d.h"
#include "src/utils/common.h"
#include "src/utils/constants.h"
#include "src/utils/cpu.h"
#include "src/utils/reference_info.h"
#include "src/utils/types.h"
#include "tests/third_party/libvpx/acm_random.h"
#include "tests/utils.h"
namespace libgav1 {
namespace dsp {
namespace {
constexpr int kMotionFieldWidth = 160;
constexpr int kMotionFieldHight = 120;
// The 'int' parameter is unused but required to allow for instantiations of C,
// NEON, etc.
class MotionFieldProjectionTest : public testing::TestWithParam<int> {
public:
MotionFieldProjectionTest() = default;
MotionFieldProjectionTest(const MotionFieldProjectionTest&) = delete;
MotionFieldProjectionTest& operator=(const MotionFieldProjectionTest&) =
delete;
~MotionFieldProjectionTest() override = default;
void SetUp() override {
test_utils::ResetDspTable(8);
MotionFieldProjectionInit_C();
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/")) {
} else if (absl::StartsWith(test_case, "NEON/")) {
MotionFieldProjectionInit_NEON();
} else if (absl::StartsWith(test_case, "SSE41/")) {
if ((GetCpuInfo() & kSSE4_1) != 0) {
MotionFieldProjectionInit_SSE4_1();
}
} else {
FAIL() << "Unrecognized architecture prefix in test case name: "
<< test_case;
}
const Dsp* const dsp = GetDspTable(8);
ASSERT_NE(dsp, nullptr);
target_motion_field_projection_kernel_func_ =
dsp->motion_field_projection_kernel;
}
void SetInputData(int motion_field_width, libvpx_test::ACMRandom* rnd);
void TestRandomValues(bool speed);
private:
MotionFieldProjectionKernelFunc target_motion_field_projection_kernel_func_;
ReferenceInfo reference_info_;
TemporalMotionField motion_field_;
};
void MotionFieldProjectionTest::SetInputData(
const int motion_field_width, libvpx_test::ACMRandom* const rnd) {
ASSERT_TRUE(reference_info_.Reset(kMotionFieldHight, motion_field_width));
ASSERT_TRUE(motion_field_.mv.Reset(kMotionFieldHight, motion_field_width,
/*zero_initialize=*/false));
ASSERT_TRUE(motion_field_.reference_offset.Reset(kMotionFieldHight,
motion_field_width,
/*zero_initialize=*/false));
constexpr int order_hint_bits = 6;
unsigned int order_hint_shift_bits = Mod32(32 - order_hint_bits);
const unsigned int current_frame_order_hint =
rnd->Rand8() & ((1 << order_hint_bits) - 1); // [0, 63]
uint8_t reference_frame_order_hint = 0;
reference_info_.relative_distance_to[0] = 0;
reference_info_.skip_references[kReferenceFrameIntra] = true;
reference_info_.projection_divisions[kReferenceFrameIntra] = 0;
for (int i = kReferenceFrameLast; i < kNumReferenceFrameTypes; ++i) {
reference_frame_order_hint =
rnd->Rand8() & ((1 << order_hint_bits) - 1); // [0, 63]
const int relative_distance_to =
GetRelativeDistance(current_frame_order_hint,
reference_frame_order_hint, order_hint_shift_bits);
reference_info_.relative_distance_to[i] = relative_distance_to;
reference_info_.skip_references[i] =
relative_distance_to > kMaxFrameDistance || relative_distance_to <= 0;
reference_info_.projection_divisions[i] =
reference_info_.skip_references[i]
? 0
: kProjectionMvDivisionLookup[relative_distance_to];
}
for (int y = 0; y < kMotionFieldHight; ++y) {
for (int x = 0; x < motion_field_width; ++x) {
reference_info_.motion_field_reference_frame[y][x] =
static_cast<ReferenceFrameType>(rnd->Rand16() &
kReferenceFrameAlternate);
reference_info_.motion_field_mv[y][x].mv[0] = rnd->Rand16Signed() / 512;
reference_info_.motion_field_mv[y][x].mv[1] = rnd->Rand16Signed() / 512;
}
}
MotionVector invalid_mv;
invalid_mv.mv[0] = kInvalidMvValue;
invalid_mv.mv[1] = kInvalidMvValue;
MotionVector* const motion_field_mv = &motion_field_.mv[0][0];
int8_t* const motion_field_reference_offset =
&motion_field_.reference_offset[0][0];
std::fill(motion_field_mv, motion_field_mv + motion_field_.mv.size(),
invalid_mv);
std::fill(
motion_field_reference_offset,
motion_field_reference_offset + motion_field_.reference_offset.size(),
-128);
}
void MotionFieldProjectionTest::TestRandomValues(bool speed) {
static const char* const kDigestMv[8] = {
"87c2a74538f5c015809492ac2e521075", "ba7b4a5d82c6083b13a5b02eb7655ab7",
"8c37d96bf1744d5553860bf44a4f60a3", "720aa644f85e48995db9785e87cd02e3",
"9289c0c66524bb77a605870d78285f35", "f0326509885c2b2c89feeac53698cd47",
"6b9ad1d672dec825cb1803063d35badc", "dfe06c57cc9c70d27246df7fd0afa0b2"};
static const char* const kDigestReferenceOffset[8] = {
"d8d1384268d7cf5c4514b39c329f94fb", "7f30e79ceb064befbad64a20d206a540",
"61e2eb5644edbd3a91b939403edc891e", "7a018f1bf88193e86934241af445dc36",
"2d6166bf8bbe1db77baf687ecf71d028", "95fee61f0219e06076d6f0e1073b1a4e",
"64d0a63751267bdc573cab761f1fe685", "906a99e0e791dbcb9183c9b68ecc4ea3"};
const int num_tests = speed ? 2000 : 1;
if (target_motion_field_projection_kernel_func_ == nullptr) return;
libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed());
for (int width_idx = 0; width_idx < 8; ++width_idx) {
const int motion_field_width = kMotionFieldWidth + width_idx;
SetInputData(motion_field_width, &rnd);
const int dst_sign = ((rnd.Rand16() & 1) != 0) ? 0 : -1;
const int reference_to_current_with_sign =
rnd.PseudoUniform(2 * kMaxFrameDistance + 1) - kMaxFrameDistance;
assert(std::abs(reference_to_current_with_sign) <= kMaxFrameDistance);
// Step of y8 and x8 is at least 16 except the last hop.
for (int step = 16; step <= 80; step += 16) {
const absl::Time start = absl::Now();
for (int k = 0; k < num_tests; ++k) {
for (int y8 = 0; y8 < kMotionFieldHight; y8 += step) {
const int y8_end = std::min(y8 + step, kMotionFieldHight);
for (int x8 = 0; x8 < motion_field_width; x8 += step) {
const int x8_end = std::min(x8 + step, motion_field_width);
target_motion_field_projection_kernel_func_(
reference_info_, reference_to_current_with_sign, dst_sign, y8,
y8_end, x8, x8_end, &motion_field_);
}
}
}
const absl::Duration elapsed_time = absl::Now() - start;
test_utils::CheckMd5Digest(
"MotionFieldProjectionKernel",
absl::StrFormat("(mv) width %d step %d", motion_field_width, step)
.c_str(),
kDigestMv[width_idx], motion_field_.mv[0],
sizeof(motion_field_.mv[0][0]) * motion_field_.mv.size(),
elapsed_time);
test_utils::CheckMd5Digest(
"MotionFieldProjectionKernel",
absl::StrFormat("(ref offset) width %d step %d", motion_field_width,
step)
.c_str(),
kDigestReferenceOffset[width_idx], motion_field_.reference_offset[0],
sizeof(motion_field_.reference_offset[0][0]) *
motion_field_.reference_offset.size(),
elapsed_time);
}
}
}
TEST_P(MotionFieldProjectionTest, Correctness) { TestRandomValues(false); }
TEST_P(MotionFieldProjectionTest, DISABLED_Speed) { TestRandomValues(true); }
INSTANTIATE_TEST_SUITE_P(C, MotionFieldProjectionTest, testing::Values(0));
#if LIBGAV1_ENABLE_NEON
INSTANTIATE_TEST_SUITE_P(NEON, MotionFieldProjectionTest, testing::Values(0));
#endif
#if LIBGAV1_ENABLE_SSE4_1
INSTANTIATE_TEST_SUITE_P(SSE41, MotionFieldProjectionTest, testing::Values(0));
#endif
} // namespace
} // namespace dsp
} // namespace libgav1