• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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