diff --git a/CMakeLists.txt b/CMakeLists.txt index 531edc05..4298694c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ if(ENABLE_COVERAGE) add_compile_options(-O0 -g) endif() option(ENABLE_TESTS "Enables gtest based unit tests" ON) +option(ENABLE_BENCHMARKS "Enables google benchmark based performance tests" ON) if(POLICY CMP0135) @@ -62,6 +63,9 @@ libhevc_add_compile_options() if(ENABLE_TESTS) libhevc_add_gtest() endif() +if(ENABLE_BENCHMARKS) + libhevc_add_benchmark() +endif() libhevc_add_definitions() libhevc_set_link_libraries() @@ -75,6 +79,6 @@ include("${HEVC_ROOT}/examples/encoder/hevcenc.cmake") include("${HEVC_ROOT}/fuzzer/hevc_dec_fuzzer.cmake") include("${HEVC_ROOT}/fuzzer/hevc_enc_fuzzer.cmake") -if(ENABLE_TESTS) +if(ENABLE_TESTS OR ENABLE_BENCHMARKS) include("${HEVC_ROOT}/tests/common/common.cmake") endif() diff --git a/cmake/utils.cmake b/cmake/utils.cmake index dbaaad61..f8aee4eb 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -152,9 +152,39 @@ function(libhevc_add_gtest_executable NAME) cmake_parse_arguments(ARG "" "" "${multi_value_args}" ${ARGN}) libhevc_add_executable( - ${NAME} libhevcdec - SOURCES ${HEVC_ROOT}/tests/common/func_selector.cc - ${HEVC_ROOT}/tests/common/TestCommon.cc ${ARG_SOURCES} + ${NAME} libhevc_dsp_test_utils + SOURCES ${ARG_SOURCES} LIBS GTest::gtest_main) add_test(NAME ${NAME} COMMAND ${NAME}) endfunction() + +# Adds Google Benchmark dependency +function(libhevc_add_benchmark) + include(FetchContent) + FetchContent_Declare( + benchmark + URL https://github.com/google/benchmark/archive/refs/tags/v1.8.3.zip + ) + # Disable benchmark's internal test suite + set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(benchmark) +endfunction() + +# cmake-format: off +# Adds a target for a benchmark executable +# +# Arguments: +# NAME: Name of the executable +# +# Optional Arguments: +# SOURCES: Additional source files +# cmake-format: on +function(libhevc_add_benchmark_executable NAME) + set(multi_value_args SOURCES) + cmake_parse_arguments(ARG "" "" "${multi_value_args}" ${ARGN}) + + libhevc_add_executable( + ${NAME} libhevc_dsp_test_utils + SOURCES ${HEVC_ROOT}/tests/common/BenchmarkCommon.cc ${ARG_SOURCES} + LIBS benchmark::benchmark) +endfunction() diff --git a/fuzzer/ossfuzz.sh b/fuzzer/ossfuzz.sh index a79a2a81..3d5653c7 100755 --- a/fuzzer/ossfuzz.sh +++ b/fuzzer/ossfuzz.sh @@ -33,7 +33,7 @@ rm -rf ${build_dir} mkdir -p ${build_dir} pushd ${build_dir} -cmake ${SRC}/libhevc -DENABLE_TESTS=0 +cmake ${SRC}/libhevc -DENABLE_TESTS=0 -DENABLE_BENCHMARKS=0 make -j$(nproc) hevc_dec_fuzzer hevc_enc_fuzzer cp ${build_dir}/hevc_dec_fuzzer $OUT/ cp ${build_dir}/hevc_enc_fuzzer $OUT/ diff --git a/tests/common/Android.bp b/tests/common/Android.bp index 3af6f2ff..2d3b6c69 100644 --- a/tests/common/Android.bp +++ b/tests/common/Android.bp @@ -41,6 +41,11 @@ cc_library_static { srcs: [ "TestCommon.cc", "func_selector.cc", + "ihevc_deblk_utils.cc", + "ihevc_inter_pred_utils.cc", + "ihevc_intra_pred_utils.cc", + "ihevc_itrans_utils.cc", + "ihevc_sao_utils.cc", "BitsBuf.cpp", "RawBuf.cpp", "Md5Wrapper.cpp", @@ -57,6 +62,16 @@ cc_library_static { ], } +cc_defaults { + name: "libhevc_benchmarks_defaults", + defaults: ["libhevc_tests_defaults"], + srcs: ["BenchmarkCommon.cc"], + static_libs: [ + "libhevc_tests_common", + "libhevccommon", + ], +} + cc_test { name: "ihevc_luma_inter_pred_test", defaults: ["libhevc_tests_defaults"], @@ -206,3 +221,123 @@ cc_test { "libhevccommon", ], } + +cc_benchmark { + name: "ihevc_itrans_recon_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_itrans_recon_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_itrans_res_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_itrans_res_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_deblk_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_deblk_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_hbd_deblk_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_hbd_deblk_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_hbd_itrans_recon_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_hbd_itrans_recon_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_sao_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_sao_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_hbd_sao_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_hbd_sao_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_luma_intra_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_luma_intra_pred_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_hbd_luma_intra_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_hbd_luma_intra_pred_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_chroma_intra_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_chroma_intra_pred_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_hbd_chroma_intra_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_hbd_chroma_intra_pred_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_luma_inter_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_luma_inter_pred_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_chroma_inter_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_chroma_inter_pred_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_hbd_luma_inter_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_hbd_luma_inter_pred_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_hbd_chroma_inter_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_hbd_chroma_inter_pred_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_chroma_itrans_recon_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_chroma_itrans_recon_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_chroma_recon_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_chroma_recon_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_recon_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_recon_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_padding_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_padding_benchmark.cc"], +} + +cc_benchmark { + name: "ihevc_weighted_pred_benchmark", + defaults: ["libhevc_benchmarks_defaults"], + srcs: ["ihevc_weighted_pred_benchmark.cc"], +} diff --git a/tests/common/BenchmarkCommon.cc b/tests/common/BenchmarkCommon.cc new file mode 100644 index 00000000..8f95ca45 --- /dev/null +++ b/tests/common/BenchmarkCommon.cc @@ -0,0 +1,54 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "BenchmarkCommon.h" + +#include + +const std::vector& GetBenchmarkArchitectures() { + static const std::vector kArches = []() { + std::vector arches = {ARCH_NA}; + for (auto a : getTstArch()) { + arches.push_back(a); + } + return arches; + }(); + return kArches; +} + +const std::vector>& GetBenchmarkLumaPUSizes() { + static const std::vector> kSizes = { + {8, 8}, {16, 16}, {32, 32}, {64, 64}, {16, 8}, {32, 16}, {64, 32}, + }; + return kSizes; +} + +const std::vector>& GetBenchmarkChromaPUSizes() { + static const std::vector> kSizes = { + {4, 4}, {8, 8}, {16, 16}, {32, 32}, {8, 4}, {16, 8}, {32, 16}, + }; + return kSizes; +} + +int main(int argc, char** argv) { + RegisterAllBenchmarks(); + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/tests/common/BenchmarkCommon.h b/tests/common/BenchmarkCommon.h new file mode 100644 index 00000000..e905ca99 --- /dev/null +++ b/tests/common/BenchmarkCommon.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "iv.h" +// clang-format on + +#include "TestCommon.h" + +// Returns human-readable architecture name ("C", "SSSE3", "SSE42", "ARMV8", etc.) +inline std::string GetArchName(IV_ARCH_T arch) { return get_arch_str(arch); } + +// Returns the list of architectures to benchmark, starting with ARCH_NA +// (generic C), followed by all supported target architectures returned by +// getTstArch(). +const std::vector& GetBenchmarkArchitectures(); + +// Common representative PU block sizes for Luma benchmarks +const std::vector>& GetBenchmarkLumaPUSizes(); + +// Common representative PU block sizes for Chroma benchmarks +const std::vector>& GetBenchmarkChromaPUSizes(); + +// Implemented by each benchmark executable to register its benchmark suite +void RegisterAllBenchmarks(); diff --git a/tests/common/TestCommon.cc b/tests/common/TestCommon.cc index fef1883a..de9f22fe 100644 --- a/tests/common/TestCommon.cc +++ b/tests/common/TestCommon.cc @@ -61,9 +61,7 @@ const std::vector>& getChromaPUBlockSizes() { const std::vector& getSrc8Buf() { static const std::vector g_src8_buf = []() { std::vector buf(kMaxSize * kMaxHeight * 4); - std::mt19937 rng(12345); - std::uniform_int_distribution dist(0, 255); - for (auto& v : buf) v = static_cast(dist(rng)); + FillRandom(buf, static_cast(0), static_cast(255), 12345); return buf; }(); return g_src8_buf; @@ -72,6 +70,9 @@ const std::vector& getSrc8Buf() { std::string get_arch_str(IV_ARCH_T arch) { std::string arch_str; switch (arch) { + case ARCH_NA: + arch_str = "C"; + break; case ARCH_X86_GENERIC: arch_str = "GENERIC"; break; diff --git a/tests/common/TestCommon.h b/tests/common/TestCommon.h index 6c407dd0..dff239e8 100644 --- a/tests/common/TestCommon.h +++ b/tests/common/TestCommon.h @@ -19,12 +19,15 @@ #pragma once #include +#include +#include #include #include #include #include #include #include +#include #include #include @@ -46,23 +49,108 @@ const std::vector>& getLumaPUBlockSizes(); const std::vector>& getChromaPUBlockSizes(); const std::vector& getSrc8Buf(); const std::vector& getTstArch(); +std::string get_arch_str(IV_ARCH_T arch); + +// Computes HEVC zero-column / zero-row bitmask for a given transform size +inline WORD32 ComputeZeroMask(int trans_size, int non_zero_count) { + if (non_zero_count >= trans_size) { + return 0; + } + WORD32 full_mask = (trans_size == 32) + ? 0xFFFFFFFFu + : ((static_cast(1u) << trans_size) - 1u); + WORD32 nz_mask = (static_cast(1u) << non_zero_count) - 1u; + return (~nz_mask) & full_mask; +} + +// Deterministic pseudo-random initialization of a buffer +template +void FillRandom(T* data, size_t count, T min_val, T max_val, + uint32_t seed = 42) { + std::mt19937 rng(seed); + if constexpr (std::is_integral_v && sizeof(T) == 1) { + std::uniform_int_distribution dist(static_cast(min_val), + static_cast(max_val)); + for (size_t i = 0; i < count; ++i) { + data[i] = static_cast(dist(rng)); + } + } else if constexpr (std::is_integral_v) { + std::uniform_int_distribution dist(min_val, max_val); + for (size_t i = 0; i < count; ++i) { + data[i] = dist(rng); + } + } +} + +template +void FillRandom(std::vector& vec, T min_val, T max_val, uint32_t seed = 42) { + FillRandom(vec.data(), vec.size(), min_val, max_val, seed); +} + +// Populates a 2D buffer (trans_size x trans_size, stride src_strd) with random +// values in [min_val, max_val] within top-left [0, nz_rows) x [0, nz_cols), +// zeroing out all other entries. +template +void FillRandomSubBlock(T* data, int trans_size, int src_strd, int nz_rows, + int nz_cols, T min_val, T max_val, + uint32_t seed = 42) { + std::fill(data, data + trans_size * src_strd, static_cast(0)); + std::mt19937 rng(seed); + std::uniform_int_distribution dist(min_val, max_val); + for (int r = 0; r < trans_size; ++r) { + for (int c = 0; c < trans_size; ++c) { + if (r < nz_rows && c < nz_cols) { + data[r * src_strd + c] = dist(rng); + } + } + } +} + +template +void FillRandomSubBlock(std::vector& vec, int trans_size, int src_strd, + int nz_rows, int nz_cols, T min_val, T max_val, + uint32_t seed = 42) { + FillRandomSubBlock(vec.data(), trans_size, src_strd, nz_rows, nz_cols, + min_val, max_val, seed); +} + +// Verifies 2D output buffers row-by-row +template +bool VerifyOutput2D(const T* ref, const T* tst, int wd, int ht, int ref_strd, + int tst_strd) { + const size_t row_bytes = static_cast(wd) * sizeof(T); + for (int r = 0; r < ht; ++r) { + if (std::memcmp(ref + r * ref_strd, tst + r * tst_strd, row_bytes) != 0) { + return false; + } + } + return true; +} + +template +bool VerifyOutput2D(const T* ref, const T* tst, int wd, int ht, int strd) { + return VerifyOutput2D(ref, tst, wd, ht, strd, strd); +} #if __has_include() #include // Compare outputs template -static void compare_output(const std::vector& ref, - const std::vector& test, int wd, int ht, +static void compare_output(const T* ref, const T* test, int wd, int ht, int dst_strd) { int size_bytes = wd * sizeof(T); for (int i = 0; i < ht; ++i) { - int cmp = memcmp(ref.data() + i * dst_strd, test.data() + i * dst_strd, - size_bytes); + int cmp = memcmp(ref + i * dst_strd, test + i * dst_strd, size_bytes); ASSERT_EQ(0, cmp) << "Mismatch at row " << i << " for size " << wd << "x" << ht; } } -#endif -std::string get_arch_str(IV_ARCH_T arch); +template +static void compare_output(const std::vector& ref, + const std::vector& test, int wd, int ht, + int dst_strd) { + compare_output(ref.data(), test.data(), wd, ht, dst_strd); +} +#endif diff --git a/tests/common/common.cmake b/tests/common/common.cmake index e2b8d805..2dcf5cf4 100644 --- a/tests/common/common.cmake +++ b/tests/common/common.cmake @@ -1,3 +1,19 @@ +add_library(libhevc_dsp_test_utils STATIC + ${HEVC_ROOT}/tests/common/func_selector.cc + ${HEVC_ROOT}/tests/common/TestCommon.cc + ${HEVC_ROOT}/tests/common/ihevc_deblk_utils.cc + ${HEVC_ROOT}/tests/common/ihevc_inter_pred_utils.cc + ${HEVC_ROOT}/tests/common/ihevc_intra_pred_utils.cc + ${HEVC_ROOT}/tests/common/ihevc_itrans_utils.cc + ${HEVC_ROOT}/tests/common/ihevc_sao_utils.cc +) +target_include_directories(libhevc_dsp_test_utils PUBLIC + ${HEVC_ROOT}/tests/common + ${HEVC_ROOT}/common +) +target_link_libraries(libhevc_dsp_test_utils PUBLIC libhevcdec) + +if(ENABLE_TESTS) enable_testing() libhevc_add_gtest_executable( ihevc_luma_inter_pred_test @@ -93,4 +109,106 @@ FetchContent_MakeAvailable(hevc_test_data) include("${HEVC_ROOT}/tests/decoder/DecHelper.cmake") include("${HEVC_ROOT}/tests/encoder/EncHelper.cmake") +endif() +if(ENABLE_BENCHMARKS) + libhevc_add_benchmark_executable( + ihevc_itrans_recon_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_itrans_recon_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_itrans_res_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_itrans_res_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_deblk_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_deblk_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_hbd_deblk_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_hbd_deblk_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_hbd_itrans_recon_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_hbd_itrans_recon_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_sao_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_sao_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_hbd_sao_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_hbd_sao_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_luma_intra_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_luma_intra_pred_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_hbd_luma_intra_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_hbd_luma_intra_pred_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_chroma_intra_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_chroma_intra_pred_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_hbd_chroma_intra_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_hbd_chroma_intra_pred_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_luma_inter_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_luma_inter_pred_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_chroma_inter_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_chroma_inter_pred_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_hbd_luma_inter_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_hbd_luma_inter_pred_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_hbd_chroma_inter_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_hbd_chroma_inter_pred_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_chroma_itrans_recon_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_chroma_itrans_recon_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_chroma_recon_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_chroma_recon_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_recon_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_recon_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_padding_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_padding_benchmark.cc + ) + + libhevc_add_benchmark_executable( + ihevc_weighted_pred_benchmark + SOURCES ${HEVC_ROOT}/tests/common/ihevc_weighted_pred_benchmark.cc + ) +endif() diff --git a/tests/common/func_selector.cc b/tests/common/func_selector.cc index 72315d17..90dba739 100644 --- a/tests/common/func_selector.cc +++ b/tests/common/func_selector.cc @@ -90,6 +90,8 @@ const ihevc_func_selector_t* get_ref_func_ptr() { return &ref; } const ihevc_func_selector_t* get_tst_func_ptr(IV_ARCH_T arch) { switch (arch) { + case ARCH_NA: + return &ref; #if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ defined(_M_IX86) case ARCH_X86_SSSE3: diff --git a/tests/common/func_selector.h b/tests/common/func_selector.h index 0c5d8731..de36f38c 100644 --- a/tests/common/func_selector.h +++ b/tests/common/func_selector.h @@ -28,11 +28,17 @@ // clang-format off #include "ihevc_typedefs.h" +extern "C" { #include "ihevc_function_selector.h" #include "iv.h" +} // clang-format on const ihevc_func_selector_t* get_ref_func_ptr(); const ihevc_func_selector_t* get_tst_func_ptr(IV_ARCH_T arch); +inline const ihevc_func_selector_t* get_func_ptr(IV_ARCH_T arch) { + return get_tst_func_ptr(arch); +} + #endif /* __FUNC_SELECTOR_H__ */ diff --git a/tests/common/ihevc_chroma_inter_pred_benchmark.cc b/tests/common/ihevc_chroma_inter_pred_benchmark.cc new file mode 100644 index 00000000..3c060657 --- /dev/null +++ b/tests/common/ihevc_chroma_inter_pred_benchmark.cc @@ -0,0 +1,99 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_inter_pred_utils.h" + +namespace { + +void BM_ChromaInterPred(benchmark::State& state, InterPredConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const InterPredOp op = config.op; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + WORD8* pi1_coeff = gai1_ihevc_chroma_filter[config.coeff_idx]; + const bool verify = (arch != ARCH_NA); + + if (!IsW16Inp(op) && !IsW16Out(op)) { + RunInterPredBenchmarkCase( + state, GetChromaInterPredFn(selector, op), + GetChromaInterPredFn(get_ref_func_ptr(), op), wd, ht, pi1_coeff, + verify); + } else if (!IsW16Inp(op) && IsW16Out(op)) { + RunInterPredBenchmarkCase( + state, GetChromaInterPredW16outFn(selector, op), + GetChromaInterPredW16outFn(get_ref_func_ptr(), op), wd, ht, pi1_coeff, + verify); + } else if (IsW16Inp(op) && !IsW16Out(op)) { + RunInterPredBenchmarkCase( + state, GetChromaInterPredW16inpFn(selector, op), + GetChromaInterPredW16inpFn(get_ref_func_ptr(), op), wd, ht, pi1_coeff, + verify); + } else { + RunInterPredBenchmarkCase( + state, GetChromaInterPredW16inpW16outFn(selector, op), + GetChromaInterPredW16inpW16outFn(get_ref_func_ptr(), op), wd, ht, + pi1_coeff, verify); + } +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + // Register Chroma Inter Prediction benchmarks + for (const auto& op_info : GetInterPredOps()) { + for (const auto& size : GetInterPredChromaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + if (!get_func_ptr(arch)) continue; + std::string arch_name = GetArchName(arch); + std::string name = "BM_ChromaInterPred/" + std::string(op_info.name) + + "/" + std::to_string(wd) + "x" + std::to_string(ht) + + "/" + arch_name; + InterPredConfig cfg{wd, + ht, + op_info.op, + arch, + /*bit_depth=*/8, + /*coeff_idx=*/1}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_ChromaInterPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_chroma_inter_pred_test.cc b/tests/common/ihevc_chroma_inter_pred_test.cc index 137b2537..aa1f79a6 100644 --- a/tests/common/ihevc_chroma_inter_pred_test.cc +++ b/tests/common/ihevc_chroma_inter_pred_test.cc @@ -34,69 +34,13 @@ #include "iv.h" #include "func_selector.h" #include "TestCommon.h" +#include "ihevc_inter_pred_utils.h" // clang-format on -// Test parameters: width, height, src_stride_mul, dst_stride_mul, coeff_idx, -// arch -using ChromaInterPredTestParam = - std::tuple, int, int, int, IV_ARCH_T>; +using ChromaInterPredTestParam = InterPredTestParam; template -class ChromaInterPredTest - : public ::testing::TestWithParam { - protected: - void SetUp() override { - std::pair block_size; - std::tie(block_size, src_strd_mul, dst_strd_mul, coeff_idx, arch) = - GetParam(); - std::tie(wd, ht) = block_size; - src_strd = 2 * wd * src_strd_mul; - dst_strd = 2 * wd * dst_strd_mul; - - // TODO: Increase allocations for x86/x86_64 to avoid out-of-bounds - // reads/writes in SIMD implementations. -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ - defined(_M_IX86) - int pad_dst = 16; -#else - int pad_dst = 0; -#endif - - dst_buf_ref.resize(dst_strd * ht + pad_dst); - dst_buf_tst.resize(dst_strd * ht + pad_dst); - - // Set pv_src to a valid position within src_buf to allow negative indexing - pv_src = (srcType*)getSrc8Buf().data() + kTapSize / 2 * src_strd; - pv_dst_ref = dst_buf_ref.data(); - pv_dst_tst = dst_buf_tst.data(); - - pi1_coeffs = gai1_ihevc_chroma_filter[coeff_idx]; - tst = get_tst_func_ptr(arch); - ref = get_ref_func_ptr(); - } - - template - void RunTest(FuncPtr func_ptr) { - (ref->*func_ptr)(pv_src, pv_dst_ref, src_strd, dst_strd, pi1_coeffs, ht, - wd); - (tst->*func_ptr)(pv_src, pv_dst_tst, src_strd, dst_strd, pi1_coeffs, ht, - wd); - ASSERT_NO_FATAL_FAILURE(compare_output(dst_buf_ref, dst_buf_tst, - 2 * wd, ht, dst_strd)); - } - - int wd, ht, src_strd_mul, dst_strd_mul, coeff_idx; - int src_strd, dst_strd; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - srcType* pv_src; - dstType* pv_dst_ref; - dstType* pv_dst_tst; - WORD8* pi1_coeffs; - IV_ARCH_T arch; - const ihevc_func_selector_t* tst; - const ihevc_func_selector_t* ref; -}; +using ChromaInterPredTest = InterPredTestBase; class ChromaInterPred_8_8_Test : public ChromaInterPredTest {}; class ChromaInterPred_8_16_Test : public ChromaInterPredTest {}; @@ -157,50 +101,34 @@ auto kChromaInterPredTestParams = ::testing::Combine( ::testing::ValuesIn(getTstArch()) // arch ); -std::string PrintChromaInterPredTestParam( - const testing::TestParamInfo& info) { - int wd, ht, src_strd_mul, dst_strd_mul, coeff_idx; - IV_ARCH_T arch; - std::pair block_size; - std::tie(block_size, src_strd_mul, dst_strd_mul, coeff_idx, arch) = - info.param; - std::tie(wd, ht) = block_size; - std::stringstream ss; - // Width in elements is 2 * wd for interleaved chroma - ss << wd << "x" << ht << "_src_stride_" << src_strd_mul * 2 * wd - << "_dst_stride_" << dst_strd_mul * 2 * wd << "_coeff_" << coeff_idx << "_" - << get_arch_str(arch); - return ss.str(); -} - INSTANTIATE_TEST_SUITE_P(ChromaCopyTest, ChromaInterPred_8_8_Test, kChromaInterPredTestParams, - PrintChromaInterPredTestParam); + PrintInterPredTestParam<2>); INSTANTIATE_TEST_SUITE_P(ChromaHorzTest, ChromaInterPred_8_8_Test, kChromaInterPredTestParams, - PrintChromaInterPredTestParam); + PrintInterPredTestParam<2>); INSTANTIATE_TEST_SUITE_P(ChromaVertTest, ChromaInterPred_8_8_Test, kChromaInterPredTestParams, - PrintChromaInterPredTestParam); + PrintInterPredTestParam<2>); INSTANTIATE_TEST_SUITE_P(ChromaCopyTest, ChromaInterPred_8_16_Test, kChromaInterPredTestParams, - PrintChromaInterPredTestParam); + PrintInterPredTestParam<2>); INSTANTIATE_TEST_SUITE_P(ChromaHorzTest, ChromaInterPred_8_16_Test, kChromaInterPredTestParams, - PrintChromaInterPredTestParam); + PrintInterPredTestParam<2>); INSTANTIATE_TEST_SUITE_P(ChromaVertTest, ChromaInterPred_8_16_Test, kChromaInterPredTestParams, - PrintChromaInterPredTestParam); + PrintInterPredTestParam<2>); INSTANTIATE_TEST_SUITE_P(ChromaVertTest, ChromaInterPred_16_8_Test, kChromaInterPredTestParams, - PrintChromaInterPredTestParam); + PrintInterPredTestParam<2>); INSTANTIATE_TEST_SUITE_P(ChromaVertTest, ChromaInterPred_16_16_Test, kChromaInterPredTestParams, - PrintChromaInterPredTestParam); + PrintInterPredTestParam<2>); diff --git a/tests/common/ihevc_chroma_intra_pred_benchmark.cc b/tests/common/ihevc_chroma_intra_pred_benchmark.cc new file mode 100644 index 00000000..1412000f --- /dev/null +++ b/tests/common/ihevc_chroma_intra_pred_benchmark.cc @@ -0,0 +1,106 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_intra_pred_utils.h" + +namespace { + +void BM_ChromaIntraPred(benchmark::State& state, IntraPredConfig config) { + const int nt = config.nt; + const int mode = config.mode; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ChromaIntraPredFn fn = GetChromaIntraPredFn(selector, mode); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + IntraPredChromaBuffers buf = + CreateIntraPredChromaBuffers(nt, config.dst_strd_mul); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + ChromaIntraPredFn ref_fn = GetChromaIntraPredFn(get_ref_func_ptr(), mode); + if (ref_fn) { + IntraPredChromaBuffers ref_buf = buf; + IntraPredChromaBuffers tst_buf = buf; + ref_fn(ref_buf.ref.data(), ref_buf.src_strd, ref_buf.dst.data(), + ref_buf.dst_strd, nt, mode); + fn(tst_buf.ref.data(), tst_buf.src_strd, tst_buf.dst.data(), + tst_buf.dst_strd, nt, mode); + if (!VerifyOutput2D(ref_buf.dst.data(), tst_buf.dst.data(), 2 * nt, nt, + ref_buf.dst_strd, tst_buf.dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.ref.data(), buf.src_strd, buf.dst.data(), buf.dst_strd, nt, mode); + benchmark::DoNotOptimize(buf.dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * 2 * nt * nt); + state.SetBytesProcessed(state.iterations() * 2 * nt * nt * sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + // Register Chroma Intra Prediction benchmarks + for (const auto& mode_info : GetIntraPredBenchmarkModes()) { + for (int nt : GetIntraPredBenchmarkSizes()) { + for (auto arch : arches) { + const ihevc_func_selector_t* sel = get_func_ptr(arch); + if (!sel || !GetChromaIntraPredFn(sel, mode_info.mode)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = "BM_ChromaIntraPred/" + std::string(mode_info.name) + + "/" + std::to_string(nt) + "x" + std::to_string(nt) + + "/" + arch_name; + IntraPredConfig cfg{nt, mode_info.mode, /*dst_strd_mul=*/1, + /*disable_boundary_filter=*/0, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_ChromaIntraPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_chroma_intra_pred_test.cc b/tests/common/ihevc_chroma_intra_pred_test.cc index e93c8886..23741f6b 100644 --- a/tests/common/ihevc_chroma_intra_pred_test.cc +++ b/tests/common/ihevc_chroma_intra_pred_test.cc @@ -36,6 +36,8 @@ #include "TestCommon.h" // clang-format on +#include "ihevc_intra_pred_utils.h" + // Test parameters: block_size, mode, dst_stride_mul, arch using ChromaIntraPredTestParam = std::tuple; @@ -44,91 +46,33 @@ class ChromaIntraPredTest protected: void SetUp() override { std::tie(nt, mode, dst_strd_mul, arch) = GetParam(); - src_strd = 1; // Intra pred reference is usually dense - dst_strd = 2 * nt * dst_strd_mul; - - // TODO: Increase allocations for x86/x86_64 to avoid out-of-bounds - // reads/writes in SIMD implementations. -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ - defined(_M_IX86) - int pad_ref = 16; - int pad_dst = 16; -#else - int pad_ref = 0; - int pad_dst = 0; -#endif - - // Reference buffer size: 2 * (4 * nt + 1) = 8 * nt + 2 - int ref_size = 8 * nt + 2; - ref_buf.resize(ref_size + pad_ref); - - // Initialize reference buffer with random data - std::mt19937 rng(12345); - std::uniform_int_distribution dist(0, 255); - for (auto& v : ref_buf) { - v = static_cast(dist(rng)); - } - - pu1_ref = ref_buf.data(); - - dst_buf_ref.resize(dst_strd * nt + pad_dst); - dst_buf_tst.resize(dst_strd * nt + pad_dst); - - // Initialize dst buffers with pattern to detect over/under writes - std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xCD); - std::fill(dst_buf_tst.begin(), dst_buf_tst.end(), 0xCD); - - pu1_dst_ref = dst_buf_ref.data(); - pu1_dst_tst = dst_buf_tst.data(); + buf_ref = CreateIntraPredChromaBuffers(nt, dst_strd_mul, 8, 12345); + buf_tst = buf_ref; tst = get_tst_func_ptr(arch); ref = get_ref_func_ptr(); } - template - void RunTest(FuncPtr func_ptr) { - (ref->*func_ptr)(pu1_ref, src_strd, pu1_dst_ref, dst_strd, nt, mode); - (tst->*func_ptr)(pu1_ref, src_strd, pu1_dst_tst, dst_strd, nt, mode); - ASSERT_NO_FATAL_FAILURE( - compare_output(dst_buf_ref, dst_buf_tst, 2 * nt, nt, dst_strd)); - } - int nt, mode, dst_strd_mul; - int src_strd, dst_strd; - std::vector ref_buf; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - UWORD8* pu1_ref; - UWORD8* pu1_dst_ref; - UWORD8* pu1_dst_tst; + IntraPredChromaBuffers buf_ref; + IntraPredChromaBuffers buf_tst; IV_ARCH_T arch; const ihevc_func_selector_t* tst; const ihevc_func_selector_t* ref; }; TEST_P(ChromaIntraPredTest, Run) { - if (mode == 0) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_planar_fptr); - else if (mode == 1) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_dc_fptr); - else if (mode == 2) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_mode2_fptr); - else if (mode >= 3 && mode <= 9) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_mode_3_to_9_fptr); - else if (mode == 10) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_horz_fptr); - else if (mode >= 11 && mode <= 17) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_mode_11_to_17_fptr); - else if (mode == 18 || mode == 34) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_mode_18_34_fptr); - else if (mode >= 19 && mode <= 25) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_mode_19_to_25_fptr); - else if (mode == 26) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_ver_fptr); - else if (mode >= 27 && mode <= 33) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_chroma_mode_27_to_33_fptr); - else - FAIL() << "Invalid mode: " << mode; + ChromaIntraPredFn ref_fn = GetChromaIntraPredFn(ref, mode); + ChromaIntraPredFn tst_fn = GetChromaIntraPredFn(tst, mode); + ASSERT_NE(ref_fn, nullptr); + ASSERT_NE(tst_fn, nullptr); + + ref_fn(buf_ref.ref.data(), buf_ref.src_strd, buf_ref.dst.data(), + buf_ref.dst_strd, nt, mode); + tst_fn(buf_tst.ref.data(), buf_tst.src_strd, buf_tst.dst.data(), + buf_tst.dst_strd, nt, mode); + ASSERT_NO_FATAL_FAILURE(compare_output(buf_ref.dst, buf_tst.dst, + 2 * nt, nt, buf_ref.dst_strd)); } std::string PrintChromaIntraPredTestParam( diff --git a/tests/common/ihevc_chroma_itrans_recon_benchmark.cc b/tests/common/ihevc_chroma_itrans_recon_benchmark.cc new file mode 100644 index 00000000..f9985fab --- /dev/null +++ b/tests/common/ihevc_chroma_itrans_recon_benchmark.cc @@ -0,0 +1,145 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "ihevc_defs.h" +extern "C" { +#include "ihevc_chroma_itrans_recon.h" +#include "ihevc_function_selector.h" +#include "ihevc_macros.h" +#include "ihevc_platform_macros.h" +#include "ihevc_structs.h" +#include "iv.h" +} +// clang-format on + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_itrans_utils.h" + +namespace { + +void BM_ChromaITransRecon(benchmark::State& state, ITransBenchConfig config) { + const int trans_size = config.trans_size; + const int nz_cols = config.non_zero_cols; + const int nz_rows = config.non_zero_rows; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ChromaITransReconFn fn = GetChromaITransReconFn(selector, trans_size); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + // Padding and temporary buffer allocation + const int pad_pred = 16; + const int pad_tmp = 16; + const size_t tmp_size = 3 * trans_size * trans_size + pad_tmp; + + const WORD32 src_strd = trans_size; + const WORD32 pred_strd = 2 * trans_size; + const WORD32 dst_strd = 2 * trans_size; + + std::vector pi2_src(trans_size * trans_size); + std::vector pi2_tmp(tmp_size); + std::vector pu1_pred(pred_strd * trans_size + pad_pred); + std::vector pu1_dst(dst_strd * trans_size); + + WORD32 zero_cols = 0; + WORD32 zero_rows = 0; + GenerateITransInput(trans_size, /*ttype=*/0, nz_cols, nz_rows, pi2_src.data(), + static_cast(nullptr), &zero_cols, &zero_rows); + + FillRandom(pu1_pred, static_cast(0), static_cast(255), 1337); + std::fill(pu1_dst.begin(), pu1_dst.end(), 0xAA); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ChromaITransReconFn ref_fn = + GetChromaITransReconFn(ref_selector, trans_size); + if (ref_fn) { + std::vector ref_tmp(tmp_size); + std::vector ref_dst(dst_strd * trans_size, 0xAA); + ref_fn(pi2_src.data(), ref_tmp.data(), pu1_pred.data(), ref_dst.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows); + fn(pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), pu1_dst.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows); + if (!VerifyOutput2D(ref_dst.data(), pu1_dst.data(), 2 * trans_size, + trans_size, dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), pu1_dst.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows); + benchmark::DoNotOptimize(pu1_dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * trans_size * trans_size); + state.SetBytesProcessed(state.iterations() * trans_size * trans_size * + sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (const auto& tc : GetITransTestCases()) { + if (tc.ttype != 0) continue; // Chroma uses DCT only + for (const auto& nz : tc.nz_regions) { + for (auto arch : arches) { + const ihevc_func_selector_t* sel = get_func_ptr(arch); + if (!sel || !GetChromaITransReconFn(sel, tc.size)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = "BM_ChromaITransRecon/" + std::to_string(tc.size) + + "x" + std::to_string(tc.size) + "/nz_" + + std::to_string(nz.cols) + "x" + + std::to_string(nz.rows) + "/" + arch_name; + + ITransBenchConfig cfg{tc.size, /*ttype=*/0, nz.cols, nz.rows, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_ChromaITransRecon(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_chroma_itrans_recon_test.cc b/tests/common/ihevc_chroma_itrans_recon_test.cc index 770a950f..d486a049 100644 --- a/tests/common/ihevc_chroma_itrans_recon_test.cc +++ b/tests/common/ihevc_chroma_itrans_recon_test.cc @@ -30,6 +30,7 @@ #include "ihevc_typedefs.h" #include "TestCommon.h" #include "func_selector.h" +#include "ihevc_itrans_utils.h" // clang-format on namespace { @@ -58,53 +59,28 @@ class ChromaITransReconTest tst = get_tst_func_ptr(arch); } - template - void RunTest(FuncPtr func_ptr) { - std::mt19937 rng(0); - std::uniform_int_distribution coeff_dist_full(-32768, 32767); - std::uniform_int_distribution pixel_dist(0, 255); - - std::fill(pi2_src.begin(), pi2_src.end(), 0); - for (int i = 0; i < trans_size; i++) { - for (int j = 0; j < trans_size; j++) { - if (i < num_non_zero_rows && j < num_non_zero_cols) { - pi2_src[i * src_strd + j] = coeff_dist_full(rng); - } - } - } - - for (auto& v : pu1_pred) { - v = pixel_dist(rng); - } + void RunTest() { + FillRandomSubBlock(pi2_src.data(), trans_size, src_strd, num_non_zero_rows, + num_non_zero_cols, static_cast(-32768), + static_cast(32767), 0); + FillRandom(pu1_pred, static_cast(0), static_cast(255), 1); std::fill(pu1_dst_ref.begin(), pu1_dst_ref.end(), 0xAA); std::fill(pu1_dst_tst.begin(), pu1_dst_tst.end(), 0xAA); - WORD32 non_zero_rows_mask = 0; - for (int i = 0; i < num_non_zero_rows && i < trans_size; i++) { - non_zero_rows_mask |= (1u << i); - } - - WORD32 non_zero_cols_mask = 0; - for (int j = 0; j < num_non_zero_cols && j < trans_size; j++) { - non_zero_cols_mask |= (1u << j); - } + WORD32 zero_cols = ComputeZeroMask(trans_size, num_non_zero_cols); + WORD32 zero_rows = ComputeZeroMask(trans_size, num_non_zero_rows); - WORD32 mask = (trans_size == 32) - ? 0xFFFFFFFFu - : ((static_cast(1u) << trans_size) - 1u); - WORD32 zero_cols = (~non_zero_cols_mask) & mask; - WORD32 zero_rows = (~non_zero_rows_mask) & mask; + ChromaITransReconFn ref_fn = GetChromaITransReconFn(ref, trans_size); + ChromaITransReconFn tst_fn = GetChromaITransReconFn(tst, trans_size); // 1. Reference path (generic C) - (ref->*func_ptr)(pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), - pu1_dst_ref.data(), src_strd, pred_strd, dst_strd, - zero_cols, zero_rows); + ref_fn(pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), pu1_dst_ref.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows); // 2. Test path (SIMD) - (tst->*func_ptr)(pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), - pu1_dst_tst.data(), src_strd, pred_strd, dst_strd, - zero_cols, zero_rows); + tst_fn(pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), pu1_dst_tst.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows); ASSERT_NO_FATAL_FAILURE(compare_output( pu1_dst_ref, pu1_dst_tst, 2 * trans_size, trans_size, dst_strd)); @@ -127,17 +103,7 @@ class ChromaITransReconTest std::vector pu1_dst_tst; }; -TEST_P(ChromaITransReconTest, Run) { - if (trans_size == 4) { - RunTest(&ihevc_func_selector_t::ihevc_chroma_itrans_recon_4x4_fptr); - } else if (trans_size == 8) { - RunTest(&ihevc_func_selector_t::ihevc_chroma_itrans_recon_8x8_fptr); - } else if (trans_size == 16) { - RunTest(&ihevc_func_selector_t::ihevc_chroma_itrans_recon_16x16_fptr); - } else if (trans_size == 32) { - RunTest(&ihevc_func_selector_t::ihevc_chroma_itrans_recon_32x32_fptr); - } -} +TEST_P(ChromaITransReconTest, Run) { RunTest(); } std::string PrintChromaITransReconTestParam( const testing::TestParamInfo& info) { diff --git a/tests/common/ihevc_chroma_recon_benchmark.cc b/tests/common/ihevc_chroma_recon_benchmark.cc new file mode 100644 index 00000000..437b6526 --- /dev/null +++ b/tests/common/ihevc_chroma_recon_benchmark.cc @@ -0,0 +1,157 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "ihevc_defs.h" +extern "C" { +#include "ihevc_chroma_recon.h" +#include "ihevc_function_selector.h" +#include "ihevc_macros.h" +#include "ihevc_platform_macros.h" +#include "ihevc_structs.h" +#include "iv.h" +} +// clang-format on + +#include "BenchmarkCommon.h" +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_itrans_utils.h" + +namespace { + +struct ChromaReconBenchConfig { + int trans_size; + int non_zero_cols; + int offset; // 0 for U, 1 for V + IV_ARCH_T arch; +}; + +void BM_ChromaRecon(benchmark::State& state, ChromaReconBenchConfig config) { + const int trans_size = config.trans_size; + const int nz_cols = config.non_zero_cols; + const int offset = config.offset; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ChromaReconFn fn = GetChromaReconFn(selector, trans_size); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + const WORD32 src_strd = trans_size; + const WORD32 pred_strd = 2 * trans_size; + const WORD32 dst_strd = 2 * trans_size; + + std::vector pi2_src(trans_size * trans_size); + std::vector pu1_pred(pred_strd * trans_size); + std::vector pu1_dst(dst_strd * trans_size); + + FillRandomSubBlock(pi2_src.data(), src_strd, trans_size, trans_size, nz_cols, + static_cast(-512), static_cast(511)); + FillRandom(pu1_pred, static_cast(0), static_cast(255)); + std::fill(pu1_dst.begin(), pu1_dst.end(), 0xAA); + + WORD32 zero_cols = ComputeZeroMask(trans_size, nz_cols); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ChromaReconFn ref_fn = GetChromaReconFn(ref_selector, trans_size); + if (ref_fn) { + std::vector ref_dst(dst_strd * trans_size, 0xAA); + ref_fn(pi2_src.data(), pu1_pred.data() + offset, ref_dst.data() + offset, + src_strd, pred_strd, dst_strd, zero_cols); + fn(pi2_src.data(), pu1_pred.data() + offset, pu1_dst.data() + offset, + src_strd, pred_strd, dst_strd, zero_cols); + if (!VerifyOutput2D(ref_dst.data(), pu1_dst.data(), 2 * trans_size, + trans_size, dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(pi2_src.data(), pu1_pred.data() + offset, pu1_dst.data() + offset, + src_strd, pred_strd, dst_strd, zero_cols); + benchmark::DoNotOptimize(pu1_dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * trans_size * trans_size); + state.SetBytesProcessed(state.iterations() * trans_size * trans_size * + sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const int sizes[] = {4, 8, 16, 32}; + const int offsets[] = {0, 1}; + + for (int size : sizes) { + std::vector nz_options; + if (size == 4) { + nz_options = {1, 2, 4}; + } else if (size == 8) { + nz_options = {2, 4, 8}; + } else if (size == 16) { + nz_options = {4, 8, 16}; + } else { + nz_options = {8, 16, 32}; + } + + for (int nz : nz_options) { + for (int offset : offsets) { + for (auto arch : GetBenchmarkArchitectures()) { + const ihevc_func_selector_t* sel = get_func_ptr(arch); + if (!sel || !GetChromaReconFn(sel, size)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = "BM_ChromaRecon/" + std::to_string(size) + "x" + + std::to_string(size) + "/nz_" + + std::to_string(nz) + "/" + + (offset == 0 ? "U" : "V") + "/" + arch_name; + + ChromaReconBenchConfig cfg{size, nz, offset, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_ChromaRecon(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_chroma_recon_test.cc b/tests/common/ihevc_chroma_recon_test.cc index fb212594..4edc8c31 100644 --- a/tests/common/ihevc_chroma_recon_test.cc +++ b/tests/common/ihevc_chroma_recon_test.cc @@ -33,6 +33,7 @@ #include "ihevc_structs.h" #include "ihevc_typedefs.h" #include "TestCommon.h" +#include "ihevc_itrans_utils.h" // clang-format on namespace { @@ -58,45 +59,26 @@ class ChromaReconTest : public ::testing::TestWithParam { tst = get_tst_func_ptr(arch); } - template - void RunTest(FuncPtr func_ptr) { - std::mt19937 rng(0); - std::uniform_int_distribution coeff_dist(-512, 511); - std::uniform_int_distribution pixel_dist(0, 255); - - // Populate pi2_src so that columns [0, num_non_zero_cols) are non-zero. - std::fill(pi2_src.begin(), pi2_src.end(), 0); - for (int i = 0; i < trans_size; i++) { - for (int j = 0; j < trans_size; j++) { - if (j < num_non_zero_cols) { - pi2_src[i * src_strd + j] = coeff_dist(rng); - } - } - } - - for (auto& v : pu1_pred) { - v = pixel_dist(rng); - } + void RunTest() { + FillRandomSubBlock(pi2_src.data(), trans_size, src_strd, trans_size, + num_non_zero_cols, static_cast(-512), + static_cast(511), 0); + FillRandom(pu1_pred, static_cast(0), static_cast(255), 1); - WORD32 non_zero_cols_mask = 0; - for (int j = 0; j < num_non_zero_cols && j < trans_size; j++) { - non_zero_cols_mask |= (1u << j); - } + WORD32 zero_cols = ComputeZeroMask(trans_size, num_non_zero_cols); - WORD32 mask = (trans_size == 32) - ? 0xFFFFFFFFu - : ((static_cast(1u) << trans_size) - 1u); - WORD32 zero_cols = (~non_zero_cols_mask) & mask; + ChromaReconFn ref_fn = GetChromaReconFn(ref, trans_size); + ChromaReconFn tst_fn = GetChromaReconFn(tst, trans_size); std::fill(pu1_dst_ref.begin(), pu1_dst_ref.end(), 0xAA); - (ref->*func_ptr)(pi2_src.data(), pu1_pred.data() + offset, - pu1_dst_ref.data() + offset, src_strd, pred_strd, dst_strd, - zero_cols); + ref_fn(pi2_src.data(), pu1_pred.data() + offset, + pu1_dst_ref.data() + offset, src_strd, pred_strd, dst_strd, + zero_cols); std::fill(pu1_dst_tst.begin(), pu1_dst_tst.end(), 0xAA); - (tst->*func_ptr)(pi2_src.data(), pu1_pred.data() + offset, - pu1_dst_tst.data() + offset, src_strd, pred_strd, dst_strd, - zero_cols); + tst_fn(pi2_src.data(), pu1_pred.data() + offset, + pu1_dst_tst.data() + offset, src_strd, pred_strd, dst_strd, + zero_cols); ASSERT_NO_FATAL_FAILURE(compare_output( pu1_dst_ref, pu1_dst_tst, trans_size * 2, trans_size, dst_strd)); @@ -118,17 +100,7 @@ class ChromaReconTest : public ::testing::TestWithParam { std::vector pu1_dst_tst; }; -TEST_P(ChromaReconTest, Run) { - if (trans_size == 4) { - RunTest(&ihevc_func_selector_t::ihevc_chroma_recon_4x4_fptr); - } else if (trans_size == 8) { - RunTest(&ihevc_func_selector_t::ihevc_chroma_recon_8x8_fptr); - } else if (trans_size == 16) { - RunTest(&ihevc_func_selector_t::ihevc_chroma_recon_16x16_fptr); - } else if (trans_size == 32) { - RunTest(&ihevc_func_selector_t::ihevc_chroma_recon_32x32_fptr); - } -} +TEST_P(ChromaReconTest, Run) { RunTest(); } std::string PrintChromaReconTestParam( const testing::TestParamInfo& info) { diff --git a/tests/common/ihevc_deblk_benchmark.cc b/tests/common/ihevc_deblk_benchmark.cc new file mode 100644 index 00000000..a320c34b --- /dev/null +++ b/tests/common/ihevc_deblk_benchmark.cc @@ -0,0 +1,210 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_deblk_utils.h" + +namespace { + +void BM_DeblkLuma(benchmark::State& state, DeblkLumaConfig config) { + const bool is_vert = config.is_vert; + const int bs = config.bs; + const int qp_p = config.qp_p; + const int qp_q = config.qp_q; + const int beta_offset = config.beta_offset; + const int tc_offset = config.tc_offset; + const int filter_p = config.filter_p; + const int filter_q = config.filter_q; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + DeblkLumaFn fn = GetLumaTargetFn(selector, is_vert); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + std::vector src(kDeblkBufSize); + InitializeDeblkBuffer(src.data(), kDeblkBufSize); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + DeblkLumaFn ref_fn = GetLumaTargetFn(ref_selector, is_vert); + if (ref_fn) { + std::vector ref_src(src); + std::vector tst_src(src); + ref_fn(ref_src.data() + kDeblkSrcOffset, kDeblkStride, bs, qp_p, qp_q, + beta_offset, tc_offset, filter_p, filter_q); + fn(tst_src.data() + kDeblkSrcOffset, kDeblkStride, bs, qp_p, qp_q, + beta_offset, tc_offset, filter_p, filter_q); + if (std::memcmp(ref_src.data(), tst_src.data(), kDeblkBufSize) != 0) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(src.data() + kDeblkSrcOffset, kDeblkStride, bs, qp_p, qp_q, beta_offset, + tc_offset, filter_p, filter_q); + benchmark::DoNotOptimize(src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * 32); + state.SetBytesProcessed(state.iterations() * 32 * sizeof(UWORD8)); +} + +void BM_DeblkChroma(benchmark::State& state, DeblkChromaConfig config) { + const bool is_vert = config.is_vert; + const int qp_p = config.qp_p; + const int qp_q = config.qp_q; + const int qp_offset_u = config.qp_offset_u; + const int qp_offset_v = config.qp_offset_v; + const int tc_offset = config.tc_offset; + const int filter_p = config.filter_p; + const int filter_q = config.filter_q; + const int chroma_fmt_idc = config.chroma_fmt_idc; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + DeblkChromaFn fn = GetChromaTargetFn(selector, is_vert); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + std::vector src(kDeblkBufSize); + InitializeDeblkBuffer(src.data(), kDeblkBufSize); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + DeblkChromaFn ref_fn = GetChromaTargetFn(ref_selector, is_vert); + if (ref_fn) { + std::vector ref_src(src); + std::vector tst_src(src); + ref_fn(ref_src.data() + kDeblkSrcOffset, kDeblkStride, qp_p, qp_q, + qp_offset_u, qp_offset_v, tc_offset, filter_p, filter_q, + static_cast(chroma_fmt_idc)); + fn(tst_src.data() + kDeblkSrcOffset, kDeblkStride, qp_p, qp_q, + qp_offset_u, qp_offset_v, tc_offset, filter_p, filter_q, + static_cast(chroma_fmt_idc)); + if (std::memcmp(ref_src.data(), tst_src.data(), kDeblkBufSize) != 0) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(src.data() + kDeblkSrcOffset, kDeblkStride, qp_p, qp_q, qp_offset_u, + qp_offset_v, tc_offset, filter_p, filter_q, + static_cast(chroma_fmt_idc)); + benchmark::DoNotOptimize(src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * 32); + state.SetBytesProcessed(state.iterations() * 32 * sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + // Register Luma deblocking benchmarks + for (bool is_vert : {true, false}) { + for (int bs : {1, 2}) { + for (int qp : {28, 44}) { + for (const auto& filter_pair : + {std::make_pair(1, 1), std::make_pair(1, 0)}) { + for (auto arch : arches) { + std::string arch_name = GetArchName(arch); + std::string name = + "BM_DeblkLuma/" + std::string(is_vert ? "vert" : "horz") + + "/bs_" + std::to_string(bs) + "/qp_" + std::to_string(qp) + + "/fP_" + std::to_string(filter_pair.first) + "_fQ_" + + std::to_string(filter_pair.second) + "/" + arch_name; + + DeblkLumaConfig cfg{is_vert, + bs, + qp, + qp, + /*beta_offset=*/0, + /*tc_offset=*/0, + filter_pair.first, + filter_pair.second, + arch}; + benchmark::RegisterBenchmark( + name.c_str(), + [cfg](benchmark::State& st) { BM_DeblkLuma(st, cfg); }) + ->Unit(benchmark::kNanosecond); + } + } + } + } + } + + // Register Chroma deblocking benchmarks + for (bool is_vert : {true, false}) { + for (int fmt : {1, 3}) { // 1: YUV420, 3: YUV444 + for (int qp : {28, 44}) { + for (auto arch : arches) { + std::string arch_name = GetArchName(arch); + std::string name = "BM_DeblkChroma/" + + std::string(is_vert ? "vert" : "horz") + "/fmt_" + + (fmt == 1 ? "420" : "444") + "/qp_" + + std::to_string(qp) + "/" + arch_name; + + DeblkChromaConfig cfg{is_vert, qp, qp, + /*qp_offset_u=*/0, + /*qp_offset_v=*/0, + /*tc_offset=*/0, + /*filter_p=*/1, + /*filter_q=*/1, fmt, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_DeblkChroma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_deblk_test.cc b/tests/common/ihevc_deblk_test.cc index 0a805bd2..0726001f 100644 --- a/tests/common/ihevc_deblk_test.cc +++ b/tests/common/ihevc_deblk_test.cc @@ -29,20 +29,11 @@ #include "ihevc_defs.h" #include "ihevc_typedefs.h" #include "TestCommon.h" +#include "ihevc_deblk_utils.h" // clang-format on namespace { -void compare_deblk_output(const UWORD8* ref, const UWORD8* tst, int stride, - int wd, int ht) { - for (int r = 0; r < ht; r++) { - for (int c = 0; c < wd; c++) { - ASSERT_EQ(ref[r * stride + c], tst[r * stride + c]) - << "Mismatch at row " << r << ", col " << c; - } - } -} - std::string format_int(int val) { if (val < 0) { return "m" + std::to_string(-val); @@ -74,31 +65,32 @@ class DeblkLumaTest : public ::testing::TestWithParam { void SetUp() override { std::tie(bs, qp_p, qp_q, beta_offset, tc_offset, filter_pair, arch) = GetParam(); - stride = 32; - buf_size = stride * 32; - src_offset = 16 * stride + 16; // Point to middle of buffer - - src_ref.resize(buf_size); - src_tst.resize(buf_size); + src_ref.resize(kDeblkBufSize); + src_tst.resize(kDeblkBufSize); ref = get_ref_func_ptr(); tst = get_tst_func_ptr(arch); } - void InitializeBuffers() { - std::mt19937 rng(42); - std::uniform_int_distribution dist(0, 255); - for (int i = 0; i < buf_size; i++) { - uint8_t val = dist(rng); - src_ref[i] = val; - src_tst[i] = val; - } + void RunTest(bool is_vert) { + InitializeDeblkBuffer(src_ref.data(), kDeblkBufSize); + src_tst = src_ref; + + DeblkLumaFn ref_fn = GetLumaTargetFn(ref, is_vert); + DeblkLumaFn tst_fn = GetLumaTargetFn(tst, is_vert); + + ref_fn(src_ref.data() + kDeblkSrcOffset, kDeblkStride, bs, qp_p, qp_q, + beta_offset, tc_offset, filter_pair.first, filter_pair.second); + tst_fn(src_tst.data() + kDeblkSrcOffset, kDeblkStride, bs, qp_p, qp_q, + beta_offset, tc_offset, filter_pair.first, filter_pair.second); + + compare_output(src_ref.data(), src_tst.data(), kDeblkStride, + kDeblkBufHeight, kDeblkStride); } int bs, qp_p, qp_q, beta_offset, tc_offset; std::pair filter_pair; IV_ARCH_T arch; - int stride, buf_size, src_offset; std::vector src_ref; std::vector src_tst; @@ -106,33 +98,9 @@ class DeblkLumaTest : public ::testing::TestWithParam { const ihevc_func_selector_t* tst; }; -TEST_P(DeblkLumaTest, LumaVert) { - InitializeBuffers(); +TEST_P(DeblkLumaTest, LumaVert) { RunTest(true); } - ref->ihevc_deblk_luma_vert_fptr(src_ref.data() + src_offset, stride, bs, qp_p, - qp_q, beta_offset, tc_offset, - filter_pair.first, filter_pair.second); - - tst->ihevc_deblk_luma_vert_fptr(src_tst.data() + src_offset, stride, bs, qp_p, - qp_q, beta_offset, tc_offset, - filter_pair.first, filter_pair.second); - - compare_deblk_output(src_ref.data(), src_tst.data(), stride, stride, 32); -} - -TEST_P(DeblkLumaTest, LumaHorz) { - InitializeBuffers(); - - ref->ihevc_deblk_luma_horz_fptr(src_ref.data() + src_offset, stride, bs, qp_p, - qp_q, beta_offset, tc_offset, - filter_pair.first, filter_pair.second); - - tst->ihevc_deblk_luma_horz_fptr(src_tst.data() + src_offset, stride, bs, qp_p, - qp_q, beta_offset, tc_offset, - filter_pair.first, filter_pair.second); - - compare_deblk_output(src_ref.data(), src_tst.data(), stride, stride, 32); -} +TEST_P(DeblkLumaTest, LumaHorz) { RunTest(false); } // ---------------------------- Chroma Test ------------------------------------ @@ -161,31 +129,34 @@ class DeblkChromaTest : public ::testing::TestWithParam { void SetUp() override { std::tie(qp_p, qp_q, qp_offset_u, qp_offset_v, tc_offset, filter_pair, chroma_fmt_idc, arch) = GetParam(); - stride = 32; - buf_size = stride * 32; - src_offset = 16 * stride + 16; // Point to middle of buffer - - src_ref.resize(buf_size); - src_tst.resize(buf_size); + src_ref.resize(kDeblkBufSize); + src_tst.resize(kDeblkBufSize); ref = get_ref_func_ptr(); tst = get_tst_func_ptr(arch); } - void InitializeBuffers() { - std::mt19937 rng(42); - std::uniform_int_distribution dist(0, 255); - for (int i = 0; i < buf_size; i++) { - uint8_t val = dist(rng); - src_ref[i] = val; - src_tst[i] = val; - } + void RunTest(bool is_vert) { + InitializeDeblkBuffer(src_ref.data(), kDeblkBufSize); + src_tst = src_ref; + + DeblkChromaFn ref_fn = GetChromaTargetFn(ref, is_vert); + DeblkChromaFn tst_fn = GetChromaTargetFn(tst, is_vert); + + ref_fn(src_ref.data() + kDeblkSrcOffset, kDeblkStride, qp_p, qp_q, + qp_offset_u, qp_offset_v, tc_offset, filter_pair.first, + filter_pair.second, chroma_fmt_idc); + tst_fn(src_tst.data() + kDeblkSrcOffset, kDeblkStride, qp_p, qp_q, + qp_offset_u, qp_offset_v, tc_offset, filter_pair.first, + filter_pair.second, chroma_fmt_idc); + + compare_output(src_ref.data(), src_tst.data(), kDeblkStride, + kDeblkBufHeight, kDeblkStride); } int qp_p, qp_q, qp_offset_u, qp_offset_v, tc_offset, chroma_fmt_idc; std::pair filter_pair; IV_ARCH_T arch; - int stride, buf_size, src_offset; std::vector src_ref; std::vector src_tst; @@ -193,33 +164,9 @@ class DeblkChromaTest : public ::testing::TestWithParam { const ihevc_func_selector_t* tst; }; -TEST_P(DeblkChromaTest, ChromaVert) { - InitializeBuffers(); - - ref->ihevc_deblk_chroma_vert_fptr( - src_ref.data() + src_offset, stride, qp_p, qp_q, qp_offset_u, qp_offset_v, - tc_offset, filter_pair.first, filter_pair.second, chroma_fmt_idc); - - tst->ihevc_deblk_chroma_vert_fptr( - src_tst.data() + src_offset, stride, qp_p, qp_q, qp_offset_u, qp_offset_v, - tc_offset, filter_pair.first, filter_pair.second, chroma_fmt_idc); +TEST_P(DeblkChromaTest, ChromaVert) { RunTest(true); } - compare_deblk_output(src_ref.data(), src_tst.data(), stride, stride, 32); -} - -TEST_P(DeblkChromaTest, ChromaHorz) { - InitializeBuffers(); - - ref->ihevc_deblk_chroma_horz_fptr( - src_ref.data() + src_offset, stride, qp_p, qp_q, qp_offset_u, qp_offset_v, - tc_offset, filter_pair.first, filter_pair.second, chroma_fmt_idc); - - tst->ihevc_deblk_chroma_horz_fptr( - src_tst.data() + src_offset, stride, qp_p, qp_q, qp_offset_u, qp_offset_v, - tc_offset, filter_pair.first, filter_pair.second, chroma_fmt_idc); - - compare_deblk_output(src_ref.data(), src_tst.data(), stride, stride, 32); -} +TEST_P(DeblkChromaTest, ChromaHorz) { RunTest(false); } // ---------------------------- Instantiation -------------------------------- diff --git a/tests/common/ihevc_deblk_utils.cc b/tests/common/ihevc_deblk_utils.cc new file mode 100644 index 00000000..1d5a7a04 --- /dev/null +++ b/tests/common/ihevc_deblk_utils.cc @@ -0,0 +1,66 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "ihevc_deblk_utils.h" + +#include + +DeblkLumaFn GetLumaTargetFn(const ihevc_func_selector_t* selector, + bool is_vert) { + if (!selector) { + return nullptr; + } + return is_vert ? selector->ihevc_deblk_luma_vert_fptr + : selector->ihevc_deblk_luma_horz_fptr; +} + +DeblkChromaFn GetChromaTargetFn(const ihevc_func_selector_t* selector, + bool is_vert) { + if (!selector) { + return nullptr; + } + return is_vert ? selector->ihevc_deblk_chroma_vert_fptr + : selector->ihevc_deblk_chroma_horz_fptr; +} + +HbdDeblkLumaFn GetHbdLumaTargetFn(IV_ARCH_T arch, bool is_vert) { + if (arch == ARCH_NA) { + return is_vert ? ihevc_hbd_deblk_luma_vert : ihevc_hbd_deblk_luma_horz; + } + // Currently HBD deblocking only has C reference implementations. + // SIMD implementations can be plugged in here when available. + return nullptr; +} + +HbdDeblkChromaFn GetHbdChromaTargetFn(IV_ARCH_T arch, bool is_vert) { + if (arch == ARCH_NA) { + return is_vert ? ihevc_hbd_deblk_chroma_vert : ihevc_hbd_deblk_chroma_horz; + } + // Currently HBD deblocking only has C reference implementations. + // SIMD implementations can be plugged in here when available. + return nullptr; +} + +void InitializeDeblkBuffer(UWORD8* buf, size_t size) { + FillRandom(buf, size, static_cast(0), static_cast(255), 42); +} + +void InitializeDeblkBuffer(UWORD16* buf, size_t size, int bit_depth) { + FillRandom(buf, size, static_cast(0), + static_cast((1 << bit_depth) - 1), 42); +} diff --git a/tests/common/ihevc_deblk_utils.h b/tests/common/ihevc_deblk_utils.h new file mode 100644 index 00000000..b26bbf03 --- /dev/null +++ b/tests/common/ihevc_deblk_utils.h @@ -0,0 +1,118 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "ihevc_defs.h" +extern "C" { +#include "ihevc_deblk.h" +#include "ihevc_function_selector.h" +#include "iv.h" +} +// clang-format on + +#include "BenchmarkCommon.h" +#include "TestCommon.h" + +// Buffer constants for deblocking tests and benchmarks +constexpr int kDeblkStride = 32; +constexpr int kDeblkBufHeight = 32; +constexpr int kDeblkBufSize = kDeblkStride * kDeblkBufHeight; +constexpr int kDeblkSrcOffset = 16 * kDeblkStride + 16; + +// Function pointer types for 8-bit deblocking +using DeblkLumaFn = void (*)(UWORD8* pu1_src, WORD32 src_strd, WORD32 bs, + WORD32 quant_param_p, WORD32 quant_param_q, + WORD32 beta_offset_div2, WORD32 tc_offset_div2, + WORD32 filter_flag_p, WORD32 filter_flag_q); + +using DeblkChromaFn = void (*)(UWORD8* pu1_src, WORD32 src_strd, + WORD32 quant_param_p, WORD32 quant_param_q, + WORD32 qp_offset_u, WORD32 qp_offset_v, + WORD32 tc_offset_div2, WORD32 filter_flag_p, + WORD32 filter_flag_q, WORD8 chroma_fmt_idc); + +// Function pointer types for High Bit Depth (HBD) deblocking +using HbdDeblkLumaFn = void (*)(UWORD16* pu2_src, WORD32 src_strd, WORD32 bs, + WORD32 quant_param_p, WORD32 quant_param_q, + WORD32 beta_offset_div2, WORD32 tc_offset_div2, + WORD32 filter_flag_p, WORD32 filter_flag_q, + UWORD8 bit_depth); + +using HbdDeblkChromaFn = void (*)(UWORD16* pu2_src, WORD32 src_strd, + WORD32 quant_param_p, WORD32 quant_param_q, + WORD32 qp_offset_u, WORD32 qp_offset_v, + WORD32 tc_offset_div2, WORD32 filter_flag_p, + WORD32 filter_flag_q, UWORD8 bit_depth, + WORD8 chroma_fmt_idc); + +// Configuration for Luma deblocking benchmark +struct DeblkLumaConfig { + bool is_vert; + int bs; + int qp_p; + int qp_q; + int beta_offset; + int tc_offset; + int filter_p; + int filter_q; + IV_ARCH_T arch; + int bit_depth = 8; +}; + +// Configuration for Chroma deblocking benchmark +struct DeblkChromaConfig { + bool is_vert; + int qp_p; + int qp_q; + int qp_offset_u; + int qp_offset_v; + int tc_offset; + int filter_p; + int filter_q; + int chroma_fmt_idc; + IV_ARCH_T arch; + int bit_depth = 8; +}; + +// Returns target 8-bit Luma deblocking function pointer +DeblkLumaFn GetLumaTargetFn(const ihevc_func_selector_t* selector, + bool is_vert); + +// Returns target 8-bit Chroma deblocking function pointer +DeblkChromaFn GetChromaTargetFn(const ihevc_func_selector_t* selector, + bool is_vert); + +// Returns target HBD Luma deblocking function pointer +HbdDeblkLumaFn GetHbdLumaTargetFn(IV_ARCH_T arch, bool is_vert); + +// Returns target HBD Chroma deblocking function pointer +HbdDeblkChromaFn GetHbdChromaTargetFn(IV_ARCH_T arch, bool is_vert); + +// Initializes 8-bit deblocking buffer with deterministic random data +void InitializeDeblkBuffer(UWORD8* buf, size_t size); + +// Initializes 16-bit HBD deblocking buffer with deterministic random data +void InitializeDeblkBuffer(UWORD16* buf, size_t size, int bit_depth = 8); diff --git a/tests/common/ihevc_hbd_chroma_inter_pred_benchmark.cc b/tests/common/ihevc_hbd_chroma_inter_pred_benchmark.cc new file mode 100644 index 00000000..51ed7a3f --- /dev/null +++ b/tests/common/ihevc_hbd_chroma_inter_pred_benchmark.cc @@ -0,0 +1,111 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "ihevc_inter_pred_utils.h" + +namespace { + +bool HasHbdChromaInterPredFn(IV_ARCH_T arch, InterPredOp op) { + if (!IsW16Inp(op) && !IsW16Out(op)) { + return GetHbdChromaInterPredFn(arch, op) != nullptr; + } else if (!IsW16Inp(op) && IsW16Out(op)) { + return GetHbdChromaInterPredW16outFn(arch, op) != nullptr; + } else if (IsW16Inp(op) && !IsW16Out(op)) { + return GetHbdChromaInterPredW16inpFn(arch, op) != nullptr; + } else { + return GetHbdChromaInterPredW16inpW16outFn(arch, op) != nullptr; + } +} + +void BM_HbdChromaInterPred(benchmark::State& state, InterPredConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const InterPredOp op = config.op; + const IV_ARCH_T arch = config.arch; + const UWORD8 bit_depth = static_cast(config.bit_depth); + + WORD8* pi1_coeff = gai1_ihevc_chroma_filter[config.coeff_idx]; + const bool verify = (arch != ARCH_NA); + + if (!IsW16Inp(op) && !IsW16Out(op)) { + RunHbdInterPredBenchmarkCase( + state, GetHbdChromaInterPredFn(arch, op), + GetHbdChromaInterPredFn(ARCH_NA, op), wd, ht, pi1_coeff, bit_depth, + verify); + } else if (!IsW16Inp(op) && IsW16Out(op)) { + RunHbdInterPredBenchmarkCase( + state, GetHbdChromaInterPredW16outFn(arch, op), + GetHbdChromaInterPredW16outFn(ARCH_NA, op), wd, ht, pi1_coeff, + bit_depth, verify); + } else if (IsW16Inp(op) && !IsW16Out(op)) { + RunHbdInterPredBenchmarkCase( + state, GetHbdChromaInterPredW16inpFn(arch, op), + GetHbdChromaInterPredW16inpFn(ARCH_NA, op), wd, ht, pi1_coeff, + bit_depth, verify); + } else { + RunHbdInterPredBenchmarkCase( + state, GetHbdChromaInterPredW16inpW16outFn(arch, op), + GetHbdChromaInterPredW16inpW16outFn(ARCH_NA, op), wd, ht, pi1_coeff, + bit_depth, verify); + } +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (int bit_depth : {8, 10}) { + // Register HBD Chroma Inter Prediction benchmarks + for (const auto& op_info : GetInterPredOps()) { + for (const auto& size : GetInterPredChromaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + if (arch != ARCH_NA && !HasHbdChromaInterPredFn(arch, op_info.op)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = + "BM_HbdChromaInterPred/" + std::string(op_info.name) + "/" + + std::to_string(wd) + "x" + std::to_string(ht) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + InterPredConfig cfg{wd, + ht, + op_info.op, + arch, + bit_depth, + /*coeff_idx=*/1}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_HbdChromaInterPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_hbd_chroma_intra_pred_benchmark.cc b/tests/common/ihevc_hbd_chroma_intra_pred_benchmark.cc new file mode 100644 index 00000000..72b0a709 --- /dev/null +++ b/tests/common/ihevc_hbd_chroma_intra_pred_benchmark.cc @@ -0,0 +1,107 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "ihevc_intra_pred_utils.h" + +namespace { + +void BM_HbdChromaIntraPred(benchmark::State& state, IntraPredConfig config) { + const int nt = config.nt; + const int mode = config.mode; + const IV_ARCH_T arch = config.arch; + + HbdChromaIntraPredFn fn = GetHbdChromaIntraPredFn(arch, mode); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + IntraPredChromaBuffers buf = CreateIntraPredChromaBuffers( + nt, config.dst_strd_mul, config.bit_depth); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + HbdChromaIntraPredFn ref_fn = GetHbdChromaIntraPredFn(ARCH_NA, mode); + if (ref_fn) { + IntraPredChromaBuffers ref_buf = buf; + IntraPredChromaBuffers tst_buf = buf; + ref_fn(ref_buf.ref.data(), ref_buf.src_strd, ref_buf.dst.data(), + ref_buf.dst_strd, nt, mode); + fn(tst_buf.ref.data(), tst_buf.src_strd, tst_buf.dst.data(), + tst_buf.dst_strd, nt, mode); + if (!VerifyOutput2D(ref_buf.dst.data(), tst_buf.dst.data(), 2 * nt, nt, + ref_buf.dst_strd, tst_buf.dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.ref.data(), buf.src_strd, buf.dst.data(), buf.dst_strd, nt, mode); + benchmark::DoNotOptimize(buf.dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * 2 * nt * nt); + state.SetBytesProcessed(state.iterations() * 2 * nt * nt * sizeof(UWORD16)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (int bit_depth : {8, 10}) { + // Register HBD Chroma Intra Prediction benchmarks + for (const auto& mode_info : GetIntraPredBenchmarkModes()) { + for (int nt : GetIntraPredBenchmarkSizes()) { + for (auto arch : arches) { + if (arch != ARCH_NA && + !GetHbdChromaIntraPredFn(arch, mode_info.mode)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = + "BM_HbdChromaIntraPred/" + std::string(mode_info.name) + "/" + + std::to_string(nt) + "x" + std::to_string(nt) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + IntraPredConfig cfg{nt, + mode_info.mode, + /*dst_strd_mul=*/1, + /*disable_boundary_filter=*/0, + arch, + bit_depth}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_HbdChromaIntraPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_hbd_deblk_benchmark.cc b/tests/common/ihevc_hbd_deblk_benchmark.cc new file mode 100644 index 00000000..b0586de2 --- /dev/null +++ b/tests/common/ihevc_hbd_deblk_benchmark.cc @@ -0,0 +1,212 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "ihevc_deblk_utils.h" + +namespace { + +void BM_HbdDeblkLuma(benchmark::State& state, DeblkLumaConfig config) { + const bool is_vert = config.is_vert; + const int bs = config.bs; + const int qp_p = config.qp_p; + const int qp_q = config.qp_q; + const int beta_offset = config.beta_offset; + const int tc_offset = config.tc_offset; + const int filter_p = config.filter_p; + const int filter_q = config.filter_q; + const IV_ARCH_T arch = config.arch; + const UWORD8 bit_depth = static_cast(config.bit_depth); + + HbdDeblkLumaFn fn = GetHbdLumaTargetFn(arch, is_vert); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + std::vector src(kDeblkBufSize); + InitializeDeblkBuffer(src.data(), kDeblkBufSize, bit_depth); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + HbdDeblkLumaFn ref_fn = GetHbdLumaTargetFn(ARCH_NA, is_vert); + if (ref_fn) { + std::vector ref_src(src); + std::vector tst_src(src); + ref_fn(ref_src.data() + kDeblkSrcOffset, kDeblkStride, bs, qp_p, qp_q, + beta_offset, tc_offset, filter_p, filter_q, bit_depth); + fn(tst_src.data() + kDeblkSrcOffset, kDeblkStride, bs, qp_p, qp_q, + beta_offset, tc_offset, filter_p, filter_q, bit_depth); + if (std::memcmp(ref_src.data(), tst_src.data(), + kDeblkBufSize * sizeof(UWORD16)) != 0) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(src.data() + kDeblkSrcOffset, kDeblkStride, bs, qp_p, qp_q, beta_offset, + tc_offset, filter_p, filter_q, bit_depth); + benchmark::DoNotOptimize(src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * 32); + state.SetBytesProcessed(state.iterations() * 32 * sizeof(UWORD16)); +} + +void BM_HbdDeblkChroma(benchmark::State& state, DeblkChromaConfig config) { + const bool is_vert = config.is_vert; + const int qp_p = config.qp_p; + const int qp_q = config.qp_q; + const int qp_offset_u = config.qp_offset_u; + const int qp_offset_v = config.qp_offset_v; + const int tc_offset = config.tc_offset; + const int filter_p = config.filter_p; + const int filter_q = config.filter_q; + const int chroma_fmt_idc = config.chroma_fmt_idc; + const IV_ARCH_T arch = config.arch; + const UWORD8 bit_depth = static_cast(config.bit_depth); + + HbdDeblkChromaFn fn = GetHbdChromaTargetFn(arch, is_vert); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + std::vector src(kDeblkBufSize); + InitializeDeblkBuffer(src.data(), kDeblkBufSize, bit_depth); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + HbdDeblkChromaFn ref_fn = GetHbdChromaTargetFn(ARCH_NA, is_vert); + if (ref_fn) { + std::vector ref_src(src); + std::vector tst_src(src); + ref_fn(ref_src.data() + kDeblkSrcOffset, kDeblkStride, qp_p, qp_q, + qp_offset_u, qp_offset_v, tc_offset, filter_p, filter_q, bit_depth, + static_cast(chroma_fmt_idc)); + fn(tst_src.data() + kDeblkSrcOffset, kDeblkStride, qp_p, qp_q, + qp_offset_u, qp_offset_v, tc_offset, filter_p, filter_q, bit_depth, + static_cast(chroma_fmt_idc)); + if (std::memcmp(ref_src.data(), tst_src.data(), + kDeblkBufSize * sizeof(UWORD16)) != 0) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(src.data() + kDeblkSrcOffset, kDeblkStride, qp_p, qp_q, qp_offset_u, + qp_offset_v, tc_offset, filter_p, filter_q, bit_depth, + static_cast(chroma_fmt_idc)); + benchmark::DoNotOptimize(src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * 32); + state.SetBytesProcessed(state.iterations() * 32 * sizeof(UWORD16)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + // Register HBD Luma deblocking benchmarks + for (int bit_depth : {8, 10}) { + for (bool is_vert : {true, false}) { + for (int bs : {1, 2}) { + for (int qp : {28, 44}) { + for (const auto& filter_pair : + {std::make_pair(1, 1), std::make_pair(1, 0)}) { + for (auto arch : arches) { + if (arch != ARCH_NA && !GetHbdLumaTargetFn(arch, is_vert)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = + "BM_HbdDeblkLuma/" + std::string(is_vert ? "vert" : "horz") + + "/bs_" + std::to_string(bs) + "/qp_" + std::to_string(qp) + + "/fP_" + std::to_string(filter_pair.first) + "_fQ_" + + std::to_string(filter_pair.second) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + + DeblkLumaConfig cfg{is_vert, + bs, + qp, + qp, + /*beta_offset=*/0, + /*tc_offset=*/0, + filter_pair.first, + filter_pair.second, + arch, + bit_depth}; + benchmark::RegisterBenchmark( + name.c_str(), + [cfg](benchmark::State& st) { BM_HbdDeblkLuma(st, cfg); }) + ->Unit(benchmark::kNanosecond); + } + } + } + } + } + } + + // Register HBD Chroma deblocking benchmarks + for (int bit_depth : {8, 10}) { + for (bool is_vert : {true, false}) { + for (int fmt : {1, 3}) { // 1: YUV420, 3: YUV444 + for (int qp : {28, 44}) { + for (auto arch : arches) { + if (arch != ARCH_NA && !GetHbdChromaTargetFn(arch, is_vert)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = "BM_HbdDeblkChroma/" + + std::string(is_vert ? "vert" : "horz") + + "/fmt_" + (fmt == 1 ? "420" : "444") + "/qp_" + + std::to_string(qp) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + + DeblkChromaConfig cfg{is_vert, qp, qp, + /*qp_offset_u=*/0, + /*qp_offset_v=*/0, + /*tc_offset=*/0, + /*filter_p=*/1, + /*filter_q=*/1, fmt, arch, bit_depth}; + benchmark::RegisterBenchmark( + name.c_str(), + [cfg](benchmark::State& st) { BM_HbdDeblkChroma(st, cfg); }) + ->Unit(benchmark::kNanosecond); + } + } + } + } + } +} diff --git a/tests/common/ihevc_hbd_itrans_recon_benchmark.cc b/tests/common/ihevc_hbd_itrans_recon_benchmark.cc new file mode 100644 index 00000000..4f199538 --- /dev/null +++ b/tests/common/ihevc_hbd_itrans_recon_benchmark.cc @@ -0,0 +1,141 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "ihevc_defs.h" +extern "C" { +#include "ihevc_itrans_recon.h" +#include "ihevc_structs.h" +#include "iv.h" +} +// clang-format on + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_itrans_utils.h" + +namespace { + +void BM_HbdITransRecon(benchmark::State& state, ITransBenchConfig config) { + const int trans_size = config.trans_size; + const int ttype = config.ttype; + const int nz_cols = config.non_zero_cols; + const int nz_rows = config.non_zero_rows; + const IV_ARCH_T arch = config.arch; + const UWORD8 bit_depth = static_cast(config.bit_depth); + + HbdITransReconFn fn = GetHbdITransReconFn(arch, trans_size, ttype); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + // Worst-case padding and temporary buffer size for safe SIMD loads/stores + // across all platforms. + const int pad_pred = 8; + const int pad_dst = 8; + const int pad_tmp = 8; + const size_t tmp_size = 3 * trans_size * trans_size + pad_tmp; + + std::vector pi2_coeffs(trans_size * trans_size); + std::vector pi2_tmp(tmp_size); + std::vector pu2_pred_recon(trans_size * trans_size + pad_pred); + std::vector pu2_dst(trans_size * trans_size + pad_dst); + + WORD32 zero_cols = 0; + WORD32 zero_rows = 0; + GenerateITransInput(trans_size, ttype, nz_cols, nz_rows, pi2_coeffs.data(), + pu2_pred_recon.data(), &zero_cols, &zero_rows, + config.bit_depth); + + const WORD32 src_strd = trans_size; + const WORD32 pred_strd = trans_size; + const WORD32 dst_strd = trans_size; + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + HbdITransReconFn ref_fn = GetHbdITransReconFn(ARCH_NA, trans_size, ttype); + if (ref_fn) { + std::vector ref_tmp(tmp_size); + std::vector ref_dst(trans_size * trans_size); + ref_fn(pi2_coeffs.data(), ref_tmp.data(), pu2_pred_recon.data(), + ref_dst.data(), src_strd, pred_strd, dst_strd, zero_cols, + zero_rows, bit_depth); + fn(pi2_coeffs.data(), pi2_tmp.data(), pu2_pred_recon.data(), + pu2_dst.data(), src_strd, pred_strd, dst_strd, zero_cols, zero_rows, + bit_depth); + if (std::memcmp(ref_dst.data(), pu2_dst.data(), + trans_size * trans_size * sizeof(UWORD16)) != 0) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(pi2_coeffs.data(), pi2_tmp.data(), pu2_pred_recon.data(), pu2_dst.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows, bit_depth); + benchmark::DoNotOptimize(pu2_dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * trans_size * trans_size); + state.SetBytesProcessed(state.iterations() * trans_size * trans_size * + sizeof(UWORD16)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (int bit_depth : {8, 10}) { + for (const auto& tc : GetITransTestCases()) { + for (const auto& nz : tc.nz_regions) { + for (auto arch : arches) { + if (arch != ARCH_NA && !GetHbdITransReconFn(arch, tc.size, tc.ttype)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = + "BM_HbdITransRecon/" + std::to_string(tc.size) + "x" + + std::to_string(tc.size) + (tc.ttype == 1 ? "_dst" : "_dct") + + "/nz_" + std::to_string(nz.cols) + "x" + std::to_string(nz.rows) + + "/" + std::to_string(bit_depth) + "bit/" + arch_name; + + ITransBenchConfig cfg{tc.size, tc.ttype, nz.cols, + nz.rows, arch, bit_depth}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_HbdITransRecon(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_hbd_luma_inter_pred_benchmark.cc b/tests/common/ihevc_hbd_luma_inter_pred_benchmark.cc new file mode 100644 index 00000000..7fc01479 --- /dev/null +++ b/tests/common/ihevc_hbd_luma_inter_pred_benchmark.cc @@ -0,0 +1,111 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "ihevc_inter_pred_utils.h" + +namespace { + +bool HasHbdLumaInterPredFn(IV_ARCH_T arch, InterPredOp op) { + if (!IsW16Inp(op) && !IsW16Out(op)) { + return GetHbdLumaInterPredFn(arch, op) != nullptr; + } else if (!IsW16Inp(op) && IsW16Out(op)) { + return GetHbdLumaInterPredW16outFn(arch, op) != nullptr; + } else if (IsW16Inp(op) && !IsW16Out(op)) { + return GetHbdLumaInterPredW16inpFn(arch, op) != nullptr; + } else { + return GetHbdLumaInterPredW16inpW16outFn(arch, op) != nullptr; + } +} + +void BM_HbdLumaInterPred(benchmark::State& state, InterPredConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const InterPredOp op = config.op; + const IV_ARCH_T arch = config.arch; + const UWORD8 bit_depth = static_cast(config.bit_depth); + + WORD8* pi1_coeff = gai1_ihevc_luma_filter[config.coeff_idx]; + const bool verify = (arch != ARCH_NA); + + if (!IsW16Inp(op) && !IsW16Out(op)) { + RunHbdInterPredBenchmarkCase( + state, GetHbdLumaInterPredFn(arch, op), + GetHbdLumaInterPredFn(ARCH_NA, op), wd, ht, pi1_coeff, bit_depth, + verify); + } else if (!IsW16Inp(op) && IsW16Out(op)) { + RunHbdInterPredBenchmarkCase( + state, GetHbdLumaInterPredW16outFn(arch, op), + GetHbdLumaInterPredW16outFn(ARCH_NA, op), wd, ht, pi1_coeff, bit_depth, + verify); + } else if (IsW16Inp(op) && !IsW16Out(op)) { + RunHbdInterPredBenchmarkCase( + state, GetHbdLumaInterPredW16inpFn(arch, op), + GetHbdLumaInterPredW16inpFn(ARCH_NA, op), wd, ht, pi1_coeff, bit_depth, + verify); + } else { + RunHbdInterPredBenchmarkCase( + state, GetHbdLumaInterPredW16inpW16outFn(arch, op), + GetHbdLumaInterPredW16inpW16outFn(ARCH_NA, op), wd, ht, pi1_coeff, + bit_depth, verify); + } +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (int bit_depth : {8, 10}) { + // Register HBD Luma Inter Prediction benchmarks + for (const auto& op_info : GetInterPredOps()) { + for (const auto& size : GetInterPredLumaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + if (arch != ARCH_NA && !HasHbdLumaInterPredFn(arch, op_info.op)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = + "BM_HbdLumaInterPred/" + std::string(op_info.name) + "/" + + std::to_string(wd) + "x" + std::to_string(ht) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + InterPredConfig cfg{wd, + ht, + op_info.op, + arch, + bit_depth, + /*coeff_idx=*/1}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_HbdLumaInterPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_hbd_luma_intra_pred_benchmark.cc b/tests/common/ihevc_hbd_luma_intra_pred_benchmark.cc new file mode 100644 index 00000000..0bf89272 --- /dev/null +++ b/tests/common/ihevc_hbd_luma_intra_pred_benchmark.cc @@ -0,0 +1,110 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "ihevc_intra_pred_utils.h" + +namespace { + +void BM_HbdLumaIntraPred(benchmark::State& state, IntraPredConfig config) { + const int nt = config.nt; + const int mode = config.mode; + const int mode_or_flag = + (mode == 10 || mode == 26) ? config.disable_boundary_filter : mode; + const IV_ARCH_T arch = config.arch; + const UWORD8 bit_depth = static_cast(config.bit_depth); + + HbdLumaIntraPredFn fn = GetHbdLumaIntraPredFn(arch, mode); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + IntraPredLumaBuffers buf = CreateIntraPredLumaBuffers( + nt, config.dst_strd_mul, config.bit_depth); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + HbdLumaIntraPredFn ref_fn = GetHbdLumaIntraPredFn(ARCH_NA, mode); + if (ref_fn) { + IntraPredLumaBuffers ref_buf = buf; + IntraPredLumaBuffers tst_buf = buf; + ref_fn(ref_buf.ref.data(), ref_buf.src_strd, ref_buf.dst.data(), + ref_buf.dst_strd, nt, mode_or_flag, bit_depth); + fn(tst_buf.ref.data(), tst_buf.src_strd, tst_buf.dst.data(), + tst_buf.dst_strd, nt, mode_or_flag, bit_depth); + if (!VerifyOutput2D(ref_buf.dst.data(), tst_buf.dst.data(), nt, nt, + ref_buf.dst_strd, tst_buf.dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.ref.data(), buf.src_strd, buf.dst.data(), buf.dst_strd, nt, + mode_or_flag, bit_depth); + benchmark::DoNotOptimize(buf.dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * nt * nt); + state.SetBytesProcessed(state.iterations() * nt * nt * sizeof(UWORD16)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (int bit_depth : {8, 10}) { + // Register HBD Luma Intra Prediction benchmarks + for (const auto& mode_info : GetIntraPredBenchmarkModes()) { + for (int nt : GetIntraPredBenchmarkSizes()) { + for (auto arch : arches) { + if (arch != ARCH_NA && !GetHbdLumaIntraPredFn(arch, mode_info.mode)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = + "BM_HbdLumaIntraPred/" + std::string(mode_info.name) + "/" + + std::to_string(nt) + "x" + std::to_string(nt) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + IntraPredConfig cfg{nt, + mode_info.mode, + /*dst_strd_mul=*/1, + /*disable_boundary_filter=*/0, + arch, + bit_depth}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_HbdLumaIntraPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_hbd_sao_benchmark.cc b/tests/common/ihevc_hbd_sao_benchmark.cc new file mode 100644 index 00000000..e5ab413a --- /dev/null +++ b/tests/common/ihevc_hbd_sao_benchmark.cc @@ -0,0 +1,357 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "ihevc_sao_utils.h" + +namespace { + +void BM_HbdSaoBandOffsetLuma(benchmark::State& state, SaoBandConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const int sao_band_pos = config.sao_band_pos; + const IV_ARCH_T arch = config.arch; + const UWORD32 bit_depth = static_cast(config.bit_depth); + + ihevc_hbd_sao_band_offset_luma_ft* fn = GetHbdSaoBandOffsetLumaFn(arch); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + SaoLumaBuffers buf = + CreateSaoLumaBuffers(wd, ht, config.bit_depth); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + ihevc_hbd_sao_band_offset_luma_ft* ref_fn = + GetHbdSaoBandOffsetLumaFn(ARCH_NA); + if (ref_fn) { + SaoLumaBuffers ref_buf = buf; + SaoLumaBuffers tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), sao_band_pos, + ref_buf.sao_offset.data(), wd, ht, bit_depth); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), sao_band_pos, tst_buf.sao_offset.data(), + wd, ht, bit_depth); + if (std::memcmp(ref_buf.src.data(), tst_buf.src.data(), + ref_buf.src.size() * sizeof(UWORD16)) != 0 || + ref_buf.src_left != tst_buf.src_left || + ref_buf.src_top != tst_buf.src_top || + ref_buf.src_top_left != tst_buf.src_top_left) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.stride, buf.src_left.data(), + buf.src_top.data(), buf.src_top_left.data(), sao_band_pos, + buf.sao_offset.data(), wd, ht, bit_depth); + benchmark::DoNotOptimize(buf.src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * wd * ht); + state.SetBytesProcessed(state.iterations() * wd * ht * sizeof(UWORD16)); +} + +void BM_HbdSaoBandOffsetChroma(benchmark::State& state, SaoBandConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const int interleaved_wd = 2 * wd; + const int sao_band_pos_u = config.sao_band_pos; + const int sao_band_pos_v = (config.sao_band_pos + 4) % 32; + const IV_ARCH_T arch = config.arch; + const UWORD32 bit_depth = static_cast(config.bit_depth); + + ihevc_hbd_sao_band_offset_chroma_ft* fn = GetHbdSaoBandOffsetChromaFn(arch); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + SaoChromaBuffers buf = + CreateSaoChromaBuffers(wd, ht, config.bit_depth); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + ihevc_hbd_sao_band_offset_chroma_ft* ref_fn = + GetHbdSaoBandOffsetChromaFn(ARCH_NA); + if (ref_fn) { + SaoChromaBuffers ref_buf = buf; + SaoChromaBuffers tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), sao_band_pos_u, sao_band_pos_v, + ref_buf.sao_offset_u.data(), ref_buf.sao_offset_v.data(), + interleaved_wd, ht, bit_depth); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), sao_band_pos_u, sao_band_pos_v, + tst_buf.sao_offset_u.data(), tst_buf.sao_offset_v.data(), + interleaved_wd, ht, bit_depth); + if (std::memcmp(ref_buf.src.data(), tst_buf.src.data(), + ref_buf.src.size() * sizeof(UWORD16)) != 0 || + ref_buf.src_left != tst_buf.src_left || + ref_buf.src_top != tst_buf.src_top || + ref_buf.src_top_left != tst_buf.src_top_left) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.stride, buf.src_left.data(), + buf.src_top.data(), buf.src_top_left.data(), sao_band_pos_u, + sao_band_pos_v, buf.sao_offset_u.data(), buf.sao_offset_v.data(), + interleaved_wd, ht, bit_depth); + benchmark::DoNotOptimize(buf.src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * interleaved_wd * ht); + state.SetBytesProcessed(state.iterations() * interleaved_wd * ht * + sizeof(UWORD16)); +} + +void BM_HbdSaoEdgeOffsetLuma(benchmark::State& state, SaoEdgeConfig config) { + const int edge_class = config.edge_class; + const int wd = config.wd; + const int ht = config.ht; + const IV_ARCH_T arch = config.arch; + const UWORD32 bit_depth = static_cast(config.bit_depth); + + ihevc_hbd_sao_edge_offset_class0_ft* fn = + GetHbdSaoEdgeOffsetLumaFn(arch, edge_class); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + SaoLumaBuffers buf = + CreateSaoLumaBuffers(wd, ht, config.bit_depth); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + ihevc_hbd_sao_edge_offset_class0_ft* ref_fn = + GetHbdSaoEdgeOffsetLumaFn(ARCH_NA, edge_class); + if (ref_fn) { + SaoLumaBuffers ref_buf = buf; + SaoLumaBuffers tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), ref_buf.src_top_right.data(), + ref_buf.src_bot_left.data(), ref_buf.avail.data(), + ref_buf.sao_offset.data(), wd, ht, bit_depth); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), tst_buf.src_top_right.data(), + tst_buf.src_bot_left.data(), tst_buf.avail.data(), + tst_buf.sao_offset.data(), wd, ht, bit_depth); + if (std::memcmp(ref_buf.src.data(), tst_buf.src.data(), + ref_buf.src.size() * sizeof(UWORD16)) != 0 || + ref_buf.src_left != tst_buf.src_left || + ref_buf.src_top != tst_buf.src_top || + ref_buf.src_top_left != tst_buf.src_top_left) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.stride, buf.src_left.data(), + buf.src_top.data(), buf.src_top_left.data(), buf.src_top_right.data(), + buf.src_bot_left.data(), buf.avail.data(), buf.sao_offset.data(), wd, ht, + bit_depth); + benchmark::DoNotOptimize(buf.src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * wd * ht); + state.SetBytesProcessed(state.iterations() * wd * ht * sizeof(UWORD16)); +} + +void BM_HbdSaoEdgeOffsetChroma(benchmark::State& state, SaoEdgeConfig config) { + const int edge_class = config.edge_class; + const int wd = config.wd; + const int ht = config.ht; + const int interleaved_wd = 2 * wd; + const IV_ARCH_T arch = config.arch; + const UWORD32 bit_depth = static_cast(config.bit_depth); + + ihevc_hbd_sao_edge_offset_class0_chroma_ft* fn = + GetHbdSaoEdgeOffsetChromaFn(arch, edge_class); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + SaoChromaBuffers buf = + CreateSaoChromaBuffers(wd, ht, config.bit_depth); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + ihevc_hbd_sao_edge_offset_class0_chroma_ft* ref_fn = + GetHbdSaoEdgeOffsetChromaFn(ARCH_NA, edge_class); + if (ref_fn) { + SaoChromaBuffers ref_buf = buf; + SaoChromaBuffers tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), ref_buf.src_top_right.data(), + ref_buf.src_bot_left.data(), ref_buf.avail.data(), + ref_buf.sao_offset_u.data(), ref_buf.sao_offset_v.data(), + interleaved_wd, ht, bit_depth); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), tst_buf.src_top_right.data(), + tst_buf.src_bot_left.data(), tst_buf.avail.data(), + tst_buf.sao_offset_u.data(), tst_buf.sao_offset_v.data(), + interleaved_wd, ht, bit_depth); + if (std::memcmp(ref_buf.src.data(), tst_buf.src.data(), + ref_buf.src.size() * sizeof(UWORD16)) != 0 || + ref_buf.src_left != tst_buf.src_left || + ref_buf.src_top != tst_buf.src_top || + ref_buf.src_top_left != tst_buf.src_top_left) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.stride, buf.src_left.data(), + buf.src_top.data(), buf.src_top_left.data(), buf.src_top_right.data(), + buf.src_bot_left.data(), buf.avail.data(), buf.sao_offset_u.data(), + buf.sao_offset_v.data(), interleaved_wd, ht, bit_depth); + benchmark::DoNotOptimize(buf.src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * interleaved_wd * ht); + state.SetBytesProcessed(state.iterations() * interleaved_wd * ht * + sizeof(UWORD16)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (int bit_depth : {8, 10}) { + // Register HBD SAO Band Offset Luma benchmarks + for (const auto& size : GetSaoLumaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + if (arch != ARCH_NA && !GetHbdSaoBandOffsetLumaFn(arch)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = "BM_HbdSaoBandOffsetLuma/" + std::to_string(wd) + + "x" + std::to_string(ht) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + SaoBandConfig cfg{wd, ht, /*sao_band_pos=*/0, arch, bit_depth}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_HbdSaoBandOffsetLuma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + + // Register HBD SAO Band Offset Chroma benchmarks + for (const auto& size : GetSaoChromaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + if (arch != ARCH_NA && !GetHbdSaoBandOffsetChromaFn(arch)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = "BM_HbdSaoBandOffsetChroma/" + std::to_string(wd) + + "x" + std::to_string(ht) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + SaoBandConfig cfg{wd, ht, /*sao_band_pos=*/0, arch, bit_depth}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_HbdSaoBandOffsetChroma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + + // Register HBD SAO Edge Offset Luma benchmarks + for (int edge_class = 0; edge_class < 4; ++edge_class) { + for (const auto& size : GetSaoLumaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + if (arch != ARCH_NA && !GetHbdSaoEdgeOffsetLumaFn(arch, edge_class)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = + "BM_HbdSaoEdgeOffsetLuma/class" + std::to_string(edge_class) + + "/" + std::to_string(wd) + "x" + std::to_string(ht) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + SaoEdgeConfig cfg{edge_class, wd, ht, arch, bit_depth}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_HbdSaoEdgeOffsetLuma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + + // Register HBD SAO Edge Offset Chroma benchmarks + for (int edge_class = 0; edge_class < 4; ++edge_class) { + for (const auto& size : GetSaoChromaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + if (arch != ARCH_NA && + !GetHbdSaoEdgeOffsetChromaFn(arch, edge_class)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = + "BM_HbdSaoEdgeOffsetChroma/class" + std::to_string(edge_class) + + "/" + std::to_string(wd) + "x" + std::to_string(ht) + "/" + + std::to_string(bit_depth) + "bit/" + arch_name; + SaoEdgeConfig cfg{edge_class, wd, ht, arch, bit_depth}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_HbdSaoEdgeOffsetChroma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_inter_pred_utils.cc b/tests/common/ihevc_inter_pred_utils.cc new file mode 100644 index 00000000..5d4862b0 --- /dev/null +++ b/tests/common/ihevc_inter_pred_utils.cc @@ -0,0 +1,250 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "ihevc_inter_pred_utils.h" + +const std::vector& GetInterPredOps() { + static const std::vector kOps = { + {InterPredOp::kCopy, "copy", false, false}, + {InterPredOp::kHorz, "horz", false, false}, + {InterPredOp::kVert, "vert", false, false}, + {InterPredOp::kCopyW16out, "copy_w16out", false, true}, + {InterPredOp::kHorzW16out, "horz_w16out", false, true}, + {InterPredOp::kVertW16out, "vert_w16out", false, true}, + {InterPredOp::kVertW16inp, "vert_w16inp", true, false}, + {InterPredOp::kVertW16inpW16out, "vert_w16inp_w16out", true, true}, + }; + return kOps; +} + +ihevc_inter_pred_ft* GetLumaInterPredFn(const ihevc_func_selector_t* selector, + InterPredOp op) { + if (!selector) return nullptr; + switch (op) { + case InterPredOp::kCopy: + return selector->ihevc_inter_pred_luma_copy_fptr; + case InterPredOp::kHorz: + return selector->ihevc_inter_pred_luma_horz_fptr; + case InterPredOp::kVert: + return selector->ihevc_inter_pred_luma_vert_fptr; + default: + return nullptr; + } +} + +ihevc_inter_pred_w16out_ft* GetLumaInterPredW16outFn( + const ihevc_func_selector_t* selector, InterPredOp op) { + if (!selector) return nullptr; + switch (op) { + case InterPredOp::kCopyW16out: + return selector->ihevc_inter_pred_luma_copy_w16out_fptr; + case InterPredOp::kHorzW16out: + return selector->ihevc_inter_pred_luma_horz_w16out_fptr; + case InterPredOp::kVertW16out: + return selector->ihevc_inter_pred_luma_vert_w16out_fptr; + default: + return nullptr; + } +} + +ihevc_inter_pred_w16inp_ft* GetLumaInterPredW16inpFn( + const ihevc_func_selector_t* selector, InterPredOp op) { + if (!selector) return nullptr; + if (op == InterPredOp::kVertW16inp) { + return selector->ihevc_inter_pred_luma_vert_w16inp_fptr; + } + return nullptr; +} + +ihevc_inter_pred_w16inp_w16out_ft* GetLumaInterPredW16inpW16outFn( + const ihevc_func_selector_t* selector, InterPredOp op) { + if (!selector) return nullptr; + if (op == InterPredOp::kVertW16inpW16out) { + return selector->ihevc_inter_pred_luma_vert_w16inp_w16out_fptr; + } + return nullptr; +} + +ihevc_inter_pred_ft* GetChromaInterPredFn(const ihevc_func_selector_t* selector, + InterPredOp op) { + if (!selector) return nullptr; + switch (op) { + case InterPredOp::kCopy: + return selector->ihevc_inter_pred_chroma_copy_fptr; + case InterPredOp::kHorz: + return selector->ihevc_inter_pred_chroma_horz_fptr; + case InterPredOp::kVert: + return selector->ihevc_inter_pred_chroma_vert_fptr; + default: + return nullptr; + } +} + +ihevc_inter_pred_w16out_ft* GetChromaInterPredW16outFn( + const ihevc_func_selector_t* selector, InterPredOp op) { + if (!selector) return nullptr; + switch (op) { + case InterPredOp::kCopyW16out: + return selector->ihevc_inter_pred_chroma_copy_w16out_fptr; + case InterPredOp::kHorzW16out: + return selector->ihevc_inter_pred_chroma_horz_w16out_fptr; + case InterPredOp::kVertW16out: + return selector->ihevc_inter_pred_chroma_vert_w16out_fptr; + default: + return nullptr; + } +} + +ihevc_inter_pred_w16inp_ft* GetChromaInterPredW16inpFn( + const ihevc_func_selector_t* selector, InterPredOp op) { + if (!selector) return nullptr; + if (op == InterPredOp::kVertW16inp) { + return selector->ihevc_inter_pred_chroma_vert_w16inp_fptr; + } + return nullptr; +} + +ihevc_inter_pred_w16inp_w16out_ft* GetChromaInterPredW16inpW16outFn( + const ihevc_func_selector_t* selector, InterPredOp op) { + if (!selector) return nullptr; + if (op == InterPredOp::kVertW16inpW16out) { + return selector->ihevc_inter_pred_chroma_vert_w16inp_w16out_fptr; + } + return nullptr; +} + +ihevc_hbd_inter_pred_ft* GetHbdLumaInterPredFn(IV_ARCH_T arch, InterPredOp op) { + if (arch == ARCH_NA) { + switch (op) { + case InterPredOp::kCopy: + return ihevc_hbd_inter_pred_luma_copy; + case InterPredOp::kHorz: + return ihevc_hbd_inter_pred_luma_horz; + case InterPredOp::kVert: + return ihevc_hbd_inter_pred_luma_vert; + default: + return nullptr; + } + } + return nullptr; +} + +ihevc_hbd_inter_pred_w16out_ft* GetHbdLumaInterPredW16outFn(IV_ARCH_T arch, + InterPredOp op) { + if (arch == ARCH_NA) { + switch (op) { + case InterPredOp::kCopyW16out: + return ihevc_hbd_inter_pred_luma_copy_w16out; + case InterPredOp::kHorzW16out: + return ihevc_hbd_inter_pred_luma_horz_w16out; + case InterPredOp::kVertW16out: + return ihevc_hbd_inter_pred_luma_vert_w16out; + default: + return nullptr; + } + } + return nullptr; +} + +ihevc_hbd_inter_pred_w16inp_ft* GetHbdLumaInterPredW16inpFn(IV_ARCH_T arch, + InterPredOp op) { + if (arch == ARCH_NA) { + if (op == InterPredOp::kVertW16inp) { + return ihevc_hbd_inter_pred_luma_vert_w16inp; + } + } + return nullptr; +} + +ihevc_hbd_inter_pred_w16inp_w16out_ft* GetHbdLumaInterPredW16inpW16outFn( + IV_ARCH_T arch, InterPredOp op) { + if (arch == ARCH_NA) { + if (op == InterPredOp::kVertW16inpW16out) { + return ihevc_hbd_inter_pred_luma_vert_w16inp_w16out; + } + } + return nullptr; +} + +ihevc_hbd_inter_pred_ft* GetHbdChromaInterPredFn(IV_ARCH_T arch, + InterPredOp op) { + if (arch == ARCH_NA) { + switch (op) { + case InterPredOp::kCopy: + return ihevc_hbd_inter_pred_chroma_copy; + case InterPredOp::kHorz: + return ihevc_hbd_inter_pred_chroma_horz; + case InterPredOp::kVert: + return ihevc_hbd_inter_pred_chroma_vert; + default: + return nullptr; + } + } + return nullptr; +} + +ihevc_hbd_inter_pred_w16out_ft* GetHbdChromaInterPredW16outFn(IV_ARCH_T arch, + InterPredOp op) { + if (arch == ARCH_NA) { + switch (op) { + case InterPredOp::kCopyW16out: + return ihevc_hbd_inter_pred_chroma_copy_w16out; + case InterPredOp::kHorzW16out: + return ihevc_hbd_inter_pred_chroma_horz_w16out; + case InterPredOp::kVertW16out: + return ihevc_hbd_inter_pred_chroma_vert_w16out; + default: + return nullptr; + } + } + return nullptr; +} + +ihevc_hbd_inter_pred_w16inp_ft* GetHbdChromaInterPredW16inpFn(IV_ARCH_T arch, + InterPredOp op) { + if (arch == ARCH_NA) { + if (op == InterPredOp::kVertW16inp) { + return ihevc_hbd_inter_pred_chroma_vert_w16inp; + } + } + return nullptr; +} + +ihevc_hbd_inter_pred_w16inp_w16out_ft* GetHbdChromaInterPredW16inpW16outFn( + IV_ARCH_T arch, InterPredOp op) { + if (arch == ARCH_NA) { + if (op == InterPredOp::kVertW16inpW16out) { + return ihevc_hbd_inter_pred_chroma_vert_w16inp_w16out; + } + } + return nullptr; +} + +const std::vector>& GetInterPredLumaBenchmarkSizes() { + static const std::vector> kSizes = { + {8, 8}, {16, 8}, {16, 16}, {32, 16}, {32, 32}, {64, 64}, + }; + return kSizes; +} + +const std::vector>& GetInterPredChromaBenchmarkSizes() { + static const std::vector> kSizes = { + {4, 4}, {8, 4}, {8, 8}, {16, 8}, {16, 16}, {32, 32}, + }; + return kSizes; +} diff --git a/tests/common/ihevc_inter_pred_utils.h b/tests/common/ihevc_inter_pred_utils.h new file mode 100644 index 00000000..93fe9ab4 --- /dev/null +++ b/tests/common/ihevc_inter_pred_utils.h @@ -0,0 +1,316 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "ihevc_defs.h" +extern "C" { +#include "ihevc_inter_pred.h" +#include "ihevc_function_selector.h" +#include "iv.h" +} +// clang-format on + +#include "BenchmarkCommon.h" +#include "TestCommon.h" + +enum class InterPredOp { + kCopy, + kHorz, + kVert, + kCopyW16out, + kHorzW16out, + kVertW16out, + kVertW16inp, + kVertW16inpW16out, +}; + +struct InterPredOpInfo { + InterPredOp op; + const char* name; + bool is_w16inp; + bool is_w16out; +}; + +const std::vector& GetInterPredOps(); + +inline bool IsW16Inp(InterPredOp op) { + return op == InterPredOp::kVertW16inp || op == InterPredOp::kVertW16inpW16out; +} + +inline bool IsW16Out(InterPredOp op) { + return op == InterPredOp::kCopyW16out || op == InterPredOp::kHorzW16out || + op == InterPredOp::kVertW16out || op == InterPredOp::kVertW16inpW16out; +} + +// Configuration for inter prediction benchmarks +struct InterPredConfig { + int wd; + int ht; + InterPredOp op; + IV_ARCH_T arch = ARCH_NA; + int bit_depth = 8; + int coeff_idx = 1; +}; + +// Buffers structure for inter prediction +template +struct InterPredBuffers { + int src_strd; + int dst_strd; + int src_offset; + std::vector src; + std::vector dst; +}; + +// Creates and initializes inter prediction buffers for Luma (components=1) or +// Chroma (components=2) +template +InterPredBuffers CreateInterPredBuffers(int wd, int ht, + int components, + int bit_depth = 8) { + InterPredBuffers b; + const int margin = 16; + b.src_strd = components * wd + 2 * margin; + const int total_ht = ht + 2 * margin; + b.src_offset = margin * b.src_strd + margin; + b.src.resize(b.src_strd * total_ht); + + b.dst_strd = components * wd + 32; + b.dst.resize(b.dst_strd * (ht + 16)); + + const SrcT max_val = static_cast((1 << bit_depth) - 1); + FillRandom(b.src, static_cast(0), max_val, 42); + std::fill(b.dst.begin(), b.dst.end(), static_cast(0xCD)); + + return b; +} + +// Creates and initializes Luma inter prediction buffers +template +InterPredBuffers CreateInterPredLumaBuffers(int wd, int ht, + int bit_depth = 8) { + return CreateInterPredBuffers(wd, ht, 1, bit_depth); +} + +// Creates and initializes Chroma inter prediction buffers +template +InterPredBuffers CreateInterPredChromaBuffers(int wd, int ht, + int bit_depth = 8) { + return CreateInterPredBuffers(wd, ht, 2, bit_depth); +} + +// Function getters for 8-bit Luma and Chroma +ihevc_inter_pred_ft* GetLumaInterPredFn(const ihevc_func_selector_t* selector, + InterPredOp op); +ihevc_inter_pred_w16out_ft* GetLumaInterPredW16outFn( + const ihevc_func_selector_t* selector, InterPredOp op); +ihevc_inter_pred_w16inp_ft* GetLumaInterPredW16inpFn( + const ihevc_func_selector_t* selector, InterPredOp op); +ihevc_inter_pred_w16inp_w16out_ft* GetLumaInterPredW16inpW16outFn( + const ihevc_func_selector_t* selector, InterPredOp op); + +ihevc_inter_pred_ft* GetChromaInterPredFn(const ihevc_func_selector_t* selector, + InterPredOp op); +ihevc_inter_pred_w16out_ft* GetChromaInterPredW16outFn( + const ihevc_func_selector_t* selector, InterPredOp op); +ihevc_inter_pred_w16inp_ft* GetChromaInterPredW16inpFn( + const ihevc_func_selector_t* selector, InterPredOp op); +ihevc_inter_pred_w16inp_w16out_ft* GetChromaInterPredW16inpW16outFn( + const ihevc_func_selector_t* selector, InterPredOp op); + +// Function getters for HBD Luma and Chroma +ihevc_hbd_inter_pred_ft* GetHbdLumaInterPredFn(IV_ARCH_T arch, InterPredOp op); +ihevc_hbd_inter_pred_w16out_ft* GetHbdLumaInterPredW16outFn(IV_ARCH_T arch, + InterPredOp op); +ihevc_hbd_inter_pred_w16inp_ft* GetHbdLumaInterPredW16inpFn(IV_ARCH_T arch, + InterPredOp op); +ihevc_hbd_inter_pred_w16inp_w16out_ft* GetHbdLumaInterPredW16inpW16outFn( + IV_ARCH_T arch, InterPredOp op); + +ihevc_hbd_inter_pred_ft* GetHbdChromaInterPredFn(IV_ARCH_T arch, + InterPredOp op); +ihevc_hbd_inter_pred_w16out_ft* GetHbdChromaInterPredW16outFn(IV_ARCH_T arch, + InterPredOp op); +ihevc_hbd_inter_pred_w16inp_ft* GetHbdChromaInterPredW16inpFn(IV_ARCH_T arch, + InterPredOp op); +ihevc_hbd_inter_pred_w16inp_w16out_ft* GetHbdChromaInterPredW16inpW16outFn( + IV_ARCH_T arch, InterPredOp op); + +// Benchmark sizes +const std::vector>& GetInterPredLumaBenchmarkSizes(); +const std::vector>& GetInterPredChromaBenchmarkSizes(); + +#if defined(GTEST_TEST) || defined(GOOGLETEST_INCLUDE_GTEST_GTEST_H_) || \ + defined(GTEST_INCLUDE_GTEST_GTEST_H_) +// Test parameters: width, height, src_stride_mul, dst_stride_mul, coeff_idx, +// arch +using InterPredTestParam = + std::tuple, int, int, int, IV_ARCH_T>; + +template +std::string PrintInterPredTestParam( + const testing::TestParamInfo& info) { + int wd, ht, src_strd_mul, dst_strd_mul, coeff_idx; + IV_ARCH_T arch; + std::pair block_size; + std::tie(block_size, src_strd_mul, dst_strd_mul, coeff_idx, arch) = + info.param; + std::tie(wd, ht) = block_size; + return std::to_string(wd) + "x" + std::to_string(ht) + "_src_stride_" + + std::to_string(src_strd_mul * kComponents * wd) + "_dst_stride_" + + std::to_string(dst_strd_mul * kComponents * wd) + "_coeff_" + + std::to_string(coeff_idx) + "_" + get_arch_str(arch); +} + +template +class InterPredTestBase : public ::testing::TestWithParam { + protected: + void SetUp() override { + std::pair block_size; + std::tie(block_size, src_strd_mul, dst_strd_mul, coeff_idx, arch) = + GetParam(); + std::tie(wd, ht) = block_size; + src_strd = kComponents * wd * src_strd_mul; + dst_strd = kComponents * wd * dst_strd_mul; + + // Increase allocations for x86/x86_64 to avoid out-of-bounds reads/writes + // in SIMD implementations. +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ + defined(_M_IX86) + int pad_dst = 16; +#else + int pad_dst = 0; +#endif + + dst_buf_ref.resize(dst_strd * ht + pad_dst); + dst_buf_tst.resize(dst_strd * ht + pad_dst); + + // Set pv_src to a valid position within src_buf to allow negative indexing + pv_src = reinterpret_cast(const_cast(getSrc8Buf().data())) + + (kTapSize / 2) * src_strd; + pv_dst_ref = dst_buf_ref.data(); + pv_dst_tst = dst_buf_tst.data(); + + pi1_coeffs = (kComponents == 1) ? gai1_ihevc_luma_filter[coeff_idx] + : gai1_ihevc_chroma_filter[coeff_idx]; + tst = get_tst_func_ptr(arch); + ref = get_ref_func_ptr(); + } + + template + void RunTest(FuncPtr func_ptr) { + (ref->*func_ptr)(pv_src, pv_dst_ref, src_strd, dst_strd, pi1_coeffs, ht, + wd); + (tst->*func_ptr)(pv_src, pv_dst_tst, src_strd, dst_strd, pi1_coeffs, ht, + wd); + ASSERT_NO_FATAL_FAILURE(compare_output(dst_buf_ref, dst_buf_tst, + kComponents * wd, ht, + dst_strd)); + } + + int wd, ht, src_strd_mul, dst_strd_mul, coeff_idx; + int src_strd, dst_strd; + std::vector dst_buf_ref; + std::vector dst_buf_tst; + SrcT* pv_src; + DstT* pv_dst_ref; + DstT* pv_dst_tst; + WORD8* pi1_coeffs; + IV_ARCH_T arch; + const ihevc_func_selector_t* tst; + const ihevc_func_selector_t* ref; +}; +#endif + +#ifdef BENCHMARK_BENCHMARK_H_ +template +void RunInterPredBenchmarkCase(benchmark::State& state, FnT fn, FnT ref_fn, + int wd, int ht, WORD8* pi1_coeff, + bool verify_output) { + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + auto buf = CreateInterPredBuffers(wd, ht, kComponents); + if (verify_output && ref_fn) { + auto ref_buf = buf; + auto tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.dst.data(), + ref_buf.src_strd, ref_buf.dst_strd, pi1_coeff, ht, wd); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.dst.data(), + tst_buf.src_strd, tst_buf.dst_strd, pi1_coeff, ht, wd); + if (!VerifyOutput2D(ref_buf.dst.data(), tst_buf.dst.data(), + kComponents * wd, ht, ref_buf.dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.dst.data(), buf.src_strd, + buf.dst_strd, pi1_coeff, ht, wd); + benchmark::DoNotOptimize(buf.dst.data()); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * kComponents * wd * ht); + state.SetBytesProcessed(state.iterations() * kComponents * wd * ht * + sizeof(DstT)); +} + +template +void RunHbdInterPredBenchmarkCase(benchmark::State& state, FnT fn, FnT ref_fn, + int wd, int ht, WORD8* pi1_coeff, + UWORD8 bit_depth, bool verify_output) { + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + auto buf = CreateInterPredBuffers(wd, ht, kComponents, bit_depth); + if (verify_output && ref_fn) { + auto ref_buf = buf; + auto tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.dst.data(), + ref_buf.src_strd, ref_buf.dst_strd, pi1_coeff, ht, wd, bit_depth); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.dst.data(), + tst_buf.src_strd, tst_buf.dst_strd, pi1_coeff, ht, wd, bit_depth); + if (!VerifyOutput2D(ref_buf.dst.data(), tst_buf.dst.data(), + kComponents * wd, ht, ref_buf.dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.dst.data(), buf.src_strd, + buf.dst_strd, pi1_coeff, ht, wd, bit_depth); + benchmark::DoNotOptimize(buf.dst.data()); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * kComponents * wd * ht); + state.SetBytesProcessed(state.iterations() * kComponents * wd * ht * + sizeof(DstT)); +} +#endif diff --git a/tests/common/ihevc_intra_pred_utils.cc b/tests/common/ihevc_intra_pred_utils.cc new file mode 100644 index 00000000..07616c45 --- /dev/null +++ b/tests/common/ihevc_intra_pred_utils.cc @@ -0,0 +1,119 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "ihevc_intra_pred_utils.h" + +LumaIntraPredFn GetLumaIntraPredFn(const ihevc_func_selector_t* selector, + int mode) { + if (!selector) return nullptr; + if (mode == 0) return selector->ihevc_intra_pred_luma_planar_fptr; + if (mode == 1) return selector->ihevc_intra_pred_luma_dc_fptr; + if (mode == 2) return selector->ihevc_intra_pred_luma_mode2_fptr; + if (mode >= 3 && mode <= 9) + return selector->ihevc_intra_pred_luma_mode_3_to_9_fptr; + if (mode == 10) + return (LumaIntraPredFn)selector->ihevc_intra_pred_luma_horz_fptr; + if (mode >= 11 && mode <= 17) + return selector->ihevc_intra_pred_luma_mode_11_to_17_fptr; + if (mode == 18 || mode == 34) + return selector->ihevc_intra_pred_luma_mode_18_34_fptr; + if (mode >= 19 && mode <= 25) + return selector->ihevc_intra_pred_luma_mode_19_to_25_fptr; + if (mode == 26) + return (LumaIntraPredFn)selector->ihevc_intra_pred_luma_ver_fptr; + if (mode >= 27 && mode <= 33) + return selector->ihevc_intra_pred_luma_mode_27_to_33_fptr; + return nullptr; +} + +ChromaIntraPredFn GetChromaIntraPredFn(const ihevc_func_selector_t* selector, + int mode) { + if (!selector) return nullptr; + if (mode == 0) return selector->ihevc_intra_pred_chroma_planar_fptr; + if (mode == 1) return selector->ihevc_intra_pred_chroma_dc_fptr; + if (mode == 2) return selector->ihevc_intra_pred_chroma_mode2_fptr; + if (mode >= 3 && mode <= 9) + return selector->ihevc_intra_pred_chroma_mode_3_to_9_fptr; + if (mode == 10) return selector->ihevc_intra_pred_chroma_horz_fptr; + if (mode >= 11 && mode <= 17) + return selector->ihevc_intra_pred_chroma_mode_11_to_17_fptr; + if (mode == 18 || mode == 34) + return selector->ihevc_intra_pred_chroma_mode_18_34_fptr; + if (mode >= 19 && mode <= 25) + return selector->ihevc_intra_pred_chroma_mode_19_to_25_fptr; + if (mode == 26) return selector->ihevc_intra_pred_chroma_ver_fptr; + if (mode >= 27 && mode <= 33) + return selector->ihevc_intra_pred_chroma_mode_27_to_33_fptr; + return nullptr; +} + +HbdLumaIntraPredFn GetHbdLumaIntraPredFn(IV_ARCH_T arch, int mode) { + if (arch == ARCH_NA) { + if (mode == 0) return ihevc_hbd_intra_pred_luma_planar; + if (mode == 1) return ihevc_hbd_intra_pred_luma_dc; + if (mode == 2) return ihevc_hbd_intra_pred_luma_mode2; + if (mode >= 3 && mode <= 9) return ihevc_hbd_intra_pred_luma_mode_3_to_9; + if (mode == 10) return (HbdLumaIntraPredFn)ihevc_hbd_intra_pred_luma_horz; + if (mode >= 11 && mode <= 17) + return ihevc_hbd_intra_pred_luma_mode_11_to_17; + if (mode == 18 || mode == 34) return ihevc_hbd_intra_pred_luma_mode_18_34; + if (mode >= 19 && mode <= 25) + return ihevc_hbd_intra_pred_luma_mode_19_to_25; + if (mode == 26) return (HbdLumaIntraPredFn)ihevc_hbd_intra_pred_luma_ver; + if (mode >= 27 && mode <= 33) + return ihevc_hbd_intra_pred_luma_mode_27_to_33; + } + // Future SIMD implementations can be added here + return nullptr; +} + +HbdChromaIntraPredFn GetHbdChromaIntraPredFn(IV_ARCH_T arch, int mode) { + if (arch == ARCH_NA) { + if (mode == 0) return ihevc_hbd_intra_pred_chroma_planar; + if (mode == 1) return ihevc_hbd_intra_pred_chroma_dc; + if (mode == 2) return ihevc_hbd_intra_pred_chroma_mode2; + if (mode >= 3 && mode <= 9) return ihevc_hbd_intra_pred_chroma_mode_3_to_9; + if (mode == 10) return ihevc_hbd_intra_pred_chroma_horz; + if (mode >= 11 && mode <= 17) + return ihevc_hbd_intra_pred_chroma_mode_11_to_17; + if (mode == 18 || mode == 34) return ihevc_hbd_intra_pred_chroma_mode_18_34; + if (mode >= 19 && mode <= 25) + return ihevc_hbd_intra_pred_chroma_mode_19_to_25; + if (mode == 26) return ihevc_hbd_intra_pred_chroma_ver; + if (mode >= 27 && mode <= 33) + return ihevc_hbd_intra_pred_chroma_mode_27_to_33; + } + // Future SIMD implementations can be added here + return nullptr; +} + +const std::vector& GetIntraPredBenchmarkModes() { + static const std::vector kModes = { + {0, "planar"}, {1, "dc"}, + {2, "mode_2"}, {4, "mode_3_to_9"}, + {10, "horz"}, {14, "mode_11_to_17"}, + {18, "mode_18_34"}, {22, "mode_19_to_25"}, + {26, "ver"}, {30, "mode_27_to_33"}, + }; + return kModes; +} + +const std::vector& GetIntraPredBenchmarkSizes() { + static const std::vector kSizes = {4, 8, 16, 32}; + return kSizes; +} diff --git a/tests/common/ihevc_intra_pred_utils.h b/tests/common/ihevc_intra_pred_utils.h new file mode 100644 index 00000000..900bfc4b --- /dev/null +++ b/tests/common/ihevc_intra_pred_utils.h @@ -0,0 +1,146 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "ihevc_defs.h" +extern "C" { +#include "ihevc_intra_pred.h" +#include "ihevc_chroma_intra_pred.h" +#include "ihevc_function_selector.h" +#include "iv.h" +} +// clang-format on + +#include "BenchmarkCommon.h" +#include "TestCommon.h" + +// Configuration for intra prediction benchmarks +struct IntraPredConfig { + int nt; // 4, 8, 16, 32 + int mode; // 0..34 + int dst_strd_mul = 1; // Multiplier for dst stride + int disable_boundary_filter = 0; // For luma mode 10 and 26 + IV_ARCH_T arch = ARCH_NA; + int bit_depth = 8; +}; + +// Buffers structure for Luma and Chroma intra prediction +template +struct IntraPredBuffers { + int nt; + int src_strd; + int dst_strd; + std::vector ref; + std::vector dst; +}; + +template +using IntraPredLumaBuffers = IntraPredBuffers; + +template +using IntraPredChromaBuffers = IntraPredBuffers; + +// Creates and initializes intra prediction buffers (components = 1 for Luma, 2 for Chroma) +template +IntraPredBuffers CreateIntraPredBuffers(int nt, int components, + int dst_strd_mul = 1, + int bit_depth = 8, + uint32_t seed = 42) { + IntraPredBuffers b; + b.nt = nt; + b.src_strd = 1; + b.dst_strd = components * nt * dst_strd_mul; + + // Reference buffer: components * (4 * nt + 1) plus padding to protect SIMD over-reads + const int ref_size = components * (4 * nt + 1) + 64; + const int dst_size = b.dst_strd * nt + 64; + + b.ref.resize(ref_size); + b.dst.resize(dst_size); + + const T max_val = static_cast((1 << bit_depth) - 1); + FillRandom(b.ref, static_cast(0), max_val, seed); + std::fill(b.dst.begin(), b.dst.end(), static_cast(0xCD)); + + return b; +} + +// Creates and initializes Luma intra prediction buffers +template +IntraPredLumaBuffers CreateIntraPredLumaBuffers(int nt, int dst_strd_mul = 1, + int bit_depth = 8, + uint32_t seed = 42) { + return CreateIntraPredBuffers(nt, /*components=*/1, dst_strd_mul, + bit_depth, seed); +} + +// Creates and initializes Chroma intra prediction buffers +template +IntraPredChromaBuffers CreateIntraPredChromaBuffers(int nt, + int dst_strd_mul = 1, + int bit_depth = 8, + uint32_t seed = 42) { + return CreateIntraPredBuffers(nt, /*components=*/2, dst_strd_mul, + bit_depth, seed); +} + +// Function pointer signatures +typedef void (*LumaIntraPredFn)(UWORD8* pu1_ref, WORD32 src_strd, + UWORD8* pu1_dst, WORD32 dst_strd, WORD32 nt, + WORD32 mode_or_flag); + +typedef void (*ChromaIntraPredFn)(UWORD8* pu1_ref, WORD32 src_strd, + UWORD8* pu1_dst, WORD32 dst_strd, WORD32 nt, + WORD32 mode); + +typedef void (*HbdLumaIntraPredFn)(UWORD16* pu2_ref, WORD32 src_strd, + UWORD16* pu2_dst, WORD32 dst_strd, WORD32 nt, + WORD32 mode_or_flag, UWORD8 bit_depth); + +typedef void (*HbdChromaIntraPredFn)(UWORD16* pu2_ref, WORD32 src_strd, + UWORD16* pu2_dst, WORD32 dst_strd, + WORD32 nt, WORD32 mode); + +// Function getters for 8-bit +LumaIntraPredFn GetLumaIntraPredFn(const ihevc_func_selector_t* selector, + int mode); +ChromaIntraPredFn GetChromaIntraPredFn(const ihevc_func_selector_t* selector, + int mode); + +// Function getters for HBD +HbdLumaIntraPredFn GetHbdLumaIntraPredFn(IV_ARCH_T arch, int mode); +HbdChromaIntraPredFn GetHbdChromaIntraPredFn(IV_ARCH_T arch, int mode); + +struct IntraPredModeInfo { + int mode; + const char* name; +}; + +// Benchmark modes and block sizes +const std::vector& GetIntraPredBenchmarkModes(); +const std::vector& GetIntraPredBenchmarkSizes(); diff --git a/tests/common/ihevc_itrans_recon_benchmark.cc b/tests/common/ihevc_itrans_recon_benchmark.cc new file mode 100644 index 00000000..c5f5b78a --- /dev/null +++ b/tests/common/ihevc_itrans_recon_benchmark.cc @@ -0,0 +1,138 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "ihevc_defs.h" +#include "ihevc_function_selector.h" +#include "ihevc_itrans_recon.h" +#include "ihevc_macros.h" +#include "ihevc_resi_trans.h" +#include "ihevc_structs.h" +#include "ihevc_typedefs.h" +#include "iv.h" +} + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_itrans_utils.h" + +namespace { + +void BM_ITransRecon(benchmark::State& state, ITransBenchConfig config) { + const int trans_size = config.trans_size; + const int ttype = config.ttype; + const int nz_cols = config.non_zero_cols; + const int nz_rows = config.non_zero_rows; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ITransReconFn fn = GetITransReconFn(selector, trans_size, ttype); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + // Worst-case padding and temporary buffer size for safe SIMD loads/stores + // across all platforms. + const int pad_pred = 8; + const int pad_tmp = 8; + const size_t tmp_size = 3 * trans_size * trans_size + pad_tmp; + + std::vector pi2_coeffs(trans_size * trans_size); + std::vector pi2_tmp(tmp_size); + std::vector pu1_pred_recon(trans_size * trans_size + pad_pred); + std::vector pu1_dst(trans_size * trans_size); + + WORD32 zero_cols = 0; + WORD32 zero_rows = 0; + GenerateITransInput(trans_size, ttype, nz_cols, nz_rows, pi2_coeffs.data(), + pu1_pred_recon.data(), &zero_cols, &zero_rows); + + const WORD32 src_strd = trans_size; + const WORD32 pred_strd = trans_size; + const WORD32 dst_strd = trans_size; + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ITransReconFn ref_fn = GetITransReconFn(ref_selector, trans_size, ttype); + if (ref_fn) { + std::vector ref_tmp(tmp_size); + std::vector ref_dst(trans_size * trans_size); + ref_fn(pi2_coeffs.data(), ref_tmp.data(), pu1_pred_recon.data(), + ref_dst.data(), src_strd, pred_strd, dst_strd, zero_cols, + zero_rows); + fn(pi2_coeffs.data(), pi2_tmp.data(), pu1_pred_recon.data(), + pu1_dst.data(), src_strd, pred_strd, dst_strd, zero_cols, zero_rows); + if (std::memcmp(ref_dst.data(), pu1_dst.data(), + trans_size * trans_size) != 0) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(pi2_coeffs.data(), pi2_tmp.data(), pu1_pred_recon.data(), pu1_dst.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows); + benchmark::DoNotOptimize(pu1_dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * trans_size * trans_size); + state.SetBytesProcessed(state.iterations() * trans_size * trans_size * + sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (const auto& tc : GetITransTestCases()) { + for (const auto& nz : tc.nz_regions) { + for (auto arch : arches) { + std::string arch_name = GetArchName(arch); + std::string name = "BM_ITransRecon/" + std::to_string(tc.size) + "x" + + std::to_string(tc.size) + + (tc.ttype == 1 ? "_dst" : "_dct") + "/nz_" + + std::to_string(nz.cols) + "x" + + std::to_string(nz.rows) + "/" + arch_name; + + ITransBenchConfig cfg{tc.size, tc.ttype, nz.cols, nz.rows, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_ITransRecon(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_itrans_recon_test.cc b/tests/common/ihevc_itrans_recon_test.cc index b8010bba..d75bca0b 100644 --- a/tests/common/ihevc_itrans_recon_test.cc +++ b/tests/common/ihevc_itrans_recon_test.cc @@ -9,6 +9,7 @@ #include "TestCommon.h" #include "func_selector.h" #include "ihevc_defs.h" +#include "ihevc_itrans_utils.h" #include "ihevc_macros.h" #include "ihevc_structs.h" #include "ihevc_typedefs.h" @@ -20,7 +21,7 @@ namespace { using ITransReconTestParam = std::tuple; class ITransReconTest : public ::testing::TestWithParam { -protected: + protected: void SetUp() override { std::tie(trans_size, ttype, arch, num_non_zero_rows, num_non_zero_cols) = GetParam(); @@ -31,7 +32,7 @@ class ITransReconTest : public ::testing::TestWithParam { // TODO: Increase allocations for x86/x86_64 to avoid out-of-bounds // reads in SIMD implementations. -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ defined(_M_IX86) int pad_pred = (trans_size == 4) ? 8 : 0; int pad_tmp = (trans_size == 32) ? 8 : 0; @@ -44,12 +45,12 @@ class ITransReconTest : public ::testing::TestWithParam { // pi2_tmp needs to be large enough to hold intermediate data of width * // height 16bits. pi2_tmp.resize(trans_size * trans_size + pad_tmp); -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ defined(_M_IX86) - if (trans_size == 32) { - // SSE4.2 and SSSE3.1 require 3 times trans_size * trans_size for 32x32 - pi2_tmp.resize(3 * trans_size * trans_size + pad_tmp); - } + if (trans_size == 32) { + // SSE4.2 and SSSE3.1 require 3 times trans_size * trans_size for 32x32 + pi2_tmp.resize(3 * trans_size * trans_size + pad_tmp); + } #endif pu1_pred.resize(trans_size * trans_size + pad_pred); pu1_dst_ref.resize(trans_size * trans_size); @@ -59,48 +60,24 @@ class ITransReconTest : public ::testing::TestWithParam { tst_func_selector = get_tst_func_ptr(arch); } - template void RunTest(FuncPtr func_ptr) { - std::mt19937 rng(0); - std::uniform_int_distribution coeff_dist(-32768, 32767); - std::uniform_int_distribution pixel_dist(0, 255); - - // Populate pi2_src so that the requested number of rows and columns - // are potentially non-zero. Rows [0, non_zero_rows) and columns - // [0, non_zero_cols) form the non-zero region; everything else is zero. - std::fill(pi2_src.begin(), pi2_src.end(), 0); - for (int i = 0; i < trans_size; i++) { - for (int j = 0; j < trans_size; j++) { - if (i < num_non_zero_rows && j < num_non_zero_cols) { - pi2_src[i * src_strd + j] = coeff_dist(rng); - } - } - } + void RunTest() { + FillRandomSubBlock(pi2_src.data(), trans_size, src_strd, num_non_zero_rows, + num_non_zero_cols, static_cast(-32768), + static_cast(32767), 0); + FillRandom(pu1_pred, static_cast(0), static_cast(255), 1); - for (auto &v : pu1_pred) - v = pixel_dist(rng); + WORD32 zero_cols = ComputeZeroMask(trans_size, num_non_zero_cols); + WORD32 zero_rows = ComputeZeroMask(trans_size, num_non_zero_rows); - WORD32 non_zero_rows_mask = 0; - for (int i = 0; i < num_non_zero_rows && i < trans_size; i++) { - non_zero_rows_mask |= (1u << i); - } + ITransReconFn ref_fn = + GetITransReconFn(ref_func_selector, trans_size, ttype); + ITransReconFn tst_fn = + GetITransReconFn(tst_func_selector, trans_size, ttype); - WORD32 non_zero_cols_mask = 0; - for (int j = 0; j < num_non_zero_cols && j < trans_size; j++) { - non_zero_cols_mask |= (1u << j); - } - - WORD32 mask = (trans_size == 32) - ? 0xFFFFFFFFu - : ((static_cast(1u) << trans_size) - 1u); - WORD32 zero_cols = (~non_zero_cols_mask) & mask; - WORD32 zero_rows = (~non_zero_rows_mask) & mask; - - (ref_func_selector->*func_ptr)( - pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), pu1_dst_ref.data(), - src_strd, pred_strd, dst_strd, zero_cols, zero_rows); - (tst_func_selector->*func_ptr)( - pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), pu1_dst_tst.data(), - src_strd, pred_strd, dst_strd, zero_cols, zero_rows); + ref_fn(pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), pu1_dst_ref.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows); + tst_fn(pi2_src.data(), pi2_tmp.data(), pu1_pred.data(), pu1_dst_tst.data(), + src_strd, pred_strd, dst_strd, zero_cols, zero_rows); ASSERT_NO_FATAL_FAILURE(compare_output( pu1_dst_ref, pu1_dst_tst, trans_size, trans_size, dst_strd)); } @@ -108,8 +85,8 @@ class ITransReconTest : public ::testing::TestWithParam { int trans_size; int ttype; IV_ARCH_T arch; - const ihevc_func_selector_t *ref_func_selector; - const ihevc_func_selector_t *tst_func_selector; + const ihevc_func_selector_t* ref_func_selector; + const ihevc_func_selector_t* tst_func_selector; WORD32 src_strd; WORD32 pred_strd; @@ -123,21 +100,7 @@ class ITransReconTest : public ::testing::TestWithParam { std::vector pu1_dst_tst; }; -TEST_P(ITransReconTest, Run) { - if (trans_size == 4) { - if (ttype == 1) { - RunTest(&ihevc_func_selector_t::ihevc_itrans_recon_4x4_ttype1_fptr); - } else { - RunTest(&ihevc_func_selector_t::ihevc_itrans_recon_4x4_fptr); - } - } else if (trans_size == 8) { - RunTest(&ihevc_func_selector_t::ihevc_itrans_recon_8x8_fptr); - } else if (trans_size == 16) { - RunTest(&ihevc_func_selector_t::ihevc_itrans_recon_16x16_fptr); - } else if (trans_size == 32) { - RunTest(&ihevc_func_selector_t::ihevc_itrans_recon_32x32_fptr); - } -} +TEST_P(ITransReconTest, Run) { RunTest(); } std::string PrintITransReconTestParam( const testing::TestParamInfo &info) { diff --git a/tests/common/ihevc_itrans_res_benchmark.cc b/tests/common/ihevc_itrans_res_benchmark.cc new file mode 100644 index 00000000..ea232d24 --- /dev/null +++ b/tests/common/ihevc_itrans_res_benchmark.cc @@ -0,0 +1,136 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "ihevc_defs.h" +#include "ihevc_function_selector.h" +#include "ihevc_itrans_res.h" +#include "ihevc_macros.h" +#include "ihevc_structs.h" +#include "ihevc_typedefs.h" +#include "iv.h" +} + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_itrans_utils.h" + +namespace { + +void BM_ITransRes(benchmark::State& state, ITransBenchConfig config) { + const int trans_size = config.trans_size; + const int ttype = config.ttype; + const int nz_cols = config.non_zero_cols; + const int nz_rows = config.non_zero_rows; + const IV_ARCH_T arch = config.arch; + const UWORD8 bit_depth = static_cast(config.bit_depth); + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ITransResFn fn = GetITransResFn(selector, trans_size, ttype); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + // Worst-case padding and temporary buffer size for safe SIMD loads/stores + // across all platforms. + const int pad_dst = 8; + const int pad_tmp = 8; + const size_t tmp_size = 3 * trans_size * trans_size + pad_tmp; + + std::vector pi2_coeffs(trans_size * trans_size); + std::vector pi2_tmp(tmp_size); + std::vector pi2_dst(trans_size * trans_size + pad_dst); + std::vector pu1_pred_dummy(trans_size * trans_size); + + WORD32 zero_cols = 0; + WORD32 zero_rows = 0; + GenerateITransInput(trans_size, ttype, nz_cols, nz_rows, pi2_coeffs.data(), + pu1_pred_dummy.data(), &zero_cols, &zero_rows); + + const WORD32 src_strd = trans_size; + const WORD32 dst_strd = trans_size; + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ITransResFn ref_fn = GetITransResFn(ref_selector, trans_size, ttype); + if (ref_fn) { + std::vector ref_tmp(tmp_size); + std::vector ref_dst(trans_size * trans_size + pad_dst); + ref_fn(pi2_coeffs.data(), ref_tmp.data(), ref_dst.data(), src_strd, + dst_strd, zero_cols, zero_rows, bit_depth); + fn(pi2_coeffs.data(), pi2_tmp.data(), pi2_dst.data(), src_strd, dst_strd, + zero_cols, zero_rows, bit_depth); + if (std::memcmp(ref_dst.data(), pi2_dst.data(), + trans_size * trans_size * sizeof(WORD16)) != 0) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(pi2_coeffs.data(), pi2_tmp.data(), pi2_dst.data(), src_strd, dst_strd, + zero_cols, zero_rows, bit_depth); + benchmark::DoNotOptimize(pi2_dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * trans_size * trans_size); + state.SetBytesProcessed(state.iterations() * trans_size * trans_size * + sizeof(WORD16)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + for (const auto& tc : GetITransTestCases()) { + for (const auto& nz : tc.nz_regions) { + for (auto arch : arches) { + std::string arch_name = GetArchName(arch); + std::string name = "BM_ITransRes/" + std::to_string(tc.size) + "x" + + std::to_string(tc.size) + + (tc.ttype == 1 ? "_dst" : "_dct") + "/nz_" + + std::to_string(nz.cols) + "x" + + std::to_string(nz.rows) + "/" + arch_name; + + ITransBenchConfig cfg{tc.size, tc.ttype, nz.cols, nz.rows, arch, 8}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_ITransRes(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_itrans_res_test.cc b/tests/common/ihevc_itrans_res_test.cc index a664df08..73f073e6 100644 --- a/tests/common/ihevc_itrans_res_test.cc +++ b/tests/common/ihevc_itrans_res_test.cc @@ -33,13 +33,14 @@ #include "iv.h" #include "func_selector.h" #include "TestCommon.h" +#include "ihevc_itrans_utils.h" // clang-format on // Test parameters: trans_size, ttype (0: normal, 1: ttype1), arch using ITransResTestParam = std::tuple; class ITransResTest : public ::testing::TestWithParam { -protected: + protected: void SetUp() override { std::tie(trans_size, ttype, arch) = GetParam(); src_strd = trans_size; @@ -49,20 +50,14 @@ class ITransResTest : public ::testing::TestWithParam { src_buf.resize(trans_size * trans_size); // Temporary buffer (intermediate 16-bit data) - // Size needed: width * height * 16 bits = width * height * 2 bytes - // pi2_tmp is WORD16*, so we need width * height elements tmp_buf.resize(trans_size * trans_size); // Output buffers dst_buf_ref.resize(trans_size * trans_size); dst_buf_tst.resize(trans_size * trans_size); - std::mt19937 rng(12345); - std::uniform_int_distribution dist(-32768, 32767); - - for (auto &v : src_buf) { - v = static_cast(dist(rng)); - } + FillRandom(src_buf, static_cast(-32768), static_cast(32767), + 12345); // Fill dst buffers with pattern std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xCDCD); @@ -72,11 +67,14 @@ class ITransResTest : public ::testing::TestWithParam { ref = get_ref_func_ptr(); } - template void RunTest(FuncPtr func_ptr, UWORD8 bit_depth) { - (ref->*func_ptr)(src_buf.data(), tmp_buf.data(), dst_buf_ref.data(), - src_strd, dst_strd, 0, 0, bit_depth); - (tst->*func_ptr)(src_buf.data(), tmp_buf.data(), dst_buf_tst.data(), - src_strd, dst_strd, 0, 0, bit_depth); + void RunTest(UWORD8 bit_depth) { + ITransResFn ref_fn = GetITransResFn(ref, trans_size, ttype); + ITransResFn tst_fn = GetITransResFn(tst, trans_size, ttype); + + ref_fn(src_buf.data(), tmp_buf.data(), dst_buf_ref.data(), src_strd, + dst_strd, 0, 0, bit_depth); + tst_fn(src_buf.data(), tmp_buf.data(), dst_buf_tst.data(), src_strd, + dst_strd, 0, 0, bit_depth); ASSERT_NO_FATAL_FAILURE(compare_output( dst_buf_ref, dst_buf_tst, trans_size, trans_size, dst_strd)); @@ -90,25 +88,13 @@ class ITransResTest : public ::testing::TestWithParam { std::vector tmp_buf; std::vector dst_buf_ref; std::vector dst_buf_tst; - const ihevc_func_selector_t *tst; - const ihevc_func_selector_t *ref; + const ihevc_func_selector_t* tst; + const ihevc_func_selector_t* ref; }; TEST_P(ITransResTest, Run) { for (UWORD8 bit_depth : {8, 10}) { - if (trans_size == 4) { - if (ttype == 1) { - RunTest(&ihevc_func_selector_t::ihevc_itrans_res_4x4_ttype1_fptr, bit_depth); - } else { - RunTest(&ihevc_func_selector_t::ihevc_itrans_res_4x4_fptr, bit_depth); - } - } else if (trans_size == 8) { - RunTest(&ihevc_func_selector_t::ihevc_itrans_res_8x8_fptr, bit_depth); - } else if (trans_size == 16) { - RunTest(&ihevc_func_selector_t::ihevc_itrans_res_16x16_fptr, bit_depth); - } else if (trans_size == 32) { - RunTest(&ihevc_func_selector_t::ihevc_itrans_res_32x32_fptr, bit_depth); - } + RunTest(bit_depth); } } diff --git a/tests/common/ihevc_itrans_test.cc b/tests/common/ihevc_itrans_test.cc index 7a5ac59d..5cdc6538 100644 --- a/tests/common/ihevc_itrans_test.cc +++ b/tests/common/ihevc_itrans_test.cc @@ -30,6 +30,7 @@ #include "ihevc_macros.h" #include "ihevc_typedefs.h" #include "TestCommon.h" +#include "ihevc_itrans_utils.h" // clang-format on namespace { @@ -55,77 +56,19 @@ class ITransTest : public ::testing::TestWithParam { } void RunTest() { - std::mt19937 rng(0); - std::uniform_int_distribution coeff_dist(-32768, 32767); - - std::fill(pi2_src.begin(), pi2_src.end(), 0); - for (int i = 0; i < trans_size; i++) { - for (int j = 0; j < trans_size; j++) { - if (j < num_non_zero_cols) { - pi2_src[i * src_strd + j] = coeff_dist(rng); - } - } - } + FillRandomSubBlock(pi2_src.data(), trans_size, src_strd, trans_size, + num_non_zero_cols, static_cast(-32768), + static_cast(32767), 0); - WORD32 non_zero_cols_mask = 0; - for (int j = 0; j < num_non_zero_cols; j++) { - non_zero_cols_mask |= (1u << j); - } + WORD32 zero_cols = ComputeZeroMask(trans_size, num_non_zero_cols); - WORD32 mask = (trans_size == 32) - ? 0xFFFFFFFFu - : ((static_cast(1u) << trans_size) - 1u); - WORD32 zero_cols = (~non_zero_cols_mask) & mask; - - // 1. Reference path from selector (generic C) - if (trans_size == 4) { - if (ttype == 1) { - (ref->*(&ihevc_func_selector_t::ihevc_itrans_4x4_ttype1_fptr))( - pi2_src.data(), pi2_dst_ref.data(), src_strd, dst_strd, shift, - zero_cols); - } else { - (ref->*(&ihevc_func_selector_t::ihevc_itrans_4x4_fptr))( - pi2_src.data(), pi2_dst_ref.data(), src_strd, dst_strd, shift, - zero_cols); - } - } else if (trans_size == 8) { - (ref->*(&ihevc_func_selector_t::ihevc_itrans_8x8_fptr))( - pi2_src.data(), pi2_dst_ref.data(), src_strd, dst_strd, shift, - zero_cols); - } else if (trans_size == 16) { - (ref->*(&ihevc_func_selector_t::ihevc_itrans_16x16_fptr))( - pi2_src.data(), pi2_dst_ref.data(), src_strd, dst_strd, shift, - zero_cols); - } else if (trans_size == 32) { - (ref->*(&ihevc_func_selector_t::ihevc_itrans_32x32_fptr))( - pi2_src.data(), pi2_dst_ref.data(), src_strd, dst_strd, shift, - zero_cols); - } + ITransFn ref_fn = GetITransFn(ref, trans_size, ttype); + ITransFn tst_fn = GetITransFn(tst, trans_size, ttype); - // 2. Test path from selector (SIMD, which might fall back to C) - if (trans_size == 4) { - if (ttype == 1) { - (tst->*(&ihevc_func_selector_t::ihevc_itrans_4x4_ttype1_fptr))( - pi2_src.data(), pi2_dst_tst.data(), src_strd, dst_strd, shift, - zero_cols); - } else { - (tst->*(&ihevc_func_selector_t::ihevc_itrans_4x4_fptr))( - pi2_src.data(), pi2_dst_tst.data(), src_strd, dst_strd, shift, - zero_cols); - } - } else if (trans_size == 8) { - (tst->*(&ihevc_func_selector_t::ihevc_itrans_8x8_fptr))( - pi2_src.data(), pi2_dst_tst.data(), src_strd, dst_strd, shift, - zero_cols); - } else if (trans_size == 16) { - (tst->*(&ihevc_func_selector_t::ihevc_itrans_16x16_fptr))( - pi2_src.data(), pi2_dst_tst.data(), src_strd, dst_strd, shift, - zero_cols); - } else if (trans_size == 32) { - (tst->*(&ihevc_func_selector_t::ihevc_itrans_32x32_fptr))( - pi2_src.data(), pi2_dst_tst.data(), src_strd, dst_strd, shift, - zero_cols); - } + ref_fn(pi2_src.data(), pi2_dst_ref.data(), src_strd, dst_strd, shift, + zero_cols); + tst_fn(pi2_src.data(), pi2_dst_tst.data(), src_strd, dst_strd, shift, + zero_cols); ASSERT_NO_FATAL_FAILURE(compare_output( pi2_dst_ref, pi2_dst_tst, trans_size, trans_size, dst_strd)); diff --git a/tests/common/ihevc_itrans_utils.cc b/tests/common/ihevc_itrans_utils.cc new file mode 100644 index 00000000..94584a7e --- /dev/null +++ b/tests/common/ihevc_itrans_utils.cc @@ -0,0 +1,249 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "ihevc_itrans_utils.h" + +#include +#include +#include + +extern "C" { +#include "ihevc_defs.h" +#include "ihevc_resi_trans.h" +#include "ihevc_typedefs.h" +} + +namespace { + +void GenerateITransCoeffs(int trans_size, int ttype, int nz_cols, int nz_rows, + WORD16* pi2_coeffs, WORD32* zero_cols, + WORD32* zero_rows) { + std::vector pu1_src(trans_size * trans_size); + std::vector pu1_pred_fwd(trans_size * trans_size); + std::vector pi4_temp(trans_size * trans_size); + + FillRandom(pu1_src, static_cast(0), static_cast(255), 1337); + FillRandom(pu1_pred_fwd, static_cast(0), static_cast(255), + 1338); + + // Forward residue transform: resi = pu1_src - pu1_pred_fwd in [-255, 255], + // transformed into 16-bit coefficients. + ihevc_resi_trans_4x4_ft* resi_trans_fn = nullptr; + if (trans_size == 4) { + resi_trans_fn = + (ttype == 1) ? ihevc_resi_trans_4x4_ttype1 : ihevc_resi_trans_4x4; + } else if (trans_size == 8) { + resi_trans_fn = ihevc_resi_trans_8x8; + } else if (trans_size == 16) { + resi_trans_fn = ihevc_resi_trans_16x16; + } else if (trans_size == 32) { + resi_trans_fn = ihevc_resi_trans_32x32; + } + + if (resi_trans_fn) { + resi_trans_fn(pu1_src.data(), pu1_pred_fwd.data(), pi4_temp.data(), + pi2_coeffs, trans_size, trans_size, trans_size, NULL_PLANE); + } + + // Zero out coefficients outside the top-left nz_cols x nz_rows region + for (int r = 0; r < trans_size; ++r) { + for (int c = 0; c < trans_size; ++c) { + if (c >= nz_cols || r >= nz_rows) { + pi2_coeffs[r * trans_size + c] = 0; + } + } + } + + // Compute row/col zero bitmasks + *zero_cols = ComputeZeroMask(trans_size, nz_cols); + *zero_rows = ComputeZeroMask(trans_size, nz_rows); +} + +} // namespace + +ITransFn GetITransFn(const ihevc_func_selector_t* selector, int trans_size, + int ttype) { + if (!selector) return nullptr; + if (trans_size == 4) { + return (ttype == 1) ? selector->ihevc_itrans_4x4_ttype1_fptr + : selector->ihevc_itrans_4x4_fptr; + } else if (trans_size == 8) { + return selector->ihevc_itrans_8x8_fptr; + } else if (trans_size == 16) { + return selector->ihevc_itrans_16x16_fptr; + } else if (trans_size == 32) { + return selector->ihevc_itrans_32x32_fptr; + } + return nullptr; +} + +ITransResFn GetITransResFn(const ihevc_func_selector_t* selector, + int trans_size, int ttype) { + if (!selector) return nullptr; + if (trans_size == 4) { + return (ttype == 1) ? selector->ihevc_itrans_res_4x4_ttype1_fptr + : selector->ihevc_itrans_res_4x4_fptr; + } else if (trans_size == 8) { + return selector->ihevc_itrans_res_8x8_fptr; + } else if (trans_size == 16) { + return selector->ihevc_itrans_res_16x16_fptr; + } else if (trans_size == 32) { + return selector->ihevc_itrans_res_32x32_fptr; + } + return nullptr; +} + +ITransReconFn GetITransReconFn(const ihevc_func_selector_t* selector, + int trans_size, int ttype) { + if (!selector) return nullptr; + if (trans_size == 4) { + return (ttype == 1) ? selector->ihevc_itrans_recon_4x4_ttype1_fptr + : selector->ihevc_itrans_recon_4x4_fptr; + } else if (trans_size == 8) { + return selector->ihevc_itrans_recon_8x8_fptr; + } else if (trans_size == 16) { + return selector->ihevc_itrans_recon_16x16_fptr; + } else if (trans_size == 32) { + return selector->ihevc_itrans_recon_32x32_fptr; + } + return nullptr; +} + +ChromaITransReconFn GetChromaITransReconFn( + const ihevc_func_selector_t* selector, int trans_size) { + if (!selector) return nullptr; + if (trans_size == 4) { + return selector->ihevc_chroma_itrans_recon_4x4_fptr; + } else if (trans_size == 8) { + return selector->ihevc_chroma_itrans_recon_8x8_fptr; + } else if (trans_size == 16) { + return selector->ihevc_chroma_itrans_recon_16x16_fptr; + } else if (trans_size == 32) { + return selector->ihevc_chroma_itrans_recon_32x32_fptr; + } + return nullptr; +} + +HbdITransReconFn GetHbdITransReconFn(IV_ARCH_T arch, int trans_size, + int ttype) { + if (arch == ARCH_NA) { + if (trans_size == 4) { + return (ttype == 1) ? ihevc_hbd_itrans_recon_4x4_ttype1 + : ihevc_hbd_itrans_recon_4x4; + } else if (trans_size == 8) { + return ihevc_hbd_itrans_recon_8x8; + } else if (trans_size == 16) { + return ihevc_hbd_itrans_recon_16x16; + } else if (trans_size == 32) { + return ihevc_hbd_itrans_recon_32x32; + } + } + return nullptr; +} + +ReconFn GetReconFn(const ihevc_func_selector_t* selector, int trans_size, + int ttype) { + if (!selector) return nullptr; + if (trans_size == 4) { + return (ttype == 1) ? selector->ihevc_recon_4x4_ttype1_fptr + : selector->ihevc_recon_4x4_fptr; + } else if (trans_size == 8) { + return selector->ihevc_recon_8x8_fptr; + } else if (trans_size == 16) { + return selector->ihevc_recon_16x16_fptr; + } else if (trans_size == 32) { + return selector->ihevc_recon_32x32_fptr; + } + return nullptr; +} + +ChromaReconFn GetChromaReconFn(const ihevc_func_selector_t* selector, + int trans_size) { + if (!selector) return nullptr; + if (trans_size == 4) { + return selector->ihevc_chroma_recon_4x4_fptr; + } else if (trans_size == 8) { + return selector->ihevc_chroma_recon_8x8_fptr; + } else if (trans_size == 16) { + return selector->ihevc_chroma_recon_16x16_fptr; + } else if (trans_size == 32) { + return selector->ihevc_chroma_recon_32x32_fptr; + } + return nullptr; +} + +void GenerateITransInput(int trans_size, int ttype, int nz_cols, int nz_rows, + WORD16* pi2_coeffs, UWORD8* pu1_pred_recon, + WORD32* zero_cols, WORD32* zero_rows) { + GenerateITransCoeffs(trans_size, ttype, nz_cols, nz_rows, pi2_coeffs, + zero_cols, zero_rows); + + if (pu1_pred_recon) { + FillRandom(pu1_pred_recon, static_cast(trans_size * trans_size), + static_cast(0), static_cast(255), 1337); + } +} + +void GenerateITransInput(int trans_size, int ttype, int nz_cols, int nz_rows, + WORD16* pi2_coeffs, UWORD16* pu2_pred_recon, + WORD32* zero_cols, WORD32* zero_rows, int bit_depth) { + GenerateITransCoeffs(trans_size, ttype, nz_cols, nz_rows, pi2_coeffs, + zero_cols, zero_rows); + + if (pu2_pred_recon) { + FillRandom(pu2_pred_recon, static_cast(trans_size * trans_size), + static_cast(0), + static_cast((1 << bit_depth) - 1), 1337); + } +} + +const std::vector& GetITransTestCases() { + static const std::vector kTestCases = { + {4, 0, {{4, 4}, {4, 2}, {2, 4}}}, + {4, 1, {{4, 4}, {4, 2}, {2, 4}}}, + {8, 0, {{4, 4}, {8, 4}, {4, 8}, {8, 8}}}, + {16, + 0, + {{4, 4}, + {8, 4}, + {4, 8}, + {8, 8}, + {16, 4}, + {4, 16}, + {16, 8}, + {8, 16}, + {16, 16}}}, + {32, + 0, + {{4, 4}, + {8, 4}, + {4, 8}, + {8, 8}, + {16, 4}, + {4, 16}, + {16, 8}, + {8, 16}, + {16, 16}, + {32, 8}, + {8, 32}, + {32, 16}, + {16, 32}, + {32, 32}}}, + }; + return kTestCases; +} diff --git a/tests/common/ihevc_itrans_utils.h b/tests/common/ihevc_itrans_utils.h new file mode 100644 index 00000000..4ee3bae7 --- /dev/null +++ b/tests/common/ihevc_itrans_utils.h @@ -0,0 +1,128 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "ihevc_defs.h" +extern "C" { +#include "ihevc_itrans.h" +#include "ihevc_itrans_res.h" +#include "ihevc_itrans_recon.h" +#include "ihevc_chroma_itrans_recon.h" +#include "ihevc_recon.h" +#include "ihevc_chroma_recon.h" +#include "ihevc_function_selector.h" +#include "iv.h" +} +// clang-format on + +#include "BenchmarkCommon.h" +#include "TestCommon.h" + +using ITransFn = void (*)(WORD16* pi2_src, WORD16* pi2_dst, WORD32 src_strd, + WORD32 dst_strd, WORD32 shift, WORD32 zero_cols); + +using ITransResFn = void (*)(WORD16* pi2_src, WORD16* pi2_tmp, WORD16* pi2_dst, + WORD32 src_strd, WORD32 dst_strd, WORD32 zero_cols, + WORD32 zero_rows, UWORD8 bit_depth); + +using ITransReconFn = void (*)(WORD16* pi2_src, WORD16* pi2_tmp, + UWORD8* pu1_pred, UWORD8* pu1_dst, + WORD32 src_strd, WORD32 pred_strd, + WORD32 dst_strd, WORD32 zero_cols, + WORD32 zero_rows); + +using ChromaITransReconFn = void (*)(WORD16* pi2_src, WORD16* pi2_tmp, + UWORD8* pu1_pred, UWORD8* pu1_dst, + WORD32 src_strd, WORD32 pred_strd, + WORD32 dst_strd, WORD32 zero_cols, + WORD32 zero_rows); + +using HbdITransReconFn = void (*)(WORD16* pi2_src, WORD16* pi2_tmp, + UWORD16* pu2_pred, UWORD16* pu2_dst, + WORD32 src_strd, WORD32 pred_strd, + WORD32 dst_strd, WORD32 zero_cols, + WORD32 zero_rows, UWORD8 bit_depth); + +using ReconFn = void (*)(WORD16* pi2_src, UWORD8* pu1_pred, UWORD8* pu1_dst, + WORD32 src_strd, WORD32 pred_strd, WORD32 dst_strd, + WORD32 zero_cols); + +using ChromaReconFn = void (*)(WORD16* pi2_src, UWORD8* pu1_pred, + UWORD8* pu1_dst, WORD32 src_strd, + WORD32 pred_strd, WORD32 dst_strd, + WORD32 zero_cols); + +ITransFn GetITransFn(const ihevc_func_selector_t* selector, int trans_size, + int ttype); +ITransResFn GetITransResFn(const ihevc_func_selector_t* selector, + int trans_size, int ttype); +ITransReconFn GetITransReconFn(const ihevc_func_selector_t* selector, + int trans_size, int ttype); +ChromaITransReconFn GetChromaITransReconFn( + const ihevc_func_selector_t* selector, int trans_size); +HbdITransReconFn GetHbdITransReconFn(IV_ARCH_T arch, int trans_size, int ttype); +ReconFn GetReconFn(const ihevc_func_selector_t* selector, int trans_size, + int ttype); +ChromaReconFn GetChromaReconFn(const ihevc_func_selector_t* selector, + int trans_size); + +// Non-zero transform coefficient region dimensions +struct NonZeroRegion { + int cols; + int rows; +}; + +// Benchmark test case definition for transform size and type +struct ITransTestCase { + int size; + int ttype; + std::vector nz_regions; +}; + +// Benchmark configuration for inverse transform tests +struct ITransBenchConfig { + int trans_size; + int ttype; // 0: DCT, 1: DST (4x4 only) + int non_zero_cols; // non-zero width + int non_zero_rows; // non-zero height + IV_ARCH_T arch; + int bit_depth = 8; +}; + +// Generates inputs using ihevc_resi_trans_* by computing the difference between +// two [0, 255] inputs and performing forward transform, then zeroing out +// outside the nz_cols x nz_rows non-zero region. +void GenerateITransInput(int trans_size, int ttype, int nz_cols, int nz_rows, + WORD16* pi2_coeffs, UWORD8* pu1_pred_recon, + WORD32* zero_cols, WORD32* zero_rows); + +// Generates inputs for High Bit Depth (HBD) inverse transform benchmarks with +// prediction values in [0, (1 << bit_depth) - 1]. +void GenerateITransInput(int trans_size, int ttype, int nz_cols, int nz_rows, + WORD16* pi2_coeffs, UWORD16* pu2_pred_recon, + WORD32* zero_cols, WORD32* zero_rows, + int bit_depth = 10); + +// Returns common test case configurations for inverse transform benchmarks. +const std::vector& GetITransTestCases(); diff --git a/tests/common/ihevc_luma_inter_pred_benchmark.cc b/tests/common/ihevc_luma_inter_pred_benchmark.cc new file mode 100644 index 00000000..f5cebf63 --- /dev/null +++ b/tests/common/ihevc_luma_inter_pred_benchmark.cc @@ -0,0 +1,104 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_inter_pred_utils.h" + +namespace { + +void BM_LumaInterPred(benchmark::State& state, InterPredConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const InterPredOp op = config.op; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + WORD8* pi1_coeff = gai1_ihevc_luma_filter[config.coeff_idx]; + const bool verify = (arch != ARCH_NA); + + if (!IsW16Inp(op) && !IsW16Out(op)) { + RunInterPredBenchmarkCase( + state, GetLumaInterPredFn(selector, op), + GetLumaInterPredFn(get_ref_func_ptr(), op), wd, ht, pi1_coeff, verify); + } else if (!IsW16Inp(op) && IsW16Out(op)) { + RunInterPredBenchmarkCase( + state, GetLumaInterPredW16outFn(selector, op), + GetLumaInterPredW16outFn(get_ref_func_ptr(), op), wd, ht, pi1_coeff, + verify); + } else if (IsW16Inp(op) && !IsW16Out(op)) { + RunInterPredBenchmarkCase( + state, GetLumaInterPredW16inpFn(selector, op), + GetLumaInterPredW16inpFn(get_ref_func_ptr(), op), wd, ht, pi1_coeff, + verify); + } else { +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ + defined(_M_IX86) + const bool verify_w16inp_w16out = false; +#else + const bool verify_w16inp_w16out = verify; +#endif + RunInterPredBenchmarkCase( + state, GetLumaInterPredW16inpW16outFn(selector, op), + GetLumaInterPredW16inpW16outFn(get_ref_func_ptr(), op), wd, ht, + pi1_coeff, verify_w16inp_w16out); + } +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + // Register Luma Inter Prediction benchmarks + for (const auto& op_info : GetInterPredOps()) { + for (const auto& size : GetInterPredLumaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + if (!get_func_ptr(arch)) continue; + std::string arch_name = GetArchName(arch); + std::string name = "BM_LumaInterPred/" + std::string(op_info.name) + + "/" + std::to_string(wd) + "x" + std::to_string(ht) + + "/" + arch_name; + InterPredConfig cfg{wd, + ht, + op_info.op, + arch, + /*bit_depth=*/8, + /*coeff_idx=*/1}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_LumaInterPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_luma_inter_pred_test.cc b/tests/common/ihevc_luma_inter_pred_test.cc index b424adc9..521375b0 100644 --- a/tests/common/ihevc_luma_inter_pred_test.cc +++ b/tests/common/ihevc_luma_inter_pred_test.cc @@ -33,68 +33,13 @@ #include "iv.h" #include "func_selector.h" #include "TestCommon.h" +#include "ihevc_inter_pred_utils.h" // clang-format on -// Test parameters: width, height, src_stride_mul, dst_stride_mul, coeff_idx, -// arch -using LumaInterPredTestParam = - std::tuple, int, int, int, IV_ARCH_T>; -template -class LumaInterPredTest - : public ::testing::TestWithParam { -protected: - void SetUp() override { - - std::pair block_size; - std::tie(block_size, src_strd_mul, dst_strd_mul, coeff_idx, arch) = - GetParam(); - std::tie(wd, ht) = block_size; - src_strd = wd * src_strd_mul; - dst_strd = wd * dst_strd_mul; - - // TODO: Increase allocations for x86/x86_64 to avoid out-of-bounds - // reads/writes in SIMD implementations. -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ - defined(_M_IX86) - int pad_dst = 16; -#else - int pad_dst = 0; -#endif +using LumaInterPredTestParam = InterPredTestParam; - dst_buf_ref.resize(dst_strd * ht + pad_dst); - dst_buf_tst.resize(dst_strd * ht + pad_dst); - - // Set pv_src to a valid position within src_buf to allow negative indexing - pv_src = (srcType*)getSrc8Buf().data() + kTapSize / 2 * src_strd; - pv_dst_ref = dst_buf_ref.data(); - pv_dst_tst = dst_buf_tst.data(); - - pi1_coeffs = gai1_ihevc_luma_filter[coeff_idx]; - tst = get_tst_func_ptr(arch); - ref = get_ref_func_ptr(); - } - - template void RunTest(FuncPtr func_ptr) { - (ref->*func_ptr)(pv_src, pv_dst_ref, src_strd, dst_strd, pi1_coeffs, ht, - wd); - (tst->*func_ptr)(pv_src, pv_dst_tst, src_strd, dst_strd, pi1_coeffs, ht, - wd); - ASSERT_NO_FATAL_FAILURE( - compare_output(dst_buf_ref, dst_buf_tst, wd, ht, dst_strd)); - } - - int wd, ht, src_strd_mul, dst_strd_mul, coeff_idx; - int src_strd, dst_strd; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - srcType *pv_src; - dstType *pv_dst_ref; - dstType *pv_dst_tst; - WORD8 *pi1_coeffs; - IV_ARCH_T arch; - const ihevc_func_selector_t *tst; - const ihevc_func_selector_t *ref; -}; +template +using LumaInterPredTest = InterPredTestBase; class LumaInterPred_8_8_Test : public LumaInterPredTest {}; class LumaInterPred_8_16_Test : public LumaInterPredTest {}; @@ -130,7 +75,7 @@ TEST_P(LumaInterPred_16_8_Test, LumaVertTest) { } TEST_P(LumaInterPred_16_16_Test, LumaVertTest) { -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ defined(_M_IX86) // TODO: SSE4.2 and SSSE3 are not matching C implementation GTEST_SKIP() << "SSE4.2 and SSSE3 are not matching C implementation for " @@ -147,41 +92,26 @@ auto kLumaInterPredTestParams = ::testing::ValuesIn(getTstArch()) // arch ); -std::string PrintLumaInterPredTestParam( - const testing::TestParamInfo &info) { - int wd, ht, src_strd_mul, dst_strd_mul, coeff_idx; - IV_ARCH_T arch; - std::pair block_size; - std::tie(block_size, src_strd_mul, dst_strd_mul, coeff_idx, arch) = - info.param; - std::tie(wd, ht) = block_size; - std::stringstream ss; - ss << wd << "x" << ht << "_src_stride_" << src_strd_mul * wd << "_dst_stride_" - << dst_strd_mul * wd << "_coeff_" << coeff_idx << "_" - << get_arch_str(arch); - return ss.str(); -} - INSTANTIATE_TEST_SUITE_P(LumaCopyTest, LumaInterPred_8_8_Test, - kLumaInterPredTestParams, PrintLumaInterPredTestParam); + kLumaInterPredTestParams, PrintInterPredTestParam<1>); INSTANTIATE_TEST_SUITE_P(LumaHorzTest, LumaInterPred_8_8_Test, - kLumaInterPredTestParams, PrintLumaInterPredTestParam); + kLumaInterPredTestParams, PrintInterPredTestParam<1>); INSTANTIATE_TEST_SUITE_P(LumaVertTest, LumaInterPred_8_8_Test, - kLumaInterPredTestParams, PrintLumaInterPredTestParam); + kLumaInterPredTestParams, PrintInterPredTestParam<1>); INSTANTIATE_TEST_SUITE_P(LumaCopyTest, LumaInterPred_8_16_Test, - kLumaInterPredTestParams, PrintLumaInterPredTestParam); + kLumaInterPredTestParams, PrintInterPredTestParam<1>); INSTANTIATE_TEST_SUITE_P(LumaHorzTest, LumaInterPred_8_16_Test, - kLumaInterPredTestParams, PrintLumaInterPredTestParam); + kLumaInterPredTestParams, PrintInterPredTestParam<1>); INSTANTIATE_TEST_SUITE_P(LumaVertTest, LumaInterPred_8_16_Test, - kLumaInterPredTestParams, PrintLumaInterPredTestParam); + kLumaInterPredTestParams, PrintInterPredTestParam<1>); INSTANTIATE_TEST_SUITE_P(LumaVertTest, LumaInterPred_16_8_Test, - kLumaInterPredTestParams, PrintLumaInterPredTestParam); + kLumaInterPredTestParams, PrintInterPredTestParam<1>); INSTANTIATE_TEST_SUITE_P(LumaVertTest, LumaInterPred_16_16_Test, - kLumaInterPredTestParams, PrintLumaInterPredTestParam); + kLumaInterPredTestParams, PrintInterPredTestParam<1>); diff --git a/tests/common/ihevc_luma_intra_pred_benchmark.cc b/tests/common/ihevc_luma_intra_pred_benchmark.cc new file mode 100644 index 00000000..bbd9f655 --- /dev/null +++ b/tests/common/ihevc_luma_intra_pred_benchmark.cc @@ -0,0 +1,109 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_intra_pred_utils.h" + +namespace { + +void BM_LumaIntraPred(benchmark::State& state, IntraPredConfig config) { + const int nt = config.nt; + const int mode = config.mode; + const int mode_or_flag = + (mode == 10 || mode == 26) ? config.disable_boundary_filter : mode; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + LumaIntraPredFn fn = GetLumaIntraPredFn(selector, mode); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + IntraPredLumaBuffers buf = + CreateIntraPredLumaBuffers(nt, config.dst_strd_mul); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + LumaIntraPredFn ref_fn = GetLumaIntraPredFn(get_ref_func_ptr(), mode); + if (ref_fn) { + IntraPredLumaBuffers ref_buf = buf; + IntraPredLumaBuffers tst_buf = buf; + ref_fn(ref_buf.ref.data(), ref_buf.src_strd, ref_buf.dst.data(), + ref_buf.dst_strd, nt, mode_or_flag); + fn(tst_buf.ref.data(), tst_buf.src_strd, tst_buf.dst.data(), + tst_buf.dst_strd, nt, mode_or_flag); + if (!VerifyOutput2D(ref_buf.dst.data(), tst_buf.dst.data(), nt, nt, + ref_buf.dst_strd, tst_buf.dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.ref.data(), buf.src_strd, buf.dst.data(), buf.dst_strd, nt, + mode_or_flag); + benchmark::DoNotOptimize(buf.dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * nt * nt); + state.SetBytesProcessed(state.iterations() * nt * nt * sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + // Register Luma Intra Prediction benchmarks + for (const auto& mode_info : GetIntraPredBenchmarkModes()) { + for (int nt : GetIntraPredBenchmarkSizes()) { + for (auto arch : arches) { + const ihevc_func_selector_t* sel = get_func_ptr(arch); + if (!sel || !GetLumaIntraPredFn(sel, mode_info.mode)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = "BM_LumaIntraPred/" + std::string(mode_info.name) + + "/" + std::to_string(nt) + "x" + std::to_string(nt) + + "/" + arch_name; + IntraPredConfig cfg{nt, mode_info.mode, /*dst_strd_mul=*/1, + /*disable_boundary_filter=*/0, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_LumaIntraPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_luma_intra_pred_test.cc b/tests/common/ihevc_luma_intra_pred_test.cc index aba151ad..0d76735b 100644 --- a/tests/common/ihevc_luma_intra_pred_test.cc +++ b/tests/common/ihevc_luma_intra_pred_test.cc @@ -35,120 +35,56 @@ #include "TestCommon.h" // clang-format on +#include "ihevc_intra_pred_utils.h" + // Test parameters: block_size, mode, dst_stride_mul, arch using LumaIntraPredTestParam = std::tuple; class LumaIntraPredTest : public ::testing::TestWithParam { -protected: + protected: void SetUp() override { std::tie(nt, mode, dst_strd_mul, arch) = GetParam(); - src_strd = 1; // Intra pred reference is usually dense - dst_strd = nt * dst_strd_mul; - - // TODO: Increase allocations for x86/x86_64 to avoid out-of-bounds - // reads/writes in SIMD implementations. -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ - defined(_M_IX86) - int pad_ref = 16; - int pad_dst = 16; -#else - int pad_ref = 0; - int pad_dst = 0; -#endif - - // Reference buffer size: 4 * nt + 1 - int ref_size = 4 * nt + 1; - ref_buf.resize(ref_size + pad_ref); - - // Initialize reference buffer with random data - std::mt19937 rng(12345); - std::uniform_int_distribution dist(0, 255); - for (auto &v : ref_buf) { - v = static_cast(dist(rng)); - } - - // Use a pointer aligned or offset to ensure we have valid data - // The function expects pu1_ref to point to the start of the reference array - // Top-left is usually at index 2*nt. - // We just pass the data pointer. - pu1_ref = ref_buf.data(); - - dst_buf_ref.resize(dst_strd * nt + pad_dst); - dst_buf_tst.resize(dst_strd * nt + pad_dst); - - // Initialize dst buffers with pattern to detect over/under writes - std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xCD); - std::fill(dst_buf_tst.begin(), dst_buf_tst.end(), 0xCD); - - pu1_dst_ref = dst_buf_ref.data(); - pu1_dst_tst = dst_buf_tst.data(); + buf_ref = CreateIntraPredLumaBuffers(nt, dst_strd_mul, 8, 12345); + buf_tst = buf_ref; tst = get_tst_func_ptr(arch); ref = get_ref_func_ptr(); } - template void RunTest(FuncPtr func_ptr) { - (ref->*func_ptr)(pu1_ref, src_strd, pu1_dst_ref, dst_strd, nt, mode); - (tst->*func_ptr)(pu1_ref, src_strd, pu1_dst_tst, dst_strd, nt, mode); - ASSERT_NO_FATAL_FAILURE( - compare_output(dst_buf_ref, dst_buf_tst, nt, nt, dst_strd)); - } - - template void RunTestHorzVer(FuncPtrMember func_ptr) { - // Test with disable_boundary_filter = 0 - (ref->*func_ptr)(pu1_ref, src_strd, pu1_dst_ref, dst_strd, nt, 0); - (tst->*func_ptr)(pu1_ref, src_strd, pu1_dst_tst, dst_strd, nt, 0); - ASSERT_NO_FATAL_FAILURE( - compare_output(dst_buf_ref, dst_buf_tst, nt, nt, dst_strd)); - - // Test with disable_boundary_filter = 1 - (ref->*func_ptr)(pu1_ref, src_strd, pu1_dst_ref, dst_strd, nt, 1); - (tst->*func_ptr)(pu1_ref, src_strd, pu1_dst_tst, dst_strd, nt, 1); - ASSERT_NO_FATAL_FAILURE( - compare_output(dst_buf_ref, dst_buf_tst, nt, nt, dst_strd)); + void RunWithArg(int mode_or_flag) { + LumaIntraPredFn ref_fn = GetLumaIntraPredFn(ref, mode); + LumaIntraPredFn tst_fn = GetLumaIntraPredFn(tst, mode); + ASSERT_NE(ref_fn, nullptr); + ASSERT_NE(tst_fn, nullptr); + + ref_fn(buf_ref.ref.data(), buf_ref.src_strd, buf_ref.dst.data(), + buf_ref.dst_strd, nt, mode_or_flag); + tst_fn(buf_tst.ref.data(), buf_tst.src_strd, buf_tst.dst.data(), + buf_tst.dst_strd, nt, mode_or_flag); + ASSERT_NO_FATAL_FAILURE(compare_output(buf_ref.dst, buf_tst.dst, nt, + nt, buf_ref.dst_strd)); } int nt, mode, dst_strd_mul; - int src_strd, dst_strd; - std::vector ref_buf; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - UWORD8 *pu1_ref; - UWORD8 *pu1_dst_ref; - UWORD8 *pu1_dst_tst; + IntraPredLumaBuffers buf_ref; + IntraPredLumaBuffers buf_tst; IV_ARCH_T arch; - const ihevc_func_selector_t *tst; - const ihevc_func_selector_t *ref; + const ihevc_func_selector_t* tst; + const ihevc_func_selector_t* ref; }; TEST_P(LumaIntraPredTest, Run) { - if (mode == 0) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_luma_planar_fptr); - else if (mode == 1) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_luma_dc_fptr); - else if (mode == 2) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_luma_mode2_fptr); - else if (mode >= 3 && mode <= 9) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_luma_mode_3_to_9_fptr); - else if (mode == 10) { - RunTestHorzVer(&ihevc_func_selector_t::ihevc_intra_pred_luma_horz_fptr); - } else if (mode >= 11 && mode <= 17) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_luma_mode_11_to_17_fptr); - else if (mode == 18 || mode == 34) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_luma_mode_18_34_fptr); - else if (mode >= 19 && mode <= 25) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_luma_mode_19_to_25_fptr); - else if (mode == 26) { - RunTestHorzVer(&ihevc_func_selector_t::ihevc_intra_pred_luma_ver_fptr); - } else if (mode >= 27 && mode <= 33) - RunTest(&ihevc_func_selector_t::ihevc_intra_pred_luma_mode_27_to_33_fptr); - else - FAIL() << "Invalid mode: " << mode; + if (mode == 10 || mode == 26) { + RunWithArg(0); + RunWithArg(1); + } else { + RunWithArg(mode); + } } std::string PrintLumaIntraPredTestParam( - const testing::TestParamInfo &info) { + const testing::TestParamInfo& info) { int nt, mode, dst_strd_mul; IV_ARCH_T arch; std::tie(nt, mode, dst_strd_mul, arch) = info.param; diff --git a/tests/common/ihevc_padding_benchmark.cc b/tests/common/ihevc_padding_benchmark.cc new file mode 100644 index 00000000..6a9845ab --- /dev/null +++ b/tests/common/ihevc_padding_benchmark.cc @@ -0,0 +1,187 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "ihevc_defs.h" +#include "ihevc_function_selector.h" +#include "ihevc_macros.h" +#include "ihevc_padding.h" +#include "ihevc_platform_macros.h" +#include "ihevc_typedefs.h" +#include "iv.h" +} + +#include "BenchmarkCommon.h" +#include "TestCommon.h" +#include "func_selector.h" + +namespace { + +enum class PadType { + kLeftLuma, + kRightLuma, + kLeftChroma, + kRightChroma, +}; + +struct PaddingBenchConfig { + int wd; + int ht; + int pad_size; + PadType pad_type; + IV_ARCH_T arch; +}; + +void BM_Padding(benchmark::State& state, PaddingBenchConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const int pad_size = config.pad_size; + const PadType pad_type = config.pad_type; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + const bool is_chroma = + (pad_type == PadType::kLeftChroma || pad_type == PadType::kRightChroma); + const int block_wd = is_chroma ? (2 * wd) : wd; + const int stride = block_wd + 2 * pad_size + 32; + const int total_ht = ht + 2 * pad_size + 32; + const int src_offset = pad_size * stride + pad_size + 16; + + std::vector buf(stride * total_ht, 0xAA); + + std::mt19937 rng(42); + std::uniform_int_distribution dist(0, 255); + for (int r = 0; r < ht; ++r) { + for (int c = 0; c < block_wd; ++c) { + buf[src_offset + r * stride + c] = dist(rng); + } + } + + using PadFn = void (*)(UWORD8*, WORD32, WORD32, WORD32); + PadFn fn = nullptr; + PadFn ref_fn = nullptr; + int offset = src_offset; + + const ihevc_func_selector_t* ref_sel = get_ref_func_ptr(); + + switch (pad_type) { + case PadType::kLeftLuma: + fn = selector->ihevc_pad_left_luma_fptr; + ref_fn = ref_sel->ihevc_pad_left_luma_fptr; + offset = src_offset; + break; + case PadType::kRightLuma: + fn = selector->ihevc_pad_right_luma_fptr; + ref_fn = ref_sel->ihevc_pad_right_luma_fptr; + offset = src_offset + wd; + break; + case PadType::kLeftChroma: + fn = selector->ihevc_pad_left_chroma_fptr; + ref_fn = ref_sel->ihevc_pad_left_chroma_fptr; + offset = src_offset; + break; + case PadType::kRightChroma: + fn = selector->ihevc_pad_right_chroma_fptr; + ref_fn = ref_sel->ihevc_pad_right_chroma_fptr; + offset = src_offset + 2 * wd; + break; + } + + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + // Pre-measurement verification against reference + if (arch != ARCH_NA && ref_fn) { + auto ref_buf = buf; + auto tst_buf = buf; + ref_fn(ref_buf.data() + offset, stride, ht, pad_size); + fn(tst_buf.data() + offset, stride, ht, pad_size); + if (ref_buf != tst_buf) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + + for (auto _ : state) { + fn(buf.data() + offset, stride, ht, pad_size); + benchmark::DoNotOptimize(buf.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * ht * pad_size); + state.SetBytesProcessed(state.iterations() * ht * pad_size * sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + struct PadOpInfo { + PadType type; + const char* name; + bool is_chroma; + }; + + const PadOpInfo ops[] = { + {PadType::kLeftLuma, "left_luma", false}, + {PadType::kRightLuma, "right_luma", false}, + {PadType::kLeftChroma, "left_chroma", true}, + {PadType::kRightChroma, "right_chroma", true}, + }; + + const int pad_sizes[] = {80, 16}; + + for (const auto& op : ops) { + const auto& sizes = + op.is_chroma ? GetBenchmarkChromaPUSizes() : GetBenchmarkLumaPUSizes(); + for (const auto& size : sizes) { + const int wd = size.first; + const int ht = size.second; + for (int pad_size : pad_sizes) { + for (auto arch : GetBenchmarkArchitectures()) { + std::string arch_name = GetArchName(arch); + std::string name = "BM_Padding/" + std::string(op.name) + "/" + + std::to_string(wd) + "x" + std::to_string(ht) + + "/pad_" + std::to_string(pad_size) + "/" + + arch_name; + + PaddingBenchConfig cfg{wd, ht, pad_size, op.type, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg]( + benchmark::State& st) { + BM_Padding(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + } +} diff --git a/tests/common/ihevc_padding_test.cc b/tests/common/ihevc_padding_test.cc index 53a6ed5a..26480b76 100644 --- a/tests/common/ihevc_padding_test.cc +++ b/tests/common/ihevc_padding_test.cc @@ -48,16 +48,17 @@ std::string PrintPaddingTestParam( std::to_string(pad_size) + "_" + get_arch_str(arch); } -// ---------------------------- Luma Base Class ------------------------------ +// ---------------------------- Base Class ----------------------------------- -class PaddingLumaTest : public ::testing::TestWithParam { +template +class PaddingTestBase : public ::testing::TestWithParam { protected: void SetUp() override { std::pair block_size; std::tie(block_size, pad_size, arch) = GetParam(); std::tie(wd, ht) = block_size; - stride = wd + 2 * pad_size + 16; + stride = kComponents * wd + 2 * pad_size + 16; total_ht = ht + 2 * pad_size + 16; buf_size = stride * total_ht; @@ -74,7 +75,7 @@ class PaddingLumaTest : public ::testing::TestWithParam { std::mt19937 rng(42); std::uniform_int_distribution dist(0, 255); for (int r = 0; r < ht; r++) { - for (int c = 0; c < wd; c++) { + for (int c = 0; c < kComponents * wd; c++) { uint8_t val = dist(rng); buf_ref[src_offset + r * stride + c] = val; buf_tst[src_offset + r * stride + c] = val; @@ -91,49 +92,8 @@ class PaddingLumaTest : public ::testing::TestWithParam { const ihevc_func_selector_t* tst; }; -// --------------------------- Chroma Base Class ----------------------------- - -class PaddingChromaTest : public ::testing::TestWithParam { - protected: - void SetUp() override { - std::pair block_size; - std::tie(block_size, pad_size, arch) = GetParam(); - std::tie(wd, ht) = block_size; - - // For chroma, width of block is 2 * wd in bytes - stride = 2 * wd + 2 * pad_size + 16; - total_ht = ht + 2 * pad_size + 16; - - buf_size = stride * total_ht; - buf_ref.resize(buf_size, 0xAA); - buf_tst.resize(buf_size, 0xAA); - - src_offset = pad_size * stride + pad_size + 8; - - ref = get_ref_func_ptr(); - tst = get_tst_func_ptr(arch); - } - - void InitializeBuffers() { - std::mt19937 rng(42); - std::uniform_int_distribution dist(0, 255); - for (int r = 0; r < ht; r++) { - for (int c = 0; c < 2 * wd; c++) { - uint8_t val = dist(rng); - buf_ref[src_offset + r * stride + c] = val; - buf_tst[src_offset + r * stride + c] = val; - } - } - } - - int wd, ht, pad_size; - IV_ARCH_T arch; - int stride, total_ht, buf_size, src_offset; - std::vector buf_ref; - std::vector buf_tst; - const ihevc_func_selector_t* ref; - const ihevc_func_selector_t* tst; -}; +using PaddingLumaTest = PaddingTestBase<1>; +using PaddingChromaTest = PaddingTestBase<2>; // ---------------------------- Test cases ----------------------------------- @@ -185,21 +145,22 @@ auto kLumaPaddingParams = ::testing::Combine( ::testing::ValuesIn(getLumaPUBlockSizes()), ::testing::Values(80), ::testing::ValuesIn(getTstArch())); -const std::vector>& getChromaPUBlockSizes() { - static const std::vector> kChromaPUBlockSizes = []() { - std::vector> ret; - for (const auto& size : getLumaPUBlockSizes()) { - if ((size.second / 2) % 4 == 0) { - ret.push_back({size.first / 2, size.second / 2}); - } - } - return ret; - }(); - return kChromaPUBlockSizes; +const std::vector>& getChromaPaddingBlockSizes() { + static const std::vector> kChromaPaddingBlockSizes = + []() { + std::vector> ret; + for (const auto& size : getChromaPUBlockSizes()) { + if (size.second % 4 == 0) { + ret.push_back(size); + } + } + return ret; + }(); + return kChromaPaddingBlockSizes; } auto kChromaPaddingParams = ::testing::Combine( - ::testing::ValuesIn(getChromaPUBlockSizes()), ::testing::Values(80), + ::testing::ValuesIn(getChromaPaddingBlockSizes()), ::testing::Values(80), ::testing::ValuesIn(getTstArch())); INSTANTIATE_TEST_SUITE_P(Padding, PaddingLeftLumaTest, kLumaPaddingParams, diff --git a/tests/common/ihevc_recon_benchmark.cc b/tests/common/ihevc_recon_benchmark.cc new file mode 100644 index 00000000..a2c0f76e --- /dev/null +++ b/tests/common/ihevc_recon_benchmark.cc @@ -0,0 +1,149 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +extern "C" { +#include "ihevc_defs.h" +#include "ihevc_function_selector.h" +#include "ihevc_macros.h" +#include "ihevc_platform_macros.h" +#include "ihevc_recon.h" +#include "ihevc_structs.h" +#include "ihevc_typedefs.h" +#include "iv.h" +} + +#include "BenchmarkCommon.h" +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_itrans_utils.h" + +namespace { + +struct ReconBenchConfig { + int trans_size; + int ttype; // 0: DCT, 1: DST (4x4 only) + int non_zero_cols; + IV_ARCH_T arch; +}; + +void BM_Recon(benchmark::State& state, ReconBenchConfig config) { + const int trans_size = config.trans_size; + const int ttype = config.ttype; + const int nz_cols = config.non_zero_cols; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ReconFn fn = GetReconFn(selector, trans_size, ttype); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + const WORD32 src_strd = trans_size; + const WORD32 pred_strd = trans_size; + const WORD32 dst_strd = trans_size; + + std::vector pi2_src(trans_size * trans_size); + std::vector pu1_pred(trans_size * trans_size); + std::vector pu1_dst(trans_size * trans_size); + + FillRandomSubBlock(pi2_src.data(), src_strd, trans_size, trans_size, nz_cols, + static_cast(-512), static_cast(511)); + FillRandom(pu1_pred, static_cast(0), static_cast(255)); + std::fill(pu1_dst.begin(), pu1_dst.end(), 0xAA); + + WORD32 zero_cols = ComputeZeroMask(trans_size, nz_cols); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ReconFn ref_fn = GetReconFn(ref_selector, trans_size, ttype); + if (ref_fn) { + std::vector ref_dst(dst_strd * trans_size, 0xAA); + ref_fn(pi2_src.data(), pu1_pred.data(), ref_dst.data(), src_strd, + pred_strd, dst_strd, zero_cols); + fn(pi2_src.data(), pu1_pred.data(), pu1_dst.data(), src_strd, pred_strd, + dst_strd, zero_cols); + if (std::memcmp(ref_dst.data(), pu1_dst.data(), + trans_size * trans_size * sizeof(UWORD8)) != 0) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(pi2_src.data(), pu1_pred.data(), pu1_dst.data(), src_strd, pred_strd, + dst_strd, zero_cols); + benchmark::DoNotOptimize(pu1_dst.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * trans_size * trans_size); + state.SetBytesProcessed(state.iterations() * trans_size * trans_size * + sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + struct TestCase { + int size; + int ttype; + std::vector nz_cols; + }; + + const TestCase test_cases[] = { + {4, 0, {1, 2, 4}}, {4, 1, {1, 2, 4}}, {8, 0, {2, 4, 8}}, + {16, 0, {4, 8, 16}}, {32, 0, {8, 16, 32}}, + }; + + for (const auto& tc : test_cases) { + for (int nz : tc.nz_cols) { + for (auto arch : GetBenchmarkArchitectures()) { + const ihevc_func_selector_t* sel = get_func_ptr(arch); + if (!sel || !GetReconFn(sel, tc.size, tc.ttype)) { + continue; + } + std::string arch_name = GetArchName(arch); + std::string name = "BM_Recon/" + std::to_string(tc.size) + "x" + + std::to_string(tc.size) + + (tc.ttype == 1 ? "_dst" : "_dct") + "/nz_" + + std::to_string(nz) + "/" + arch_name; + + ReconBenchConfig cfg{tc.size, tc.ttype, nz, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_Recon(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_recon_test.cc b/tests/common/ihevc_recon_test.cc index aba01092..e85a11d8 100644 --- a/tests/common/ihevc_recon_test.cc +++ b/tests/common/ihevc_recon_test.cc @@ -31,6 +31,7 @@ #include "ihevc_platform_macros.h" #include "TestCommon.h" #include "func_selector.h" +#include "ihevc_itrans_utils.h" // clang-format on namespace { @@ -74,54 +75,33 @@ class ReconTest : public ::testing::TestWithParam { } } - template - void RunTest(FuncPtr func_ptr) { - std::mt19937 rng(0); + void RunTest() { // Residuals are typically in the range of S16 after inverse transform - std::uniform_int_distribution coeff_dist(-512, 511); - std::uniform_int_distribution pixel_dist(0, 255); - - // Populate pi2_src so that columns [0, num_non_zero_cols) are non-zero. - std::fill(pi2_src.begin(), pi2_src.end(), 0); - for (int i = 0; i < trans_size; i++) { - for (int j = 0; j < trans_size; j++) { - if (j < num_non_zero_cols) { - pi2_src[i * src_strd + j] = coeff_dist(rng); - } - } - } + FillRandomSubBlock(pi2_src.data(), trans_size, src_strd, trans_size, + num_non_zero_cols, static_cast(-512), + static_cast(511), 0); + FillRandom(pu1_pred, static_cast(0), static_cast(255), 1); - for (auto& v : pu1_pred) { - v = pixel_dist(rng); - } - - WORD32 non_zero_cols_mask = 0; - for (int j = 0; j < num_non_zero_cols && j < trans_size; j++) { - non_zero_cols_mask |= (1u << j); - } - - WORD32 mask = (trans_size == 32) - ? 0xFFFFFFFFu - : ((static_cast(1u) << trans_size) - 1u); - WORD32 zero_cols = (~non_zero_cols_mask) & mask; + WORD32 zero_cols = ComputeZeroMask(trans_size, num_non_zero_cols); // 1. Reference path (manual C++) ReferenceRecon(pi2_src, pu1_pred, pu1_dst_ref, zero_cols); // 2. Test path from selector (generic C) + ReconFn ref_fn = GetReconFn(ref, trans_size, ttype); std::fill(pu1_dst_tst.begin(), pu1_dst_tst.end(), 0xAA); - (ref->*func_ptr)(pi2_src.data(), pu1_pred.data(), pu1_dst_tst.data(), - src_strd, pred_strd, dst_strd, zero_cols); + ref_fn(pi2_src.data(), pu1_pred.data(), pu1_dst_tst.data(), src_strd, + pred_strd, dst_strd, zero_cols); ASSERT_NO_FATAL_FAILURE(compare_output( pu1_dst_ref, pu1_dst_tst, trans_size, trans_size, dst_strd)); // 3. Test path from selector (SIMD, falls back to C on x86 since no // overrides) - + ReconFn tst_fn = GetReconFn(tst, trans_size, ttype); std::fill(pu1_dst_tst.begin(), pu1_dst_tst.end(), 0xAA); - (tst->*func_ptr)(pi2_src.data(), pu1_pred.data(), pu1_dst_tst.data(), - src_strd, pred_strd, dst_strd, zero_cols); + tst_fn(pi2_src.data(), pu1_pred.data(), pu1_dst_tst.data(), src_strd, + pred_strd, dst_strd, zero_cols); ASSERT_NO_FATAL_FAILURE(compare_output( pu1_dst_ref, pu1_dst_tst, trans_size, trans_size, dst_strd)); @@ -143,21 +123,7 @@ class ReconTest : public ::testing::TestWithParam { std::vector pu1_dst_tst; }; -TEST_P(ReconTest, Run) { - if (trans_size == 4) { - if (ttype == 1) { - RunTest(&ihevc_func_selector_t::ihevc_recon_4x4_ttype1_fptr); - } else { - RunTest(&ihevc_func_selector_t::ihevc_recon_4x4_fptr); - } - } else if (trans_size == 8) { - RunTest(&ihevc_func_selector_t::ihevc_recon_8x8_fptr); - } else if (trans_size == 16) { - RunTest(&ihevc_func_selector_t::ihevc_recon_16x16_fptr); - } else if (trans_size == 32) { - RunTest(&ihevc_func_selector_t::ihevc_recon_32x32_fptr); - } -} +TEST_P(ReconTest, Run) { RunTest(); } std::string PrintReconTestParam( const testing::TestParamInfo& info) { diff --git a/tests/common/ihevc_sao_benchmark.cc b/tests/common/ihevc_sao_benchmark.cc new file mode 100644 index 00000000..cd743f4f --- /dev/null +++ b/tests/common/ihevc_sao_benchmark.cc @@ -0,0 +1,357 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "TestCommon.h" +#include "func_selector.h" +#include "ihevc_sao_utils.h" + +namespace { + +void BM_SaoBandOffsetLuma(benchmark::State& state, SaoBandConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const int sao_band_pos = config.sao_band_pos; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ihevc_sao_band_offset_luma_ft* fn = GetSaoBandOffsetLumaFn(selector); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + SaoLumaBuffers buf = CreateSaoLumaBuffers(wd, ht); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ihevc_sao_band_offset_luma_ft* ref_fn = + GetSaoBandOffsetLumaFn(ref_selector); + if (ref_fn) { + SaoLumaBuffers ref_buf = buf; + SaoLumaBuffers tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), sao_band_pos, + ref_buf.sao_offset.data(), wd, ht); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), sao_band_pos, tst_buf.sao_offset.data(), + wd, ht); + if (std::memcmp(ref_buf.src.data(), tst_buf.src.data(), + ref_buf.src.size()) != 0 || + ref_buf.src_left != tst_buf.src_left || + ref_buf.src_top != tst_buf.src_top || + ref_buf.src_top_left != tst_buf.src_top_left) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.stride, buf.src_left.data(), + buf.src_top.data(), buf.src_top_left.data(), sao_band_pos, + buf.sao_offset.data(), wd, ht); + benchmark::DoNotOptimize(buf.src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * wd * ht); + state.SetBytesProcessed(state.iterations() * wd * ht * sizeof(UWORD8)); +} + +void BM_SaoBandOffsetChroma(benchmark::State& state, SaoBandConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const int interleaved_wd = 2 * wd; + const int sao_band_pos_u = config.sao_band_pos; + const int sao_band_pos_v = (config.sao_band_pos + 4) % 32; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ihevc_sao_band_offset_chroma_ft* fn = GetSaoBandOffsetChromaFn(selector); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + SaoChromaBuffers buf = CreateSaoChromaBuffers(wd, ht); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ihevc_sao_band_offset_chroma_ft* ref_fn = + GetSaoBandOffsetChromaFn(ref_selector); + if (ref_fn) { + SaoChromaBuffers ref_buf = buf; + SaoChromaBuffers tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), sao_band_pos_u, sao_band_pos_v, + ref_buf.sao_offset_u.data(), ref_buf.sao_offset_v.data(), + interleaved_wd, ht); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), sao_band_pos_u, sao_band_pos_v, + tst_buf.sao_offset_u.data(), tst_buf.sao_offset_v.data(), + interleaved_wd, ht); + if (std::memcmp(ref_buf.src.data(), tst_buf.src.data(), + ref_buf.src.size()) != 0 || + ref_buf.src_left != tst_buf.src_left || + ref_buf.src_top != tst_buf.src_top || + ref_buf.src_top_left != tst_buf.src_top_left) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.stride, buf.src_left.data(), + buf.src_top.data(), buf.src_top_left.data(), sao_band_pos_u, + sao_band_pos_v, buf.sao_offset_u.data(), buf.sao_offset_v.data(), + interleaved_wd, ht); + benchmark::DoNotOptimize(buf.src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * interleaved_wd * ht); + state.SetBytesProcessed(state.iterations() * interleaved_wd * ht * + sizeof(UWORD8)); +} + +void BM_SaoEdgeOffsetLuma(benchmark::State& state, SaoEdgeConfig config) { + const int edge_class = config.edge_class; + const int wd = config.wd; + const int ht = config.ht; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ihevc_sao_edge_offset_class0_ft* fn = + GetSaoEdgeOffsetLumaFn(selector, edge_class); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + SaoLumaBuffers buf = CreateSaoLumaBuffers(wd, ht); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ihevc_sao_edge_offset_class0_ft* ref_fn = + GetSaoEdgeOffsetLumaFn(ref_selector, edge_class); + if (ref_fn) { + SaoLumaBuffers ref_buf = buf; + SaoLumaBuffers tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), ref_buf.src_top_right.data(), + ref_buf.src_bot_left.data(), ref_buf.avail.data(), + ref_buf.sao_offset.data(), wd, ht); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), tst_buf.src_top_right.data(), + tst_buf.src_bot_left.data(), tst_buf.avail.data(), + tst_buf.sao_offset.data(), wd, ht); + if (std::memcmp(ref_buf.src.data(), tst_buf.src.data(), + ref_buf.src.size()) != 0 || + ref_buf.src_left != tst_buf.src_left || + ref_buf.src_top != tst_buf.src_top || + ref_buf.src_top_left != tst_buf.src_top_left) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.stride, buf.src_left.data(), + buf.src_top.data(), buf.src_top_left.data(), buf.src_top_right.data(), + buf.src_bot_left.data(), buf.avail.data(), buf.sao_offset.data(), wd, + ht); + benchmark::DoNotOptimize(buf.src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * wd * ht); + state.SetBytesProcessed(state.iterations() * wd * ht * sizeof(UWORD8)); +} + +void BM_SaoEdgeOffsetChroma(benchmark::State& state, SaoEdgeConfig config) { + const int edge_class = config.edge_class; + const int wd = config.wd; + const int ht = config.ht; + const int interleaved_wd = 2 * wd; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + ihevc_sao_edge_offset_class0_chroma_ft* fn = + GetSaoEdgeOffsetChromaFn(selector, edge_class); + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + + SaoChromaBuffers buf = CreateSaoChromaBuffers(wd, ht); + + // Correctness verification against C reference prior to measurement + if (arch != ARCH_NA) { + const ihevc_func_selector_t* ref_selector = get_ref_func_ptr(); + ihevc_sao_edge_offset_class0_chroma_ft* ref_fn = + GetSaoEdgeOffsetChromaFn(ref_selector, edge_class); + if (ref_fn) { + SaoChromaBuffers ref_buf = buf; + SaoChromaBuffers tst_buf = buf; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), ref_buf.src_top_right.data(), + ref_buf.src_bot_left.data(), ref_buf.avail.data(), + ref_buf.sao_offset_u.data(), ref_buf.sao_offset_v.data(), + interleaved_wd, ht); + fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), tst_buf.src_top_right.data(), + tst_buf.src_bot_left.data(), tst_buf.avail.data(), + tst_buf.sao_offset_u.data(), tst_buf.sao_offset_v.data(), + interleaved_wd, ht); + if (std::memcmp(ref_buf.src.data(), tst_buf.src.data(), + ref_buf.src.size()) != 0 || + ref_buf.src_left != tst_buf.src_left || + ref_buf.src_top != tst_buf.src_top || + ref_buf.src_top_left != tst_buf.src_top_left) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + } + + for (auto _ : state) { + fn(buf.src.data() + buf.src_offset, buf.stride, buf.src_left.data(), + buf.src_top.data(), buf.src_top_left.data(), buf.src_top_right.data(), + buf.src_bot_left.data(), buf.avail.data(), buf.sao_offset_u.data(), + buf.sao_offset_v.data(), interleaved_wd, ht); + benchmark::DoNotOptimize(buf.src.data()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * interleaved_wd * ht); + state.SetBytesProcessed(state.iterations() * interleaved_wd * ht * + sizeof(UWORD8)); +} + +} // namespace + +void RegisterAllBenchmarks() { + const auto& arches = GetBenchmarkArchitectures(); + + // Register SAO Band Offset Luma benchmarks + for (const auto& size : GetSaoLumaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + std::string arch_name = GetArchName(arch); + std::string name = "BM_SaoBandOffsetLuma/" + std::to_string(wd) + "x" + + std::to_string(ht) + "/" + arch_name; + SaoBandConfig cfg{wd, ht, /*sao_band_pos=*/0, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_SaoBandOffsetLuma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + + // Register SAO Band Offset Chroma benchmarks + for (const auto& size : GetSaoChromaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + std::string arch_name = GetArchName(arch); + std::string name = "BM_SaoBandOffsetChroma/" + std::to_string(wd) + "x" + + std::to_string(ht) + "/" + arch_name; + SaoBandConfig cfg{wd, ht, /*sao_band_pos=*/0, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_SaoBandOffsetChroma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + + // Register SAO Edge Offset Luma benchmarks + for (int edge_class = 0; edge_class < 4; ++edge_class) { + for (const auto& size : GetSaoLumaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + std::string arch_name = GetArchName(arch); + std::string name = + "BM_SaoEdgeOffsetLuma/class" + std::to_string(edge_class) + "/" + + std::to_string(wd) + "x" + std::to_string(ht) + "/" + arch_name; + SaoEdgeConfig cfg{edge_class, wd, ht, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_SaoEdgeOffsetLuma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } + + // Register SAO Edge Offset Chroma benchmarks + for (int edge_class = 0; edge_class < 4; ++edge_class) { + for (const auto& size : GetSaoChromaBenchmarkSizes()) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : arches) { + std::string arch_name = GetArchName(arch); + std::string name = + "BM_SaoEdgeOffsetChroma/class" + std::to_string(edge_class) + "/" + + std::to_string(wd) + "x" + std::to_string(ht) + "/" + arch_name; + SaoEdgeConfig cfg{edge_class, wd, ht, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_SaoEdgeOffsetChroma(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_sao_test.cc b/tests/common/ihevc_sao_test.cc index 7ffeeb57..f908b755 100644 --- a/tests/common/ihevc_sao_test.cc +++ b/tests/common/ihevc_sao_test.cc @@ -29,20 +29,11 @@ #include "ihevc_typedefs.h" #include "func_selector.h" #include "TestCommon.h" +#include "ihevc_sao_utils.h" // clang-format on namespace { -void compare_sao_output(const UWORD8* ref, const UWORD8* tst, int stride, - int wd, int ht) { - for (int r = 0; r < ht; r++) { - for (int c = 0; c < wd; c++) { - ASSERT_EQ(ref[r * stride + c], tst[r * stride + c]) - << "Mismatch at row " << r << ", col " << c; - } - } -} - // ---------------------------- Test Param ----------------------------------- // Param: block size, sao_band_pos/edge_class (value), arch @@ -68,92 +59,50 @@ class SaoLumaTest : public ::testing::TestWithParam { std::tie(block_size, offset_val, arch) = GetParam(); std::tie(wd, ht) = block_size; - stride = wd + 32; - total_ht = ht + 32; - src_size = stride * total_ht; - src_offset = 16 * stride + 16; - - src_ref.resize(src_size); - src_tst.resize(src_size); - - src_left_ref.resize(ht + 8 + 1); - src_left_tst.resize(ht + 8 + 1); - src_top_ref.resize(wd + 8); - src_top_tst.resize(wd + 8); - src_top_left_ref.resize(8); - src_top_left_tst.resize(8); - src_top_right_ref.resize(8); - src_top_right_tst.resize(8); - src_bot_left_ref.resize(8); - src_bot_left_tst.resize(8); - - avail.resize(8); - - sao_offset.resize(8); - sao_offset[0] = 0; - ref = get_ref_func_ptr(); tst = get_tst_func_ptr(arch); } void InitializeBuffers() { - std::mt19937 rng(42); - std::uniform_int_distribution dist(0, 255); - std::uniform_int_distribution dist_avail(0, 1); - std::uniform_int_distribution dist_offset(-7, 7); - - for (int i = 0; i < src_size; i++) { - uint8_t val = dist(rng); - src_ref[i] = val; - src_tst[i] = val; - } + ref_buf = CreateSaoLumaBuffers(wd, ht); + tst_buf = ref_buf; + } - for (size_t i = 0; i < src_left_ref.size(); i++) { - uint8_t val = dist(rng); - src_left_ref[i] = val; - src_left_tst[i] = val; - } + void VerifyBuffers() { + compare_output(ref_buf.src.data() + ref_buf.src_offset, + tst_buf.src.data() + tst_buf.src_offset, wd, ht, + ref_buf.stride); + ASSERT_EQ(ref_buf.src_left, tst_buf.src_left); + ASSERT_EQ(ref_buf.src_top, tst_buf.src_top); + ASSERT_EQ(ref_buf.src_top_left, tst_buf.src_top_left); + } - for (size_t i = 0; i < src_top_ref.size(); i++) { - uint8_t val = dist(rng); - src_top_ref[i] = val; - src_top_tst[i] = val; - } + void RunEdgeOffset(int edge_class) { + InitializeBuffers(); + int left_offset = (edge_class == 2) ? 1 : 0; - for (size_t i = 0; i < src_top_left_ref.size(); i++) { - src_top_left_ref[i] = src_top_left_tst[i] = dist(rng); - src_top_right_ref[i] = src_top_right_tst[i] = dist(rng); - src_bot_left_ref[i] = src_bot_left_tst[i] = dist(rng); - } + auto* ref_fn = GetSaoEdgeOffsetLumaFn(ref, edge_class); + auto* tst_fn = GetSaoEdgeOffsetLumaFn(tst, edge_class); - src_left_ref[0] = src_left_tst[0] = src_top_left_ref[0]; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data() + left_offset, ref_buf.src_top.data(), + ref_buf.src_top_left.data(), ref_buf.src_top_right.data(), + ref_buf.src_bot_left.data(), ref_buf.avail.data(), + ref_buf.sao_offset.data(), wd, ht); - for (int i = 0; i < 8; i++) { - avail[i] = dist_avail(rng) ? 255 : 0; - } + tst_fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data() + left_offset, tst_buf.src_top.data(), + tst_buf.src_top_left.data(), tst_buf.src_top_right.data(), + tst_buf.src_bot_left.data(), tst_buf.avail.data(), + tst_buf.sao_offset.data(), wd, ht); - for (int i = 1; i < 5; i++) { - sao_offset[i] = dist_offset(rng); - } + VerifyBuffers(); } int wd, ht, offset_val; IV_ARCH_T arch; - int stride, total_ht, src_size, src_offset; - std::vector src_ref; - std::vector src_tst; - std::vector src_left_ref; - std::vector src_left_tst; - std::vector src_top_ref; - std::vector src_top_tst; - std::vector src_top_left_ref; - std::vector src_top_left_tst; - std::vector src_top_right_ref; - std::vector src_top_right_tst; - std::vector src_bot_left_ref; - std::vector src_bot_left_tst; - std::vector avail; - std::vector sao_offset; + SaoLumaBuffers ref_buf; + SaoLumaBuffers tst_buf; const ihevc_func_selector_t* ref; const ihevc_func_selector_t* tst; }; @@ -168,96 +117,52 @@ class SaoChromaTest : public ::testing::TestWithParam { std::tie(wd, ht) = block_size; // wd is chroma component size, 2 * wd is bytes - stride = 2 * wd + 32; - total_ht = ht + 32; - src_size = stride * total_ht; - src_offset = 16 * stride + 16; - - src_ref.resize(src_size); - src_tst.resize(src_size); - - src_left_ref.resize(2 * ht + 8 + 2); - src_left_tst.resize(2 * ht + 8 + 2); - src_top_ref.resize(2 * wd + 8); - src_top_tst.resize(2 * wd + 8); - src_top_left_ref.resize(8); - src_top_left_tst.resize(8); - src_top_right_ref.resize(8); - src_top_right_tst.resize(8); - src_bot_left_ref.resize(8); - src_bot_left_tst.resize(8); - - avail.resize(8); - - sao_offset_u.resize(8); - sao_offset_v.resize(8); - sao_offset_u[0] = sao_offset_v[0] = 0; - ref = get_ref_func_ptr(); tst = get_tst_func_ptr(arch); } void InitializeBuffers() { - std::mt19937 rng(42); - std::uniform_int_distribution dist(0, 255); - std::uniform_int_distribution dist_avail(0, 1); - std::uniform_int_distribution dist_offset(-7, 7); - - for (int i = 0; i < src_size; i++) { - uint8_t val = dist(rng); - src_ref[i] = val; - src_tst[i] = val; - } + ref_buf = CreateSaoChromaBuffers(wd, ht); + tst_buf = ref_buf; + } - for (size_t i = 0; i < src_left_ref.size(); i++) { - uint8_t val = dist(rng); - src_left_ref[i] = val; - src_left_tst[i] = val; - } + void VerifyBuffers() { + compare_output(ref_buf.src.data() + ref_buf.src_offset, + tst_buf.src.data() + tst_buf.src_offset, 2 * wd, ht, + ref_buf.stride); + ASSERT_EQ(ref_buf.src_left, tst_buf.src_left); + ASSERT_EQ(ref_buf.src_top, tst_buf.src_top); + ASSERT_EQ(ref_buf.src_top_left, tst_buf.src_top_left); + } - for (size_t i = 0; i < src_top_ref.size(); i++) { - uint8_t val = dist(rng); - src_top_ref[i] = val; - src_top_tst[i] = val; - } + void RunEdgeOffset(int edge_class) { + InitializeBuffers(); + int left_offset = (edge_class == 2) ? 2 : 0; - for (size_t i = 0; i < src_top_left_ref.size(); i++) { - src_top_left_ref[i] = src_top_left_tst[i] = dist(rng); - src_top_right_ref[i] = src_top_right_tst[i] = dist(rng); - src_bot_left_ref[i] = src_bot_left_tst[i] = dist(rng); - } + auto* ref_fn = GetSaoEdgeOffsetChromaFn(ref, edge_class); + auto* tst_fn = GetSaoEdgeOffsetChromaFn(tst, edge_class); - src_left_ref[0] = src_left_tst[0] = src_top_left_ref[0]; - src_left_ref[1] = src_left_tst[1] = src_top_left_ref[1]; + ref_fn(ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data() + left_offset, ref_buf.src_top.data(), + ref_buf.src_top_left.data(), ref_buf.src_top_right.data(), + ref_buf.src_bot_left.data(), ref_buf.avail.data(), + ref_buf.sao_offset_u.data(), ref_buf.sao_offset_v.data(), 2 * wd, + ht); - for (int i = 0; i < 8; i++) { - avail[i] = dist_avail(rng) ? 255 : 0; - } + tst_fn(tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data() + left_offset, tst_buf.src_top.data(), + tst_buf.src_top_left.data(), tst_buf.src_top_right.data(), + tst_buf.src_bot_left.data(), tst_buf.avail.data(), + tst_buf.sao_offset_u.data(), tst_buf.sao_offset_v.data(), 2 * wd, + ht); - for (int i = 1; i < 5; i++) { - sao_offset_u[i] = dist_offset(rng); - sao_offset_v[i] = dist_offset(rng); - } + VerifyBuffers(); } int wd, ht, offset_val; IV_ARCH_T arch; - int stride, total_ht, src_size, src_offset; - std::vector src_ref; - std::vector src_tst; - std::vector src_left_ref; - std::vector src_left_tst; - std::vector src_top_ref; - std::vector src_top_tst; - std::vector src_top_left_ref; - std::vector src_top_left_tst; - std::vector src_top_right_ref; - std::vector src_top_right_tst; - std::vector src_bot_left_ref; - std::vector src_bot_left_tst; - std::vector avail; - std::vector sao_offset_u; - std::vector sao_offset_v; + SaoChromaBuffers ref_buf; + SaoChromaBuffers tst_buf; const ihevc_func_selector_t* ref; const ihevc_func_selector_t* tst; }; @@ -268,21 +173,19 @@ class SaoBandOffsetLumaTest : public SaoLumaTest {}; TEST_P(SaoBandOffsetLumaTest, Run) { InitializeBuffers(); - ref->ihevc_sao_band_offset_luma_fptr(src_ref.data() + src_offset, stride, - src_left_ref.data(), src_top_ref.data(), - src_top_left_ref.data(), offset_val, - sao_offset.data(), wd, ht); - - tst->ihevc_sao_band_offset_luma_fptr(src_tst.data() + src_offset, stride, - src_left_tst.data(), src_top_tst.data(), - src_top_left_tst.data(), offset_val, - sao_offset.data(), wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); + GetSaoBandOffsetLumaFn(ref)( + ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), offset_val, ref_buf.sao_offset.data(), wd, + ht); + + GetSaoBandOffsetLumaFn(tst)( + tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), offset_val, tst_buf.sao_offset.data(), wd, + ht); + + VerifyBuffers(); } class SaoBandOffsetChromaTest : public SaoChromaTest {}; @@ -292,115 +195,40 @@ TEST_P(SaoBandOffsetChromaTest, Run) { int offset_val_u = offset_val; int offset_val_v = (offset_val + 4) % 32; - ref->ihevc_sao_band_offset_chroma_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data(), - src_top_ref.data(), src_top_left_ref.data(), offset_val_u, offset_val_v, - sao_offset_u.data(), sao_offset_v.data(), 2 * wd, ht); - - tst->ihevc_sao_band_offset_chroma_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data(), - src_top_tst.data(), src_top_left_tst.data(), offset_val_u, offset_val_v, - sao_offset_u.data(), sao_offset_v.data(), 2 * wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, 2 * wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); -} + GetSaoBandOffsetChromaFn(ref)( + ref_buf.src.data() + ref_buf.src_offset, ref_buf.stride, + ref_buf.src_left.data(), ref_buf.src_top.data(), + ref_buf.src_top_left.data(), offset_val_u, offset_val_v, + ref_buf.sao_offset_u.data(), ref_buf.sao_offset_v.data(), 2 * wd, ht); -class SaoEdgeOffsetClass0Test : public SaoLumaTest {}; -TEST_P(SaoEdgeOffsetClass0Test, Run) { - InitializeBuffers(); + GetSaoBandOffsetChromaFn(tst)( + tst_buf.src.data() + tst_buf.src_offset, tst_buf.stride, + tst_buf.src_left.data(), tst_buf.src_top.data(), + tst_buf.src_top_left.data(), offset_val_u, offset_val_v, + tst_buf.sao_offset_u.data(), tst_buf.sao_offset_v.data(), 2 * wd, ht); - ref->ihevc_sao_edge_offset_class0_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data(), - src_top_ref.data(), src_top_left_ref.data(), src_top_right_ref.data(), - src_bot_left_ref.data(), avail.data(), sao_offset.data(), wd, ht); - - tst->ihevc_sao_edge_offset_class0_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data(), - src_top_tst.data(), src_top_left_tst.data(), src_top_right_tst.data(), - src_bot_left_tst.data(), avail.data(), sao_offset.data(), wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); + VerifyBuffers(); } +class SaoEdgeOffsetClass0Test : public SaoLumaTest {}; +TEST_P(SaoEdgeOffsetClass0Test, Run) { RunEdgeOffset(0); } + class SaoEdgeOffsetClass0ChromaTest : public SaoChromaTest {}; TEST_P(SaoEdgeOffsetClass0ChromaTest, Run) { #if defined(__arm__) || defined(__aarch64__) || defined(__arm64__) if (wd % 8 == 4) { - GTEST_SKIP() << "Skipping failing ARM Chroma SAO Class 0 tests for width % 8 == 4"; + GTEST_SKIP() + << "Skipping failing ARM Chroma SAO Class 0 tests for width % 8 == 4"; } #endif - InitializeBuffers(); - - ref->ihevc_sao_edge_offset_class0_chroma_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data(), - src_top_ref.data(), src_top_left_ref.data(), src_top_right_ref.data(), - src_bot_left_ref.data(), avail.data(), sao_offset_u.data(), - sao_offset_v.data(), 2 * wd, ht); - - tst->ihevc_sao_edge_offset_class0_chroma_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data(), - src_top_tst.data(), src_top_left_tst.data(), src_top_right_tst.data(), - src_bot_left_tst.data(), avail.data(), sao_offset_u.data(), - sao_offset_v.data(), 2 * wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, 2 * wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); + RunEdgeOffset(0); } class SaoEdgeOffsetClass1Test : public SaoLumaTest {}; -TEST_P(SaoEdgeOffsetClass1Test, Run) { - InitializeBuffers(); - - ref->ihevc_sao_edge_offset_class1_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data(), - src_top_ref.data(), src_top_left_ref.data(), src_top_right_ref.data(), - src_bot_left_ref.data(), avail.data(), sao_offset.data(), wd, ht); - - tst->ihevc_sao_edge_offset_class1_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data(), - src_top_tst.data(), src_top_left_tst.data(), src_top_right_tst.data(), - src_bot_left_tst.data(), avail.data(), sao_offset.data(), wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); -} +TEST_P(SaoEdgeOffsetClass1Test, Run) { RunEdgeOffset(1); } class SaoEdgeOffsetClass1ChromaTest : public SaoChromaTest {}; -TEST_P(SaoEdgeOffsetClass1ChromaTest, Run) { - InitializeBuffers(); - - ref->ihevc_sao_edge_offset_class1_chroma_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data(), - src_top_ref.data(), src_top_left_ref.data(), src_top_right_ref.data(), - src_bot_left_ref.data(), avail.data(), sao_offset_u.data(), - sao_offset_v.data(), 2 * wd, ht); - - tst->ihevc_sao_edge_offset_class1_chroma_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data(), - src_top_tst.data(), src_top_left_tst.data(), src_top_right_tst.data(), - src_bot_left_tst.data(), avail.data(), sao_offset_u.data(), - sao_offset_v.data(), 2 * wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, 2 * wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); -} +TEST_P(SaoEdgeOffsetClass1ChromaTest, Run) { RunEdgeOffset(1); } class SaoEdgeOffsetClass2Test : public SaoLumaTest {}; TEST_P(SaoEdgeOffsetClass2Test, Run) { @@ -411,23 +239,7 @@ TEST_P(SaoEdgeOffsetClass2Test, Run) { GTEST_SKIP() << "Skipping Class 2 tests for x86 SIMD"; } #endif - InitializeBuffers(); - - ref->ihevc_sao_edge_offset_class2_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data() + 1, - src_top_ref.data(), src_top_left_ref.data(), src_top_right_ref.data(), - src_bot_left_ref.data(), avail.data(), sao_offset.data(), wd, ht); - - tst->ihevc_sao_edge_offset_class2_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data() + 1, - src_top_tst.data(), src_top_left_tst.data(), src_top_right_tst.data(), - src_bot_left_tst.data(), avail.data(), sao_offset.data(), wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); + RunEdgeOffset(2); } class SaoEdgeOffsetClass2ChromaTest : public SaoChromaTest {}; @@ -439,25 +251,7 @@ TEST_P(SaoEdgeOffsetClass2ChromaTest, Run) { GTEST_SKIP() << "Skipping Class 2 Chroma tests for x86 SIMD"; } #endif - InitializeBuffers(); - - ref->ihevc_sao_edge_offset_class2_chroma_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data() + 2, - src_top_ref.data(), src_top_left_ref.data(), src_top_right_ref.data(), - src_bot_left_ref.data(), avail.data(), sao_offset_u.data(), - sao_offset_v.data(), 2 * wd, ht); - - tst->ihevc_sao_edge_offset_class2_chroma_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data() + 2, - src_top_tst.data(), src_top_left_tst.data(), src_top_right_tst.data(), - src_bot_left_tst.data(), avail.data(), sao_offset_u.data(), - sao_offset_v.data(), 2 * wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, 2 * wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); + RunEdgeOffset(2); } class SaoEdgeOffsetClass3Test : public SaoLumaTest {}; @@ -469,47 +263,11 @@ TEST_P(SaoEdgeOffsetClass3Test, Run) { GTEST_SKIP() << "Skipping Class 3 tests for x86 SIMD"; } #endif - InitializeBuffers(); - - ref->ihevc_sao_edge_offset_class3_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data(), - src_top_ref.data(), src_top_left_ref.data(), src_top_right_ref.data(), - src_bot_left_ref.data(), avail.data(), sao_offset.data(), wd, ht); - - tst->ihevc_sao_edge_offset_class3_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data(), - src_top_tst.data(), src_top_left_tst.data(), src_top_right_tst.data(), - src_bot_left_tst.data(), avail.data(), sao_offset.data(), wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); + RunEdgeOffset(3); } class SaoEdgeOffsetClass3ChromaTest : public SaoChromaTest {}; -TEST_P(SaoEdgeOffsetClass3ChromaTest, Run) { - InitializeBuffers(); - - ref->ihevc_sao_edge_offset_class3_chroma_fptr( - src_ref.data() + src_offset, stride, src_left_ref.data(), - src_top_ref.data(), src_top_left_ref.data(), src_top_right_ref.data(), - src_bot_left_ref.data(), avail.data(), sao_offset_u.data(), - sao_offset_v.data(), 2 * wd, ht); - - tst->ihevc_sao_edge_offset_class3_chroma_fptr( - src_tst.data() + src_offset, stride, src_left_tst.data(), - src_top_tst.data(), src_top_left_tst.data(), src_top_right_tst.data(), - src_bot_left_tst.data(), avail.data(), sao_offset_u.data(), - sao_offset_v.data(), 2 * wd, ht); - - compare_sao_output(src_ref.data() + src_offset, src_tst.data() + src_offset, - stride, 2 * wd, ht); - ASSERT_EQ(src_left_ref, src_left_tst); - ASSERT_EQ(src_top_ref, src_top_tst); - ASSERT_EQ(src_top_left_ref, src_top_left_tst); -} +TEST_P(SaoEdgeOffsetClass3ChromaTest, Run) { RunEdgeOffset(3); } // ---------------------------- Instantiation -------------------------------- diff --git a/tests/common/ihevc_sao_utils.cc b/tests/common/ihevc_sao_utils.cc new file mode 100644 index 00000000..d4d7be29 --- /dev/null +++ b/tests/common/ihevc_sao_utils.cc @@ -0,0 +1,120 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "ihevc_sao_utils.h" + +ihevc_sao_band_offset_luma_ft* GetSaoBandOffsetLumaFn( + const ihevc_func_selector_t* selector) { + return selector ? selector->ihevc_sao_band_offset_luma_fptr : nullptr; +} + +ihevc_sao_band_offset_chroma_ft* GetSaoBandOffsetChromaFn( + const ihevc_func_selector_t* selector) { + return selector ? selector->ihevc_sao_band_offset_chroma_fptr : nullptr; +} + +ihevc_sao_edge_offset_class0_ft* GetSaoEdgeOffsetLumaFn( + const ihevc_func_selector_t* selector, int edge_class) { + if (!selector) return nullptr; + switch (edge_class) { + case 0: + return selector->ihevc_sao_edge_offset_class0_fptr; + case 1: + return selector->ihevc_sao_edge_offset_class1_fptr; + case 2: + return selector->ihevc_sao_edge_offset_class2_fptr; + case 3: + return selector->ihevc_sao_edge_offset_class3_fptr; + default: + return nullptr; + } +} + +ihevc_sao_edge_offset_class0_chroma_ft* GetSaoEdgeOffsetChromaFn( + const ihevc_func_selector_t* selector, int edge_class) { + if (!selector) return nullptr; + switch (edge_class) { + case 0: + return selector->ihevc_sao_edge_offset_class0_chroma_fptr; + case 1: + return selector->ihevc_sao_edge_offset_class1_chroma_fptr; + case 2: + return selector->ihevc_sao_edge_offset_class2_chroma_fptr; + case 3: + return selector->ihevc_sao_edge_offset_class3_chroma_fptr; + default: + return nullptr; + } +} + +ihevc_hbd_sao_band_offset_luma_ft* GetHbdSaoBandOffsetLumaFn(IV_ARCH_T arch) { + if (arch == ARCH_NA) { + return ihevc_hbd_sao_band_offset_luma; + } + // Future SIMD implementations can be added here + return nullptr; +} + +ihevc_hbd_sao_band_offset_chroma_ft* GetHbdSaoBandOffsetChromaFn( + IV_ARCH_T arch) { + if (arch == ARCH_NA) { + return ihevc_hbd_sao_band_offset_chroma; + } + // Future SIMD implementations can be added here + return nullptr; +} + +ihevc_hbd_sao_edge_offset_class0_ft* GetHbdSaoEdgeOffsetLumaFn(IV_ARCH_T arch, + int edge_class) { + if (arch == ARCH_NA) { + switch (edge_class) { + case 0: + return ihevc_hbd_sao_edge_offset_class0; + case 1: + return ihevc_hbd_sao_edge_offset_class1; + case 2: + return ihevc_hbd_sao_edge_offset_class2; + case 3: + return ihevc_hbd_sao_edge_offset_class3; + default: + return nullptr; + } + } + // Future SIMD implementations can be added here + return nullptr; +} + +ihevc_hbd_sao_edge_offset_class0_chroma_ft* GetHbdSaoEdgeOffsetChromaFn( + IV_ARCH_T arch, int edge_class) { + if (arch == ARCH_NA) { + switch (edge_class) { + case 0: + return ihevc_hbd_sao_edge_offset_class0_chroma; + case 1: + return ihevc_hbd_sao_edge_offset_class1_chroma; + case 2: + return ihevc_hbd_sao_edge_offset_class2_chroma; + case 3: + return ihevc_hbd_sao_edge_offset_class3_chroma; + default: + return nullptr; + } + } + // Future SIMD implementations can be added here + return nullptr; +} diff --git a/tests/common/ihevc_sao_utils.h b/tests/common/ihevc_sao_utils.h new file mode 100644 index 00000000..814b155e --- /dev/null +++ b/tests/common/ihevc_sao_utils.h @@ -0,0 +1,208 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +// clang-format off +#include "ihevc_typedefs.h" +#include "ihevc_defs.h" +extern "C" { +#include "ihevc_sao.h" +#include "ihevc_function_selector.h" +#include "iv.h" +} +// clang-format on + +#include "BenchmarkCommon.h" +#include "TestCommon.h" + +// Configuration for SAO Band Offset benchmarks +struct SaoBandConfig { + int wd; + int ht; + int sao_band_pos; + IV_ARCH_T arch; + int bit_depth = 8; +}; + +// Configuration for SAO Edge Offset benchmarks +struct SaoEdgeConfig { + int edge_class; // 0, 1, 2, 3 + int wd; + int ht; + IV_ARCH_T arch; + int bit_depth = 8; +}; + +// Buffers structure for SAO Luma operations +template +struct SaoLumaBuffers { + int stride; + int total_ht; + int src_size; + int src_offset; + std::vector src; + std::vector src_left; + std::vector src_top; + std::vector src_top_left; + std::vector src_top_right; + std::vector src_bot_left; + std::vector avail; + std::vector sao_offset; +}; + +// Buffers structure for SAO Chroma operations +template +struct SaoChromaBuffers { + int stride; + int total_ht; + int src_size; + int src_offset; + std::vector src; + std::vector src_left; + std::vector src_top; + std::vector src_top_left; + std::vector src_top_right; + std::vector src_bot_left; + std::vector avail; + std::vector sao_offset_u; + std::vector sao_offset_v; +}; + +// Creates and initializes Luma SAO buffers +template +SaoLumaBuffers CreateSaoLumaBuffers(int wd, int ht, int bit_depth = 8) { + SaoLumaBuffers b; + b.stride = wd + 32; + b.total_ht = ht + 32; + b.src_size = b.stride * b.total_ht; + b.src_offset = 16 * b.stride + 16; + + b.src.resize(b.src_size); + b.src_left.resize(ht + 8 + 1); + b.src_top.resize(wd + 8); + b.src_top_left.resize(8); + b.src_top_right.resize(8); + b.src_bot_left.resize(8); + b.avail.resize(8); + b.sao_offset.resize(8, 0); + + const T max_val = static_cast((1 << bit_depth) - 1); + FillRandom(b.src, static_cast(0), max_val, 42); + FillRandom(b.src_left, static_cast(0), max_val, 43); + FillRandom(b.src_top, static_cast(0), max_val, 44); + FillRandom(b.src_top_left, static_cast(0), max_val, 45); + FillRandom(b.src_top_right, static_cast(0), max_val, 46); + FillRandom(b.src_bot_left, static_cast(0), max_val, 47); + + std::mt19937 rng(48); + std::uniform_int_distribution dist_avail(0, 1); + std::uniform_int_distribution dist_offset(-7, 7); + for (size_t i = 0; i < 8; ++i) { + b.avail[i] = dist_avail(rng) ? 255 : 0; + } + b.src_left[0] = b.src_top_left[0]; + + for (int i = 1; i < 5; ++i) { + b.sao_offset[i] = dist_offset(rng); + } + return b; +} + +// Creates and initializes Chroma SAO buffers +template +SaoChromaBuffers CreateSaoChromaBuffers(int wd, int ht, int bit_depth = 8) { + SaoChromaBuffers b; + b.stride = 2 * wd + 32; + b.total_ht = ht + 32; + b.src_size = b.stride * b.total_ht; + b.src_offset = 16 * b.stride + 16; + + b.src.resize(b.src_size); + b.src_left.resize(2 * ht + 8 + 2); + b.src_top.resize(2 * wd + 8); + b.src_top_left.resize(8); + b.src_top_right.resize(8); + b.src_bot_left.resize(8); + b.avail.resize(8); + b.sao_offset_u.resize(8, 0); + b.sao_offset_v.resize(8, 0); + + const T max_val = static_cast((1 << bit_depth) - 1); + FillRandom(b.src, static_cast(0), max_val, 42); + FillRandom(b.src_left, static_cast(0), max_val, 43); + FillRandom(b.src_top, static_cast(0), max_val, 44); + FillRandom(b.src_top_left, static_cast(0), max_val, 45); + FillRandom(b.src_top_right, static_cast(0), max_val, 46); + FillRandom(b.src_bot_left, static_cast(0), max_val, 47); + + std::mt19937 rng(48); + std::uniform_int_distribution dist_avail(0, 1); + std::uniform_int_distribution dist_offset(-7, 7); + for (size_t i = 0; i < 8; ++i) { + b.avail[i] = dist_avail(rng) ? 255 : 0; + } + b.src_left[0] = b.src_top_left[0]; + b.src_left[1] = b.src_top_left[1]; + + for (int i = 1; i < 5; ++i) { + b.sao_offset_u[i] = dist_offset(rng); + b.sao_offset_v[i] = dist_offset(rng); + } + return b; +} + +// 8-bit SAO function getters +ihevc_sao_band_offset_luma_ft* GetSaoBandOffsetLumaFn( + const ihevc_func_selector_t* selector); + +ihevc_sao_band_offset_chroma_ft* GetSaoBandOffsetChromaFn( + const ihevc_func_selector_t* selector); + +ihevc_sao_edge_offset_class0_ft* GetSaoEdgeOffsetLumaFn( + const ihevc_func_selector_t* selector, int edge_class); + +ihevc_sao_edge_offset_class0_chroma_ft* GetSaoEdgeOffsetChromaFn( + const ihevc_func_selector_t* selector, int edge_class); + +// HBD SAO function getters +ihevc_hbd_sao_band_offset_luma_ft* GetHbdSaoBandOffsetLumaFn(IV_ARCH_T arch); + +ihevc_hbd_sao_band_offset_chroma_ft* GetHbdSaoBandOffsetChromaFn( + IV_ARCH_T arch); + +ihevc_hbd_sao_edge_offset_class0_ft* GetHbdSaoEdgeOffsetLumaFn(IV_ARCH_T arch, + int edge_class); + +ihevc_hbd_sao_edge_offset_class0_chroma_ft* GetHbdSaoEdgeOffsetChromaFn( + IV_ARCH_T arch, int edge_class); + +// Benchmark representative block sizes +inline const std::vector>& GetSaoLumaBenchmarkSizes() { + return GetBenchmarkLumaPUSizes(); +} + +inline const std::vector>& GetSaoChromaBenchmarkSizes() { + return GetBenchmarkChromaPUSizes(); +} diff --git a/tests/common/ihevc_weighted_pred_benchmark.cc b/tests/common/ihevc_weighted_pred_benchmark.cc new file mode 100644 index 00000000..99e155f6 --- /dev/null +++ b/tests/common/ihevc_weighted_pred_benchmark.cc @@ -0,0 +1,209 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "ihevc_defs.h" +#include "ihevc_function_selector.h" +#include "ihevc_macros.h" +#include "ihevc_platform_macros.h" +#include "ihevc_typedefs.h" +#include "iv.h" +} + +#include "BenchmarkCommon.h" +#include "TestCommon.h" +#include "func_selector.h" + +namespace { + +enum class WeightedPredType { + kUniLuma, + kUniChroma, + kBiLuma, + kBiChroma, + kBiDefaultLuma, + kBiDefaultChroma, +}; + +struct WeightedPredBenchConfig { + int wd; + int ht; + WeightedPredType type; + IV_ARCH_T arch; +}; + +void BM_WeightedPred(benchmark::State& state, WeightedPredBenchConfig config) { + const int wd = config.wd; + const int ht = config.ht; + const WeightedPredType type = config.type; + const IV_ARCH_T arch = config.arch; + + const ihevc_func_selector_t* selector = get_func_ptr(arch); + if (!selector) { + state.SkipWithError("Architecture not supported on this platform"); + return; + } + + const bool is_chroma = (type == WeightedPredType::kUniChroma || + type == WeightedPredType::kBiChroma || + type == WeightedPredType::kBiDefaultChroma); + const int cols = is_chroma ? (2 * wd) : wd; + const int src_strd = cols; + const int dst_strd = cols; + + std::vector src1(src_strd * ht + 16); + std::vector src2(src_strd * ht + 16); + FillRandom(src1, static_cast(-8192), static_cast(8191), 42); + FillRandom(src2, static_cast(-8192), static_cast(8191), 43); + + std::vector dst(dst_strd * ht + 16, 0xAA); + std::vector ref_dst(dst_strd * ht + 16, 0xAA); + + const int wgt0 = 64; + const int wgt1 = 64; + const int off0 = 0; + const int off1 = 0; + const int shift = 7; + const int lvl_shift1 = 0; + const int lvl_shift2 = 0; + + const ihevc_func_selector_t* ref_sel = get_ref_func_ptr(); + + auto run_wp_bench = [&](auto fn, auto ref_fn, auto invoke) { + if (!fn) { + state.SkipWithError("Target function pointer is null"); + return; + } + if (arch != ARCH_NA && ref_fn) { + invoke(ref_fn, ref_dst.data()); + invoke(fn, dst.data()); + if (!VerifyOutput2D(ref_dst.data(), dst.data(), cols, ht, dst_strd)) { + state.SkipWithError("Output mismatch between SIMD and C reference"); + return; + } + } + for (auto _ : state) { + invoke(fn, dst.data()); + benchmark::DoNotOptimize(dst.data()); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * cols * ht); + state.SetBytesProcessed(state.iterations() * cols * ht * sizeof(UWORD8)); + }; + + switch (type) { + case WeightedPredType::kUniLuma: + run_wp_bench(selector->ihevc_weighted_pred_uni_fptr, + ref_sel->ihevc_weighted_pred_uni_fptr, + [&](auto f, UWORD8* d) { + f(src1.data(), d, src_strd, dst_strd, wgt0, off0, shift, + lvl_shift1, ht, wd); + }); + break; + case WeightedPredType::kUniChroma: + run_wp_bench(selector->ihevc_weighted_pred_chroma_uni_fptr, + ref_sel->ihevc_weighted_pred_chroma_uni_fptr, + [&](auto f, UWORD8* d) { + f(src1.data(), d, src_strd, dst_strd, wgt0, wgt1, off0, + off1, shift, lvl_shift1, ht, wd); + }); + break; + case WeightedPredType::kBiLuma: + run_wp_bench(selector->ihevc_weighted_pred_bi_fptr, + ref_sel->ihevc_weighted_pred_bi_fptr, + [&](auto f, UWORD8* d) { + f(src1.data(), src2.data(), d, src_strd, src_strd, + dst_strd, wgt0, off0, wgt1, off1, shift, lvl_shift1, + lvl_shift2, ht, wd); + }); + break; + case WeightedPredType::kBiChroma: + run_wp_bench(selector->ihevc_weighted_pred_chroma_bi_fptr, + ref_sel->ihevc_weighted_pred_chroma_bi_fptr, + [&](auto f, UWORD8* d) { + f(src1.data(), src2.data(), d, src_strd, src_strd, + dst_strd, wgt0, wgt0, off0, off0, wgt1, wgt1, off1, off1, + shift, lvl_shift1, lvl_shift2, ht, wd); + }); + break; + case WeightedPredType::kBiDefaultLuma: + run_wp_bench(selector->ihevc_weighted_pred_bi_default_fptr, + ref_sel->ihevc_weighted_pred_bi_default_fptr, + [&](auto f, UWORD8* d) { + f(src1.data(), src2.data(), d, src_strd, src_strd, + dst_strd, lvl_shift1, lvl_shift2, ht, wd); + }); + break; + case WeightedPredType::kBiDefaultChroma: + run_wp_bench(selector->ihevc_weighted_pred_chroma_bi_default_fptr, + ref_sel->ihevc_weighted_pred_chroma_bi_default_fptr, + [&](auto f, UWORD8* d) { + f(src1.data(), src2.data(), d, src_strd, src_strd, + dst_strd, lvl_shift1, lvl_shift2, ht, wd); + }); + break; + } +} + +} // namespace + +void RegisterAllBenchmarks() { + struct WpOpInfo { + WeightedPredType type; + const char* name; + bool is_chroma; + }; + + const WpOpInfo ops[] = { + {WeightedPredType::kUniLuma, "uni_luma", false}, + {WeightedPredType::kUniChroma, "uni_chroma", true}, + {WeightedPredType::kBiLuma, "bi_luma", false}, + {WeightedPredType::kBiChroma, "bi_chroma", true}, + {WeightedPredType::kBiDefaultLuma, "bi_default_luma", false}, + {WeightedPredType::kBiDefaultChroma, "bi_default_chroma", true}, + }; + + for (const auto& op : ops) { + const auto& sizes = + op.is_chroma ? GetBenchmarkChromaPUSizes() : GetBenchmarkLumaPUSizes(); + for (const auto& size : sizes) { + const int wd = size.first; + const int ht = size.second; + for (auto arch : GetBenchmarkArchitectures()) { + std::string arch_name = GetArchName(arch); + std::string name = "BM_WeightedPred/" + std::string(op.name) + "/" + + std::to_string(wd) + "x" + std::to_string(ht) + "/" + + arch_name; + + WeightedPredBenchConfig cfg{wd, ht, op.type, arch}; + benchmark::RegisterBenchmark(name.c_str(), [cfg](benchmark::State& st) { + BM_WeightedPred(st, cfg); + })->Unit(benchmark::kNanosecond); + } + } + } +} diff --git a/tests/common/ihevc_weighted_pred_test.cc b/tests/common/ihevc_weighted_pred_test.cc index 3eb16910..d33bb4d5 100644 --- a/tests/common/ihevc_weighted_pred_test.cc +++ b/tests/common/ihevc_weighted_pred_test.cc @@ -56,7 +56,8 @@ std::string PrintWeightedPredTestParam( // ---------------------------- Test Classes --------------------------------- -class WeightedPredUniLumaTest +template +class WeightedPredTestBase : public ::testing::TestWithParam { protected: void SetUp() override { @@ -64,12 +65,18 @@ class WeightedPredUniLumaTest std::tie(block_size, src_stride_mul, dst_stride_mul, arch) = GetParam(); std::tie(wd, ht) = block_size; - src_strd = wd * src_stride_mul; - dst_strd = wd * dst_stride_mul; + src_strd = kComponents * wd * src_stride_mul; + dst_strd = kComponents * wd * dst_stride_mul; - src_buf.resize(src_strd * ht + 16); - dst_buf_ref.resize(dst_strd * ht + 16); - dst_buf_tst.resize(dst_strd * ht + 16); + src_buf1.resize(src_strd * ht + 16); + src_buf2.resize(src_strd * ht + 16); + dst_buf_ref.resize(dst_strd * ht + 16, 0xAA); + dst_buf_tst.resize(dst_strd * ht + 16, 0xAA); + + FillRandom(src_buf1, static_cast(-8192), static_cast(8191), + 42); + FillRandom(src_buf2, static_cast(-8192), static_cast(8191), + 43); tst = get_tst_func_ptr(arch); ref = get_ref_func_ptr(); @@ -77,7 +84,8 @@ class WeightedPredUniLumaTest int wd, ht, src_stride_mul, dst_stride_mul; int src_strd, dst_strd; - std::vector src_buf; + std::vector src_buf1; + std::vector src_buf2; std::vector dst_buf_ref; std::vector dst_buf_tst; IV_ARCH_T arch; @@ -85,11 +93,10 @@ class WeightedPredUniLumaTest const ihevc_func_selector_t* ref; }; +class WeightedPredUniLumaTest : public WeightedPredTestBase<1> {}; + TEST_P(WeightedPredUniLumaTest, Run) { std::mt19937 rng(42); - std::uniform_int_distribution src_dist(-8192, 8191); - for (auto& v : src_buf) v = src_dist(rng); - std::uniform_int_distribution wgt_dist(-128, 127); std::uniform_int_distribution off_dist(-128, 127); std::uniform_int_distribution shift_dist(7, 14); @@ -100,14 +107,11 @@ TEST_P(WeightedPredUniLumaTest, Run) { int shift = shift_dist(rng); int lvl_shift = lvl_shift_dist(rng) ? 8192 : 0; - std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xAA); - std::fill(dst_buf_tst.begin(), dst_buf_tst.end(), 0xAA); - - ref->ihevc_weighted_pred_uni_fptr(src_buf.data(), dst_buf_ref.data(), + ref->ihevc_weighted_pred_uni_fptr(src_buf1.data(), dst_buf_ref.data(), src_strd, dst_strd, wgt0, off0, shift, lvl_shift, ht, wd); - tst->ihevc_weighted_pred_uni_fptr(src_buf.data(), dst_buf_tst.data(), + tst->ihevc_weighted_pred_uni_fptr(src_buf1.data(), dst_buf_tst.data(), src_strd, dst_strd, wgt0, off0, shift, lvl_shift, ht, wd); @@ -117,40 +121,10 @@ TEST_P(WeightedPredUniLumaTest, Run) { // --------------------------------------------------------------------------- -class WeightedPredUniChromaTest - : public ::testing::TestWithParam { - protected: - void SetUp() override { - std::pair block_size; - std::tie(block_size, src_stride_mul, dst_stride_mul, arch) = GetParam(); - std::tie(wd, ht) = block_size; - - src_strd = 2 * wd * src_stride_mul; - dst_strd = 2 * wd * dst_stride_mul; - - src_buf.resize(src_strd * ht + 16); - dst_buf_ref.resize(dst_strd * ht + 16); - dst_buf_tst.resize(dst_strd * ht + 16); - - tst = get_tst_func_ptr(arch); - ref = get_ref_func_ptr(); - } - - int wd, ht, src_stride_mul, dst_stride_mul; - int src_strd, dst_strd; - std::vector src_buf; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - IV_ARCH_T arch; - const ihevc_func_selector_t* tst; - const ihevc_func_selector_t* ref; -}; +class WeightedPredUniChromaTest : public WeightedPredTestBase<2> {}; TEST_P(WeightedPredUniChromaTest, Run) { std::mt19937 rng(42); - std::uniform_int_distribution src_dist(-8192, 8191); - for (auto& v : src_buf) v = src_dist(rng); - std::uniform_int_distribution wgt_dist(-128, 127); std::uniform_int_distribution off_dist(-128, 127); std::uniform_int_distribution shift_dist(7, 14); @@ -162,15 +136,12 @@ TEST_P(WeightedPredUniChromaTest, Run) { int shift = shift_dist(rng); int lvl_shift = 0; - std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xAA); - std::fill(dst_buf_tst.begin(), dst_buf_tst.end(), 0xAA); - ref->ihevc_weighted_pred_chroma_uni_fptr( - src_buf.data(), dst_buf_ref.data(), src_strd, dst_strd, wgt0_cb, wgt0_cr, + src_buf1.data(), dst_buf_ref.data(), src_strd, dst_strd, wgt0_cb, wgt0_cr, off0_cb, off0_cr, shift, lvl_shift, ht, wd); tst->ihevc_weighted_pred_chroma_uni_fptr( - src_buf.data(), dst_buf_tst.data(), src_strd, dst_strd, wgt0_cb, wgt0_cr, + src_buf1.data(), dst_buf_tst.data(), src_strd, dst_strd, wgt0_cb, wgt0_cr, off0_cb, off0_cr, shift, lvl_shift, ht, wd); ASSERT_NO_FATAL_FAILURE( @@ -179,43 +150,10 @@ TEST_P(WeightedPredUniChromaTest, Run) { // --------------------------------------------------------------------------- -class WeightedPredBiLumaTest - : public ::testing::TestWithParam { - protected: - void SetUp() override { - std::pair block_size; - std::tie(block_size, src_stride_mul, dst_stride_mul, arch) = GetParam(); - std::tie(wd, ht) = block_size; - - src_strd = wd * src_stride_mul; - dst_strd = wd * dst_stride_mul; - - src_buf1.resize(src_strd * ht + 16); - src_buf2.resize(src_strd * ht + 16); - dst_buf_ref.resize(dst_strd * ht + 16); - dst_buf_tst.resize(dst_strd * ht + 16); - - tst = get_tst_func_ptr(arch); - ref = get_ref_func_ptr(); - } - - int wd, ht, src_stride_mul, dst_stride_mul; - int src_strd, dst_strd; - std::vector src_buf1; - std::vector src_buf2; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - IV_ARCH_T arch; - const ihevc_func_selector_t* tst; - const ihevc_func_selector_t* ref; -}; +class WeightedPredBiLumaTest : public WeightedPredTestBase<1> {}; TEST_P(WeightedPredBiLumaTest, Run) { std::mt19937 rng(42); - std::uniform_int_distribution src_dist(-8192, 8191); - for (auto& v : src_buf1) v = src_dist(rng); - for (auto& v : src_buf2) v = src_dist(rng); - std::uniform_int_distribution wgt_dist(-128, 127); std::uniform_int_distribution off_dist(-128, 127); std::uniform_int_distribution shift_dist(7, 14); @@ -229,9 +167,6 @@ TEST_P(WeightedPredBiLumaTest, Run) { int lvl_shift1 = lvl_shift_dist(rng) ? 8192 : 0; int lvl_shift2 = lvl_shift_dist(rng) ? 8192 : 0; - std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xAA); - std::fill(dst_buf_tst.begin(), dst_buf_tst.end(), 0xAA); - ref->ihevc_weighted_pred_bi_fptr( src_buf1.data(), src_buf2.data(), dst_buf_ref.data(), src_strd, src_strd, dst_strd, wgt0, off0, wgt1, off1, shift, lvl_shift1, lvl_shift2, ht, wd); @@ -246,43 +181,10 @@ TEST_P(WeightedPredBiLumaTest, Run) { // --------------------------------------------------------------------------- -class WeightedPredBiChromaTest - : public ::testing::TestWithParam { - protected: - void SetUp() override { - std::pair block_size; - std::tie(block_size, src_stride_mul, dst_stride_mul, arch) = GetParam(); - std::tie(wd, ht) = block_size; - - src_strd = 2 * wd * src_stride_mul; - dst_strd = 2 * wd * dst_stride_mul; - - src_buf1.resize(src_strd * ht + 16); - src_buf2.resize(src_strd * ht + 16); - dst_buf_ref.resize(dst_strd * ht + 16); - dst_buf_tst.resize(dst_strd * ht + 16); - - tst = get_tst_func_ptr(arch); - ref = get_ref_func_ptr(); - } - - int wd, ht, src_stride_mul, dst_stride_mul; - int src_strd, dst_strd; - std::vector src_buf1; - std::vector src_buf2; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - IV_ARCH_T arch; - const ihevc_func_selector_t* tst; - const ihevc_func_selector_t* ref; -}; +class WeightedPredBiChromaTest : public WeightedPredTestBase<2> {}; TEST_P(WeightedPredBiChromaTest, Run) { std::mt19937 rng(42); - std::uniform_int_distribution src_dist(-8192, 8191); - for (auto& v : src_buf1) v = src_dist(rng); - for (auto& v : src_buf2) v = src_dist(rng); - std::uniform_int_distribution wgt_dist(-128, 127); std::uniform_int_distribution off_dist(-128, 127); std::uniform_int_distribution shift_dist(7, 14); @@ -299,9 +201,6 @@ TEST_P(WeightedPredBiChromaTest, Run) { int lvl_shift1 = 0; int lvl_shift2 = 0; - std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xAA); - std::fill(dst_buf_tst.begin(), dst_buf_tst.end(), 0xAA); - ref->ihevc_weighted_pred_chroma_bi_fptr( src_buf1.data(), src_buf2.data(), dst_buf_ref.data(), src_strd, src_strd, dst_strd, wgt0_cb, wgt0_cr, off0_cb, off0_cr, wgt1_cb, wgt1_cr, off1_cb, @@ -318,50 +217,14 @@ TEST_P(WeightedPredBiChromaTest, Run) { // --------------------------------------------------------------------------- -class WeightedPredBiDefaultLumaTest - : public ::testing::TestWithParam { - protected: - void SetUp() override { - std::pair block_size; - std::tie(block_size, src_stride_mul, dst_stride_mul, arch) = GetParam(); - std::tie(wd, ht) = block_size; - - src_strd = wd * src_stride_mul; - dst_strd = wd * dst_stride_mul; - - src_buf1.resize(src_strd * ht + 16); - src_buf2.resize(src_strd * ht + 16); - dst_buf_ref.resize(dst_strd * ht + 16); - dst_buf_tst.resize(dst_strd * ht + 16); - - tst = get_tst_func_ptr(arch); - ref = get_ref_func_ptr(); - } - - int wd, ht, src_stride_mul, dst_stride_mul; - int src_strd, dst_strd; - std::vector src_buf1; - std::vector src_buf2; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - IV_ARCH_T arch; - const ihevc_func_selector_t* tst; - const ihevc_func_selector_t* ref; -}; +class WeightedPredBiDefaultLumaTest : public WeightedPredTestBase<1> {}; TEST_P(WeightedPredBiDefaultLumaTest, Run) { std::mt19937 rng(42); - std::uniform_int_distribution src_dist(-8192, 8191); - for (auto& v : src_buf1) v = src_dist(rng); - for (auto& v : src_buf2) v = src_dist(rng); - std::uniform_int_distribution lvl_shift_dist(0, 1); int lvl_shift1 = lvl_shift_dist(rng) ? 8192 : 0; int lvl_shift2 = lvl_shift_dist(rng) ? 8192 : 0; - std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xAA); - std::fill(dst_buf_tst.begin(), dst_buf_tst.end(), 0xAA); - ref->ihevc_weighted_pred_bi_default_fptr( src_buf1.data(), src_buf2.data(), dst_buf_ref.data(), src_strd, src_strd, dst_strd, lvl_shift1, lvl_shift2, ht, wd); @@ -376,49 +239,12 @@ TEST_P(WeightedPredBiDefaultLumaTest, Run) { // --------------------------------------------------------------------------- -class WeightedPredBiDefaultChromaTest - : public ::testing::TestWithParam { - protected: - void SetUp() override { - std::pair block_size; - std::tie(block_size, src_stride_mul, dst_stride_mul, arch) = GetParam(); - std::tie(wd, ht) = block_size; - - src_strd = 2 * wd * src_stride_mul; - dst_strd = 2 * wd * dst_stride_mul; - - src_buf1.resize(src_strd * ht + 16); - src_buf2.resize(src_strd * ht + 16); - dst_buf_ref.resize(dst_strd * ht + 16); - dst_buf_tst.resize(dst_strd * ht + 16); - - tst = get_tst_func_ptr(arch); - ref = get_ref_func_ptr(); - } - - int wd, ht, src_stride_mul, dst_stride_mul; - int src_strd, dst_strd; - std::vector src_buf1; - std::vector src_buf2; - std::vector dst_buf_ref; - std::vector dst_buf_tst; - IV_ARCH_T arch; - const ihevc_func_selector_t* tst; - const ihevc_func_selector_t* ref; -}; +class WeightedPredBiDefaultChromaTest : public WeightedPredTestBase<2> {}; TEST_P(WeightedPredBiDefaultChromaTest, Run) { - std::mt19937 rng(42); - std::uniform_int_distribution src_dist(-8192, 8191); - for (auto& v : src_buf1) v = src_dist(rng); - for (auto& v : src_buf2) v = src_dist(rng); - int lvl_shift1 = 0; int lvl_shift2 = 0; - std::fill(dst_buf_ref.begin(), dst_buf_ref.end(), 0xAA); - std::fill(dst_buf_tst.begin(), dst_buf_tst.end(), 0xAA); - ref->ihevc_weighted_pred_chroma_bi_default_fptr( src_buf1.data(), src_buf2.data(), dst_buf_ref.data(), src_strd, src_strd, dst_strd, lvl_shift1, lvl_shift2, ht, wd);