/* GStreamer * Copyright (C) 2021 Igalia, S.L. * Author: Víctor Jáquez * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "vasurfaceimage.h" #include "gstvavideoformat.h" #include /* XXX: find a better log category */ #define GST_CAT_DEFAULT gst_va_display_debug GST_DEBUG_CATEGORY_EXTERN (gst_va_display_debug); gboolean va_destroy_surfaces (GstVaDisplay * display, VASurfaceID * surfaces, gint num_surfaces) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; g_return_val_if_fail (num_surfaces > 0, FALSE); status = vaDestroySurfaces (dpy, surfaces, num_surfaces); if (status != VA_STATUS_SUCCESS) { GST_ERROR ("vaDestroySurfaces: %s", vaErrorStr (status)); return FALSE; } return TRUE; } static gboolean _rt_format_is_rgb (guint rt_format) { switch (rt_format) { case VA_RT_FORMAT_RGB16: case VA_RT_FORMAT_RGB32: case VA_RT_FORMAT_RGB32_10: return TRUE; default: break; } return FALSE; } gboolean va_create_surfaces (GstVaDisplay * display, guint rt_format, guint fourcc, guint width, guint height, gint usage_hint, guint64 * modifiers, guint num_modifiers, VADRMPRIMESurfaceDescriptor * desc, VASurfaceID * surfaces, guint num_surfaces) { VADisplay dpy = gst_va_display_get_va_dpy (display); /* *INDENT-OFF* */ VASurfaceAttrib attrs[6] = { { .type = VASurfaceAttribUsageHint, .flags = VA_SURFACE_ATTRIB_SETTABLE, .value.type = VAGenericValueTypeInteger, .value.value.i = usage_hint, }, { .type = VASurfaceAttribMemoryType, .flags = VA_SURFACE_ATTRIB_SETTABLE, .value.type = VAGenericValueTypeInteger, .value.value.i = (desc && desc->num_objects > 0) ? VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 : VA_SURFACE_ATTRIB_MEM_TYPE_VA, }, }; VADRMFormatModifierList modifier_list = { .num_modifiers = num_modifiers, .modifiers = modifiers, }; VASurfaceAttribExternalBuffers extbuf = { .width = width, .height = height, .num_planes = 1, .pixel_format = fourcc, }; /* *INDENT-ON* */ VAStatus status; guint num_attrs = 2; g_return_val_if_fail (num_surfaces > 0, FALSE); /* must have modifiers when num_modifiers > 0 */ g_return_val_if_fail (num_modifiers == 0 || modifiers, FALSE); if (fourcc > 0) { /* *INDENT-OFF* */ attrs[num_attrs++] = (VASurfaceAttrib) { .type = VASurfaceAttribPixelFormat, .flags = VA_SURFACE_ATTRIB_SETTABLE, .value.type = VAGenericValueTypeInteger, .value.value.i = fourcc, }; /* *INDENT-ON* */ } if (desc && desc->num_objects > 0) { /* *INDENT-OFF* */ attrs[num_attrs++] = (VASurfaceAttrib) { .type = VASurfaceAttribExternalBufferDescriptor, .flags = VA_SURFACE_ATTRIB_SETTABLE, .value.type = VAGenericValueTypePointer, .value.value.p = desc, }; /* *INDENT-ON* */ } else if (GST_VA_DISPLAY_IS_IMPLEMENTATION (display, INTEL_I965) && _rt_format_is_rgb (rt_format)) { /* HACK(victor): disable tiling for i965 driver for RGB formats */ /* *INDENT-OFF* */ attrs[num_attrs++] = (VASurfaceAttrib) { .type = VASurfaceAttribExternalBufferDescriptor, .flags = VA_SURFACE_ATTRIB_SETTABLE, .value.type = VAGenericValueTypePointer, .value.value.p = &extbuf, }; /* *INDENT-ON* */ } if (num_modifiers > 0 && modifiers) { /* *INDENT-OFF* */ attrs[num_attrs++] = (VASurfaceAttrib) { .type = VASurfaceAttribDRMFormatModifiers, .flags = VA_SURFACE_ATTRIB_SETTABLE, .value.type = VAGenericValueTypePointer, .value.value.p = &modifier_list, }; /* *INDENT-ON* */ } retry: status = vaCreateSurfaces (dpy, rt_format, width, height, surfaces, num_surfaces, attrs, num_attrs); if (status == VA_STATUS_ERROR_ATTR_NOT_SUPPORTED && attrs[num_attrs - 1].type == VASurfaceAttribDRMFormatModifiers) { int i; /* if requested modifiers contain linear, let's remove the attribute and * "hope" the driver will create linear dmabufs */ for (i = 0; i < num_modifiers; ++i) { if (modifiers[i] == DRM_FORMAT_MOD_LINEAR) { num_attrs--; goto retry; } } } if (status != VA_STATUS_SUCCESS) { GST_ERROR ("vaCreateSurfaces: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_export_surface_to_dmabuf (GstVaDisplay * display, VASurfaceID surface, guint32 flags, VADRMPRIMESurfaceDescriptor * desc) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; status = vaExportSurfaceHandle (dpy, surface, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, flags, desc); if (status != VA_STATUS_SUCCESS) { GST_INFO ("vaExportSurfaceHandle: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_destroy_image (GstVaDisplay * display, VAImageID image_id) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; status = vaDestroyImage (dpy, image_id); if (status != VA_STATUS_SUCCESS) { GST_ERROR ("vaDestroyImage: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_get_derive_image (GstVaDisplay * display, VASurfaceID surface, VAImage * image) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; VASurfaceStatus state; /* When directly accessing a surface special care must be taken to insure sync * proper synchronization with the graphics hardware. Clients should call * vaQuerySurfaceStatus to insure that a surface is not the target of * concurrent rendering or currently being displayed by an overlay. */ status = vaQuerySurfaceStatus (dpy, surface, &state); if (status != VA_STATUS_SUCCESS) { GST_WARNING ("vaQuerySurfaceStatus: %s", vaErrorStr (status)); return FALSE; } if (state != VASurfaceReady) { GST_INFO ("Surface not ready"); return FALSE; } status = vaDeriveImage (dpy, surface, image); if (status != VA_STATUS_SUCCESS) { GST_WARNING ("vaDeriveImage: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_create_image (GstVaDisplay * display, GstVideoFormat format, gint width, gint height, VAImage * image) { VADisplay dpy = gst_va_display_get_va_dpy (display); const VAImageFormat *va_format; VAStatus status; va_format = gst_va_image_format_from_video_format (format); if (!va_format) return FALSE; status = vaCreateImage (dpy, (VAImageFormat *) va_format, width, height, image); if (status != VA_STATUS_SUCCESS) { GST_ERROR ("vaCreateImage: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_get_image (GstVaDisplay * display, VASurfaceID surface, VAImage * image) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; status = vaGetImage (dpy, surface, 0, 0, image->width, image->height, image->image_id); if (status != VA_STATUS_SUCCESS) { GST_ERROR ("vaGetImage: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_sync_surface (GstVaDisplay * display, VASurfaceID surface) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; status = vaSyncSurface (dpy, surface); if (status != VA_STATUS_SUCCESS) { GST_WARNING ("vaSyncSurface: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_map_buffer (GstVaDisplay * display, VABufferID buffer, GstMapFlags flags, gpointer * data) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; #if VA_CHECK_VERSION(1, 21, 0) uint32_t vaflags = 0; if (flags & GST_MAP_READ) vaflags |= VA_MAPBUFFER_FLAG_READ; if (flags & GST_MAP_WRITE) vaflags |= VA_MAPBUFFER_FLAG_WRITE; status = vaMapBuffer2 (dpy, buffer, data, vaflags); #else status = vaMapBuffer (dpy, buffer, data); #endif if (status != VA_STATUS_SUCCESS) { GST_WARNING ("vaMapBuffer: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_unmap_buffer (GstVaDisplay * display, VABufferID buffer) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; status = vaUnmapBuffer (dpy, buffer); if (status != VA_STATUS_SUCCESS) { GST_WARNING ("vaUnmapBuffer: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_put_image (GstVaDisplay * display, VASurfaceID surface, VAImage * image) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; if (!va_sync_surface (display, surface)) return FALSE; status = vaPutImage (dpy, surface, image->image_id, 0, 0, image->width, image->height, 0, 0, image->width, image->height); if (status != VA_STATUS_SUCCESS) { GST_ERROR ("vaPutImage: %s", vaErrorStr (status)); return FALSE; } return TRUE; } gboolean va_ensure_image (GstVaDisplay * display, VASurfaceID surface, GstVideoInfo * info, VAImage * image, gboolean derived) { gboolean ret = TRUE; if (image->image_id != VA_INVALID_ID) return TRUE; if (!va_sync_surface (display, surface)) return FALSE; if (derived) { ret = va_get_derive_image (display, surface, image); } else { ret = va_create_image (display, GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), image); } return ret; } gboolean va_check_surface (GstVaDisplay * display, VASurfaceID surface) { return va_check_surface_has_status (display, surface, 0); } #ifndef GST_DISABLE_GST_DEBUG static const char *surface_status_str_map[] = { [VASurfaceRendering] = "rendering", [VASurfaceDisplaying] = "displaying", [VASurfaceReady] = "ready", [VASurfaceSkipped] = "skipped" }; #endif gboolean va_check_surface_has_status (GstVaDisplay * display, VASurfaceID surface, VASurfaceStatus surface_status) { VADisplay dpy = gst_va_display_get_va_dpy (display); VAStatus status; VASurfaceStatus state; status = vaQuerySurfaceStatus (dpy, surface, &state); if (status != VA_STATUS_SUCCESS) { GST_ERROR ("vaQuerySurfaceStatus: %s", vaErrorStr (status)); return FALSE; } GST_LOG ("surface %#x status: %s", surface, surface_status_str_map[state]); /* Just query the surface, no flag to compare, we succeed. */ if (!surface_status) return TRUE; return ((state & surface_status) == surface_status); } gboolean va_copy_surface (GstVaDisplay * display, VASurfaceID dst, VASurfaceID src) { VADisplay dpy = gst_va_display_get_va_dpy (display); /* *INDENT-OFF* */ VACopyObject obj_src = { .obj_type = VACopyObjectSurface, .object = { .surface_id = src, }, }; VACopyObject obj_dst = { .obj_type = VACopyObjectSurface, .object = { .surface_id = dst, }, }; VACopyOption option = { .bits = { .va_copy_sync = VA_EXEC_SYNC, .va_copy_mode = VA_EXEC_MODE_DEFAULT, }, }; /* *INDENT-ON* */ VAStatus status; status = vaCopy (dpy, &obj_dst, &obj_src, option); if (status != VA_STATUS_SUCCESS) { GST_INFO ("vaCopy: %s", vaErrorStr (status)); return FALSE; } return TRUE; } guint va_get_surface_usage_hint (GstVaDisplay * display, VAEntrypoint entrypoint, GstPadDirection dir, gboolean is_dma) { switch (entrypoint) { case VAEntrypointVideoProc:{ /* For DMA kind caps, we use VA_SURFACE_ATTRIB_USAGE_HINT_VPP_READ | VA_SURFACE_ATTRIB_USAGE_HINT_VPP_WRITE to detect the modifiers. And in runtime, we should use the same flags in order to keep the same modifiers. */ if (is_dma) return VA_SURFACE_ATTRIB_USAGE_HINT_VPP_READ | VA_SURFACE_ATTRIB_USAGE_HINT_VPP_WRITE; if (dir == GST_PAD_SINK) return VA_SURFACE_ATTRIB_USAGE_HINT_VPP_READ; else if (dir == GST_PAD_SRC) return VA_SURFACE_ATTRIB_USAGE_HINT_VPP_WRITE; break; } case VAEntrypointVLD: return VA_SURFACE_ATTRIB_USAGE_HINT_DECODER; case VAEntrypointEncSlice: case VAEntrypointEncSliceLP: case VAEntrypointEncPicture: return VA_SURFACE_ATTRIB_USAGE_HINT_ENCODER; default: break; } return VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC; }