1 // Copyright 2020 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/dsp/weight_mask.h"
16 
17 #include <algorithm>
18 #include <cstdint>
19 #include <ostream>
20 #include <string>
21 #include <type_traits>
22 
23 #include "absl/strings/match.h"
24 #include "absl/strings/str_format.h"
25 #include "absl/time/clock.h"
26 #include "absl/time/time.h"
27 #include "gtest/gtest.h"
28 #include "src/dsp/dsp.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/third_party/libvpx/acm_random.h"
34 #include "tests/utils.h"
35 
36 namespace libgav1 {
37 namespace dsp {
38 namespace {
39 
40 constexpr int kNumSpeedTests = 50000;
41 constexpr int kMaxPredictionSize = 128;
42 // weight_mask is only used with kCompoundPredictionTypeDiffWeighted with
43 // convolve producing the most extreme ranges.
44 // This includes kCompoundOffset in 10bpp and 12bpp.
45 // see: src/dsp/convolve.cc & src/dsp/warp.cc.
46 constexpr int kCompoundPredictionRange[3][2] = {
47     // 8bpp
48     {-5132, 9212},
49     // 10bpp
50     {3988, 61532},
51     // 12bpp
52     {3974, 61559},
53 };
54 
GetDigest8bpp(int id)55 const char* GetDigest8bpp(int id) {
56   static const char* const kDigest[] = {
57       "035267cb2ac5a0f8ff50c2d30ad52226",
58       "3231f4972dd858b734e0cc48c4cd001e",
59       "7e163b69721a13ec9f75b5cd74ffee3f",
60       "" /*kBlock4x16*/,
61       "b75e90abc224acca8754c82039b3ba93",
62       "9f555f3a2c1a933a663d6103b8118dea",
63       "8539e54f34cd6668ff6e6606210be201",
64       "20f85c9db7c878c21fbf2052936f269e",
65       "620ec166de57b0639260b2d72eebfc3e",
66       "be666394b5a894d78f4097b6cca272fe",
67       "57a96816e84cdb381f596c23827b5922",
68       "f2e0d348f608f246b6d8d799b66c189e",
69       "161ac051f38372d9339d36728b9926ba",
70       "d5fad48aaf132a81cb62bba4f07bbebb",
71       "e10be2dca2f7dae38dae75150fc1612d",
72       "7f744481eb551bbc224b5236c82cbade",
73       "0d99bbf31ecddc1c2d5063a68c0e9375",
74       "5fb8ec5f582f0ebfe519ed55860f67c4",
75 
76       // mask_is_inverse = true.
77       "a4250ca39daa700836138371d36d465f",
78       "abe9a9a1c3a5accda9bfefd4d6e81ccb",
79       "e95b08878d0bb5f2293c27c3a6fe0253",
80       "" /*kBlock4x16*/,
81       "e1c52be02ce9ab2800015bb08b866c31",
82       "eea1dc73811f73866edfeb4555865f20",
83       "3178e64085645bd819256a8ab43c7b0a",
84       "ee83884e4d5cd2c9ac04879116bab681",
85       "d107eff7d5ae9ba14d2c6b3b8d9fca49",
86       "400aeea7d299626fc336c46b1ad7a9d8",
87       "e9e26a400f67f3ad36350fe4171fc613",
88       "4c31ad714f470f34127febaf1bac714b",
89       "bbdcb1097c66d561dd4ea16b3fb73f97",
90       "3a21dfbf53e4c964e303a75a3308ce15",
91       "3416dab4512fd0dc61d788b433cd624e",
92       "68ace8f01fdd74aec3fee528c8167738",
93       "9fabe05a6523da81a45150e19f75acff",
94       "7c0643e4d02421d06d7ca71822a94e1d",
95   };
96   return kDigest[id];
97 }
98 
99 #if LIBGAV1_MAX_BITDEPTH >= 10
GetDigest10bpp(int id)100 const char* GetDigest10bpp(int id) {
101   static const char* const kDigest[] = {
102       "1dc9bdd042e5228705b857b42798e364",
103       "c054c8644bd482ce78a139d8e063e013",
104       "bbe4ac48f013f34c84779da05b0bcbe0",
105       "" /*kBlock4x16*/,
106       "13d4759277637a607f25439182553708",
107       "f089667610561a47d50f9f930ad7c454",
108       "46715e6f7819f59725bdb083f4403255",
109       "3774541c339ae3af920ef2b1d6abf6a1",
110       "94913b01d226cb5eb273dfee84b51f65",
111       "be0c0847629dfff8e0e991ed67697a7d",
112       "716b5398b77d7459274d4ea9c91ebd8e",
113       "f5c1b0b461df4182529949472242b421",
114       "5e9576ea4cf107249ce4ae89a72b9c95",
115       "da021bcdf7936f7bd9a2399c69e4d37c",
116       "b3a310a39c1900e00f992839ff188656",
117       "9f3a15351af5945615f296242ec56a38",
118       "b6e0bd03c521c5f00e90530daa7d4432",
119       "3270d7f621d488aec5b76bcf121debd0",
120 
121       // mask_is_inverse = true.
122       "33df96dd246683133eefe4caea6e3f7d",
123       "73e0ccc5d42806548a4b59f856256c1e",
124       "3561a0358cf831aee9477d07feafae2d",
125       "" /*kBlock4x16*/,
126       "c5a2e633c0cd6925e68f21f47f0e2d84",
127       "8755a2d3840dde5fd6a0cce6bd6642c5",
128       "85ec538b72cecd6ea1fddab5ce3b4e64",
129       "a53e0dec84c675c4c6b1f5792b0232ff",
130       "86180da325f9727670a98cf2dbf7410e",
131       "a5fdc95104948047e179b2bc3d47f51d",
132       "9b95b3858187838e4669180e2ddb295e",
133       "6e40ca55608f6bf2f8cd91c8dbf3ddbf",
134       "d3a092672e921b588279d57e50b31888",
135       "9883eb19b733ee9f1cb6a6b6a1a00bb5",
136       "dd34764e068b228b7820321b06864e63",
137       "6c743dc9c8c87c7044151d29993e5042",
138       "44925dab01011a98b8ab1f0308fa852a",
139       "6d984b2ccfa056278e2130771127a943",
140   };
141   return kDigest[id];
142 }
143 #endif  // LIBGAV1_MAX_BITDEPTH >= 10
144 
145 struct WeightMaskTestParam {
WeightMaskTestParamlibgav1::dsp::__anonbf17a3520111::WeightMaskTestParam146   WeightMaskTestParam(int width, int height, bool mask_is_inverse)
147       : width(width), height(height), mask_is_inverse(mask_is_inverse) {}
148   int width;
149   int height;
150   bool mask_is_inverse;
151 };
152 
operator <<(std::ostream & os,const WeightMaskTestParam & param)153 std::ostream& operator<<(std::ostream& os, const WeightMaskTestParam& param) {
154   return os << param.width << "x" << param.height
155             << ", mask_is_inverse: " << param.mask_is_inverse;
156 }
157 
158 template <int bitdepth>
159 class WeightMaskTest : public testing::TestWithParam<WeightMaskTestParam>,
160                        public test_utils::MaxAlignedAllocable {
161  public:
162   WeightMaskTest() = default;
163   ~WeightMaskTest() override = default;
164 
SetUp()165   void SetUp() override {
166     test_utils::ResetDspTable(bitdepth);
167     WeightMaskInit_C();
168     const dsp::Dsp* const dsp = dsp::GetDspTable(bitdepth);
169     ASSERT_NE(dsp, nullptr);
170     const int width_index = FloorLog2(width_) - 3;
171     const int height_index = FloorLog2(height_) - 3;
172     const testing::TestInfo* const test_info =
173         testing::UnitTest::GetInstance()->current_test_info();
174     const char* const test_case = test_info->test_suite_name();
175     if (absl::StartsWith(test_case, "C/")) {
176     } else if (absl::StartsWith(test_case, "NEON/")) {
177       WeightMaskInit_NEON();
178     } else if (absl::StartsWith(test_case, "SSE41/")) {
179       WeightMaskInit_SSE4_1();
180     }
181     func_ = dsp->weight_mask[width_index][height_index][mask_is_inverse_];
182   }
183 
184  protected:
185   void SetInputData(bool use_fixed_values, int value_1, int value_2);
186   void Test(int num_runs, bool use_fixed_values, int value_1, int value_2);
187 
188  private:
189   const int width_ = GetParam().width;
190   const int height_ = GetParam().height;
191   const bool mask_is_inverse_ = GetParam().mask_is_inverse;
192   using PredType =
193       typename std::conditional<bitdepth == 8, int16_t, uint16_t>::type;
194   alignas(
195       kMaxAlignment) PredType block_1_[kMaxPredictionSize * kMaxPredictionSize];
196   alignas(
197       kMaxAlignment) PredType block_2_[kMaxPredictionSize * kMaxPredictionSize];
198   uint8_t mask_[kMaxPredictionSize * kMaxPredictionSize] = {};
199   dsp::WeightMaskFunc func_;
200 };
201 
202 template <int bitdepth>
SetInputData(const bool use_fixed_values,const int value_1,const int value_2)203 void WeightMaskTest<bitdepth>::SetInputData(const bool use_fixed_values,
204                                             const int value_1,
205                                             const int value_2) {
206   if (use_fixed_values) {
207     std::fill(block_1_, block_1_ + kMaxPredictionSize * kMaxPredictionSize,
208               value_1);
209     std::fill(block_2_, block_2_ + kMaxPredictionSize * kMaxPredictionSize,
210               value_2);
211   } else {
212     constexpr int bitdepth_index = (bitdepth - 8) >> 1;
213     libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed());
214     for (int y = 0; y < height_; ++y) {
215       for (int x = 0; x < width_; ++x) {
216         const int min_val = kCompoundPredictionRange[bitdepth_index][0];
217         const int max_val = kCompoundPredictionRange[bitdepth_index][1];
218         block_1_[y * width_ + x] =
219             static_cast<PredType>(rnd(max_val - min_val) + min_val);
220         block_2_[y * width_ + x] =
221             static_cast<PredType>(rnd(max_val - min_val) + min_val);
222       }
223     }
224   }
225 }
226 
DimensionsToBlockSize(int width,int height)227 BlockSize DimensionsToBlockSize(int width, int height) {
228   if (width == 4) {
229     if (height == 4) return kBlock4x4;
230     if (height == 8) return kBlock4x8;
231     if (height == 16) return kBlock4x16;
232     return kBlockInvalid;
233   }
234   if (width == 8) {
235     if (height == 4) return kBlock8x4;
236     if (height == 8) return kBlock8x8;
237     if (height == 16) return kBlock8x16;
238     if (height == 32) return kBlock8x32;
239     return kBlockInvalid;
240   }
241   if (width == 16) {
242     if (height == 4) return kBlock16x4;
243     if (height == 8) return kBlock16x8;
244     if (height == 16) return kBlock16x16;
245     if (height == 32) return kBlock16x32;
246     if (height == 64) return kBlock16x64;
247     return kBlockInvalid;
248   }
249   if (width == 32) {
250     if (height == 8) return kBlock32x8;
251     if (height == 16) return kBlock32x16;
252     if (height == 32) return kBlock32x32;
253     if (height == 64) return kBlock32x64;
254     return kBlockInvalid;
255   }
256   if (width == 64) {
257     if (height == 16) return kBlock64x16;
258     if (height == 32) return kBlock64x32;
259     if (height == 64) return kBlock64x64;
260     if (height == 128) return kBlock64x128;
261     return kBlockInvalid;
262   }
263   if (width == 128) {
264     if (height == 64) return kBlock128x64;
265     if (height == 128) return kBlock128x128;
266     return kBlockInvalid;
267   }
268   return kBlockInvalid;
269 }
270 
271 template <int bitdepth>
Test(const int num_runs,const bool use_fixed_values,const int value_1,const int value_2)272 void WeightMaskTest<bitdepth>::Test(const int num_runs,
273                                     const bool use_fixed_values,
274                                     const int value_1, const int value_2) {
275   if (func_ == nullptr) return;
276   SetInputData(use_fixed_values, value_1, value_2);
277   const absl::Time start = absl::Now();
278   for (int i = 0; i < num_runs; ++i) {
279     func_(block_1_, block_2_, mask_, kMaxPredictionSize);
280   }
281   const absl::Duration elapsed_time = absl::Now() - start;
282   if (use_fixed_values) {
283     int fixed_value = (value_1 - value_2 == 0) ? 38 : 64;
284     if (mask_is_inverse_) fixed_value = 64 - fixed_value;
285     for (int y = 0; y < height_; ++y) {
286       for (int x = 0; x < width_; ++x) {
287         ASSERT_EQ(static_cast<int>(mask_[y * kMaxPredictionSize + x]),
288                   fixed_value)
289             << "x: " << x << " y: " << y;
290       }
291     }
292   } else {
293     const int id_offset = mask_is_inverse_ ? kMaxBlockSizes - 4 : 0;
294     const int id = id_offset +
295                    static_cast<int>(DimensionsToBlockSize(width_, height_)) - 4;
296     if (bitdepth == 8) {
297       test_utils::CheckMd5Digest(
298           absl::StrFormat("BlockSize %dx%d", width_, height_).c_str(),
299           "WeightMask", GetDigest8bpp(id), mask_, sizeof(mask_), elapsed_time);
300 #if LIBGAV1_MAX_BITDEPTH >= 10
301     } else {
302       test_utils::CheckMd5Digest(
303           absl::StrFormat("BlockSize %dx%d", width_, height_).c_str(),
304           "WeightMask", GetDigest10bpp(id), mask_, sizeof(mask_), elapsed_time);
305 #endif
306     }
307   }
308 }
309 
310 const WeightMaskTestParam weight_mask_test_param[] = {
311     WeightMaskTestParam(8, 8, false),     WeightMaskTestParam(8, 16, false),
312     WeightMaskTestParam(8, 32, false),    WeightMaskTestParam(16, 8, false),
313     WeightMaskTestParam(16, 16, false),   WeightMaskTestParam(16, 32, false),
314     WeightMaskTestParam(16, 64, false),   WeightMaskTestParam(32, 8, false),
315     WeightMaskTestParam(32, 16, false),   WeightMaskTestParam(32, 32, false),
316     WeightMaskTestParam(32, 64, false),   WeightMaskTestParam(64, 16, false),
317     WeightMaskTestParam(64, 32, false),   WeightMaskTestParam(64, 64, false),
318     WeightMaskTestParam(64, 128, false),  WeightMaskTestParam(128, 64, false),
319     WeightMaskTestParam(128, 128, false), WeightMaskTestParam(8, 8, true),
320     WeightMaskTestParam(8, 16, true),     WeightMaskTestParam(8, 32, true),
321     WeightMaskTestParam(16, 8, true),     WeightMaskTestParam(16, 16, true),
322     WeightMaskTestParam(16, 32, true),    WeightMaskTestParam(16, 64, true),
323     WeightMaskTestParam(32, 8, true),     WeightMaskTestParam(32, 16, true),
324     WeightMaskTestParam(32, 32, true),    WeightMaskTestParam(32, 64, true),
325     WeightMaskTestParam(64, 16, true),    WeightMaskTestParam(64, 32, true),
326     WeightMaskTestParam(64, 64, true),    WeightMaskTestParam(64, 128, true),
327     WeightMaskTestParam(128, 64, true),   WeightMaskTestParam(128, 128, true),
328 };
329 
330 using WeightMaskTest8bpp = WeightMaskTest<8>;
331 
TEST_P(WeightMaskTest8bpp,FixedValues)332 TEST_P(WeightMaskTest8bpp, FixedValues) {
333   const int min = kCompoundPredictionRange[0][0];
334   const int max = kCompoundPredictionRange[0][1];
335   Test(1, true, min, min);
336   Test(1, true, min, max);
337   Test(1, true, max, min);
338   Test(1, true, max, max);
339 }
340 
TEST_P(WeightMaskTest8bpp,RandomValues)341 TEST_P(WeightMaskTest8bpp, RandomValues) { Test(1, false, -1, -1); }
342 
TEST_P(WeightMaskTest8bpp,DISABLED_Speed)343 TEST_P(WeightMaskTest8bpp, DISABLED_Speed) {
344   Test(kNumSpeedTests, false, -1, -1);
345 }
346 
347 INSTANTIATE_TEST_SUITE_P(C, WeightMaskTest8bpp,
348                          testing::ValuesIn(weight_mask_test_param));
349 #if LIBGAV1_ENABLE_NEON
350 INSTANTIATE_TEST_SUITE_P(NEON, WeightMaskTest8bpp,
351                          testing::ValuesIn(weight_mask_test_param));
352 #endif
353 #if LIBGAV1_ENABLE_SSE4_1
354 INSTANTIATE_TEST_SUITE_P(SSE41, WeightMaskTest8bpp,
355                          testing::ValuesIn(weight_mask_test_param));
356 #endif
357 
358 #if LIBGAV1_MAX_BITDEPTH >= 10
359 using WeightMaskTest10bpp = WeightMaskTest<10>;
360 
TEST_P(WeightMaskTest10bpp,FixedValues)361 TEST_P(WeightMaskTest10bpp, FixedValues) {
362   const int min = kCompoundPredictionRange[1][0];
363   const int max = kCompoundPredictionRange[1][1];
364   Test(1, true, min, min);
365   Test(1, true, min, max);
366   Test(1, true, max, min);
367   Test(1, true, max, max);
368 }
369 
TEST_P(WeightMaskTest10bpp,RandomValues)370 TEST_P(WeightMaskTest10bpp, RandomValues) { Test(1, false, -1, -1); }
371 
TEST_P(WeightMaskTest10bpp,DISABLED_Speed)372 TEST_P(WeightMaskTest10bpp, DISABLED_Speed) {
373   Test(kNumSpeedTests, false, -1, -1);
374 }
375 
376 INSTANTIATE_TEST_SUITE_P(C, WeightMaskTest10bpp,
377                          testing::ValuesIn(weight_mask_test_param));
378 #if LIBGAV1_ENABLE_NEON
379 INSTANTIATE_TEST_SUITE_P(NEON, WeightMaskTest10bpp,
380                          testing::ValuesIn(weight_mask_test_param));
381 #endif
382 #if LIBGAV1_ENABLE_SSE4_1
383 INSTANTIATE_TEST_SUITE_P(SSE41, WeightMaskTest10bpp,
384                          testing::ValuesIn(weight_mask_test_param));
385 #endif
386 #endif  // LIBGAV1_MAX_BITDEPTH >= 10
387 
388 }  // namespace
389 }  // namespace dsp
390 }  // namespace libgav1
391