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