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="--fresh -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 case "${arch}" in 359 arm64) 360 # Speed 0 is not tested as arm64 is run under emulation. 361 local min_cpu_used=1 362 local max_cpu_used=6 363 ;; 364 x86) 365 # x86 has a good amount of overlap with x86-64. Only a few values are 366 # tested to improve the runtime of the script. 367 local min_cpu_used=2 368 local max_cpu_used=3 369 ;; 370 *) 371 local min_cpu_used=0 372 local max_cpu_used=6 373 ;; 374 esac 375 local test_params=av1_encode_good_params 376 elif [ "${preset}" = "rt" ]; then 377 local min_cpu_used=5 378 local max_cpu_used=11 379 local test_params=av1_encode_rt_params 380 else 381 elog "Invalid preset" 382 return 1 383 fi 384 385 for cpu in $(seq $min_cpu_used $max_cpu_used); do 386 if [ "${preset}" = "good" ]; then 387 if [ "${arch}" = "x86_64" -o "${arch}" = "arm64" ]; then 388 if [ "${cpu}" -lt 2 ]; then 389 local test_clips="${LOWBD_CIF_CLIP} ${HIGHBD_CLIP}" 390 elif [ "${cpu}" -lt 5 ]; then 391 local test_clips="${LOWBD_480p_CLIP} ${HIGHBD_CLIP}" 392 else 393 local test_clips="${LOWBD_720p_CLIP} ${HIGHBD_CLIP}" 394 fi 395 elif [ "${arch}" = "x86" ]; then 396 local test_clips="${LOWBD_CIF_CLIP} ${HIGHBD_CLIP}" 397 else 398 elog "Unknown architecture: ${arch}" 399 return 1 400 fi 401 elif [ "${preset}" = "rt" ]; then 402 if [ "${cpu}" -lt 8 ]; then 403 local test_clips="${LOWBD_CIF_CLIP} ${SC_CLIP}" 404 else 405 local test_clips="${LOWBD_480p_CLIP} ${SC_CLIP}" 406 fi 407 else 408 elog "Invalid preset" 409 return 1 410 fi 411 412 for clip in ${test_clips}; do 413 local test_bitrates=$(get_bitrates ${clip} ${preset}) 414 for bitrate in ${test_bitrates}; do 415 eval "${encoder}" $($clip) $($test_params) \ 416 "--limit=${AV1_ENCODE_C_VS_SIMD_TEST_FRAME_LIMIT}" \ 417 "--cpu-used=${cpu}" "--target-bitrate=${bitrate}" "-o" \ 418 ${AOM_TEST_OUTPUT_DIR}/Out-${target}-"${clip}"-${preset}-${bitrate}kbps-cpu${cpu}${OUT_FILE_SUFFIX} \ 419 ${devnull} 420 421 if [ "${target}" != "generic" ]; then 422 if ! compare_enc_output ${target} $cpu ${clip} $bitrate ${preset}; then 423 # Found a mismatch 424 return 1 425 fi 426 fi 427 done 428 done 429 done 430} 431 432av1_test_generic() { 433 local arch=$1 434 local target="generic" 435 if [ $arch = "x86_64" ]; then 436 local cmake_command="cmake $LIBAOM_SOURCE_DIR -DAOM_TARGET_CPU=${target}" 437 elif [ $arch = "x86" ]; then 438 # As AV1 encode output differs for x86 32-bit and 64-bit platforms 439 # (BUG=aomedia:3479), the x86 32-bit C-only build is generated separately. 440 # The cmake command line option -DENABLE_MMX=0 flag disables all SIMD 441 # optimizations, and generates a C-only binary. 442 local cmake_command="cmake $LIBAOM_SOURCE_DIR -DENABLE_MMX=0 \ 443 -DCMAKE_TOOLCHAIN_FILE=${LIBAOM_SOURCE_DIR}/build/cmake/toolchains/i686-linux-gcc.cmake" 444 fi 445 446 echo "Build for: Generic ${arch}" 447 if ! av1_enc_build "${target}" "${cmake_command}"; then 448 return 1 449 fi 450 451 for preset in $PRESETS; do 452 local encoder="$(av1_enc_tool_path "${target}" "${preset}")" 453 av1_enc_test $encoder "${arch}" "${target}" "${preset}" 454 done 455} 456 457# This function encodes AV1 bitstream by enabling SSE2, SSE3, SSSE3, SSE4_1, SSE4_2, AVX, AVX2 as 458# there are no functions with MMX, SSE and AVX512 specialization. 459# The value of environment variable 'AOM_SIMD_CAPS_MASK' controls enabling of different instruction 460# set extension optimizations. The value of the flag 'AOM_SIMD_CAPS_MASK' and the corresponding 461# instruction set extension optimization enabled are as follows: 462# SSE4_2 AVX2 AVX SSE4_1 SSSE3 SSE3 SSE2 SSE MMX 463# 1 1 1 1 1 1 1 1 1 -> 0x1FF -> Enable AVX2 and lower variants 464# 1 0 1 1 1 1 1 1 1 -> 0x17F -> Enable AVX and lower variants 465# 1 0 0 1 1 1 1 1 1 -> 0x13F -> Enable SSE4_2 and lower variants 466# 0 0 0 1 1 1 1 1 1 -> 0x03F -> Enable SSE4_1 and lower variants 467# 0 0 0 0 1 1 1 1 1 -> 0x01F -> Enable SSSE3 and lower variants 468# 0 0 0 0 0 1 1 1 1 -> 0x00F -> Enable SSE3 and lower variants 469# 0 0 0 0 0 0 1 1 1 -> 0x007 -> Enable SSE2 and lower variants 470# 0 0 0 0 0 0 0 1 1 -> 0x003 -> Enable SSE and lower variants 471# 0 0 0 0 0 0 0 0 1 -> 0x001 -> Enable MMX 472## NOTE: In x86_64 platform, it is not possible to enable sse/mmx/c using "AOM_SIMD_CAPS_MASK" as 473# all x86_64 platforms implement sse2. 474av1_test_x86() { 475 local arch=$1 476 477 if ! uname -m | grep -q "x86"; then 478 elog "Machine architecture is not x86 or x86_64" 479 return 0 480 fi 481 482 if [ $arch = "x86" ]; then 483 local target="x86-linux" 484 local cmake_command="cmake \ 485 $LIBAOM_SOURCE_DIR \ 486 -DCMAKE_TOOLCHAIN_FILE=${LIBAOM_SOURCE_DIR}/build/cmake/toolchains/i686-linux-gcc.cmake" 487 elif [ $arch = "x86_64" ]; then 488 local target="x86_64-linux" 489 local cmake_command="cmake $LIBAOM_SOURCE_DIR" 490 fi 491 492 # Available x86 isa variants: "avx2 avx sse4_2 sse4_1 ssse3 sse3 sse2" 493 local x86_isa_variants="avx2 sse4_2 sse2" 494 495 echo "Build for x86: ${target}" 496 if ! av1_enc_build "${target}" "${cmake_command}"; then 497 return 1 498 fi 499 500 for preset in $PRESETS; do 501 local encoder="$(av1_enc_tool_path "${target}" "${preset}")" 502 for isa in $x86_isa_variants; do 503 # Note that if has_x86_isa_extn returns 1, it is false, and vice versa. 504 if ! has_x86_isa_extn $isa; then 505 echo "${isa} is not supported in this machine" 506 continue 507 fi 508 export AOM_SIMD_CAPS_MASK=$($isa) 509 if ! av1_enc_test $encoder "${arch}" "${target}" "${preset}"; then 510 # Found a mismatch 511 return 1 512 fi 513 unset AOM_SIMD_CAPS_MASK 514 done 515 done 516} 517 518av1_test_arm() { 519 local arch="arm64" 520 local target="arm64-linux-gcc" 521 local cmake_command="cmake $LIBAOM_SOURCE_DIR \ 522 -DCMAKE_TOOLCHAIN_FILE=$LIBAOM_SOURCE_DIR/build/cmake/toolchains/${target}.cmake \ 523 -DCMAKE_C_FLAGS=-Wno-maybe-uninitialized" 524 echo "Build for arm64: ${target}" 525 if ! av1_enc_build "${target}" "${cmake_command}"; then 526 return 1 527 fi 528 529 for preset in $PRESETS; do 530 local encoder="$(av1_enc_tool_path "${target}" "${preset}")" 531 if ! av1_enc_test "qemu-aarch64 -L /usr/aarch64-linux-gnu ${encoder}" "${arch}" "${target}" "${preset}"; then 532 # Found a mismatch 533 return 1 534 fi 535 done 536} 537 538av1_c_vs_simd_enc_test () { 539 # Test x86 (32 bit) 540 # x86 requires the i686-linux-gnu toolchain: 541 # $ sudo apt-get install g++-i686-linux-gnu 542 echo "av1 test for x86 (32 bit): Started." 543 # Encode 'C' only 544 av1_test_generic "x86" 545 # Encode with SIMD optimizations enabled 546 if ! av1_test_x86 "x86"; then 547 echo "av1 test for x86 (32 bit): Done, test failed." 548 return 1 549 else 550 echo "av1 test for x86 (32 bit): Done, all tests passed." 551 fi 552 553 # Test x86_64 (64 bit) 554 if [ "$(eval uname -m)" = "x86_64" ]; then 555 echo "av1 test for x86_64 (64 bit): Started." 556 # Encode 'C' only 557 av1_test_generic "x86_64" 558 # Encode with SIMD optimizations enabled 559 if ! av1_test_x86 "x86_64"; then 560 echo "av1 test for x86_64 (64 bit): Done, test failed." 561 return 1 562 else 563 echo "av1 test for x86_64 (64 bit): Done, all tests passed." 564 fi 565 fi 566 567 # Test ARM 568 echo "av1_test_arm: Started." 569 if ! av1_test_arm; then 570 echo "av1 test for arm: Done, test failed." 571 return 1 572 else 573 echo "av1 test for arm: Done, all tests passed." 574 fi 575} 576 577run_tests av1_c_vs_simd_enc_verify_environment av1_c_vs_simd_enc_test 578