/*
 * Functional test for VA-API
 *
 * Copyright © 2020-2025 Collabora Ltd.
 * Copyright © 2025 David Rosca
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <assert.h>
#include <stdbool.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>

#include <va/va.h>
#include <va/va_x11.h>

#include "steam-runtime-tools/libc-utils-internal.h"

static void
clear_display (Display **displayp)
{
  clear_pointer (displayp, XCloseDisplay);
}

static void
clear_va_display (VADisplay *displayp)
{
  clear_pointer (displayp, vaTerminate);
}

static void
clear_surfaces (VADisplay va_display,
                VASurfaceID *surfaces,
                int surfaces_count)
{
  if (va_display && surfaces && surfaces[0] != VA_INVALID_SURFACE)
    vaDestroySurfaces (va_display, surfaces, surfaces_count);

  for (int i = 0; i < surfaces_count; i++)
    surfaces[i] = VA_INVALID_SURFACE;
}

static bool verbose = false;

enum
{
  OPTION_HELP = 1,
  OPTION_ALL,
  OPTION_VERBOSE,
  OPTION_VERSION,
};

struct option long_options[] =
{
  { "help", no_argument, NULL, OPTION_HELP },
  { "all", no_argument, NULL, OPTION_ALL },
  { "verbose", no_argument, NULL, OPTION_VERBOSE },
  { "version", no_argument, NULL, OPTION_VERSION },
  { NULL, 0, NULL, 0 }
};

/* Pseudo-randomly generated MPEG2 video clip with one I-frame */
static unsigned char clip_mpeg2[] =
{
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x28, 0x00, 0x2f, 0x64, 0x36, 0x00, 0x2d, 0xd0, 0x40, 0x00, 0x2d, 0x60,
  0x12, 0x00, 0x2a, 0x61, 0x20, 0x00, 0x3e, 0x65,
};

#define CLIP_SIZE_MPEG2 sizeof(clip_mpeg2) / sizeof(clip_mpeg2[0])

static VAPictureParameterBufferMPEG2 pic_param_mpeg2 =
{
  /* Limit the picture buffer to 16x16 */
  .horizontal_size = 16,
  .vertical_size = 16,
  .picture_coding_type = 1, /* I-frame */
};

static VAIQMatrixBufferMPEG2 iq_matrix_mpeg2 =
{
  /* Do not do anything particular here */
  .load_intra_quantiser_matrix = 1,
  .load_non_intra_quantiser_matrix = 1,
  .load_chroma_intra_quantiser_matrix = 0,
  .load_chroma_non_intra_quantiser_matrix = 0,
  .intra_quantiser_matrix = {0},
  .non_intra_quantiser_matrix = {0},
  .chroma_intra_quantiser_matrix = {0},
  .chroma_non_intra_quantiser_matrix = {0},
};

static VASliceParameterBufferMPEG2 slice_param_mpeg2 =
{
  .slice_data_size = CLIP_SIZE_MPEG2,
  .slice_data_offset = 0,
  .slice_data_flag = 0,
  /* Assume a slice with a 64bit header */
  .macroblock_offset = 64,
};

/* Pseudo-randomly generated H264 video clip with one I-frame */
static unsigned int clip_h264[] =
{
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
  0xca123456, 0xe2255446, 0x9a61c747, 0xe10133c7, 0x71ccf20f, 0xfd2e5af3,
};

#define CLIP_SIZE_H264 sizeof(clip_h264) / sizeof(clip_h264[0])

static VAPictureParameterBufferH264 pic_param_h264 =
{
  /* The size has been arbitrarily chosen */
  .picture_width_in_mbs_minus1 = 10,
  .picture_height_in_mbs_minus1 = 10,
  .num_ref_frames = 0,
};

static VAIQMatrixBufferH264 iq_matrix_h264 =
{
  /* Do not do anything particular here */
  .ScalingList4x4 = {{0}},
  .ScalingList8x8 = {{0}},
};

static VASliceParameterBufferH264 slice_param_h264 =
{
  .slice_data_size = CLIP_SIZE_H264,
  .slice_data_offset = 0,
  .slice_data_flag = 0,
};

static void usage (int code) __attribute__((__noreturn__));

/*
 * Print usage information and exit with status @code.
 */
static void
usage (int code)
{
  FILE *fp;

  if (code == 0)
    fp = stdout;
  else
    fp = stderr;

  fprintf (fp, "Usage: %s [OPTIONS]\n",
           program_invocation_short_name);
  fprintf (fp, "Options:\n");
  fprintf (fp, "--help\t\tShow this help and exit\n");
  fprintf (fp, "--verbose\tBe more verbose\n");
  fprintf (fp, "--version\tShow version and exit\n");
  exit (code);
}

static bool
_do_vaapi (const char *description,
           VAStatus va_status)
{
  if (va_status != VA_STATUS_SUCCESS)
    {
      fprintf (stderr, "%s failed: %s (%d)\n", description, vaErrorStr (va_status), va_status);
      return false;
    }
  return true;
}

static bool
create_surfaces (VADisplay va_display,
                 VAConfigID config,
                 int width,
                 int height,
                 VASurfaceID *surfaces,
                 int surfaces_count)
{
  uint32_t fourcc = 0;
  VASurfaceAttrib attr = {
      .type = VASurfaceAttribPixelFormat,
      .flags = VA_SURFACE_ATTRIB_SETTABLE,
      .value = { .type = VAGenericValueTypeInteger },
  };
  VAImage img = { .image_id = VA_INVALID_ID };
  VAImageFormat image_format = { .fourcc = 0 };
  unsigned int num_attribs = 0;
  autofree VASurfaceAttrib *attrib_list = NULL;
  int num_formats;
  autofree VAImageFormat *format_list = NULL;
  bool ret = false;
  VAStatus status;

#define do_vaapi_or_exit(expr) if (! _do_vaapi (#expr, expr)) goto out;

  status = vaQuerySurfaceAttributes (va_display, config, NULL, &num_attribs);

  if (status != VA_STATUS_SUCCESS && status != VA_STATUS_ERROR_MAX_NUM_EXCEEDED)
    {
      _do_vaapi ("vaQuerySurfaceAttributes", status);
      goto out;
    }

  attrib_list = xcalloc (num_attribs, sizeof (*attrib_list));
  do_vaapi_or_exit (vaQuerySurfaceAttributes (va_display, config, attrib_list, &num_attribs));

  for (unsigned int i = 0; i < num_attribs; i++)
    {
      if (attrib_list[i].type == VASurfaceAttribPixelFormat)
        {
          /* Select 8-bit YUV 4:2:0 fourcc */
          switch (attrib_list[i].value.value.i)
            {
              case VA_FOURCC_I420:
              case VA_FOURCC_NV12:
              case VA_FOURCC_NV21:
              case VA_FOURCC_YV12:
                fourcc = attrib_list[i].value.value.i;
                break;

              default:
                break;
            }
          if (fourcc != 0)
            break;
        }
    }

  if (fourcc == 0)
    {
      fprintf (stderr, "No supported pixel format\n");
      goto out;
    }

  num_formats = vaMaxNumImageFormats (va_display);

  format_list = xcalloc (num_formats, sizeof (*format_list));
  do_vaapi_or_exit (vaQueryImageFormats (va_display, format_list, &num_formats));

  for (int j = 0; j < num_formats; j++)
    {
      if (format_list[j].fourcc == fourcc)
        {
          image_format = format_list[j];
          break;
        }
    }

  if (image_format.fourcc != fourcc)
    {
      fprintf (stderr, "Unable to find an image format with fourcc 0x%08x\n", fourcc);
      goto out;
    }

  attr.value.value.i = fourcc;

  /* Test the creation of two surfaces and an image */
  do_vaapi_or_exit (vaCreateSurfaces (va_display, VA_RT_FORMAT_YUV420, width, height,
                                      surfaces, surfaces_count, &attr, 1));
  do_vaapi_or_exit (vaCreateImage (va_display, &image_format, width, height, &img));

  /* Get an image from the first surface */
  do_vaapi_or_exit (vaGetImage (va_display, surfaces[0], 0, 0, width, height, img.image_id));

  /* Render the image back to the second surface */
  do_vaapi_or_exit (vaPutImage (va_display, surfaces[1], img.image_id,
                                0, 0, width, height,
                                0, 0, width, height));

  /* Wait for all operations to complete */
  do_vaapi_or_exit (vaSyncSurface (va_display, surfaces[1]));

  if (verbose)
    fprintf (stderr, "... successfully created surfaces\n");

  ret = true;

out:
  if (img.image_id != VA_INVALID_ID)
    vaDestroyImage (va_display, img.image_id);

  return ret;
}

static bool
test_decode_capability (VADisplay va_display,
                        VAProfile profile,
                        int width,
                        int height,
                        VARectangle input_region,
                        VARectangle output_region,
                        VASurfaceID *surfaces,
                        int surfaces_count)
{
  VAConfigID config = VA_INVALID_ID;
  VAContextID context = VA_INVALID_ID;
  VABufferID pic_param_buf = VA_INVALID_ID;
  VABufferID iq_matrix_buf = VA_INVALID_ID;
  VABufferID slice_param_buf = VA_INVALID_ID;
  VABufferID slice_data_buf = VA_INVALID_ID;
  VAConfigAttrib conf_attrib;
  void *in_pic_param = NULL;
  void *in_iq_matrix = NULL;
  void *in_slice_param = NULL;
  void *in_clip = NULL;
  unsigned int in_pic_param_size = 0;
  unsigned int in_iq_matrix_size = 0;
  unsigned int in_slice_param_size = 0;
  unsigned int in_clip_size = 0;
  bool ret = false;

#define do_vaapi_or_exit(expr) if (! _do_vaapi (#expr, expr)) goto out;

/* There are a lot of values in VAProfile, and we just support
 * VAProfileH264Main and VAProfileMPEG2Simple. There is no need to
 * list them all next to our "default". */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
  switch (profile)
    {
      case VAProfileH264Main:
        fprintf (stderr, "Testing H264Main decoding\n");

        in_pic_param = &pic_param_h264;
        in_pic_param_size = sizeof (pic_param_h264);

        in_iq_matrix = &iq_matrix_h264;
        in_iq_matrix_size = sizeof (iq_matrix_h264);

        in_slice_param = &slice_param_h264;
        in_slice_param_size = sizeof (slice_param_h264);

        in_clip = clip_h264;
        in_clip_size = CLIP_SIZE_H264;
        break;

      case VAProfileMPEG2Simple:
        fprintf (stderr, "Testing MPEG2Simple decoding\n");

        in_pic_param = &pic_param_mpeg2;
        in_pic_param_size = sizeof (pic_param_mpeg2);

        in_iq_matrix = &iq_matrix_mpeg2;
        in_iq_matrix_size = sizeof (iq_matrix_mpeg2);

        in_slice_param = &slice_param_mpeg2;
        in_slice_param_size = sizeof (slice_param_mpeg2);

        in_clip = clip_mpeg2;
        in_clip_size = CLIP_SIZE_MPEG2;
        break;

      default:
        exit (1);
        break; /* not reached */
    }
#pragma GCC diagnostic pop

  conf_attrib.type = VAConfigAttribRTFormat;
  do_vaapi_or_exit (vaGetConfigAttributes(va_display, profile, VAEntrypointVLD,
                                          &conf_attrib, 1));
  if ((conf_attrib.value & VA_RT_FORMAT_YUV420) == 0)
    {
      fprintf (stderr, "This profile doesn't support the YUV420 format\n");
      goto out;
    }

  do_vaapi_or_exit (vaCreateConfig (va_display, profile, VAEntrypointVLD,
                                    NULL, 0, &config));

  if (!create_surfaces (va_display, config, width, height, surfaces, surfaces_count))
    goto out;

  do_vaapi_or_exit (vaCreateContext (va_display, config, width, height,
                                     VA_PROGRESSIVE, surfaces,
                                     surfaces_count, &context));

  do_vaapi_or_exit (vaCreateBuffer (va_display, context,
                                    VAPictureParameterBufferType,
                                    in_pic_param_size, 1, in_pic_param,
                                    &pic_param_buf));

  do_vaapi_or_exit (vaCreateBuffer (va_display, context,
                                    VAIQMatrixBufferType,
                                    in_iq_matrix_size, 1, in_iq_matrix,
                                    &iq_matrix_buf));

  do_vaapi_or_exit (vaCreateBuffer (va_display, context,
                                    VASliceParameterBufferType,
                                    in_slice_param_size, 1, in_slice_param,
                                    &slice_param_buf));

  do_vaapi_or_exit (vaCreateBuffer (va_display, context,
                                    VASliceDataBufferType,
                                    in_clip_size, 1, in_clip,
                                    &slice_data_buf));

  do_vaapi_or_exit (vaBeginPicture (va_display, context, surfaces[1]));
  /* Send the buffers to the server */
  do_vaapi_or_exit (vaRenderPicture (va_display, context, &pic_param_buf, 1));
  do_vaapi_or_exit (vaRenderPicture (va_display, context, &iq_matrix_buf, 1));
  do_vaapi_or_exit (vaRenderPicture (va_display, context, &slice_param_buf, 1));
  do_vaapi_or_exit (vaRenderPicture (va_display, context, &slice_data_buf, 1));
  /* We are done with the sending, now the server will start to process all
  * pending operations */
  do_vaapi_or_exit (vaEndPicture (va_display, context));
  /* Blocks until all pending operations ends */
  do_vaapi_or_exit (vaSyncSurface (va_display, surfaces[1]));

  if (verbose)
    fprintf (stderr, "... success\n");

  ret = true;

out:
  if (context != VA_INVALID_ID)
    vaDestroyContext (va_display, context);
  if (config != VA_INVALID_ID)
    vaDestroyConfig(va_display, config);
  if (pic_param_buf != VA_INVALID_ID)
    vaDestroyBuffer (va_display, pic_param_buf);
  if (iq_matrix_buf != VA_INVALID_ID)
    vaDestroyBuffer (va_display, iq_matrix_buf);
  if (slice_param_buf != VA_INVALID_ID)
    vaDestroyBuffer (va_display, slice_param_buf);
  if (slice_data_buf != VA_INVALID_ID)
    vaDestroyBuffer (va_display, slice_data_buf);

  clear_surfaces (va_display, surfaces, surfaces_count);

  return ret;
}

static bool
test_pp_capability (VADisplay va_display,
                    int width,
                    int height,
                    VARectangle input_region,
                    VARectangle output_region,
                    VASurfaceID *surfaces,
                    int surfaces_count)
{
  bool ret = false;
  VAConfigID config = VA_INVALID_ID;
  VAContextID context = VA_INVALID_ID;
  VABufferID misc_buf_id = VA_INVALID_ID;
  VABufferID pipeline_param_buf_id = VA_INVALID_ID;
  VAEncMiscParameterBuffer *misc_param_buffer;
  VAEncMiscParameterBufferQualityLevel *buffer_quality_level;
  VAProcPipelineParameterBuffer pipeline_param_buf;

#define do_vaapi_or_exit(expr) if (! _do_vaapi (#expr, expr)) goto out;

  fprintf (stderr, "Testing post-processing with VAProfileNone\n");

  do_vaapi_or_exit (vaCreateConfig (va_display, VAProfileNone, VAEntrypointVideoProc,
                                    NULL, 0, &config));

  if (!create_surfaces (va_display, config, width, height, surfaces, surfaces_count))
    goto out;

  do_vaapi_or_exit (vaCreateContext (va_display, config, width, height, 0,
                                     surfaces, surfaces_count, &context));

  /* Try to render a picture, tuning its encode quality */
  do_vaapi_or_exit (vaCreateBuffer (va_display, context,
                                    VAEncMiscParameterBufferType,
                                    sizeof (VAEncMiscParameterBuffer) +
                                      sizeof (VAEncMiscParameterBufferQualityLevel),
                                    1, NULL, &misc_buf_id));
  do_vaapi_or_exit (vaMapBuffer (va_display, misc_buf_id, (void **)&misc_param_buffer));
  misc_param_buffer->type = VAEncMiscParameterTypeQualityLevel;
  buffer_quality_level = (VAEncMiscParameterBufferQualityLevel *)misc_param_buffer->data;
  /* 1 is always the highest possible quality level, we don't need to check
   * VAConfigAttribEncQualityRange */
  buffer_quality_level->quality_level = 1;
  do_vaapi_or_exit (vaUnmapBuffer (va_display, misc_buf_id));
  do_vaapi_or_exit (vaBeginPicture (va_display, context, surfaces[1]));
  do_vaapi_or_exit (vaRenderPicture (va_display, context, &misc_buf_id, 1));
  do_vaapi_or_exit (vaSyncSurface (va_display, surfaces[1]));

  /* Try to render a picture from the first surface to the second,
   * applying a crop to it */
  memset (&pipeline_param_buf, 0, sizeof (pipeline_param_buf));
  pipeline_param_buf.surface = surfaces[0];
  pipeline_param_buf.surface_region = &input_region;
  pipeline_param_buf.output_region = &output_region;
  /* Set a green background */
  pipeline_param_buf.output_background_color = 0xff00ff00;
  pipeline_param_buf.output_color_standard = VAProcColorStandardNone;
  do_vaapi_or_exit (vaCreateBuffer (va_display, context,
                                    VAProcPipelineParameterBufferType,
                                    sizeof (pipeline_param_buf), 1,
                                    &pipeline_param_buf, &pipeline_param_buf_id));
  do_vaapi_or_exit (vaBeginPicture (va_display, context, surfaces[1]));
  do_vaapi_or_exit (vaRenderPicture (va_display, context, &pipeline_param_buf_id, 1));
  do_vaapi_or_exit (vaEndPicture (va_display, context));
  do_vaapi_or_exit (vaSyncSurface (va_display, surfaces[1]));

  if (verbose)
    fprintf (stderr, "... success\n");

  ret = true;

out:
  if (config != VA_INVALID_ID)
    vaDestroyConfig(va_display, config);
  if (pipeline_param_buf_id != VA_INVALID_ID)
    vaDestroyBuffer (va_display, pipeline_param_buf_id);
  if (misc_buf_id != VA_INVALID_ID)
    vaDestroyBuffer (va_display, misc_buf_id);
  if (context != VA_INVALID_ID)
    vaDestroyContext (va_display, context);

  clear_surfaces (va_display, surfaces, surfaces_count);

  return ret;
}

int
main (int argc,
      char **argv)
{

#define do_vaapi_or_exit(expr) if (! _do_vaapi (#expr, expr)) goto out;

  bool try_all = false;
  int opt;
  int surfaces_count = 2;
  int ret = 1;
  int max_profiles;
  int num_profiles;
  int major_version;
  int minor_version;
  unsigned int width = 1280;
  unsigned int height = 720;
  autoclear(clear_display) Display *display = NULL;
  autoclear(clear_va_display) VADisplay va_display = NULL;
  autofree VASurfaceID *surfaces = NULL;
  autofree VAProfile *profiles = NULL;
  VARectangle input_region;
  VARectangle output_region;

  while ((opt = getopt_long (argc, argv, "", long_options, NULL)) != -1)
    {
      switch (opt)
        {
          case OPTION_HELP:
            usage (0);
            break;

          case OPTION_ALL:
            try_all = true;
            break;

          case OPTION_VERBOSE:
            verbose = true;
            break;

          case OPTION_VERSION:
            /* Output version number as YAML for machine-readability,
             * inspired by `ostree --version` and `docker version` */
            printf (
                "%s:\n"
                " Package: steam-runtime-tools\n"
                " Version: %s\n",
                argv[0], VERSION);
            return 0;

          case '?':
          default:
            usage (1);
            break;  /* not reached */
        }
    }

  /* Use the whole input surface */
  input_region.x = 0;
  input_region.y = 0;
  input_region.width = width;
  input_region.height = height;
  /* Crop the output a few pixels from every corner */
  output_region.x = 10;
  output_region.y = 20;
  output_region.width = width - 30;
  output_region.height = height - 30;

  display = XOpenDisplay (NULL);
  if (!display)
    {
      fprintf (stderr, "An error occurred trying to open a connection to the X server\n");
      return 1;
    }

  va_display = vaGetDisplay (display);
  if (!va_display)
    {
      fprintf (stderr, "An error occurred trying to get a suitable VADisplay for VA-API\n");
      goto out;
    }

  do_vaapi_or_exit (vaInitialize (va_display, &major_version, &minor_version));

  if (verbose)
    printf ("%s\n", vaQueryVendorString (va_display));

  /* Test the ability to get the supported profiles and that they are not more than
   * the maximum number from the implementation */
  max_profiles = vaMaxNumProfiles (va_display);
  if (max_profiles < 1)
    {
      fprintf (stderr,
               "vaMaxNumProfiles failed: unexpected number of maximum profiles (%i)\n", max_profiles);
      goto out;
    }
  profiles = xcalloc (max_profiles, sizeof (VAProfile));
  do_vaapi_or_exit (vaQueryConfigProfiles (va_display, profiles, &num_profiles));
  if (num_profiles > max_profiles)
    {
      fprintf (stderr,
               "vaQueryConfigProfiles failed: the number of profiles (%i) exceed the maximum (%i)\n",
               num_profiles, max_profiles);
      goto out;
    }

  surfaces = xcalloc (surfaces_count, sizeof (VASurfaceID));
  surfaces[0] = VA_INVALID_SURFACE;

  /* We assume to have at least one of VAProfileH264Main, VAProfileMPEG2Simple
   * or VAProfileNone */
  if ((ret != 0 || try_all)
      && test_decode_capability (va_display, VAProfileH264Main, width, height,
                                 input_region, output_region, surfaces, surfaces_count))
    ret = 0;

  if ((ret != 0 || try_all)
      && test_decode_capability (va_display, VAProfileMPEG2Simple, width, height,
                                 input_region, output_region, surfaces, surfaces_count))
    ret = 0;

  if ((ret != 0 || try_all)
      && test_pp_capability (va_display, width, height, input_region,
                             output_region, surfaces, surfaces_count))
    ret = 0;

out:
  return ret;
}
