#!/bin/sh ## Copyright (c) 2023, Alliance for Open Media. All rights reserved ## ## This source code is subject to the terms of the BSD 2 Clause License and ## the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License ## was not distributed with this source code in the LICENSE file, you can ## obtain it at www.aomedia.org/license/software. If the Alliance for Open ## Media Patent License 1.0 was not distributed with this source code in the ## PATENTS file, you can obtain it at www.aomedia.org/license/patent. ## ## This script checks the bit exactness between C and SIMD ## implementations of AV1 encoder. ## . $(dirname $0)/tools_common.sh PRESETS="good rt" LOWBD_CIF_CLIP="yuv_raw_input" LOWBD_480p_CLIP="yuv_480p_raw_input" LOWBD_720p_CLIP="y4m_720p_input" HIGHBD_CLIP="y4m_360p_10bit_input" SC_CLIP="y4m_screen_input" OUT_FILE_SUFFIX=".ivf" SCRIPT_DIR=$(dirname "$0") LIBAOM_SOURCE_DIR=$(cd ${SCRIPT_DIR}/..; pwd) # Clips used in test. YUV_RAW_INPUT="${LIBAOM_TEST_DATA_PATH}/hantro_collage_w352h288.yuv" YUV_480P_RAW_INPUT="${LIBAOM_TEST_DATA_PATH}/niklas_640_480_30.yuv" Y4M_360P_10BIT_INPUT="${LIBAOM_TEST_DATA_PATH}/crowd_run_360p_10_150f.y4m" Y4M_720P_INPUT="${LIBAOM_TEST_DATA_PATH}/niklas_1280_720_30.y4m" Y4M_SCREEN_INPUT="${LIBAOM_TEST_DATA_PATH}/wikipedia_420_360p_60f.y4m" # Number of frames to test. AV1_ENCODE_C_VS_SIMD_TEST_FRAME_LIMIT=35 # Create a temporary directory for output files. if [ -n "${TMPDIR}" ]; then AOM_TEST_TEMP_ROOT="${TMPDIR}" elif [ -n "${TEMPDIR}" ]; then AOM_TEST_TEMP_ROOT="${TEMPDIR}" else AOM_TEST_TEMP_ROOT=/tmp fi AOM_TEST_OUTPUT_DIR="${AOM_TEST_TEMP_ROOT}/av1_test_$$" if ! mkdir -p "${AOM_TEST_OUTPUT_DIR}" || \ [ ! -d "${AOM_TEST_OUTPUT_DIR}" ]; then echo "${0##*/}: Cannot create output directory, giving up." echo "${0##*/}: AOM_TEST_OUTPUT_DIR=${AOM_TEST_OUTPUT_DIR}" exit 1 fi elog() { echo "$@" 1>&2 } # Echoes path to $1 when it's executable and exists in ${AOM_TEST_OUTPUT_DIR}, # or an empty string. Caller is responsible for testing the string once the # function returns. av1_enc_tool_path() { local target="$1" local preset="$2" local tool_path="${AOM_TEST_OUTPUT_DIR}/build_target_${target}/aomenc_${preset}" if [ ! -x "${tool_path}" ]; then tool_path="" fi echo "${tool_path}" } # Environment check: Make sure input and source directories are available. av1_c_vs_simd_enc_verify_environment () { if [ ! -e "${YUV_RAW_INPUT}" ]; then elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." return 1 fi if [ ! -e "${Y4M_360P_10BIT_INPUT}" ]; then elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." return 1 fi if [ ! -e "${YUV_480P_RAW_INPUT}" ]; then elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." return 1 fi if [ ! -e "${Y4M_720P_INPUT}" ]; then elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." return 1 fi if [ ! -e "${Y4M_SCREEN_INPUT}" ]; then elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." return 1 fi if [ ! -d "$LIBAOM_SOURCE_DIR" ]; then elog "LIBAOM_SOURCE_DIR does not exist." return 1 fi } # This is not needed since tools_common.sh does the same cleanup. # Keep the code here for our reference. # cleanup() { # rm -rf ${AOM_TEST_OUTPUT_DIR} # } # Echo AOM_SIMD_CAPS_MASK for different instruction set architecture. avx2() { echo "0x1FF" } avx() { echo "0x17F" } sse4_2() { echo "0x13F" } sse4_1() { echo "0x03F" } ssse3() { echo "0x01F" } sse3() { echo "0x00F" } sse2() { echo "0x007" } get_bitrates() { local content=$1 local preset=$2 # Bit-rates: local bitrate_lowres_good="300" local bitrate_480p_good="500" local bitrate_720p_good="1000" local bitrate_scc_360p_good="500" local bitrate_lowres_rt="200" local bitrate_480p_rt="300" local bitrate_720p_rt="600" local bitrate_scc_360p_rt="300" local bitrate_hbd_360p="500" if [ "${preset}" = "good" ]; then if [ "${content}" = "yuv_raw_input" ]; then echo "${bitrate_lowres_good}" elif [ "${content}" = "yuv_480p_raw_input" ]; then echo "${bitrate_480p_good}" elif [ "${content}" = "y4m_720p_input" ]; then echo "${bitrate_720p_good}" elif [ "${content}" = "y4m_screen_input" ]; then echo "${bitrate_scc_360p_good}" elif [ "${content}" = "y4m_360p_10bit_input" ]; then echo "${bitrate_hbd_360p}" else elog "Invalid content" fi elif [ "${preset}" = "rt" ]; then if [ "${content}" = "yuv_raw_input" ]; then echo "${bitrate_lowres_rt}" elif [ "${content}" = "yuv_480p_raw_input" ]; then echo "${bitrate_480p_rt}" elif [ "${content}" = "y4m_720p_input" ]; then echo "${bitrate_720p_rt}" elif [ "${content}" = "y4m_screen_input" ]; then echo "${bitrate_scc_360p_rt}" elif [ "${content}" = "y4m_360p_10bit_input" ]; then echo "${bitrate_hbd_360p}" else elog "Invalid content" fi else elog "invalid preset" fi } # Echo clip details to be used as input to aomenc. yuv_raw_input() { echo ""${YUV_RAW_INPUT}" --width=352 --height=288 --bit-depth=8" } y4m_360p_10bit_input() { echo ""${Y4M_360P_10BIT_INPUT}" --bit-depth=10" } yuv_480p_raw_input() { echo ""${YUV_480P_RAW_INPUT}" --width=640 --height=480 --bit-depth=8" } y4m_720p_input() { echo ""${Y4M_720P_INPUT}" --bit-depth=8" } y4m_screen_input() { echo ""${Y4M_SCREEN_INPUT}" --tune-content=screen --enable-palette=1 --bit-depth=8" } has_x86_isa_extn() { instruction_set=$1 if ! grep -q "$instruction_set" /proc/cpuinfo; then # This instruction set is not supported. return 1 fi } # Echo good encode params for use with AV1 encoder. av1_encode_good_params() { echo "--good \ --ivf \ --profile=0 \ --static-thresh=0 \ --threads=1 \ --tile-columns=0 \ --tile-rows=0 \ --verbose \ --end-usage=vbr \ --kf-max-dist=160 \ --kf-min-dist=0 \ --max-q=63 \ --min-q=0 \ --overshoot-pct=100 \ --undershoot-pct=100 \ --passes=2 \ --arnr-maxframes=7 \ --arnr-strength=5 \ --auto-alt-ref=1 \ --drop-frame=0 \ --frame-parallel=0 \ --lag-in-frames=35 \ --maxsection-pct=2000 \ --minsection-pct=0 \ --sharpness=0" } # Echo realtime encode params for use with AV1 encoder. av1_encode_rt_params() { echo "--rt \ --ivf \ --profile=0 \ --static-thresh=0 \ --threads=1 \ --tile-columns=0 \ --tile-rows=0 \ --verbose \ --end-usage=cbr \ --kf-max-dist=90000 \ --max-q=58 \ --min-q=2 \ --overshoot-pct=50 \ --undershoot-pct=50 \ --passes=1 \ --aq-mode=3 \ --buf-initial-sz=500 \ --buf-optimal-sz=600 \ --buf-sz=1000 \ --coeff-cost-upd-freq=3 \ --dv-cost-upd-freq=3 \ --mode-cost-upd-freq=3 \ --mv-cost-upd-freq=3 \ --deltaq-mode=0 \ --enable-global-motion=0 \ --enable-obmc=0 \ --enable-order-hint=0 \ --enable-ref-frame-mvs=0 \ --enable-tpl-model=0 \ --enable-warped-motion=0 \ --lag-in-frames=0 \ --max-intra-rate=300 \ --noise-sensitivity=0" } # Configures for the given target in AOM_TEST_OUTPUT_DIR/build_target_${target} # directory. av1_enc_build() { local target="$1" local cmake_command="$2" local tmp_build_dir=${AOM_TEST_OUTPUT_DIR}/build_target_${target} if [ -d "$tmp_build_dir" ]; then rm -rf $tmp_build_dir fi mkdir -p $tmp_build_dir cd $tmp_build_dir local cmake_common_args="-DCONFIG_EXCLUDE_SIMD_MISMATCH=1 \ -DCMAKE_BUILD_TYPE=Release \ -DENABLE_CCACHE=1 \ '-DCMAKE_C_FLAGS_RELEASE=-O3 -g' \ '-DCMAKE_CXX_FLAGS_RELEASE=-O3 -g' \ -DENABLE_DOCS=0 -DENABLE_TESTS=0 -DENABLE_TOOLS=0" for preset in $PRESETS; do echo "Building target[${preset} encoding]: ${target}" if [ "${preset}" = "good" ]; then local cmake_extra_args="-DCONFIG_AV1_HIGHBITDEPTH=1" elif [ "${preset}" = "rt" ]; then local cmake_extra_args="-DCONFIG_REALTIME_ONLY=1 -DCONFIG_AV1_HIGHBITDEPTH=0" else elog "Invalid preset" return 1 fi if ! eval "$cmake_command" "${cmake_common_args}" "${cmake_extra_args}" \ ${devnull}; then elog "cmake failure" return 1 fi if ! eval make -j$(nproc) aomenc ${devnull}; then elog "build failure" return 1 fi mv aomenc aomenc_${preset} done echo "Done building target: ${target}" } compare_enc_output() { local target=$1 local cpu=$2 local clip=$3 local bitrate=$4 local preset=$5 if ! diff -q ${AOM_TEST_OUTPUT_DIR}/Out-generic-"${clip}"-${preset}-${bitrate}kbps-cpu${cpu}${OUT_FILE_SUFFIX} \ ${AOM_TEST_OUTPUT_DIR}/Out-${target}-"${clip}"-${preset}-${bitrate}kbps-cpu${cpu}${OUT_FILE_SUFFIX}; then elog "C vs ${target} encode mismatches for ${clip}, at ${bitrate} kbps, speed ${cpu}, ${preset} preset" return 1 fi } av1_enc_test() { local encoder="$1" local arch="$2" local target="$3" local preset="$4" if [ -z "$(av1_enc_tool_path "${target}" "${preset}")" ]; then elog "aomenc_{preset} not found. It must exist in ${AOM_TEST_OUTPUT_DIR}/build_target_${target} path" return 1 fi if [ "${preset}" = "good" ]; then if [ "${arch}" = "x86_64" ]; then local min_cpu_used=0 local max_cpu_used=6 elif [ "${arch}" = "x86" ]; then local min_cpu_used=2 local max_cpu_used=3 fi local test_params=av1_encode_good_params elif [ "${preset}" = "rt" ]; then local min_cpu_used=5 local max_cpu_used=11 local test_params=av1_encode_rt_params else elog "Invalid preset" return 1 fi for cpu in $(seq $min_cpu_used $max_cpu_used); do if [ "${preset}" = "good" ]; then if [ "${arch}" = "x86_64" ]; then if [ "${cpu}" -lt 2 ]; then local test_clips="${LOWBD_CIF_CLIP} ${HIGHBD_CLIP}" elif [ "${cpu}" -lt 5 ]; then local test_clips="${LOWBD_480p_CLIP} ${HIGHBD_CLIP}" else local test_clips="${LOWBD_720p_CLIP} ${HIGHBD_CLIP}" fi elif [ "${arch}" = "x86" ]; then local test_clips="${LOWBD_CIF_CLIP} ${HIGHBD_CLIP}" elif [ "${arch}" = "arm64" ]; then local test_clips="${LOWBD_CIF_CLIP} ${HIGHBD_CLIP}" fi elif [ "${preset}" = "rt" ]; then if [ "${cpu}" -lt 8 ]; then local test_clips="${LOWBD_CIF_CLIP} ${SC_CLIP}" else local test_clips="${LOWBD_480p_CLIP} ${SC_CLIP}" fi else elog "Invalid preset" return 1 fi for clip in ${test_clips}; do local test_bitrates=$(get_bitrates ${clip} ${preset}) for bitrate in ${test_bitrates}; do eval "${encoder}" $($clip) $($test_params) \ "--limit=${AV1_ENCODE_C_VS_SIMD_TEST_FRAME_LIMIT}" \ "--cpu-used=${cpu}" "--target-bitrate=${bitrate}" "-o" \ ${AOM_TEST_OUTPUT_DIR}/Out-${target}-"${clip}"-${preset}-${bitrate}kbps-cpu${cpu}${OUT_FILE_SUFFIX} \ ${devnull} if [ "${target}" != "generic" ]; then if ! compare_enc_output ${target} $cpu ${clip} $bitrate ${preset}; then # Found a mismatch return 1 fi fi done done done } av1_test_generic() { local arch=$1 local target="generic" if [ $arch = "x86_64" ]; then local cmake_command="cmake $LIBAOM_SOURCE_DIR -DAOM_TARGET_CPU=${target}" elif [ $arch = "x86" ]; then # As AV1 encode output differs for x86 32-bit and 64-bit platforms # (BUG=aomedia:3479), the x86 32-bit C-only build is generated separately. # The cmake command line option -DENABLE_MMX=0 flag disables all SIMD # optimizations, and generates a C-only binary. local cmake_command="cmake $LIBAOM_SOURCE_DIR -DENABLE_MMX=0 \ -DCMAKE_TOOLCHAIN_FILE=${LIBAOM_SOURCE_DIR}/build/cmake/toolchains/i686-linux-gcc.cmake" fi echo "Build for: Generic ${arch}" if ! av1_enc_build "${target}" "${cmake_command}"; then return 1 fi for preset in $PRESETS; do local encoder="$(av1_enc_tool_path "${target}" "${preset}")" av1_enc_test $encoder "${arch}" "${target}" "${preset}" done } # This function encodes AV1 bitstream by enabling SSE2, SSE3, SSSE3, SSE4_1, SSE4_2, AVX, AVX2 as # there are no functions with MMX, SSE and AVX512 specialization. # The value of environment variable 'AOM_SIMD_CAPS_MASK' controls enabling of different instruction # set extension optimizations. The value of the flag 'AOM_SIMD_CAPS_MASK' and the corresponding # instruction set extension optimization enabled are as follows: # SSE4_2 AVX2 AVX SSE4_1 SSSE3 SSE3 SSE2 SSE MMX # 1 1 1 1 1 1 1 1 1 -> 0x1FF -> Enable AVX2 and lower variants # 1 0 1 1 1 1 1 1 1 -> 0x17F -> Enable AVX and lower variants # 1 0 0 1 1 1 1 1 1 -> 0x13F -> Enable SSE4_2 and lower variants # 0 0 0 1 1 1 1 1 1 -> 0x03F -> Enable SSE4_1 and lower variants # 0 0 0 0 1 1 1 1 1 -> 0x01F -> Enable SSSE3 and lower variants # 0 0 0 0 0 1 1 1 1 -> 0x00F -> Enable SSE3 and lower variants # 0 0 0 0 0 0 1 1 1 -> 0x007 -> Enable SSE2 and lower variants # 0 0 0 0 0 0 0 1 1 -> 0x003 -> Enable SSE and lower variants # 0 0 0 0 0 0 0 0 1 -> 0x001 -> Enable MMX ## NOTE: In x86_64 platform, it is not possible to enable sse/mmx/c using "AOM_SIMD_CAPS_MASK" as # all x86_64 platforms implement sse2. av1_test_x86() { local arch=$1 if ! uname -m | grep -q "x86"; then elog "Machine architecture is not x86 or x86_64" return 0 fi if [ $arch = "x86" ]; then local target="x86-linux" local cmake_command="cmake \ $LIBAOM_SOURCE_DIR \ -DCMAKE_TOOLCHAIN_FILE=${LIBAOM_SOURCE_DIR}/build/cmake/toolchains/i686-linux-gcc.cmake" elif [ $arch = "x86_64" ]; then local target="x86_64-linux" local cmake_command="cmake $LIBAOM_SOURCE_DIR" fi # Available x86 isa variants: "avx2 avx sse4_2 sse4_1 ssse3 sse3 sse2" local x86_isa_variants="avx2 sse4_2 sse2" echo "Build for x86: ${target}" if ! av1_enc_build "${target}" "${cmake_command}"; then return 1 fi for preset in $PRESETS; do local encoder="$(av1_enc_tool_path "${target}" "${preset}")" for isa in $x86_isa_variants; do # Note that if has_x86_isa_extn returns 1, it is false, and vice versa. if ! has_x86_isa_extn $isa; then echo "${isa} is not supported in this machine" continue fi export AOM_SIMD_CAPS_MASK=$($isa) if ! av1_enc_test $encoder "${arch}" "${target}" "${preset}"; then # Found a mismatch return 1 fi unset AOM_SIMD_CAPS_MASK done done } av1_test_arm() { local arch="arm64" local target="arm64-linux-gcc" local cmake_command="cmake $LIBAOM_SOURCE_DIR \ -DCMAKE_TOOLCHAIN_FILE=$LIBAOM_SOURCE_DIR/build/cmake/toolchains/${target}.cmake \ -DCMAKE_C_FLAGS=-Wno-maybe-uninitialized" echo "Build for arm64: ${target}" if ! av1_enc_build "${target}" "${cmake_command}"; then return 1 fi for preset in $PRESETS; do local encoder="$(av1_enc_tool_path "${target}" "${preset}")" if ! av1_enc_test "qemu-aarch64 -L /usr/aarch64-linux-gnu ${encoder}" "${arch}" "${target}" "${preset}"; then # Found a mismatch return 1 fi done } av1_c_vs_simd_enc_test () { # Test x86 (32 bit) # x86 requires the i686-linux-gnu toolchain: # $ sudo apt-get install g++-i686-linux-gnu echo "av1 test for x86 (32 bit): Started." # Encode 'C' only av1_test_generic "x86" # Encode with SIMD optimizations enabled if ! av1_test_x86 "x86"; then echo "av1 test for x86 (32 bit): Done, test failed." return 1 else echo "av1 test for x86 (32 bit): Done, all tests passed." fi # Test x86_64 (64 bit) if [ "$(eval uname -m)" = "x86_64" ]; then echo "av1 test for x86_64 (64 bit): Started." # Encode 'C' only av1_test_generic "x86_64" # Encode with SIMD optimizations enabled if ! av1_test_x86 "x86_64"; then echo "av1 test for x86_64 (64 bit): Done, test failed." return 1 else echo "av1 test for x86_64 (64 bit): Done, all tests passed." fi fi # Test ARM echo "av1_test_arm: Started." if ! av1_test_arm; then echo "av1 test for arm: Done, test failed." return 1 else echo "av1 test for arm: Done, all tests passed." fi } run_tests av1_c_vs_simd_enc_verify_environment av1_c_vs_simd_enc_test