blob: fd780b3c020ea1703858e2f5242b40c4881adaca [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/reconstruction.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include "absl/strings/match.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/dsp/constants.h"
#include "src/dsp/dsp.h"
#include "src/dsp/inverse_transform.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/memory.h"
#include "tests/block_utils.h"
#include "tests/utils.h"
namespace libgav1 {
namespace {
// Import the scan tables in the anonymous namespace.
#include "src/scan_tables.inc"
constexpr int kTestTransformSize = 4;
constexpr int8_t kTestBitdepth = 8;
using testing::ElementsAreArray;
// The 'int' parameter is unused but required to allow for instantiations of C,
// NEON, etc.
class ReconstructionTest : public testing::TestWithParam<int> {
public:
ReconstructionTest() = default;
ReconstructionTest(const ReconstructionTest&) = delete;
ReconstructionTest& operator=(const ReconstructionTest&) = delete;
~ReconstructionTest() override = default;
protected:
void SetUp() override {
test_utils::ResetDspTable(kTestBitdepth);
dsp::InverseTransformInit_C();
dsp_ = dsp::GetDspTable(kTestBitdepth);
ASSERT_NE(dsp_, nullptr);
const testing::TestInfo* const test_info =
testing::UnitTest::GetInstance()->current_test_info();
if (test_info->value_param() != nullptr) {
const char* const test_case = test_info->test_suite_name();
if (absl::StartsWith(test_case, "C/")) {
} else if (absl::StartsWith(test_case, "SSE41/")) {
if ((GetCpuInfo() & kSSE4_1) != 0) {
dsp::InverseTransformInit_SSE4_1();
}
} else if (absl::StartsWith(test_case, "NEON/")) {
dsp::InverseTransformInit_NEON();
} else {
FAIL() << "Unrecognized architecture prefix in test case name: "
<< test_case;
}
}
InitBuffers();
}
void InitBuffers(int width = kTestTransformSize,
int height = kTestTransformSize) {
const int size = width * height;
buffer_.clear();
buffer_.resize(size);
residual_buffer_.clear();
residual_buffer_.resize(size);
for (int i = 0; i < size; ++i) {
buffer_[i] = residual_buffer_[i] = i % 256;
}
frame_buffer_.Reset(height, width, buffer_.data());
}
template <int bitdepth>
void TestWht();
std::vector<uint8_t> buffer_;
std::vector<int16_t> residual_buffer_;
// |frame_buffer_| is just a 2D array view into the |buffer_|.
Array2DView<uint8_t> frame_buffer_;
const dsp::Dsp* dsp_;
};
template <int bitdepth>
void ReconstructionTest::TestWht() {
static_assert(bitdepth == kBitdepth8 || bitdepth == kBitdepth10, "");
for (const auto transform :
dsp_->inverse_transforms[dsp::kTransform1dWht][dsp::kTransform1dSize4]) {
if (transform == nullptr) {
GTEST_SKIP() << "No function available for dsp::kTransform1dWht";
}
}
constexpr int max = 16 << bitdepth;
constexpr int min = -max;
static constexpr int16_t residual_inputs[][16]{
{64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, max - 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, min - 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
// Note these are unrealistic inputs, but serve to test each position in
// the array and match extremes in some commercial test vectors.
{max, max, max, max, max, max, max, max, max, max, max, max, max, max,
max, max},
{min, min, min, min, min, min, min, min, min, min, min, min, min, min,
min, min}};
// Before the Reconstruct() call, the frame buffer is filled with all 127.
// After the Reconstruct() call, the frame buffer is expected to have the
// following values.
static constexpr uint8_t frame_outputs[][16]{
{131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131,
131, 131},
{132, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131,
131, 131},
{255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255},
{0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0},
{255, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127},
{0, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127},
};
const TransformSize tx_size = kTransformSize4x4;
const TransformType tx_type = kTransformTypeDctDct;
const int tx_width = kTransformWidth[tx_size];
const int tx_height = kTransformHeight[tx_size];
const uint16_t* const scan = kScan[GetTransformClass(tx_type)][tx_size];
InitBuffers(tx_width, tx_height);
const int num_tests = sizeof(residual_inputs) / sizeof(residual_inputs[0]);
for (int i = 0; i < num_tests; ++i) {
int16_t eob; // Also known as non_zero_coeff_count.
for (eob = 15; eob >= 0; --eob) {
if (residual_inputs[i][scan[eob]] != 0) break;
}
++eob;
memcpy(residual_buffer_.data(), residual_inputs[i],
sizeof(residual_inputs[i]));
memset(buffer_.data(), 127, sizeof(frame_outputs[i]));
Reconstruct(*dsp_, tx_type, tx_size, /*lossless=*/true,
residual_buffer_.data(), 0, 0, &frame_buffer_, eob);
EXPECT_TRUE(test_utils::CompareBlocks(buffer_.data(), frame_outputs[i],
tx_width, tx_height, tx_width,
tx_width, false, true))
<< "Mismatch WHT test case " << i;
}
}
TEST_P(ReconstructionTest, ReconstructionSimple) {
for (const auto transform :
dsp_->inverse_transforms[dsp::kTransform1dIdentity]
[dsp::kTransform1dSize4]) {
if (transform == nullptr) GTEST_SKIP();
}
Reconstruct(*dsp_, kTransformTypeIdentityIdentity, kTransformSize4x4, false,
residual_buffer_.data(), 0, 0, &frame_buffer_, 16);
// clang-format off
static constexpr uint8_t expected_output_buffer[] = {
0, 1, 2, 3,
5, 6, 7, 8,
9, 10, 11, 12,
14, 15, 16, 17
};
// clang-format on
EXPECT_THAT(buffer_, ElementsAreArray(expected_output_buffer));
}
TEST_P(ReconstructionTest, ReconstructionFlipY) {
for (const auto transform :
dsp_->inverse_transforms[dsp::kTransform1dIdentity]
[dsp::kTransform1dSize4]) {
if (transform == nullptr) GTEST_SKIP();
}
Reconstruct(*dsp_, kTransformTypeIdentityFlipadst, kTransformSize4x4, false,
residual_buffer_.data(), 0, 0, &frame_buffer_, 16);
// clang-format off
static constexpr uint8_t expected_buffer[] = {
0, 1, 2, 3,
4, 5, 6, 7,
7, 8, 9, 10,
14, 15, 16, 17
};
// clang-format on
EXPECT_THAT(buffer_, ElementsAreArray(expected_buffer));
}
TEST_P(ReconstructionTest, ReconstructionFlipX) {
for (const auto transform :
dsp_->inverse_transforms[dsp::kTransform1dIdentity]
[dsp::kTransform1dSize4]) {
if (transform == nullptr) GTEST_SKIP();
}
Reconstruct(*dsp_, kTransformTypeFlipadstIdentity, kTransformSize4x4, false,
residual_buffer_.data(), 0, 0, &frame_buffer_, 16);
// clang-format off
static constexpr uint8_t expected_buffer[] = {
0, 1, 2, 3,
4, 5, 6, 8,
8, 10, 10, 13,
12, 14, 14, 18
};
// clang-format on
EXPECT_THAT(buffer_, ElementsAreArray(expected_buffer));
}
TEST_P(ReconstructionTest, ReconstructionFlipXAndFlipY) {
for (const auto transform :
dsp_->inverse_transforms[dsp::kTransform1dIdentity]
[dsp::kTransform1dSize4]) {
if (transform == nullptr) GTEST_SKIP();
}
Reconstruct(*dsp_, kTransformTypeFlipadstFlipadst, kTransformSize4x4, false,
residual_buffer_.data(), 0, 0, &frame_buffer_, 16);
// clang-format off
static constexpr uint8_t expected_buffer[] = {
0, 1, 2, 3,
4, 5, 6, 8,
8, 8, 10, 9,
12, 14, 14, 19
};
// clang-format on
EXPECT_THAT(buffer_, ElementsAreArray(expected_buffer));
}
TEST_P(ReconstructionTest, ReconstructionNonZeroStart) {
uint8_t buffer[64] = {};
Array2DView<uint8_t> frame_buffer(8, 8, buffer);
int k = 0;
for (int i = 0; i < kTestTransformSize; ++i) {
for (int j = 0; j < kTestTransformSize; ++j) {
frame_buffer[i + 4][j + 4] = k++;
}
}
for (const auto transform :
dsp_->inverse_transforms[dsp::kTransform1dIdentity]
[dsp::kTransform1dSize4]) {
if (transform == nullptr) GTEST_SKIP();
}
Reconstruct(*dsp_, kTransformTypeIdentityIdentity, kTransformSize4x4, false,
residual_buffer_.data(), 4, 4, &frame_buffer, 64);
// clang-format off
static constexpr uint8_t expected_buffer[] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 2, 3,
0, 0, 0, 0, 5, 6, 7, 8,
0, 0, 0, 0, 9, 10, 11, 12,
0, 0, 0, 0, 14, 15, 16, 17
};
// clang-format on
EXPECT_THAT(buffer, ElementsAreArray(expected_buffer));
}
TEST_P(ReconstructionTest, Wht8bit) { TestWht<kBitdepth8>(); }
#if LIBGAV1_MAX_BITDEPTH >= 10
TEST_P(ReconstructionTest, Wht10bit) { TestWht<kBitdepth10>(); }
#endif
INSTANTIATE_TEST_SUITE_P(C, ReconstructionTest, testing::Values(0));
#if LIBGAV1_ENABLE_SSE4_1
INSTANTIATE_TEST_SUITE_P(SSE41, ReconstructionTest, testing::Values(0));
#endif
#if LIBGAV1_ENABLE_NEON
INSTANTIATE_TEST_SUITE_P(NEON, ReconstructionTest, testing::Values(0));
#endif
} // namespace
} // namespace libgav1