389 lines
11 KiB
C++
389 lines
11 KiB
C++
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <numeric>
|
|
|
|
#undef HWY_TARGET_INCLUDE
|
|
#define HWY_TARGET_INCLUDE "lib/jxl/dct_test.cc"
|
|
#include <hwy/foreach_target.h>
|
|
#include <hwy/highway.h>
|
|
#include <hwy/tests/hwy_gtest.h>
|
|
|
|
#include "lib/jxl/dct-inl.h"
|
|
#include "lib/jxl/dct_for_test.h"
|
|
#include "lib/jxl/test_utils.h"
|
|
#include "lib/jxl/testing.h"
|
|
|
|
HWY_BEFORE_NAMESPACE();
|
|
namespace jxl {
|
|
namespace HWY_NAMESPACE {
|
|
|
|
// Computes the in-place NxN DCT of block.
|
|
// Requires that block is HWY_ALIGN'ed.
|
|
//
|
|
// Performs ComputeTransposedScaledDCT and then transposes and scales it to
|
|
// obtain "vanilla" DCT.
|
|
template <size_t N>
|
|
void ComputeDCT(float block[N * N]) {
|
|
HWY_ALIGN float tmp_block[N * N];
|
|
HWY_ALIGN float scratch_space[4 * N * N];
|
|
ComputeScaledDCT<N, N>()(DCTFrom(block, N), tmp_block, scratch_space);
|
|
|
|
// Untranspose.
|
|
Transpose<N, N>::Run(DCTFrom(tmp_block, N), DCTTo(block, N));
|
|
}
|
|
|
|
// Computes the in-place 8x8 iDCT of block.
|
|
// Requires that block is HWY_ALIGN'ed.
|
|
template <int N>
|
|
void ComputeIDCT(float block[N * N]) {
|
|
HWY_ALIGN float tmp_block[N * N];
|
|
HWY_ALIGN float scratch_space[4 * N * N];
|
|
// Untranspose.
|
|
Transpose<N, N>::Run(DCTFrom(block, N), DCTTo(tmp_block, N));
|
|
|
|
ComputeScaledIDCT<N, N>()(tmp_block, DCTTo(block, N), scratch_space);
|
|
}
|
|
|
|
template <size_t N>
|
|
void TransposeTestT(float accuracy) {
|
|
constexpr size_t kBlockSize = N * N;
|
|
HWY_ALIGN float src[kBlockSize];
|
|
DCTTo to_src(src, N);
|
|
for (size_t y = 0; y < N; ++y) {
|
|
for (size_t x = 0; x < N; ++x) {
|
|
to_src.Write(y * N + x, y, x);
|
|
}
|
|
}
|
|
HWY_ALIGN float dst[kBlockSize];
|
|
Transpose<N, N>::Run(DCTFrom(src, N), DCTTo(dst, N));
|
|
DCTFrom from_dst(dst, N);
|
|
for (size_t y = 0; y < N; ++y) {
|
|
for (size_t x = 0; x < N; ++x) {
|
|
float expected = x * N + y;
|
|
float actual = from_dst.Read(y, x);
|
|
EXPECT_NEAR(expected, actual, accuracy) << "x = " << x << ", y = " << y;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TransposeTest() {
|
|
TransposeTestT<8>(1e-7f);
|
|
TransposeTestT<16>(1e-7f);
|
|
TransposeTestT<32>(1e-7f);
|
|
}
|
|
|
|
template <size_t N>
|
|
void ColumnDctRoundtripT(float accuracy) {
|
|
constexpr size_t kBlockSize = N * N;
|
|
// Though we are only interested in single column result, dct.h has built-in
|
|
// limit on minimal number of columns processed. So, to be safe, we do
|
|
// regular 8x8 block transformation. On the bright side - we could check all
|
|
// 8 basis vectors at once.
|
|
HWY_ALIGN float block[kBlockSize];
|
|
HWY_ALIGN float scratch[3 * kBlockSize];
|
|
DCTTo to(block, N);
|
|
DCTFrom from(block, N);
|
|
for (size_t i = 0; i < N; ++i) {
|
|
for (size_t j = 0; j < N; ++j) {
|
|
to.Write((i == j) ? 1.0f : 0.0f, i, j);
|
|
}
|
|
}
|
|
|
|
// Running (I)DCT on the same memory block seems to trigger a compiler bug on
|
|
// ARMv7 with clang6.
|
|
HWY_ALIGN float tmp[kBlockSize];
|
|
DCTTo to_tmp(tmp, N);
|
|
DCTFrom from_tmp(tmp, N);
|
|
|
|
DCT1D<N, N>()(from, to_tmp, scratch);
|
|
IDCT1D<N, N>()(from_tmp, to, scratch);
|
|
|
|
for (size_t i = 0; i < N; ++i) {
|
|
for (size_t j = 0; j < N; ++j) {
|
|
float expected = (i == j) ? 1.0f : 0.0f;
|
|
float actual = from.Read(i, j);
|
|
EXPECT_NEAR(expected, actual, accuracy) << " i=" << i << ", j=" << j;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ColumnDctRoundtrip() {
|
|
ColumnDctRoundtripT<8>(1e-6f);
|
|
ColumnDctRoundtripT<16>(1e-6f);
|
|
ColumnDctRoundtripT<32>(1e-6f);
|
|
}
|
|
|
|
template <size_t N>
|
|
void TestDctAccuracy(float accuracy, size_t start = 0, size_t end = N * N) {
|
|
constexpr size_t kBlockSize = N * N;
|
|
for (size_t i = start; i < end; i++) {
|
|
HWY_ALIGN float fast[kBlockSize] = {0.0f};
|
|
double slow[kBlockSize] = {0.0};
|
|
fast[i] = 1.0;
|
|
slow[i] = 1.0;
|
|
DCTSlow<N>(slow);
|
|
ComputeDCT<N>(fast);
|
|
for (size_t k = 0; k < kBlockSize; ++k) {
|
|
EXPECT_NEAR(fast[k], slow[k], accuracy / N)
|
|
<< "i = " << i << ", k = " << k << ", N = " << N;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <size_t N>
|
|
void TestIdctAccuracy(float accuracy, size_t start = 0, size_t end = N * N) {
|
|
constexpr size_t kBlockSize = N * N;
|
|
for (size_t i = start; i < end; i++) {
|
|
HWY_ALIGN float fast[kBlockSize] = {0.0f};
|
|
double slow[kBlockSize] = {0.0};
|
|
fast[i] = 1.0;
|
|
slow[i] = 1.0;
|
|
IDCTSlow<N>(slow);
|
|
ComputeIDCT<N>(fast);
|
|
for (size_t k = 0; k < kBlockSize; ++k) {
|
|
EXPECT_NEAR(fast[k], slow[k], accuracy * N)
|
|
<< "i = " << i << ", k = " << k << ", N = " << N;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <size_t N>
|
|
void TestInverseT(float accuracy) {
|
|
test::ThreadPoolForTests pool(N < 32 ? 0 : 8);
|
|
enum { kBlockSize = N * N };
|
|
const auto process_block = [accuracy](const uint32_t task,
|
|
size_t /*thread*/) -> Status {
|
|
const size_t i = static_cast<size_t>(task);
|
|
HWY_ALIGN float x[kBlockSize] = {0.0f};
|
|
x[i] = 1.0;
|
|
|
|
ComputeIDCT<N>(x);
|
|
ComputeDCT<N>(x);
|
|
|
|
for (size_t k = 0; k < kBlockSize; ++k) {
|
|
EXPECT_NEAR(x[k], (k == i) ? 1.0f : 0.0f, accuracy)
|
|
<< "i = " << i << ", k = " << k;
|
|
}
|
|
return true;
|
|
};
|
|
EXPECT_TRUE(RunOnPool(pool.get(), 0, kBlockSize, ThreadPool::NoInit,
|
|
process_block, "TestInverse"));
|
|
}
|
|
|
|
void InverseTest() {
|
|
TestInverseT<8>(1e-6f);
|
|
TestInverseT<16>(1e-6f);
|
|
TestInverseT<32>(3e-6f);
|
|
}
|
|
|
|
template <size_t N>
|
|
void TestDctTranspose(float accuracy, size_t start = 0, size_t end = N * N) {
|
|
constexpr size_t kBlockSize = N * N;
|
|
for (size_t i = start; i < end; i++) {
|
|
for (size_t j = 0; j < kBlockSize; ++j) {
|
|
// We check that <e_i, Me_j> = <M^\dagger{}e_i, e_j>.
|
|
// That means (Me_j)_i = (M^\dagger{}e_i)_j
|
|
|
|
// x := Me_j
|
|
HWY_ALIGN float x[kBlockSize] = {0.0f};
|
|
x[j] = 1.0;
|
|
ComputeIDCT<N>(x);
|
|
// y := M^\dagger{}e_i
|
|
HWY_ALIGN float y[kBlockSize] = {0.0f};
|
|
y[i] = 1.0;
|
|
ComputeDCT<N>(y);
|
|
|
|
EXPECT_NEAR(x[i] / N, y[j] * N, accuracy) << "i = " << i << ", j = " << j;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <size_t N>
|
|
void TestSlowInverse(float accuracy, size_t start = 0, size_t end = N * N) {
|
|
constexpr size_t kBlockSize = N * N;
|
|
for (size_t i = start; i < end; i++) {
|
|
double x[kBlockSize] = {0.0f};
|
|
x[i] = 1.0;
|
|
|
|
DCTSlow<N>(x);
|
|
IDCTSlow<N>(x);
|
|
|
|
for (size_t k = 0; k < kBlockSize; ++k) {
|
|
EXPECT_NEAR(x[k], (k == i) ? 1.0f : 0.0f, accuracy)
|
|
<< "i = " << i << ", k = " << k;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <size_t ROWS, size_t COLS>
|
|
void TestRectInverseT(float accuracy) {
|
|
constexpr size_t kBlockSize = ROWS * COLS;
|
|
for (size_t i = 0; i < kBlockSize; ++i) {
|
|
HWY_ALIGN float x[kBlockSize] = {0.0f};
|
|
HWY_ALIGN float out[kBlockSize] = {0.0f};
|
|
x[i] = 1.0;
|
|
HWY_ALIGN float coeffs[kBlockSize] = {0.0f};
|
|
HWY_ALIGN float scratch_space[kBlockSize * 5];
|
|
|
|
ComputeScaledDCT<ROWS, COLS>()(DCTFrom(x, COLS), coeffs, scratch_space);
|
|
ComputeScaledIDCT<ROWS, COLS>()(coeffs, DCTTo(out, COLS), scratch_space);
|
|
|
|
for (size_t k = 0; k < kBlockSize; ++k) {
|
|
EXPECT_NEAR(out[k], (k == i) ? 1.0f : 0.0f, accuracy)
|
|
<< "i = " << i << ", k = " << k << " ROWS = " << ROWS
|
|
<< " COLS = " << COLS;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestRectInverse() {
|
|
TestRectInverseT<16, 32>(1e-6f);
|
|
TestRectInverseT<8, 32>(1e-6f);
|
|
TestRectInverseT<8, 16>(1e-6f);
|
|
TestRectInverseT<4, 8>(1e-6f);
|
|
TestRectInverseT<2, 4>(1e-6f);
|
|
TestRectInverseT<1, 4>(1e-6f);
|
|
TestRectInverseT<1, 2>(1e-6f);
|
|
|
|
TestRectInverseT<32, 16>(1e-6f);
|
|
TestRectInverseT<32, 8>(1e-6f);
|
|
TestRectInverseT<16, 8>(1e-6f);
|
|
TestRectInverseT<8, 4>(1e-6f);
|
|
TestRectInverseT<4, 2>(1e-6f);
|
|
TestRectInverseT<4, 1>(1e-6f);
|
|
TestRectInverseT<2, 1>(1e-6f);
|
|
}
|
|
|
|
template <size_t ROWS, size_t COLS>
|
|
void TestRectTransposeT(float accuracy) {
|
|
constexpr size_t kBlockSize = ROWS * COLS;
|
|
HWY_ALIGN float scratch_space[kBlockSize * 5];
|
|
for (size_t px = 0; px < COLS; ++px) {
|
|
for (size_t py = 0; py < ROWS; ++py) {
|
|
HWY_ALIGN float x1[kBlockSize] = {0.0f};
|
|
HWY_ALIGN float x2[kBlockSize] = {0.0f};
|
|
HWY_ALIGN float coeffs1[kBlockSize] = {0.0f};
|
|
HWY_ALIGN float coeffs2[kBlockSize] = {0.0f};
|
|
x1[py * COLS + px] = 1;
|
|
x2[px * ROWS + py] = 1;
|
|
|
|
constexpr size_t OUT_ROWS = ROWS < COLS ? ROWS : COLS;
|
|
constexpr size_t OUT_COLS = ROWS < COLS ? COLS : ROWS;
|
|
|
|
ComputeScaledDCT<ROWS, COLS>()(DCTFrom(x1, COLS), coeffs1, scratch_space);
|
|
ComputeScaledDCT<COLS, ROWS>()(DCTFrom(x2, ROWS), coeffs2, scratch_space);
|
|
|
|
for (size_t x = 0; x < OUT_COLS; ++x) {
|
|
for (size_t y = 0; y < OUT_ROWS; ++y) {
|
|
EXPECT_NEAR(coeffs1[y * OUT_COLS + x], coeffs2[y * OUT_COLS + x],
|
|
accuracy)
|
|
<< " px = " << px << ", py = " << py << ", x = " << x
|
|
<< ", y = " << y;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestRectTranspose() {
|
|
TestRectTransposeT<16, 32>(1e-6f);
|
|
TestRectTransposeT<8, 32>(1e-6f);
|
|
TestRectTransposeT<8, 16>(1e-6f);
|
|
TestRectTransposeT<4, 8>(1e-6f);
|
|
TestRectTransposeT<2, 4>(1e-6f);
|
|
TestRectTransposeT<1, 4>(1e-6f);
|
|
TestRectTransposeT<1, 2>(1e-6f);
|
|
|
|
// Identical to 8, 16
|
|
// TestRectTranspose<16, 8>(1e-6f);
|
|
}
|
|
|
|
void TestDctAccuracyShard(size_t shard) {
|
|
if (shard == 0) {
|
|
TestDctAccuracy<1>(1.1E-7f);
|
|
TestDctAccuracy<2>(1.1E-7f);
|
|
TestDctAccuracy<4>(1.1E-7f);
|
|
TestDctAccuracy<8>(1.1E-7f);
|
|
TestDctAccuracy<16>(1.3E-7f);
|
|
}
|
|
TestDctAccuracy<32>(1.1E-7f, 32 * shard, 32 * (shard + 1));
|
|
}
|
|
|
|
void TestIdctAccuracyShard(size_t shard) {
|
|
if (shard == 0) {
|
|
TestIdctAccuracy<1>(1E-7f);
|
|
TestIdctAccuracy<2>(1E-7f);
|
|
TestIdctAccuracy<4>(1E-7f);
|
|
TestIdctAccuracy<8>(1E-7f);
|
|
TestIdctAccuracy<16>(1E-7f);
|
|
}
|
|
TestIdctAccuracy<32>(1E-7f, 32 * shard, 32 * (shard + 1));
|
|
}
|
|
|
|
void TestDctTransposeShard(size_t shard) {
|
|
if (shard == 0) {
|
|
TestDctTranspose<8>(1E-6f);
|
|
TestDctTranspose<16>(1E-6f);
|
|
}
|
|
TestDctTranspose<32>(3E-6f, 32 * shard, 32 * (shard + 1));
|
|
}
|
|
|
|
void TestSlowInverseShard(size_t shard) {
|
|
if (shard == 0) {
|
|
TestSlowInverse<1>(1E-5f);
|
|
TestSlowInverse<2>(1E-5f);
|
|
TestSlowInverse<4>(1E-5f);
|
|
TestSlowInverse<8>(1E-5f);
|
|
TestSlowInverse<16>(1E-5f);
|
|
}
|
|
TestSlowInverse<32>(1E-5f, 32 * shard, 32 * (shard + 1));
|
|
}
|
|
|
|
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
|
} // namespace HWY_NAMESPACE
|
|
} // namespace jxl
|
|
HWY_AFTER_NAMESPACE();
|
|
|
|
#if HWY_ONCE
|
|
namespace jxl {
|
|
|
|
class TransposeTest : public hwy::TestWithParamTarget {};
|
|
|
|
HWY_TARGET_INSTANTIATE_TEST_SUITE_P(TransposeTest);
|
|
|
|
HWY_EXPORT_AND_TEST_P(TransposeTest, TransposeTest);
|
|
HWY_EXPORT_AND_TEST_P(TransposeTest, InverseTest);
|
|
HWY_EXPORT_AND_TEST_P(TransposeTest, ColumnDctRoundtrip);
|
|
HWY_EXPORT_AND_TEST_P(TransposeTest, TestRectInverse);
|
|
HWY_EXPORT_AND_TEST_P(TransposeTest, TestRectTranspose);
|
|
|
|
// Tests in the DctShardedTest class are sharded for N=32.
|
|
class DctShardedTest : public ::hwy::TestWithParamTargetAndT<uint32_t> {};
|
|
|
|
template <size_t n>
|
|
std::vector<uint32_t> ShardRange() {
|
|
#ifdef JXL_DISABLE_SLOW_TESTS
|
|
static_assert(n > 6);
|
|
std::vector<uint32_t> ret = {0, 1, 3, 5, n - 1};
|
|
#else
|
|
std::vector<uint32_t> ret(n);
|
|
std::iota(ret.begin(), ret.end(), 0);
|
|
#endif // JXL_DISABLE_SLOW_TESTS
|
|
return ret;
|
|
}
|
|
|
|
HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T(DctShardedTest,
|
|
::testing::ValuesIn(ShardRange<32>()));
|
|
|
|
HWY_EXPORT_AND_TEST_P_T(DctShardedTest, TestDctAccuracyShard);
|
|
HWY_EXPORT_AND_TEST_P_T(DctShardedTest, TestIdctAccuracyShard);
|
|
HWY_EXPORT_AND_TEST_P_T(DctShardedTest, TestDctTransposeShard);
|
|
HWY_EXPORT_AND_TEST_P_T(DctShardedTest, TestSlowInverseShard);
|
|
|
|
} // namespace jxl
|
|
#endif // HWY_ONCE
|