ac_strategy_test.cc (10467B)
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/ac_strategy.h" 7 8 #include <jxl/memory_manager.h> 9 10 #include <algorithm> 11 #include <cstring> 12 #include <hwy/base.h> // HWY_ALIGN_MAX 13 #include <hwy/tests/hwy_gtest.h> 14 15 #include "lib/jxl/base/random.h" 16 #include "lib/jxl/coeff_order_fwd.h" 17 #include "lib/jxl/dec_transforms_testonly.h" 18 #include "lib/jxl/enc_transforms.h" 19 #include "lib/jxl/memory_manager_internal.h" 20 #include "lib/jxl/simd_util.h" 21 #include "lib/jxl/test_memory_manager.h" 22 #include "lib/jxl/test_utils.h" 23 #include "lib/jxl/testing.h" 24 25 namespace jxl { 26 namespace { 27 28 // Test that DCT -> IDCT is a noop. 29 class AcStrategyRoundtrip : public ::hwy::TestWithParamTargetAndT<int> { 30 protected: 31 void Run() { 32 JxlMemoryManager* memory_manager = test::MemoryManager(); 33 const AcStrategyType type = static_cast<AcStrategyType>(GetParam()); 34 const AcStrategy acs = AcStrategy::FromRawStrategy(type); 35 const size_t dct_scratch_size = 36 3 * (MaxVectorSize() / sizeof(float)) * AcStrategy::kMaxBlockDim; 37 38 size_t mem_bytes = 39 (4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof(float); 40 JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem, 41 AlignedMemory::Create(memory_manager, mem_bytes)); 42 float* coeffs = mem.address<float>(); 43 float* idct = coeffs + AcStrategy::kMaxCoeffArea; 44 float* input = idct + AcStrategy::kMaxCoeffArea; 45 float* scratch_space = input + AcStrategy::kMaxCoeffArea; 46 47 Rng rng(static_cast<uint64_t>(type) * 65537 + 13); 48 49 for (size_t j = 0; j < 64; j++) { 50 size_t i = (acs.log2_covered_blocks() 51 ? rng.UniformU(0, 64u << acs.log2_covered_blocks()) 52 : j); 53 std::fill_n(input, AcStrategy::kMaxCoeffArea, 0); 54 input[i] = 0.2f; 55 TransformFromPixels(type, input, acs.covered_blocks_x() * 8, coeffs, 56 scratch_space); 57 ASSERT_NEAR(coeffs[0], 0.2 / (64 << acs.log2_covered_blocks()), 1e-6) 58 << " i = " << i; 59 TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8, 60 scratch_space); 61 for (size_t j = 0; j < 64u << acs.log2_covered_blocks(); j++) { 62 ASSERT_NEAR(idct[j], j == i ? 0.2f : 0, 2e-6) 63 << "j = " << j << " i = " << i << " acs " << static_cast<int>(type); 64 } 65 } 66 // Test DC. 67 std::fill_n(idct, AcStrategy::kMaxCoeffArea, 0); 68 for (size_t y = 0; y < acs.covered_blocks_y(); y++) { 69 for (size_t x = 0; x < acs.covered_blocks_x(); x++) { 70 float* dc = idct + AcStrategy::kMaxCoeffArea; 71 std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0); 72 dc[y * acs.covered_blocks_x() * 8 + x] = 0.2; 73 LowestFrequenciesFromDC(type, dc, acs.covered_blocks_x() * 8, coeffs, 74 scratch_space); 75 DCFromLowestFrequencies(type, coeffs, idct, acs.covered_blocks_x() * 8); 76 std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0); 77 dc[y * acs.covered_blocks_x() * 8 + x] = 0.2; 78 for (size_t j = 0; j < 64u << acs.log2_covered_blocks(); j++) { 79 ASSERT_NEAR(idct[j], dc[j], 1e-6) 80 << "j = " << j << " x = " << x << " y = " << y << " acs " 81 << static_cast<int>(type); 82 } 83 } 84 } 85 } 86 }; 87 88 HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( 89 AcStrategyRoundtrip, 90 ::testing::Range(0, static_cast<int>(AcStrategy::kNumValidStrategies))); 91 92 TEST_P(AcStrategyRoundtrip, Test) { Run(); } 93 94 // Test that DC(2x2) -> DCT coefficients -> IDCT -> downsampled IDCT is a noop. 95 class AcStrategyRoundtripDownsample 96 : public ::hwy::TestWithParamTargetAndT<int> { 97 protected: 98 void Run() { 99 JxlMemoryManager* memory_manager = test::MemoryManager(); 100 const AcStrategyType type = static_cast<AcStrategyType>(GetParam()); 101 const AcStrategy acs = AcStrategy::FromRawStrategy(type); 102 const size_t dct_scratch_size = 103 3 * (MaxVectorSize() / sizeof(float)) * AcStrategy::kMaxBlockDim; 104 105 size_t mem_bytes = 106 (4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof(float); 107 JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem, 108 AlignedMemory::Create(memory_manager, mem_bytes)); 109 float* coeffs = mem.address<float>(); 110 float* idct = coeffs + AcStrategy::kMaxCoeffArea; 111 float* dc = idct + AcStrategy::kMaxCoeffArea; 112 float* scratch_space = dc + AcStrategy::kMaxCoeffArea; 113 114 std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0.0f); 115 Rng rng(static_cast<uint64_t>(type) * 65537 + 13); 116 117 for (size_t y = 0; y < acs.covered_blocks_y(); y++) { 118 for (size_t x = 0; x < acs.covered_blocks_x(); x++) { 119 if (x > 4 || y > 4) { 120 if (rng.Bernoulli(0.9f)) continue; 121 } 122 std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0); 123 dc[y * acs.covered_blocks_x() * 8 + x] = 0.2f; 124 LowestFrequenciesFromDC(type, dc, acs.covered_blocks_x() * 8, coeffs, 125 scratch_space); 126 TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8, 127 scratch_space); 128 std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0.0f); 129 std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0); 130 dc[y * acs.covered_blocks_x() * 8 + x] = 0.2f; 131 // Downsample 132 for (size_t dy = 0; dy < acs.covered_blocks_y(); dy++) { 133 for (size_t dx = 0; dx < acs.covered_blocks_x(); dx++) { 134 float sum = 0; 135 for (size_t iy = 0; iy < 8; iy++) { 136 for (size_t ix = 0; ix < 8; ix++) { 137 sum += idct[(dy * 8 + iy) * 8 * acs.covered_blocks_x() + 138 dx * 8 + ix]; 139 } 140 } 141 sum /= 64.0f; 142 ASSERT_NEAR(sum, dc[dy * 8 * acs.covered_blocks_x() + dx], 1e-6) 143 << "acs " << static_cast<int>(type); 144 } 145 } 146 } 147 } 148 } 149 }; 150 151 HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( 152 AcStrategyRoundtripDownsample, 153 ::testing::Range(0, static_cast<int>(AcStrategy::kNumValidStrategies))); 154 155 TEST_P(AcStrategyRoundtripDownsample, Test) { Run(); } 156 157 // Test that IDCT(block with zeros in the non-topleft corner) -> downsampled 158 // IDCT is the same as IDCT -> DC(2x2) of the same block. 159 class AcStrategyDownsample : public ::hwy::TestWithParamTargetAndT<int> { 160 protected: 161 void Run() { 162 JxlMemoryManager* memory_manager = test::MemoryManager(); 163 const AcStrategyType type = static_cast<AcStrategyType>(GetParam()); 164 const AcStrategy acs = AcStrategy::FromRawStrategy(type); 165 const size_t dct_scratch_size = 166 3 * (MaxVectorSize() / sizeof(float)) * AcStrategy::kMaxBlockDim; 167 size_t cx = acs.covered_blocks_y(); 168 size_t cy = acs.covered_blocks_x(); 169 CoefficientLayout(&cy, &cx); 170 171 size_t mem_bytes = 172 (4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof(float); 173 JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem, 174 AlignedMemory::Create(memory_manager, mem_bytes)); 175 float* idct = mem.address<float>(); 176 float* idct_acs_downsampled = idct + AcStrategy::kMaxCoeffArea; 177 float* coeffs = idct + AcStrategy::kMaxCoeffArea; 178 float* scratch_space = coeffs + AcStrategy::kMaxCoeffArea; 179 180 Rng rng(static_cast<uint64_t>(type) * 65537 + 13); 181 182 for (size_t y = 0; y < cy; y++) { 183 for (size_t x = 0; x < cx; x++) { 184 if (x > 4 || y > 4) { 185 if (rng.Bernoulli(0.9f)) continue; 186 } 187 float* coeffs = idct + AcStrategy::kMaxCoeffArea; 188 std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0); 189 coeffs[y * cx * 8 + x] = 0.2f; 190 TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8, 191 scratch_space); 192 std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0); 193 coeffs[y * cx * 8 + x] = 0.2f; 194 DCFromLowestFrequencies(type, coeffs, idct_acs_downsampled, 195 acs.covered_blocks_x() * 8); 196 // Downsample 197 for (size_t dy = 0; dy < acs.covered_blocks_y(); dy++) { 198 for (size_t dx = 0; dx < acs.covered_blocks_x(); dx++) { 199 float sum = 0; 200 for (size_t iy = 0; iy < 8; iy++) { 201 for (size_t ix = 0; ix < 8; ix++) { 202 sum += idct[(dy * 8 + iy) * 8 * acs.covered_blocks_x() + 203 dx * 8 + ix]; 204 } 205 } 206 sum /= 64; 207 ASSERT_NEAR( 208 sum, idct_acs_downsampled[dy * 8 * acs.covered_blocks_x() + dx], 209 1e-6) 210 << " acs " << static_cast<int>(type); 211 } 212 } 213 } 214 } 215 } 216 }; 217 218 HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( 219 AcStrategyDownsample, 220 ::testing::Range(0, static_cast<int>(AcStrategy::kNumValidStrategies))); 221 222 TEST_P(AcStrategyDownsample, Test) { Run(); } 223 224 class AcStrategyTargetTest : public ::hwy::TestWithParamTarget {}; 225 HWY_TARGET_INSTANTIATE_TEST_SUITE_P(AcStrategyTargetTest); 226 227 TEST_P(AcStrategyTargetTest, RoundtripAFVDCT) { 228 HWY_ALIGN_MAX float idct[16]; 229 for (size_t i = 0; i < 16; i++) { 230 HWY_ALIGN_MAX float pixels[16] = {}; 231 pixels[i] = 1; 232 HWY_ALIGN_MAX float coeffs[16] = {}; 233 234 AFVDCT4x4(pixels, coeffs); 235 AFVIDCT4x4(coeffs, idct); 236 for (size_t j = 0; j < 16; j++) { 237 EXPECT_NEAR(idct[j], pixels[j], 1e-6); 238 } 239 } 240 } 241 242 TEST_P(AcStrategyTargetTest, BenchmarkAFV) { 243 JxlMemoryManager* memory_manager = test::MemoryManager(); 244 const AcStrategyType type = AcStrategyType::AFV0; 245 HWY_ALIGN_MAX float pixels[64] = {1}; 246 HWY_ALIGN_MAX float coeffs[64] = {}; 247 const size_t dct_scratch_size = 248 3 * (MaxVectorSize() / sizeof(float)) * AcStrategy::kMaxBlockDim; 249 size_t mem_bytes = (64 + dct_scratch_size) * sizeof(float); 250 JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem, 251 AlignedMemory::Create(memory_manager, mem_bytes)); 252 float* scratch_space = mem.address<float>(); 253 for (size_t i = 0; i < 1 << 14; i++) { 254 TransformToPixels(type, coeffs, pixels, 8, scratch_space); 255 TransformFromPixels(type, pixels, 8, coeffs, scratch_space); 256 } 257 EXPECT_NEAR(pixels[0], 0.0, 1E-6); 258 } 259 260 TEST_P(AcStrategyTargetTest, BenchmarkAFVDCT) { 261 HWY_ALIGN_MAX float pixels[64] = {1}; 262 HWY_ALIGN_MAX float coeffs[64] = {}; 263 for (size_t i = 0; i < 1 << 14; i++) { 264 AFVDCT4x4(pixels, coeffs); 265 AFVIDCT4x4(coeffs, pixels); 266 } 267 EXPECT_NEAR(pixels[0], 1.0, 1E-6); 268 } 269 270 } // namespace 271 } // namespace jxl