convolve_test.cc (10428B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include "lib/jxl/convolve.h" 7 8 #include <jxl/memory_manager.h> 9 #include <jxl/types.h> 10 11 #include <cinttypes> // PRIx64 12 #include <ctime> 13 14 #undef HWY_TARGET_INCLUDE 15 #define HWY_TARGET_INCLUDE "lib/jxl/convolve_test.cc" 16 #include <hwy/foreach_target.h> 17 #include <hwy/highway.h> 18 #include <hwy/nanobenchmark.h> 19 #include <hwy/tests/hwy_gtest.h> 20 #include <vector> 21 22 #include "lib/jxl/base/compiler_specific.h" 23 #include "lib/jxl/base/data_parallel.h" 24 #include "lib/jxl/base/printf_macros.h" 25 #include "lib/jxl/base/random.h" 26 #include "lib/jxl/base/rect.h" 27 #include "lib/jxl/image_ops.h" 28 #include "lib/jxl/image_test_utils.h" 29 #include "lib/jxl/test_memory_manager.h" 30 #include "lib/jxl/test_utils.h" 31 #include "lib/jxl/testing.h" 32 33 #ifndef JXL_DEBUG_CONVOLVE 34 #define JXL_DEBUG_CONVOLVE 0 35 #endif 36 37 #include "lib/jxl/convolve-inl.h" 38 39 HWY_BEFORE_NAMESPACE(); 40 namespace jxl { 41 namespace HWY_NAMESPACE { 42 43 void TestNeighbors() { 44 const Neighbors::D d; 45 const Neighbors::V v = Iota(d, 0); 46 constexpr size_t kMaxVectorSize = 64; 47 constexpr size_t M = kMaxVectorSize / sizeof(float); 48 HWY_ALIGN float actual[M] = {0}; 49 50 HWY_ALIGN float first_l1[M] = {0, 0, 1, 2, 3, 4, 5, 6, 51 7, 8, 9, 10, 11, 12, 13, 14}; 52 Store(Neighbors::FirstL1(v), d, actual); 53 const size_t N = Lanes(d); 54 ASSERT_LE(N, M); 55 EXPECT_EQ(std::vector<float>(first_l1, first_l1 + N), 56 std::vector<float>(actual, actual + N)); 57 58 #if HWY_TARGET != HWY_SCALAR 59 HWY_ALIGN float first_l2[M] = {1, 0, 0, 1, 2, 3, 4, 5, 60 6, 7, 8, 9, 10, 11, 12, 13}; 61 Store(Neighbors::FirstL2(v), d, actual); 62 EXPECT_EQ(std::vector<float>(first_l2, first_l2 + N), 63 std::vector<float>(actual, actual + N)); 64 65 HWY_ALIGN float first_l3[] = {2, 1, 0, 0, 1, 2, 3, 4, 66 5, 6, 7, 8, 9, 10, 11, 12}; 67 Store(Neighbors::FirstL3(v), d, actual); 68 EXPECT_EQ(std::vector<float>(first_l3, first_l3 + N), 69 std::vector<float>(actual, actual + N)); 70 #endif // HWY_TARGET != HWY_SCALAR 71 } 72 73 void VerifySymmetric3(const size_t xsize, const size_t ysize, ThreadPool* pool, 74 Rng* rng) { 75 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 76 const Rect rect(0, 0, xsize, ysize); 77 78 JXL_TEST_ASSIGN_OR_DIE(ImageF in, 79 ImageF::Create(memory_manager, xsize, ysize)); 80 GenerateImage(*rng, &in, 0.0f, 1.0f); 81 82 JXL_TEST_ASSIGN_OR_DIE(ImageF out_expected, 83 ImageF::Create(memory_manager, xsize, ysize)); 84 JXL_TEST_ASSIGN_OR_DIE(ImageF out_actual, 85 ImageF::Create(memory_manager, xsize, ysize)); 86 87 const WeightsSymmetric3& weights = WeightsSymmetric3Lowpass(); 88 ASSERT_TRUE(Symmetric3(in, rect, weights, pool, &out_expected)); 89 ASSERT_TRUE(SlowSymmetric3(in, rect, weights, pool, &out_actual)); 90 91 JXL_TEST_ASSERT_OK( 92 VerifyRelativeError(out_expected, out_actual, 1E-5f, 1E-5f, _)); 93 } 94 95 std::vector<Rect> GenerateTestRectangles(size_t xsize, size_t ysize) { 96 std::vector<Rect> out; 97 for (size_t tl : {0, 1, 13}) { 98 for (size_t br : {0, 1, 13}) { 99 if (xsize > tl + br && ysize > tl + br) { 100 out.emplace_back(tl, tl, xsize - tl - br, ysize - tl - br); 101 } 102 } 103 } 104 return out; 105 } 106 107 // Ensures Symmetric and Separable give the same result. 108 void VerifySymmetric5(const size_t xsize, const size_t ysize, ThreadPool* pool, 109 Rng* rng) { 110 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 111 JXL_TEST_ASSIGN_OR_DIE(ImageF in, 112 ImageF::Create(memory_manager, xsize, ysize)); 113 GenerateImage(*rng, &in, 0.0f, 1.0f); 114 115 for (const Rect& in_rect : GenerateTestRectangles(xsize, ysize)) { 116 JXL_DEBUG(JXL_DEBUG_CONVOLVE, 117 "in_rect: %" PRIuS "x%" PRIuS "+%" PRIuS ",%" PRIuS "", 118 in_rect.xsize(), in_rect.ysize(), in_rect.x0(), in_rect.y0()); 119 { 120 Rect out_rect = in_rect; 121 JXL_TEST_ASSIGN_OR_DIE(ImageF out_expected, 122 ImageF::Create(memory_manager, xsize, ysize)); 123 JXL_TEST_ASSIGN_OR_DIE(ImageF out_actual, 124 ImageF::Create(memory_manager, xsize, ysize)); 125 FillImage(-1.0f, &out_expected); 126 FillImage(-1.0f, &out_actual); 127 128 ASSERT_TRUE(SlowSeparable5(in, in_rect, WeightsSeparable5Lowpass(), pool, 129 &out_expected, out_rect)); 130 ASSERT_TRUE(Symmetric5(in, in_rect, WeightsSymmetric5Lowpass(), pool, 131 &out_actual, out_rect)); 132 133 JXL_TEST_ASSERT_OK( 134 VerifyRelativeError(out_expected, out_actual, 1E-5f, 1E-5f, _)); 135 } 136 { 137 Rect out_rect(0, 0, in_rect.xsize(), in_rect.ysize()); 138 JXL_TEST_ASSIGN_OR_DIE( 139 ImageF out_expected, 140 ImageF::Create(memory_manager, out_rect.xsize(), out_rect.ysize())); 141 JXL_TEST_ASSIGN_OR_DIE( 142 ImageF out_actual, 143 ImageF::Create(memory_manager, out_rect.xsize(), out_rect.ysize())); 144 145 ASSERT_TRUE(SlowSeparable5(in, in_rect, WeightsSeparable5Lowpass(), pool, 146 &out_expected, out_rect)); 147 ASSERT_TRUE(Symmetric5(in, in_rect, WeightsSymmetric5Lowpass(), pool, 148 &out_actual, out_rect)); 149 150 JXL_TEST_ASSERT_OK( 151 VerifyRelativeError(out_expected, out_actual, 1E-5f, 1E-5f, _)); 152 } 153 } 154 } 155 156 void VerifySeparable5(const size_t xsize, const size_t ysize, ThreadPool* pool, 157 Rng* rng) { 158 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 159 const Rect rect(0, 0, xsize, ysize); 160 161 JXL_TEST_ASSIGN_OR_DIE(ImageF in, 162 ImageF::Create(memory_manager, xsize, ysize)); 163 GenerateImage(*rng, &in, 0.0f, 1.0f); 164 165 JXL_TEST_ASSIGN_OR_DIE(ImageF out_expected, 166 ImageF::Create(memory_manager, xsize, ysize)); 167 JXL_TEST_ASSIGN_OR_DIE(ImageF out_actual, 168 ImageF::Create(memory_manager, xsize, ysize)); 169 170 const WeightsSeparable5& weights = WeightsSeparable5Lowpass(); 171 ASSERT_TRUE(SlowSeparable5(in, rect, weights, pool, &out_expected, rect)); 172 ASSERT_TRUE(Separable5(in, rect, weights, pool, &out_actual)); 173 174 JXL_TEST_ASSERT_OK( 175 VerifyRelativeError(out_expected, out_actual, 1E-5f, 1E-5f, _)); 176 } 177 178 // For all xsize/ysize and kernels: 179 void TestConvolve() { 180 TestNeighbors(); 181 182 test::ThreadPoolForTests pool(4); 183 const auto do_test = [](const uint32_t task, size_t /*thread*/) -> Status { 184 const size_t xsize = task; 185 Rng rng(129 + 13 * xsize); 186 187 ThreadPool* null_pool = nullptr; 188 test::ThreadPoolForTests pool3(3); 189 for (size_t ysize = kConvolveMaxRadius; ysize < 16; ++ysize) { 190 JXL_DEBUG(JXL_DEBUG_CONVOLVE, 191 "%" PRIuS " x %" PRIuS " (target %" PRIx64 192 ")===============================", 193 xsize, ysize, static_cast<int64_t>(HWY_TARGET)); 194 195 JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym3------------------"); 196 VerifySymmetric3(xsize, ysize, null_pool, &rng); 197 VerifySymmetric3(xsize, ysize, pool3.get(), &rng); 198 199 JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym5------------------"); 200 VerifySymmetric5(xsize, ysize, null_pool, &rng); 201 VerifySymmetric5(xsize, ysize, pool3.get(), &rng); 202 203 JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sep5------------------"); 204 VerifySeparable5(xsize, ysize, null_pool, &rng); 205 VerifySeparable5(xsize, ysize, pool3.get(), &rng); 206 } 207 return true; 208 }; 209 EXPECT_EQ(true, RunOnPool(pool.get(), kConvolveMaxRadius, 40, 210 ThreadPool::NoInit, do_test, "TestConvolve")); 211 } 212 213 // Measures durations, verifies results, prints timings. `unpredictable1` 214 // must have value 1 (unknown to the compiler to prevent elision). 215 template <class Conv> 216 void BenchmarkConv(const char* caption, const Conv& conv, 217 const hwy::FuncInput unpredictable1) { 218 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 219 const size_t kNumInputs = 1; 220 const hwy::FuncInput inputs[kNumInputs] = {unpredictable1}; 221 hwy::Result results[kNumInputs]; 222 223 const size_t kDim = 160; // in+out fit in L2 224 JXL_TEST_ASSIGN_OR_DIE(ImageF in, ImageF::Create(memory_manager, kDim, kDim)); 225 ZeroFillImage(&in); 226 in.Row(kDim / 2)[kDim / 2] = unpredictable1; 227 JXL_TEST_ASSIGN_OR_DIE(ImageF out, 228 ImageF::Create(memory_manager, kDim, kDim)); 229 230 hwy::Params p; 231 p.verbose = false; 232 p.max_evals = 7; 233 p.target_rel_mad = 0.002; 234 const size_t num_results = MeasureClosure( 235 [&in, &conv, &out](const hwy::FuncInput input) { 236 conv(in, &out); 237 return out.Row(input)[0]; 238 }, 239 inputs, kNumInputs, results, p); 240 if (num_results != kNumInputs) { 241 fprintf(stderr, "MeasureClosure failed.\n"); 242 } 243 for (size_t i = 0; i < num_results; ++i) { 244 const double seconds = static_cast<double>(results[i].ticks) / 245 hwy::platform::InvariantTicksPerSecond(); 246 printf("%12s: %7.2f MP/s (MAD=%4.2f%%)\n", caption, 247 kDim * kDim * 1E-6 / seconds, 248 static_cast<double>(results[i].variability) * 100.0); 249 } 250 } 251 252 struct ConvSymmetric3 { 253 void operator()(const ImageF& in, ImageF* JXL_RESTRICT out) const { 254 ThreadPool* null_pool = nullptr; 255 ASSERT_TRUE( 256 Symmetric3(in, Rect(in), WeightsSymmetric3Lowpass(), null_pool, out)); 257 } 258 }; 259 260 struct ConvSeparable5 { 261 void operator()(const ImageF& in, ImageF* JXL_RESTRICT out) const { 262 ThreadPool* null_pool = nullptr; 263 ASSERT_TRUE( 264 Separable5(in, Rect(in), WeightsSeparable5Lowpass(), null_pool, out)); 265 } 266 }; 267 268 void BenchmarkAll() { 269 #if JXL_FALSE // disabled to avoid test timeouts, run manually on demand 270 const hwy::FuncInput unpredictable1 = time(nullptr) != 1234; 271 BenchmarkConv("Symmetric3", ConvSymmetric3(), unpredictable1); 272 BenchmarkConv("Separable5", ConvSeparable5(), unpredictable1); 273 #endif 274 } 275 276 // NOLINTNEXTLINE(google-readability-namespace-comments) 277 } // namespace HWY_NAMESPACE 278 } // namespace jxl 279 HWY_AFTER_NAMESPACE(); 280 281 #if HWY_ONCE 282 namespace jxl { 283 284 class ConvolveTest : public hwy::TestWithParamTarget {}; 285 HWY_TARGET_INSTANTIATE_TEST_SUITE_P(ConvolveTest); 286 287 HWY_EXPORT_AND_TEST_P(ConvolveTest, TestConvolve); 288 289 HWY_EXPORT_AND_TEST_P(ConvolveTest, BenchmarkAll); 290 291 } // namespace jxl 292 #endif