1#!/bin/sh 2## Copyright (c) 2023, Alliance for Open Media. All rights reserved 3## 4## This source code is subject to the terms of the BSD 2 Clause License and 5## the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License 6## was not distributed with this source code in the LICENSE file, you can 7## obtain it at www.aomedia.org/license/software. If the Alliance for Open 8## Media Patent License 1.0 was not distributed with this source code in the 9## PATENTS file, you can obtain it at www.aomedia.org/license/patent. 10## 11## This script checks the bit exactness between C and SIMD 12## implementations of AV1 encoder. 13## 14. $(dirname $0)/tools_common.sh 15 16PRESETS="good rt" 17LOWBD_CIF_CLIP="yuv_raw_input" 18LOWBD_480p_CLIP="yuv_480p_raw_input" 19LOWBD_720p_CLIP="y4m_720p_input" 20HIGHBD_CLIP="y4m_360p_10bit_input" 21SC_CLIP="y4m_screen_input" 22OUT_FILE_SUFFIX=".ivf" 23SCRIPT_DIR=$(dirname "$0") 24LIBAOM_SOURCE_DIR=$(cd ${SCRIPT_DIR}/..; pwd) 25 26# Clips used in test. 27YUV_RAW_INPUT="${LIBAOM_TEST_DATA_PATH}/hantro_collage_w352h288.yuv" 28YUV_480P_RAW_INPUT="${LIBAOM_TEST_DATA_PATH}/niklas_640_480_30.yuv" 29Y4M_360P_10BIT_INPUT="${LIBAOM_TEST_DATA_PATH}/crowd_run_360p_10_150f.y4m" 30Y4M_720P_INPUT="${LIBAOM_TEST_DATA_PATH}/niklas_1280_720_30.y4m" 31Y4M_SCREEN_INPUT="${LIBAOM_TEST_DATA_PATH}/wikipedia_420_360p_60f.y4m" 32 33# Number of frames to test. 34AV1_ENCODE_C_VS_SIMD_TEST_FRAME_LIMIT=35 35 36# Create a temporary directory for output files. 37if [ -n "${TMPDIR}" ]; then 38 AOM_TEST_TEMP_ROOT="${TMPDIR}" 39elif [ -n "${TEMPDIR}" ]; then 40 AOM_TEST_TEMP_ROOT="${TEMPDIR}" 41else 42 AOM_TEST_TEMP_ROOT=/tmp 43fi 44 45AOM_TEST_OUTPUT_DIR="${AOM_TEST_TEMP_ROOT}/av1_test_$$" 46 47if ! mkdir -p "${AOM_TEST_OUTPUT_DIR}" || \ 48 [ ! -d "${AOM_TEST_OUTPUT_DIR}" ]; then 49 echo "${0##*/}: Cannot create output directory, giving up." 50 echo "${0##*/}: AOM_TEST_OUTPUT_DIR=${AOM_TEST_OUTPUT_DIR}" 51 exit 1 52fi 53 54elog() { 55 echo "$@" 1>&2 56} 57 58# Echoes path to $1 when it's executable and exists in ${AOM_TEST_OUTPUT_DIR}, 59# or an empty string. Caller is responsible for testing the string once the 60# function returns. 61av1_enc_tool_path() { 62 local target="$1" 63 local preset="$2" 64 local tool_path="${AOM_TEST_OUTPUT_DIR}/build_target_${target}/aomenc_${preset}" 65 66 if [ ! -x "${tool_path}" ]; then 67 tool_path="" 68 fi 69 echo "${tool_path}" 70} 71 72# Environment check: Make sure input and source directories are available. 73av1_c_vs_simd_enc_verify_environment () { 74 if [ ! -e "${YUV_RAW_INPUT}" ]; then 75 elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." 76 return 1 77 fi 78 if [ ! -e "${Y4M_360P_10BIT_INPUT}" ]; then 79 elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." 80 return 1 81 fi 82 if [ ! -e "${YUV_480P_RAW_INPUT}" ]; then 83 elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." 84 return 1 85 fi 86 if [ ! -e "${Y4M_720P_INPUT}" ]; then 87 elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." 88 return 1 89 fi 90 if [ ! -e "${Y4M_SCREEN_INPUT}" ]; then 91 elog "libaom test data must exist in LIBAOM_TEST_DATA_PATH." 92 return 1 93 fi 94 if [ ! -d "$LIBAOM_SOURCE_DIR" ]; then 95 elog "LIBAOM_SOURCE_DIR does not exist." 96 return 1 97 fi 98} 99 100# This is not needed since tools_common.sh does the same cleanup. 101# Keep the code here for our reference. 102# cleanup() { 103# rm -rf ${AOM_TEST_OUTPUT_DIR} 104# } 105 106# Echo AOM_SIMD_CAPS_MASK for different instruction set architecture. 107avx2() { 108 echo "0x1FF" 109} 110 111avx() { 112 echo "0x17F" 113} 114 115sse4_2() { 116 echo "0x13F" 117} 118 119sse4_1() { 120 echo "0x03F" 121} 122 123ssse3() { 124 echo "0x01F" 125} 126 127sse3() { 128 echo "0x00F" 129} 130 131sse2() { 132 echo "0x007" 133} 134 135get_bitrates() { 136 local content=$1 137 local preset=$2 138 139 # Bit-rates: 140 local bitrate_lowres_good="300" 141 local bitrate_480p_good="500" 142 local bitrate_720p_good="1000" 143 local bitrate_scc_360p_good="500" 144 local bitrate_lowres_rt="200" 145 local bitrate_480p_rt="300" 146 local bitrate_720p_rt="600" 147 local bitrate_scc_360p_rt="300" 148 local bitrate_hbd_360p="500" 149 150 if [ "${preset}" = "good" ]; then 151 if [ "${content}" = "yuv_raw_input" ]; then 152 echo "${bitrate_lowres_good}" 153 elif [ "${content}" = "yuv_480p_raw_input" ]; then 154 echo "${bitrate_480p_good}" 155 elif [ "${content}" = "y4m_720p_input" ]; then 156 echo "${bitrate_720p_good}" 157 elif [ "${content}" = "y4m_screen_input" ]; then 158 echo "${bitrate_scc_360p_good}" 159 elif [ "${content}" = "y4m_360p_10bit_input" ]; then 160 echo "${bitrate_hbd_360p}" 161 else 162 elog "Invalid content" 163 fi 164 elif [ "${preset}" = "rt" ]; then 165 if [ "${content}" = "yuv_raw_input" ]; then 166 echo "${bitrate_lowres_rt}" 167 elif [ "${content}" = "yuv_480p_raw_input" ]; then 168 echo "${bitrate_480p_rt}" 169 elif [ "${content}" = "y4m_720p_input" ]; then 170 echo "${bitrate_720p_rt}" 171 elif [ "${content}" = "y4m_screen_input" ]; then 172 echo "${bitrate_scc_360p_rt}" 173 elif [ "${content}" = "y4m_360p_10bit_input" ]; then 174 echo "${bitrate_hbd_360p}" 175 else 176 elog "Invalid content" 177 fi 178 else 179 elog "invalid preset" 180 fi 181} 182 183# Echo clip details to be used as input to aomenc. 184yuv_raw_input() { 185 echo ""${YUV_RAW_INPUT}" 186 --width=352 187 --height=288 188 --bit-depth=8" 189} 190 191y4m_360p_10bit_input() { 192 echo ""${Y4M_360P_10BIT_INPUT}" 193 --bit-depth=10" 194} 195 196yuv_480p_raw_input() { 197 echo ""${YUV_480P_RAW_INPUT}" 198 --width=640 199 --height=480 200 --bit-depth=8" 201} 202 203y4m_720p_input() { 204 echo ""${Y4M_720P_INPUT}" 205 --bit-depth=8" 206} 207 208y4m_screen_input() { 209 echo ""${Y4M_SCREEN_INPUT}" 210 --tune-content=screen 211 --enable-palette=1 212 --bit-depth=8" 213} 214 215has_x86_isa_extn() { 216 instruction_set=$1 217 if ! grep -q "$instruction_set" /proc/cpuinfo; then 218 # This instruction set is not supported. 219 return 1 220 fi 221} 222 223# Echo good encode params for use with AV1 encoder. 224av1_encode_good_params() { 225 echo "--good \ 226 --ivf \ 227 --profile=0 \ 228 --static-thresh=0 \ 229 --threads=1 \ 230 --tile-columns=0 \ 231 --tile-rows=0 \ 232 --verbose \ 233 --end-usage=vbr \ 234 --kf-max-dist=160 \ 235 --kf-min-dist=0 \ 236 --max-q=63 \ 237 --min-q=0 \ 238 --overshoot-pct=100 \ 239 --undershoot-pct=100 \ 240 --passes=2 \ 241 --arnr-maxframes=7 \ 242 --arnr-strength=5 \ 243 --auto-alt-ref=1 \ 244 --drop-frame=0 \ 245 --frame-parallel=0 \ 246 --lag-in-frames=35 \ 247 --maxsection-pct=2000 \ 248 --minsection-pct=0 \ 249 --sharpness=0" 250} 251 252# Echo realtime encode params for use with AV1 encoder. 253av1_encode_rt_params() { 254 echo "--rt \ 255 --ivf \ 256 --profile=0 \ 257 --static-thresh=0 \ 258 --threads=1 \ 259 --tile-columns=0 \ 260 --tile-rows=0 \ 261 --verbose \ 262 --end-usage=cbr \ 263 --kf-max-dist=90000 \ 264 --max-q=58 \ 265 --min-q=2 \ 266 --overshoot-pct=50 \ 267 --undershoot-pct=50 \ 268 --passes=1 \ 269 --aq-mode=3 \ 270 --buf-initial-sz=500 \ 271 --buf-optimal-sz=600 \ 272 --buf-sz=1000 \ 273 --coeff-cost-upd-freq=3 \ 274 --dv-cost-upd-freq=3 \ 275 --mode-cost-upd-freq=3 \ 276 --mv-cost-upd-freq=3 \ 277 --deltaq-mode=0 \ 278 --enable-global-motion=0 \ 279 --enable-obmc=0 \ 280 --enable-order-hint=0 \ 281 --enable-ref-frame-mvs=0 \ 282 --enable-tpl-model=0 \ 283 --enable-warped-motion=0 \ 284 --lag-in-frames=0 \ 285 --max-intra-rate=300 \ 286 --noise-sensitivity=0" 287} 288 289# Configures for the given target in AOM_TEST_OUTPUT_DIR/build_target_${target} 290# directory. 291av1_enc_build() { 292 local target="$1" 293 local cmake_command="$2" 294 local tmp_build_dir=${AOM_TEST_OUTPUT_DIR}/build_target_${target} 295 if [ -d "$tmp_build_dir" ]; then 296 rm -rf $tmp_build_dir 297 fi 298 299 mkdir -p $tmp_build_dir 300 cd $tmp_build_dir 301 302 local cmake_common_args="-DCONFIG_EXCLUDE_SIMD_MISMATCH=1 \ 303 -DCMAKE_BUILD_TYPE=Release \ 304 -DENABLE_CCACHE=1 \ 305 '-DCMAKE_C_FLAGS_RELEASE=-O3 -g' \ 306 '-DCMAKE_CXX_FLAGS_RELEASE=-O3 -g' \ 307 -DENABLE_DOCS=0 -DENABLE_TESTS=0 -DENABLE_TOOLS=0" 308 309 for preset in $PRESETS; do 310 echo "Building target[${preset} encoding]: ${target}" 311 if [ "${preset}" = "good" ]; then 312 local cmake_extra_args="-DCONFIG_AV1_HIGHBITDEPTH=1" 313 elif [ "${preset}" = "rt" ]; then 314 local cmake_extra_args="-DCONFIG_REALTIME_ONLY=1 -DCONFIG_AV1_HIGHBITDEPTH=0" 315 else 316 elog "Invalid preset" 317 return 1 318 fi 319 if ! eval "$cmake_command" "${cmake_common_args}" "${cmake_extra_args}" \ 320 ${devnull}; then 321 elog "cmake failure" 322 return 1 323 fi 324 if ! eval make -j$(nproc) aomenc ${devnull}; then 325 elog "build failure" 326 return 1 327 fi 328 329 mv aomenc aomenc_${preset} 330 done 331 echo "Done building target: ${target}" 332} 333 334compare_enc_output() { 335 local target=$1 336 local cpu=$2 337 local clip=$3 338 local bitrate=$4 339 local preset=$5 340 if ! diff -q ${AOM_TEST_OUTPUT_DIR}/Out-generic-"${clip}"-${preset}-${bitrate}kbps-cpu${cpu}${OUT_FILE_SUFFIX} \ 341 ${AOM_TEST_OUTPUT_DIR}/Out-${target}-"${clip}"-${preset}-${bitrate}kbps-cpu${cpu}${OUT_FILE_SUFFIX}; then 342 elog "C vs ${target} encode mismatches for ${clip}, at ${bitrate} kbps, speed ${cpu}, ${preset} preset" 343 return 1 344 fi 345} 346 347av1_enc_test() { 348 local encoder="$1" 349 local arch="$2" 350 local target="$3" 351 local preset="$4" 352 if [ -z "$(av1_enc_tool_path "${target}" "${preset}")" ]; then 353 elog "aomenc_{preset} not found. It must exist in ${AOM_TEST_OUTPUT_DIR}/build_target_${target} path" 354 return 1 355 fi 356 357 if [ "${preset}" = "good" ]; then 358 if [ "${arch}" = "x86_64" ]; then 359 local min_cpu_used=0 360 local max_cpu_used=6 361 elif [ "${arch}" = "x86" ]; then 362 local min_cpu_used=2 363 local max_cpu_used=3 364 fi 365 local test_params=av1_encode_good_params 366 elif [ "${preset}" = "rt" ]; then 367 local min_cpu_used=5 368 local max_cpu_used=11 369 local test_params=av1_encode_rt_params 370 else 371 elog "Invalid preset" 372 return 1 373 fi 374 375 for cpu in $(seq $min_cpu_used $max_cpu_used); do 376 if [ "${preset}" = "good" ]; then 377 if [ "${arch}" = "x86_64" ]; then 378 if [ "${cpu}" -lt 2 ]; then 379 local test_clips="${LOWBD_CIF_CLIP} ${HIGHBD_CLIP}" 380 elif [ "${cpu}" -lt 5 ]; then 381 local test_clips="${LOWBD_480p_CLIP} ${HIGHBD_CLIP}" 382 else 383 local test_clips="${LOWBD_720p_CLIP} ${HIGHBD_CLIP}" 384 fi 385 elif [ "${arch}" = "x86" ]; then 386 local test_clips="${LOWBD_CIF_CLIP} ${HIGHBD_CLIP}" 387 elif [ "${arch}" = "arm64" ]; then 388 local test_clips="${LOWBD_CIF_CLIP} ${HIGHBD_CLIP}" 389 fi 390 elif [ "${preset}" = "rt" ]; then 391 if [ "${cpu}" -lt 8 ]; then 392 local test_clips="${LOWBD_CIF_CLIP} ${SC_CLIP}" 393 else 394 local test_clips="${LOWBD_480p_CLIP} ${SC_CLIP}" 395 fi 396 else 397 elog "Invalid preset" 398 return 1 399 fi 400 401 for clip in ${test_clips}; do 402 local test_bitrates=$(get_bitrates ${clip} ${preset}) 403 for bitrate in ${test_bitrates}; do 404 eval "${encoder}" $($clip) $($test_params) \ 405 "--limit=${AV1_ENCODE_C_VS_SIMD_TEST_FRAME_LIMIT}" \ 406 "--cpu-used=${cpu}" "--target-bitrate=${bitrate}" "-o" \ 407 ${AOM_TEST_OUTPUT_DIR}/Out-${target}-"${clip}"-${preset}-${bitrate}kbps-cpu${cpu}${OUT_FILE_SUFFIX} \ 408 ${devnull} 409 410 if [ "${target}" != "generic" ]; then 411 if ! compare_enc_output ${target} $cpu ${clip} $bitrate ${preset}; then 412 # Found a mismatch 413 return 1 414 fi 415 fi 416 done 417 done 418 done 419} 420 421av1_test_generic() { 422 local arch=$1 423 local target="generic" 424 if [ $arch = "x86_64" ]; then 425 local cmake_command="cmake $LIBAOM_SOURCE_DIR -DAOM_TARGET_CPU=${target}" 426 elif [ $arch = "x86" ]; then 427 # As AV1 encode output differs for x86 32-bit and 64-bit platforms 428 # (BUG=aomedia:3479), the x86 32-bit C-only build is generated separately. 429 # The cmake command line option -DENABLE_MMX=0 flag disables all SIMD 430 # optimizations, and generates a C-only binary. 431 local cmake_command="cmake $LIBAOM_SOURCE_DIR -DENABLE_MMX=0 \ 432 -DCMAKE_TOOLCHAIN_FILE=${LIBAOM_SOURCE_DIR}/build/cmake/toolchains/i686-linux-gcc.cmake" 433 fi 434 435 echo "Build for: Generic ${arch}" 436 if ! av1_enc_build "${target}" "${cmake_command}"; then 437 return 1 438 fi 439 440 for preset in $PRESETS; do 441 local encoder="$(av1_enc_tool_path "${target}" "${preset}")" 442 av1_enc_test $encoder "${arch}" "${target}" "${preset}" 443 done 444} 445 446# This function encodes AV1 bitstream by enabling SSE2, SSE3, SSSE3, SSE4_1, SSE4_2, AVX, AVX2 as 447# there are no functions with MMX, SSE and AVX512 specialization. 448# The value of environment variable 'AOM_SIMD_CAPS_MASK' controls enabling of different instruction 449# set extension optimizations. The value of the flag 'AOM_SIMD_CAPS_MASK' and the corresponding 450# instruction set extension optimization enabled are as follows: 451# SSE4_2 AVX2 AVX SSE4_1 SSSE3 SSE3 SSE2 SSE MMX 452# 1 1 1 1 1 1 1 1 1 -> 0x1FF -> Enable AVX2 and lower variants 453# 1 0 1 1 1 1 1 1 1 -> 0x17F -> Enable AVX and lower variants 454# 1 0 0 1 1 1 1 1 1 -> 0x13F -> Enable SSE4_2 and lower variants 455# 0 0 0 1 1 1 1 1 1 -> 0x03F -> Enable SSE4_1 and lower variants 456# 0 0 0 0 1 1 1 1 1 -> 0x01F -> Enable SSSE3 and lower variants 457# 0 0 0 0 0 1 1 1 1 -> 0x00F -> Enable SSE3 and lower variants 458# 0 0 0 0 0 0 1 1 1 -> 0x007 -> Enable SSE2 and lower variants 459# 0 0 0 0 0 0 0 1 1 -> 0x003 -> Enable SSE and lower variants 460# 0 0 0 0 0 0 0 0 1 -> 0x001 -> Enable MMX 461## NOTE: In x86_64 platform, it is not possible to enable sse/mmx/c using "AOM_SIMD_CAPS_MASK" as 462# all x86_64 platforms implement sse2. 463av1_test_x86() { 464 local arch=$1 465 466 if ! uname -m | grep -q "x86"; then 467 elog "Machine architecture is not x86 or x86_64" 468 return 0 469 fi 470 471 if [ $arch = "x86" ]; then 472 local target="x86-linux" 473 local cmake_command="cmake \ 474 $LIBAOM_SOURCE_DIR \ 475 -DCMAKE_TOOLCHAIN_FILE=${LIBAOM_SOURCE_DIR}/build/cmake/toolchains/i686-linux-gcc.cmake" 476 elif [ $arch = "x86_64" ]; then 477 local target="x86_64-linux" 478 local cmake_command="cmake $LIBAOM_SOURCE_DIR" 479 fi 480 481 # Available x86 isa variants: "avx2 avx sse4_2 sse4_1 ssse3 sse3 sse2" 482 local x86_isa_variants="avx2 sse4_2 sse2" 483 484 echo "Build for x86: ${target}" 485 if ! av1_enc_build "${target}" "${cmake_command}"; then 486 return 1 487 fi 488 489 for preset in $PRESETS; do 490 local encoder="$(av1_enc_tool_path "${target}" "${preset}")" 491 for isa in $x86_isa_variants; do 492 # Note that if has_x86_isa_extn returns 1, it is false, and vice versa. 493 if ! has_x86_isa_extn $isa; then 494 echo "${isa} is not supported in this machine" 495 continue 496 fi 497 export AOM_SIMD_CAPS_MASK=$($isa) 498 if ! av1_enc_test $encoder "${arch}" "${target}" "${preset}"; then 499 # Found a mismatch 500 return 1 501 fi 502 unset AOM_SIMD_CAPS_MASK 503 done 504 done 505} 506 507av1_test_arm() { 508 local arch="arm64" 509 local target="arm64-linux-gcc" 510 local cmake_command="cmake $LIBAOM_SOURCE_DIR \ 511 -DCMAKE_TOOLCHAIN_FILE=$LIBAOM_SOURCE_DIR/build/cmake/toolchains/${target}.cmake \ 512 -DCMAKE_C_FLAGS=-Wno-maybe-uninitialized" 513 echo "Build for arm64: ${target}" 514 if ! av1_enc_build "${target}" "${cmake_command}"; then 515 return 1 516 fi 517 518 for preset in $PRESETS; do 519 local encoder="$(av1_enc_tool_path "${target}" "${preset}")" 520 if ! av1_enc_test "qemu-aarch64 -L /usr/aarch64-linux-gnu ${encoder}" "${arch}" "${target}" "${preset}"; then 521 # Found a mismatch 522 return 1 523 fi 524 done 525} 526 527av1_c_vs_simd_enc_test () { 528 # Test x86 (32 bit) 529 # x86 requires the i686-linux-gnu toolchain: 530 # $ sudo apt-get install g++-i686-linux-gnu 531 echo "av1 test for x86 (32 bit): Started." 532 # Encode 'C' only 533 av1_test_generic "x86" 534 # Encode with SIMD optimizations enabled 535 if ! av1_test_x86 "x86"; then 536 echo "av1 test for x86 (32 bit): Done, test failed." 537 return 1 538 else 539 echo "av1 test for x86 (32 bit): Done, all tests passed." 540 fi 541 542 # Test x86_64 (64 bit) 543 if [ "$(eval uname -m)" = "x86_64" ]; then 544 echo "av1 test for x86_64 (64 bit): Started." 545 # Encode 'C' only 546 av1_test_generic "x86_64" 547 # Encode with SIMD optimizations enabled 548 if ! av1_test_x86 "x86_64"; then 549 echo "av1 test for x86_64 (64 bit): Done, test failed." 550 return 1 551 else 552 echo "av1 test for x86_64 (64 bit): Done, all tests passed." 553 fi 554 fi 555 556 # Test ARM 557 echo "av1_test_arm: Started." 558 if ! av1_test_arm; then 559 echo "av1 test for arm: Done, test failed." 560 return 1 561 else 562 echo "av1 test for arm: Done, all tests passed." 563 fi 564} 565 566run_tests av1_c_vs_simd_enc_verify_environment av1_c_vs_simd_enc_test 567