From 0f6af77ca91cce58b1156cbc2d7b86d4ffd9d000 Mon Sep 17 00:00:00 2001 From: Ludovico de Nittis <ludovico.denittis@collabora.com> Date: Thu, 5 Mar 2020 09:38:40 +0100 Subject: [PATCH] Add check-va-api helper With this helper we are able to test if VA-API is available and usable. It tests the ability to gather the supported configuration profiles, then it creates two surfaces and perform a few simple rendering transformations. Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com> --- debian/control | 1 + debian/gitlab-ci.yml | 1 + helpers/check-va-api.c | 298 +++++++++++++++++++++++++++++++++++++++++ helpers/meson.build | 11 ++ meson.build | 8 ++ 5 files changed, 319 insertions(+) create mode 100644 helpers/check-va-api.c diff --git a/debian/control b/debian/control index a7bdd339c..75d0b1adc 100644 --- a/debian/control +++ b/debian/control @@ -14,6 +14,7 @@ Build-Depends: libglib2.0-dev, libjson-glib-dev (>= 1.0), libtheora0 <!nocheck>, + libva-dev, libvdpau-dev, libvulkan-dev, libx11-dev, diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml index 57e38cf01..b1220f45f 100644 --- a/debian/gitlab-ci.yml +++ b/debian/gitlab-ci.yml @@ -19,6 +19,7 @@ variables: libglib2.0-dev libjson-glib-dev libtheora-dev + libva-dev libvdpau-dev libvulkan-dev libx11-dev diff --git a/helpers/check-va-api.c b/helpers/check-va-api.c new file mode 100644 index 000000000..172e1c98a --- /dev/null +++ b/helpers/check-va-api.c @@ -0,0 +1,298 @@ +/* + * Functional test for VA-API + * + * Copyright © 2020 Collabora Ltd. + * + * 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> + +enum +{ + OPTION_HELP = 1, + OPTION_VERSION, +}; + +struct option long_options[] = +{ + { "help", no_argument, NULL, OPTION_HELP }, + { "version", no_argument, NULL, OPTION_VERSION }, + { NULL, 0, NULL, 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); + 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; +} + +int +main (int argc, + char **argv) +{ + +#define do_vaapi_or_exit(expr) if (! _do_vaapi (#expr, expr)) goto out; + + 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; + Display *display = NULL; + VADisplay va_display = NULL; + VASurfaceAttrib attr; + VAImage img; + VASurfaceID *surfaces = NULL; + VAImageFormat image_format; + VAProfile *profiles = NULL; + VAConfigID config; + 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; + 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_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 */ + } + } + + img.image_id = VA_INVALID_ID; + + attr.type = VASurfaceAttribPixelFormat; + attr.flags = VA_SURFACE_ATTRIB_SETTABLE; + attr.value.type = VAGenericValueTypeInteger; + /* Arbitrarily use the 8-bit RGBA, assuming to be widely supported. + * In case the current system doesn't support it, vaCreateSurfaces will fail + * and this test will exit returning 1 */ + attr.value.value.i = VA_FOURCC_RGBA; + image_format.fourcc = VA_FOURCC_RGBA; + image_format.byte_order = VA_LSB_FIRST; + image_format.bits_per_pixel = 32; + + /* 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)); + + /* 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 = calloc (max_profiles, sizeof (VAProfile)); + + if (profiles == NULL) + { + fprintf (stderr, "Out of memory\n"); + goto out; + } + + 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 = calloc (surfaces_count, sizeof (VASurfaceID)); + + if (surfaces == NULL) + { + fprintf (stderr, "Out of memory\n"); + goto out; + } + + /* Test the creation of two surfaces and an image */ + do_vaapi_or_exit (vaCreateSurfaces (va_display, VA_RT_FORMAT_RGB32, 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])); + + + /* Assume VAProfileNone to be available. If it isn't vaCreateConfig will not return + * "VA_STATUS_SUCCESS" and this test will exit returning 1 */ + do_vaapi_or_exit (vaCreateConfig (va_display, VAProfileNone, VAEntrypointVideoProc, + NULL, 0, &config)); + + 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])); + + ret = 0; + +out: + if (va_display) + { + 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); + if (img.image_id != VA_INVALID_ID) + vaDestroyImage (va_display, img.image_id); + if (surfaces) + vaDestroySurfaces (va_display, surfaces, surfaces_count); + + vaTerminate (va_display); + } + if (display) + XCloseDisplay (display); + + free (profiles); + free (surfaces); + + return ret; +} diff --git a/helpers/meson.build b/helpers/meson.build index 06436e5b2..ea906175c 100644 --- a/helpers/meson.build +++ b/helpers/meson.build @@ -54,6 +54,17 @@ executable( # a game with no special linking would be able to load a library. ) +executable( + multiarch + '-check-va-api', + 'check-va-api.c', + dependencies : [libva, libva_x11, xlib], + include_directories : project_include_dirs, + install : true, + install_dir : pkglibexecdir, + # Deliberately no RPATH/RUNPATH here: we want to determine whether + # a game with no special linking would be able to use VA-API. +) + executable( multiarch + '-check-vdpau', 'check-vdpau.c', diff --git a/meson.build b/meson.build index 24323a80c..8338e753b 100644 --- a/meson.build +++ b/meson.build @@ -196,6 +196,14 @@ if not libelf.found() libelf = c_compiler.find_library('elf', required: true) endif +libva = dependency( + 'libva', +) + +libva_x11 = dependency( + 'libva-x11', +) + vdpau = dependency( 'vdpau', ) -- GitLab