1 // Copyright 2021 The libgav1 Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/reconstruction.h"
16
17 #include <cstddef>
18 #include <cstdint>
19 #include <cstring>
20 #include <vector>
21
22 #include "absl/strings/match.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include "src/dsp/constants.h"
26 #include "src/dsp/dsp.h"
27 #include "src/dsp/inverse_transform.h"
28 #include "src/utils/array_2d.h"
29 #include "src/utils/common.h"
30 #include "src/utils/constants.h"
31 #include "src/utils/cpu.h"
32 #include "src/utils/memory.h"
33 #include "tests/block_utils.h"
34 #include "tests/utils.h"
35
36 namespace libgav1 {
37 namespace {
38
39 // Import the scan tables in the anonymous namespace.
40 #include "src/scan_tables.inc"
41
42 constexpr int kTestTransformSize = 4;
43 constexpr int8_t kTestBitdepth = 8;
44
45 using testing::ElementsAreArray;
46
47 // The 'int' parameter is unused but required to allow for instantiations of C,
48 // NEON, etc.
49 class ReconstructionTest : public testing::TestWithParam<int> {
50 public:
51 ReconstructionTest() = default;
52 ReconstructionTest(const ReconstructionTest&) = delete;
53 ReconstructionTest& operator=(const ReconstructionTest&) = delete;
54 ~ReconstructionTest() override = default;
55
56 protected:
SetUp()57 void SetUp() override {
58 test_utils::ResetDspTable(kTestBitdepth);
59 dsp::InverseTransformInit_C();
60 dsp_ = dsp::GetDspTable(kTestBitdepth);
61 ASSERT_NE(dsp_, nullptr);
62 const testing::TestInfo* const test_info =
63 testing::UnitTest::GetInstance()->current_test_info();
64 if (test_info->value_param() != nullptr) {
65 const char* const test_case = test_info->test_suite_name();
66 if (absl::StartsWith(test_case, "C/")) {
67 } else if (absl::StartsWith(test_case, "SSE41/")) {
68 if ((GetCpuInfo() & kSSE4_1) == 0) GTEST_SKIP() << "No SSE4.1 support!";
69 dsp::InverseTransformInit_SSE4_1();
70 } else if (absl::StartsWith(test_case, "NEON/")) {
71 dsp::InverseTransformInit_NEON();
72 } else {
73 FAIL() << "Unrecognized architecture prefix in test case name: "
74 << test_case;
75 }
76 }
77 InitBuffers();
78 }
79
InitBuffers(int width=kTestTransformSize,int height=kTestTransformSize)80 void InitBuffers(int width = kTestTransformSize,
81 int height = kTestTransformSize) {
82 const int size = width * height;
83 buffer_.clear();
84 buffer_.resize(size);
85 residual_buffer_.clear();
86 residual_buffer_.resize(size);
87 for (int i = 0; i < size; ++i) {
88 buffer_[i] = residual_buffer_[i] = i % 256;
89 }
90 frame_buffer_.Reset(height, width, buffer_.data());
91 }
92
93 template <int bitdepth>
94 void TestWht();
95
96 std::vector<uint8_t> buffer_;
97 std::vector<int16_t> residual_buffer_;
98 // |frame_buffer_| is just a 2D array view into the |buffer_|.
99 Array2DView<uint8_t> frame_buffer_;
100 const dsp::Dsp* dsp_;
101 };
102
103 template <int bitdepth>
TestWht()104 void ReconstructionTest::TestWht() {
105 static_assert(bitdepth == kBitdepth8 || bitdepth == kBitdepth10, "");
106 for (const auto transform :
107 dsp_->inverse_transforms[dsp::kTransform1dWht][dsp::kTransform1dSize4]) {
108 if (transform == nullptr) {
109 GTEST_SKIP() << "No function available for dsp::kTransform1dWht";
110 }
111 }
112 constexpr int max = 16 << bitdepth;
113 constexpr int min = -max;
114 static constexpr int16_t residual_inputs[][16]{
115 {64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
116 {69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
117 {0, 0, 0, 0, 0, max - 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
118 {0, 0, 0, 0, 0, min - 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
119 // Note these are unrealistic inputs, but serve to test each position in
120 // the array and match extremes in some commercial test vectors.
121 {max, max, max, max, max, max, max, max, max, max, max, max, max, max,
122 max, max},
123 {min, min, min, min, min, min, min, min, min, min, min, min, min, min,
124 min, min}};
125 // Before the Reconstruct() call, the frame buffer is filled with all 127.
126 // After the Reconstruct() call, the frame buffer is expected to have the
127 // following values.
128 static constexpr uint8_t frame_outputs[][16]{
129 {131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131,
130 131, 131},
131 {132, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131,
132 131, 131},
133 {255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255},
134 {0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0},
135 {255, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
136 127, 127},
137 {0, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
138 127},
139 };
140
141 const TransformSize tx_size = kTransformSize4x4;
142 const TransformType tx_type = kTransformTypeDctDct;
143 const int tx_width = kTransformWidth[tx_size];
144 const int tx_height = kTransformHeight[tx_size];
145 const uint16_t* const scan = kScan[GetTransformClass(tx_type)][tx_size];
146
147 InitBuffers(tx_width, tx_height);
148
149 const int num_tests = sizeof(residual_inputs) / sizeof(residual_inputs[0]);
150 for (int i = 0; i < num_tests; ++i) {
151 int16_t eob; // Also known as non_zero_coeff_count.
152 for (eob = 15; eob >= 0; --eob) {
153 if (residual_inputs[i][scan[eob]] != 0) break;
154 }
155 ++eob;
156 memcpy(residual_buffer_.data(), residual_inputs[i],
157 sizeof(residual_inputs[i]));
158 memset(buffer_.data(), 127, sizeof(frame_outputs[i]));
159 Reconstruct(*dsp_, tx_type, tx_size, /*lossless=*/true,
160 residual_buffer_.data(), 0, 0, &frame_buffer_, eob);
161
162 EXPECT_TRUE(test_utils::CompareBlocks(buffer_.data(), frame_outputs[i],
163 tx_width, tx_height, tx_width,
164 tx_width, false, true))
165 << "Mismatch WHT test case " << i;
166 }
167 }
168
TEST_P(ReconstructionTest,ReconstructionSimple)169 TEST_P(ReconstructionTest, ReconstructionSimple) {
170 for (const auto transform :
171 dsp_->inverse_transforms[dsp::kTransform1dIdentity]
172 [dsp::kTransform1dSize4]) {
173 if (transform == nullptr) GTEST_SKIP();
174 }
175 Reconstruct(*dsp_, kTransformTypeIdentityIdentity, kTransformSize4x4, false,
176 residual_buffer_.data(), 0, 0, &frame_buffer_, 16);
177 // clang-format off
178 static constexpr uint8_t expected_output_buffer[] = {
179 0, 1, 2, 3,
180 5, 6, 7, 8,
181 9, 10, 11, 12,
182 14, 15, 16, 17
183 };
184 // clang-format on
185 EXPECT_THAT(buffer_, ElementsAreArray(expected_output_buffer));
186 }
187
TEST_P(ReconstructionTest,ReconstructionFlipY)188 TEST_P(ReconstructionTest, ReconstructionFlipY) {
189 for (const auto transform :
190 dsp_->inverse_transforms[dsp::kTransform1dIdentity]
191 [dsp::kTransform1dSize4]) {
192 if (transform == nullptr) GTEST_SKIP();
193 }
194 Reconstruct(*dsp_, kTransformTypeIdentityFlipadst, kTransformSize4x4, false,
195 residual_buffer_.data(), 0, 0, &frame_buffer_, 16);
196 // clang-format off
197 static constexpr uint8_t expected_buffer[] = {
198 0, 1, 2, 3,
199 4, 5, 6, 7,
200 7, 8, 9, 10,
201 14, 15, 16, 17
202 };
203 // clang-format on
204 EXPECT_THAT(buffer_, ElementsAreArray(expected_buffer));
205 }
206
TEST_P(ReconstructionTest,ReconstructionFlipX)207 TEST_P(ReconstructionTest, ReconstructionFlipX) {
208 for (const auto transform :
209 dsp_->inverse_transforms[dsp::kTransform1dIdentity]
210 [dsp::kTransform1dSize4]) {
211 if (transform == nullptr) GTEST_SKIP();
212 }
213 Reconstruct(*dsp_, kTransformTypeFlipadstIdentity, kTransformSize4x4, false,
214 residual_buffer_.data(), 0, 0, &frame_buffer_, 16);
215 // clang-format off
216 static constexpr uint8_t expected_buffer[] = {
217 0, 1, 2, 3,
218 4, 5, 6, 8,
219 8, 10, 10, 13,
220 12, 14, 14, 18
221 };
222 // clang-format on
223 EXPECT_THAT(buffer_, ElementsAreArray(expected_buffer));
224 }
225
TEST_P(ReconstructionTest,ReconstructionFlipXAndFlipY)226 TEST_P(ReconstructionTest, ReconstructionFlipXAndFlipY) {
227 for (const auto transform :
228 dsp_->inverse_transforms[dsp::kTransform1dIdentity]
229 [dsp::kTransform1dSize4]) {
230 if (transform == nullptr) GTEST_SKIP();
231 }
232 Reconstruct(*dsp_, kTransformTypeFlipadstFlipadst, kTransformSize4x4, false,
233 residual_buffer_.data(), 0, 0, &frame_buffer_, 16);
234 // clang-format off
235 static constexpr uint8_t expected_buffer[] = {
236 0, 1, 2, 3,
237 4, 5, 6, 8,
238 8, 8, 10, 9,
239 12, 14, 14, 19
240 };
241 // clang-format on
242 EXPECT_THAT(buffer_, ElementsAreArray(expected_buffer));
243 }
244
TEST_P(ReconstructionTest,ReconstructionNonZeroStart)245 TEST_P(ReconstructionTest, ReconstructionNonZeroStart) {
246 uint8_t buffer[64] = {};
247 Array2DView<uint8_t> frame_buffer(8, 8, buffer);
248 int k = 0;
249 for (int i = 0; i < kTestTransformSize; ++i) {
250 for (int j = 0; j < kTestTransformSize; ++j) {
251 frame_buffer[i + 4][j + 4] = k++;
252 }
253 }
254 for (const auto transform :
255 dsp_->inverse_transforms[dsp::kTransform1dIdentity]
256 [dsp::kTransform1dSize4]) {
257 if (transform == nullptr) GTEST_SKIP();
258 }
259 Reconstruct(*dsp_, kTransformTypeIdentityIdentity, kTransformSize4x4, false,
260 residual_buffer_.data(), 4, 4, &frame_buffer, 64);
261 // clang-format off
262 static constexpr uint8_t expected_buffer[] = {
263 0, 0, 0, 0, 0, 0, 0, 0,
264 0, 0, 0, 0, 0, 0, 0, 0,
265 0, 0, 0, 0, 0, 0, 0, 0,
266 0, 0, 0, 0, 0, 0, 0, 0,
267 0, 0, 0, 0, 0, 1, 2, 3,
268 0, 0, 0, 0, 5, 6, 7, 8,
269 0, 0, 0, 0, 9, 10, 11, 12,
270 0, 0, 0, 0, 14, 15, 16, 17
271 };
272 // clang-format on
273 EXPECT_THAT(buffer, ElementsAreArray(expected_buffer));
274 }
275
TEST_P(ReconstructionTest,Wht8bit)276 TEST_P(ReconstructionTest, Wht8bit) { TestWht<kBitdepth8>(); }
277
278 #if LIBGAV1_MAX_BITDEPTH >= 10
TEST_P(ReconstructionTest,Wht10bit)279 TEST_P(ReconstructionTest, Wht10bit) { TestWht<kBitdepth10>(); }
280 #endif
281
282 INSTANTIATE_TEST_SUITE_P(C, ReconstructionTest, testing::Values(0));
283
284 #if LIBGAV1_ENABLE_SSE4_1
285 INSTANTIATE_TEST_SUITE_P(SSE41, ReconstructionTest, testing::Values(0));
286 #endif
287
288 #if LIBGAV1_ENABLE_NEON
289 INSTANTIATE_TEST_SUITE_P(NEON, ReconstructionTest, testing::Values(0));
290 #endif
291
292 } // namespace
293 } // namespace libgav1
294