xref: /aosp_15_r20/external/libaom/test/av1_c_vs_simd_encode.sh (revision 77c1e3ccc04c968bd2bc212e87364f250e820521)
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