mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-11 11:51:34 +00:00
94e6a6e3de
Remove allocator member variable for use derived feature which doesn't need to be kept, it's just for configuration purposes. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5799>
2296 lines
61 KiB
C
2296 lines
61 KiB
C
/* GStreamer
|
|
* Copyright (C) 2020 Igalia, S.L.
|
|
* Author: Víctor Jáquez <vjaquez@igalia.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:gstvaallocator
|
|
* @title: VA allocators
|
|
* @short_description: VA allocators
|
|
* @sources:
|
|
* - gstvaallocator.h
|
|
*
|
|
* There are two types of VA allocators:
|
|
*
|
|
* * #GstVaAllocator
|
|
* * #GstVaDmabufAllocator
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstvaallocator.h"
|
|
|
|
#ifndef G_OS_WIN32
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <stdio.h> /* sscanf */
|
|
|
|
#include "gstvasurfacecopy.h"
|
|
#include "gstvavideoformat.h"
|
|
#include "vasurfaceimage.h"
|
|
|
|
#ifndef DRM_FORMAT_INVALID
|
|
#define DRM_FORMAT_INVALID 0
|
|
#endif
|
|
|
|
#define GST_CAT_DEFAULT gst_va_memory_debug
|
|
GST_DEBUG_CATEGORY (gst_va_memory_debug);
|
|
|
|
static void
|
|
_init_debug_category (void)
|
|
{
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
static gsize _init = 0;
|
|
|
|
if (g_once_init_enter (&_init)) {
|
|
GST_DEBUG_CATEGORY_INIT (gst_va_memory_debug, "vamemory", 0, "VA memory");
|
|
g_once_init_leave (&_init, 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*=========================== Quarks for GstMemory ===========================*/
|
|
|
|
static GQuark
|
|
gst_va_buffer_surface_quark (void)
|
|
{
|
|
static gsize surface_quark = 0;
|
|
|
|
if (g_once_init_enter (&surface_quark)) {
|
|
GQuark quark = g_quark_from_string ("GstVaBufferSurface");
|
|
g_once_init_leave (&surface_quark, quark);
|
|
}
|
|
|
|
return surface_quark;
|
|
}
|
|
|
|
static GQuark
|
|
gst_va_buffer_aux_surface_quark (void)
|
|
{
|
|
static gsize surface_quark = 0;
|
|
|
|
if (g_once_init_enter (&surface_quark)) {
|
|
GQuark quark = g_quark_from_string ("GstVaBufferAuxSurface");
|
|
g_once_init_leave (&surface_quark, quark);
|
|
}
|
|
|
|
return surface_quark;
|
|
}
|
|
|
|
/*========================= GstVaBufferSurface ===============================*/
|
|
|
|
typedef struct _GstVaBufferSurface GstVaBufferSurface;
|
|
struct _GstVaBufferSurface
|
|
{
|
|
GstVaDisplay *display;
|
|
VASurfaceID surface;
|
|
guint n_mems;
|
|
GstMemory *mems[GST_VIDEO_MAX_PLANES];
|
|
gint ref_count;
|
|
gint ref_mems_count;
|
|
};
|
|
|
|
static void
|
|
gst_va_buffer_surface_unref (gpointer data)
|
|
{
|
|
GstVaBufferSurface *buf = data;
|
|
|
|
g_return_if_fail (buf && GST_IS_VA_DISPLAY (buf->display));
|
|
|
|
if (g_atomic_int_dec_and_test (&buf->ref_count)) {
|
|
GST_LOG_OBJECT (buf->display, "Destroying surface %#x", buf->surface);
|
|
va_destroy_surfaces (buf->display, &buf->surface, 1);
|
|
gst_clear_object (&buf->display);
|
|
g_free (buf);
|
|
}
|
|
}
|
|
|
|
static GstVaBufferSurface *
|
|
gst_va_buffer_surface_new (VASurfaceID surface)
|
|
{
|
|
GstVaBufferSurface *buf = g_new (GstVaBufferSurface, 1);
|
|
|
|
g_atomic_int_set (&buf->ref_count, 0);
|
|
g_atomic_int_set (&buf->ref_mems_count, 0);
|
|
buf->surface = surface;
|
|
buf->display = NULL;
|
|
buf->n_mems = 0;
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*=========================== GstVaMemoryPool ================================*/
|
|
|
|
/* queue for disposed surfaces */
|
|
typedef struct _GstVaMemoryPool GstVaMemoryPool;
|
|
struct _GstVaMemoryPool
|
|
{
|
|
GstAtomicQueue *queue;
|
|
gint surface_count;
|
|
|
|
GMutex lock;
|
|
};
|
|
|
|
#define GST_VA_MEMORY_POOL_CAST(obj) ((GstVaMemoryPool *)obj)
|
|
#define GST_VA_MEMORY_POOL_LOCK(obj) g_mutex_lock (&GST_VA_MEMORY_POOL_CAST(obj)->lock)
|
|
#define GST_VA_MEMORY_POOL_UNLOCK(obj) g_mutex_unlock (&GST_VA_MEMORY_POOL_CAST(obj)->lock)
|
|
|
|
static void
|
|
gst_va_memory_pool_init (GstVaMemoryPool * self)
|
|
{
|
|
self->queue = gst_atomic_queue_new (2);
|
|
|
|
g_mutex_init (&self->lock);
|
|
|
|
self->surface_count = 0;
|
|
}
|
|
|
|
static void
|
|
gst_va_memory_pool_finalize (GstVaMemoryPool * self)
|
|
{
|
|
g_mutex_clear (&self->lock);
|
|
|
|
gst_atomic_queue_unref (self->queue);
|
|
}
|
|
|
|
static void
|
|
gst_va_memory_pool_flush_unlocked (GstVaMemoryPool * self,
|
|
GstVaDisplay * display)
|
|
{
|
|
GstMemory *mem;
|
|
GstVaBufferSurface *buf;
|
|
|
|
while ((mem = gst_atomic_queue_pop (self->queue))) {
|
|
/* destroy the surface */
|
|
buf = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
|
|
gst_va_buffer_surface_quark ());
|
|
if (buf) {
|
|
if (g_atomic_int_dec_and_test (&buf->ref_count)) {
|
|
GST_LOG ("Destroying surface %#x", buf->surface);
|
|
va_destroy_surfaces (display, &buf->surface, 1);
|
|
self->surface_count -= 1; /* GstVaDmabufAllocator */
|
|
g_free (buf);
|
|
}
|
|
} else {
|
|
self->surface_count -= 1; /* GstVaAllocator */
|
|
}
|
|
|
|
GST_MINI_OBJECT_CAST (mem)->dispose = NULL;
|
|
/* when mem are pushed available queue its allocator is unref,
|
|
* then now it is required to ref the allocator here because
|
|
* memory's finalize will unref it again */
|
|
gst_object_ref (mem->allocator);
|
|
gst_memory_unref (mem);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_va_memory_pool_flush (GstVaMemoryPool * self, GstVaDisplay * display)
|
|
{
|
|
GST_VA_MEMORY_POOL_LOCK (self);
|
|
gst_va_memory_pool_flush_unlocked (self, display);
|
|
GST_VA_MEMORY_POOL_UNLOCK (self);
|
|
}
|
|
|
|
static inline void
|
|
gst_va_memory_pool_push (GstVaMemoryPool * self, GstMemory * mem)
|
|
{
|
|
gst_atomic_queue_push (self->queue, gst_memory_ref (mem));
|
|
}
|
|
|
|
static inline GstMemory *
|
|
gst_va_memory_pool_pop (GstVaMemoryPool * self)
|
|
{
|
|
return gst_atomic_queue_pop (self->queue);
|
|
}
|
|
|
|
static inline GstMemory *
|
|
gst_va_memory_pool_peek (GstVaMemoryPool * self)
|
|
{
|
|
return gst_atomic_queue_peek (self->queue);
|
|
}
|
|
|
|
static inline guint
|
|
gst_va_memory_pool_surface_count (GstVaMemoryPool * self)
|
|
{
|
|
return g_atomic_int_get (&self->surface_count);
|
|
}
|
|
|
|
static inline void
|
|
gst_va_memory_pool_surface_inc (GstVaMemoryPool * self)
|
|
{
|
|
g_atomic_int_inc (&self->surface_count);
|
|
}
|
|
|
|
/*=========================== GstVaDmabufAllocator ===========================*/
|
|
|
|
/**
|
|
* GstVaDmabufAllocator:
|
|
*
|
|
* A pooled memory allocator backed by the DMABufs exported from a
|
|
* VASurfaceID. Also it is possible to import DMAbufs into a
|
|
* VASurfaceID.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
typedef struct _GstVaDmabufAllocator GstVaDmabufAllocator;
|
|
typedef struct _GstVaDmabufAllocatorClass GstVaDmabufAllocatorClass;
|
|
|
|
struct _GstVaDmabufAllocator
|
|
{
|
|
GstDmaBufAllocator parent;
|
|
|
|
GstVaDisplay *display;
|
|
|
|
GstMemoryMapFunction parent_map;
|
|
GstMemoryCopyFunction parent_copy;
|
|
|
|
GstVideoInfoDmaDrm info;
|
|
guint usage_hint;
|
|
|
|
GstVaSurfaceCopy *copy;
|
|
|
|
GstVaMemoryPool pool;
|
|
};
|
|
|
|
struct _GstVaDmabufAllocatorClass
|
|
{
|
|
GstDmaBufAllocatorClass parent_class;
|
|
};
|
|
|
|
#define gst_va_dmabuf_allocator_parent_class dmabuf_parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstVaDmabufAllocator, gst_va_dmabuf_allocator,
|
|
GST_TYPE_DMABUF_ALLOCATOR, _init_debug_category ());
|
|
|
|
static GstVaSurfaceCopy *
|
|
_ensure_surface_copy (GstVaSurfaceCopy ** old, GstVaDisplay * display,
|
|
GstVideoInfo * info)
|
|
{
|
|
GstVaSurfaceCopy *surface_copy;
|
|
|
|
surface_copy = g_atomic_pointer_get (old);
|
|
if (!surface_copy) {
|
|
surface_copy = gst_va_surface_copy_new (display, info);
|
|
|
|
/* others create a new one and set it before us */
|
|
if (surface_copy &&
|
|
!g_atomic_pointer_compare_and_exchange (old, NULL, surface_copy)) {
|
|
gst_va_surface_copy_free (surface_copy);
|
|
surface_copy = g_atomic_pointer_get (old);
|
|
}
|
|
}
|
|
|
|
return surface_copy;
|
|
}
|
|
|
|
/* If a buffer contains multiple memories (dmabuf objects) its very
|
|
* difficult to provide a realiable way to fast-copy single memories:
|
|
* While VA API sees surfaces with dependant dmabufs, GStreamer only
|
|
* copies dmabufs in isolation; trying to solve it while keeping a
|
|
* reference of the copied buffer and dmabuf index is very fragile. */
|
|
static GstMemory *
|
|
gst_va_dmabuf_mem_copy (GstMemory * gmem, gssize offset, gssize size)
|
|
{
|
|
GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (gmem->allocator);
|
|
GstVaBufferSurface *buf;
|
|
gsize mem_size;
|
|
|
|
buf = gst_mini_object_get_qdata (GST_MINI_OBJECT (gmem),
|
|
gst_va_buffer_surface_quark ());
|
|
|
|
if (buf->n_mems > 1 && self->info.drm_modifier != DRM_FORMAT_MOD_LINEAR) {
|
|
GST_ERROR_OBJECT (self, "Failed to copy multi-dmabuf because non-linear "
|
|
"modifier: %#" G_GINT64_MODIFIER "x.", self->info.drm_modifier);
|
|
return NULL;
|
|
}
|
|
|
|
/* check if it's full memory copy */
|
|
mem_size = gst_memory_get_sizes (gmem, NULL, NULL);
|
|
|
|
if (size == -1)
|
|
size = mem_size > offset ? mem_size - offset : 0;
|
|
|
|
/* @XXX: if one-memory buffer it's possible to copy */
|
|
if (offset == 0 && size == mem_size && buf->n_mems == 1) {
|
|
GstVaBufferSurface *buf_copy = NULL;
|
|
GstMemory *copy;
|
|
GstVaSurfaceCopy *copy_func;
|
|
|
|
GST_VA_MEMORY_POOL_LOCK (&self->pool);
|
|
copy = gst_va_memory_pool_pop (&self->pool);
|
|
GST_VA_MEMORY_POOL_UNLOCK (&self->pool);
|
|
|
|
if (copy) {
|
|
gst_object_ref (copy->allocator);
|
|
|
|
buf_copy = gst_mini_object_get_qdata (GST_MINI_OBJECT (copy),
|
|
gst_va_buffer_surface_quark ());
|
|
|
|
g_assert (g_atomic_int_get (&buf_copy->ref_mems_count) == 0);
|
|
|
|
g_atomic_int_add (&buf_copy->ref_mems_count, 1);
|
|
} else {
|
|
GstBuffer *buffer = gst_buffer_new ();
|
|
|
|
if (!gst_va_dmabuf_allocator_setup_buffer (gmem->allocator, buffer)) {
|
|
GST_WARNING_OBJECT (self, "Failed to create a new dmabuf memory");
|
|
return NULL;
|
|
}
|
|
|
|
copy = gst_buffer_get_memory (buffer, 0);
|
|
gst_buffer_unref (buffer);
|
|
|
|
buf_copy = gst_mini_object_get_qdata (GST_MINI_OBJECT (copy),
|
|
gst_va_buffer_surface_quark ());
|
|
}
|
|
|
|
g_assert (buf_copy->n_mems == 1);
|
|
|
|
copy_func =
|
|
_ensure_surface_copy (&self->copy, self->display, &self->info.vinfo);
|
|
if (copy_func
|
|
&& gst_va_surface_copy (copy_func, buf_copy->surface, buf->surface))
|
|
return copy;
|
|
|
|
gst_memory_unref (copy);
|
|
|
|
/* try system memory */
|
|
}
|
|
|
|
if (self->info.drm_modifier != DRM_FORMAT_MOD_LINEAR) {
|
|
GST_ERROR_OBJECT (self, "Failed to copy dmabuf because non-linear "
|
|
"modifier: %#" G_GINT64_MODIFIER "x.", self->info.drm_modifier);
|
|
return NULL;
|
|
}
|
|
|
|
/* fallback to system memory */
|
|
return self->parent_copy (gmem, offset, size);
|
|
|
|
}
|
|
|
|
static gpointer
|
|
gst_va_dmabuf_mem_map (GstMemory * gmem, gsize maxsize, GstMapFlags flags)
|
|
{
|
|
GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (gmem->allocator);
|
|
VASurfaceID surface = gst_va_memory_get_surface (gmem);
|
|
|
|
if (self->info.drm_modifier != DRM_FORMAT_MOD_LINEAR) {
|
|
GST_ERROR_OBJECT (self, "Failed to map the dmabuf because the modifier "
|
|
"is: %#" G_GINT64_MODIFIER "x, which is not linear.",
|
|
self->info.drm_modifier);
|
|
return NULL;
|
|
}
|
|
|
|
if (!va_sync_surface (self->display, surface))
|
|
return NULL;
|
|
|
|
return self->parent_map (gmem, maxsize, flags);
|
|
}
|
|
|
|
static void
|
|
gst_va_dmabuf_allocator_finalize (GObject * object)
|
|
{
|
|
GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (object);
|
|
|
|
g_clear_pointer (&self->copy, gst_va_surface_copy_free);
|
|
gst_va_memory_pool_finalize (&self->pool);
|
|
gst_clear_object (&self->display);
|
|
|
|
G_OBJECT_CLASS (dmabuf_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_va_dmabuf_allocator_dispose (GObject * object)
|
|
{
|
|
GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (object);
|
|
|
|
gst_va_memory_pool_flush_unlocked (&self->pool, self->display);
|
|
if (gst_va_memory_pool_surface_count (&self->pool) != 0) {
|
|
GST_WARNING_OBJECT (self, "Surfaces leaked: %d",
|
|
gst_va_memory_pool_surface_count (&self->pool));
|
|
}
|
|
|
|
G_OBJECT_CLASS (dmabuf_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_va_dmabuf_allocator_class_init (GstVaDmabufAllocatorClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = gst_va_dmabuf_allocator_dispose;
|
|
object_class->finalize = gst_va_dmabuf_allocator_finalize;
|
|
}
|
|
|
|
static void
|
|
gst_va_dmabuf_allocator_init (GstVaDmabufAllocator * self)
|
|
{
|
|
GstAllocator *allocator = GST_ALLOCATOR (self);
|
|
|
|
self->parent_map = allocator->mem_map;
|
|
allocator->mem_map = gst_va_dmabuf_mem_map;
|
|
self->parent_copy = allocator->mem_copy;
|
|
allocator->mem_copy = gst_va_dmabuf_mem_copy;
|
|
|
|
gst_video_info_dma_drm_init (&self->info);
|
|
|
|
gst_va_memory_pool_init (&self->pool);
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_allocator_new:
|
|
* @display: a #GstVaDisplay
|
|
*
|
|
* Instanciate a new pooled allocator backed with both DMABuf and
|
|
* VASurfaceID.
|
|
*
|
|
* Returns: a new allocated #GstAllocator
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
GstAllocator *
|
|
gst_va_dmabuf_allocator_new (GstVaDisplay * display)
|
|
{
|
|
GstVaDmabufAllocator *self;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_DISPLAY (display), NULL);
|
|
|
|
self = g_object_new (GST_TYPE_VA_DMABUF_ALLOCATOR, NULL);
|
|
self->display = gst_object_ref (display);
|
|
gst_object_ref_sink (self);
|
|
|
|
return GST_ALLOCATOR (self);
|
|
}
|
|
|
|
static inline goffset
|
|
_get_fd_size (gint fd)
|
|
{
|
|
#ifndef G_OS_WIN32
|
|
return lseek (fd, 0, SEEK_END);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static gboolean
|
|
gst_va_dmabuf_memory_release (GstMiniObject * mini_object)
|
|
{
|
|
GstMemory *mem = GST_MEMORY_CAST (mini_object);
|
|
GstVaBufferSurface *buf;
|
|
GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (mem->allocator);
|
|
guint i;
|
|
|
|
buf = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
|
|
gst_va_buffer_surface_quark ());
|
|
if (!buf)
|
|
return TRUE; /* free this unknown buffer */
|
|
|
|
/* if this is the last reference to the GstVaBufferSurface, iterates
|
|
* its array of memories to push them into the queue with thread
|
|
* safetly. */
|
|
GST_VA_MEMORY_POOL_LOCK (&self->pool);
|
|
if (g_atomic_int_dec_and_test (&buf->ref_mems_count)) {
|
|
for (i = 0; i < buf->n_mems; i++) {
|
|
GST_LOG_OBJECT (self, "releasing %p: dmabuf %d, va surface %#x",
|
|
buf->mems[i], gst_dmabuf_memory_get_fd (buf->mems[i]), buf->surface);
|
|
gst_va_memory_pool_push (&self->pool, buf->mems[i]);
|
|
}
|
|
}
|
|
GST_VA_MEMORY_POOL_UNLOCK (&self->pool);
|
|
|
|
/* note: if ref_mem_count doesn't reach zero, that memory will
|
|
* "float" until it's pushed back into the pool by the last va
|
|
* buffer surface ref */
|
|
|
|
/* Keep last in case we are holding on the last allocator ref */
|
|
gst_object_unref (mem->allocator);
|
|
|
|
/* don't call mini_object's free */
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
_modifier_found (guint64 modifier, guint64 * modifiers, guint num_modifiers)
|
|
{
|
|
guint i;
|
|
|
|
/* user doesn't care the returned modifier */
|
|
if (num_modifiers == 0)
|
|
return TRUE;
|
|
|
|
for (i = 0; i < num_modifiers; i++)
|
|
if (modifier == modifiers[i])
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
_va_create_surface_and_export_to_dmabuf (GstVaDisplay * display,
|
|
guint usage_hint, guint64 * modifiers, guint num_modifiers,
|
|
GstVideoInfo * info, VASurfaceID * ret_surface,
|
|
VADRMPRIMESurfaceDescriptor * ret_desc)
|
|
{
|
|
VADRMPRIMESurfaceDescriptor desc = { 0, };
|
|
guint32 i, fourcc, rt_format, export_flags;
|
|
GstVideoFormat format;
|
|
VASurfaceID surface;
|
|
guint64 prev_modifier = DRM_FORMAT_MOD_INVALID;
|
|
|
|
_init_debug_category ();
|
|
|
|
format = GST_VIDEO_INFO_FORMAT (info);
|
|
|
|
fourcc = gst_va_fourcc_from_video_format (format);
|
|
rt_format = gst_va_chroma_from_video_format (format);
|
|
if (fourcc == 0 || rt_format == 0)
|
|
return FALSE;
|
|
|
|
if (!va_create_surfaces (display, rt_format, fourcc,
|
|
GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info),
|
|
usage_hint, modifiers, num_modifiers, NULL, &surface, 1))
|
|
return FALSE;
|
|
|
|
/* workaround for missing layered dmabuf formats in i965 */
|
|
if (GST_VA_DISPLAY_IS_IMPLEMENTATION (display, INTEL_I965)
|
|
&& (fourcc == VA_FOURCC_YUY2 || fourcc == VA_FOURCC_UYVY)) {
|
|
/* These are not representable as separate planes */
|
|
export_flags = VA_EXPORT_SURFACE_COMPOSED_LAYERS;
|
|
} else {
|
|
/* Each layer will contain exactly one plane. For example, an NV12
|
|
* surface will be exported as two layers */
|
|
export_flags = VA_EXPORT_SURFACE_SEPARATE_LAYERS;
|
|
}
|
|
|
|
export_flags |= VA_EXPORT_SURFACE_READ_WRITE;
|
|
|
|
if (!va_export_surface_to_dmabuf (display, surface, export_flags, &desc))
|
|
goto failed;
|
|
|
|
if (GST_VIDEO_INFO_N_PLANES (info) != desc.num_layers)
|
|
goto failed;
|
|
|
|
/* YUY2 and YUYV are the same. radeonsi returns always YUYV.
|
|
* There's no reason to fail if the different fourcc if there're dups.
|
|
* https://fourcc.org/pixel-format/yuv-yuy2/ */
|
|
if (fourcc != desc.fourcc) {
|
|
GST_INFO ("Different fourcc: requested %" GST_FOURCC_FORMAT " - returned %"
|
|
GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc),
|
|
GST_FOURCC_ARGS (desc.fourcc));
|
|
}
|
|
|
|
if (desc.num_objects == 0) {
|
|
GST_ERROR ("Failed to export surface to dmabuf");
|
|
goto failed;
|
|
}
|
|
|
|
for (i = 0; i < desc.num_objects; i++) {
|
|
guint64 modifier = desc.objects[i].drm_format_modifier;
|
|
|
|
if (!_modifier_found (modifier, modifiers, num_modifiers)) {
|
|
GST_ERROR ("driver set a modifier different from allowed list: "
|
|
"0x%016" G_GINT64_MODIFIER "x", modifier);
|
|
goto failed;
|
|
}
|
|
/* XXX: all dmabufs in buffer have to have the same modifier, otherwise the
|
|
* drm-format field in caps is ill-designed */
|
|
if (i > 0 && modifier != prev_modifier) {
|
|
GST_ERROR ("Different objects have different modifier");
|
|
goto failed;
|
|
}
|
|
|
|
prev_modifier = modifier;
|
|
}
|
|
|
|
*ret_surface = surface;
|
|
if (ret_desc)
|
|
*ret_desc = desc;
|
|
|
|
return TRUE;
|
|
|
|
failed:
|
|
{
|
|
va_destroy_surfaces (display, &surface, 1);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_get_modifier_for_format:
|
|
* @display: a #GstVaDisplay
|
|
* @format: a #GstVideoFormat
|
|
* @usage_hint: VA usage hint
|
|
*
|
|
* Get the underlying modifier for specified @format and @usage_hint.
|
|
*
|
|
* Returns: the underlying modifier.
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
guint64
|
|
gst_va_dmabuf_get_modifier_for_format (GstVaDisplay * display,
|
|
GstVideoFormat format, guint usage_hint)
|
|
{
|
|
VADRMPRIMESurfaceDescriptor desc = { 0, };
|
|
VASurfaceID surface;
|
|
GstVideoInfo info;
|
|
|
|
gst_video_info_init (&info);
|
|
gst_video_info_set_format (&info, format, 64, 64);
|
|
|
|
if (!_va_create_surface_and_export_to_dmabuf (display, usage_hint,
|
|
NULL, 0, &info, &surface, &desc))
|
|
return DRM_FORMAT_MOD_INVALID;
|
|
|
|
va_destroy_surfaces (display, &surface, 1);
|
|
|
|
return desc.objects[0].drm_format_modifier;
|
|
}
|
|
|
|
/* Creates an exported VASurfaceID and adds it as @buffer's memories
|
|
* qdata
|
|
*
|
|
* If @info is not NULL, a dummy (non-pooled) buffer is created to
|
|
* update offsets and strides, and it has to be unrefed immediately.
|
|
*/
|
|
static gboolean
|
|
gst_va_dmabuf_allocator_setup_buffer_full (GstAllocator * allocator,
|
|
GstBuffer * buffer, GstVideoInfoDmaDrm * info)
|
|
{
|
|
GstVaBufferSurface *buf;
|
|
GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (allocator);
|
|
VADRMPRIMESurfaceDescriptor desc = { 0, };
|
|
VASurfaceID surface;
|
|
guint32 i;
|
|
GDestroyNotify buffer_destroy = NULL;
|
|
gsize object_offset[4];
|
|
|
|
g_return_val_if_fail (GST_IS_VA_DMABUF_ALLOCATOR (allocator), FALSE);
|
|
|
|
if (!_va_create_surface_and_export_to_dmabuf (self->display, self->usage_hint,
|
|
&self->info.drm_modifier, 1, &self->info.vinfo, &surface, &desc))
|
|
return FALSE;
|
|
|
|
buf = gst_va_buffer_surface_new (surface);
|
|
if (G_UNLIKELY (info))
|
|
*info = self->info;
|
|
|
|
buf->n_mems = desc.num_objects;
|
|
|
|
for (i = 0; i < desc.num_objects; i++) {
|
|
gint fd = desc.objects[i].fd;
|
|
/* prime descriptor reports the total size of the object, including regions
|
|
* which aren't part surface's space. Let's just grab the surface's size: */
|
|
gsize size = _get_fd_size (fd);
|
|
GstMemory *mem = gst_dmabuf_allocator_alloc (allocator, fd, size);
|
|
|
|
if (desc.objects[i].size < size) {
|
|
GST_WARNING_OBJECT (self, "driver bug: fd size (%" G_GSIZE_FORMAT
|
|
") is bigger than object descriptor size (%" G_GUINT32_FORMAT ")",
|
|
size, desc.objects[i].size);
|
|
}
|
|
|
|
object_offset[i] = gst_buffer_get_size (buffer);
|
|
gst_buffer_append_memory (buffer, mem);
|
|
buf->mems[i] = mem;
|
|
|
|
if (G_LIKELY (!info)) {
|
|
GST_MINI_OBJECT (mem)->dispose = gst_va_dmabuf_memory_release;
|
|
g_atomic_int_add (&buf->ref_mems_count, 1);
|
|
} else {
|
|
/* if no @info, surface will be destroyed as soon as buffer is
|
|
* destroyed (e.g. gst_va_dmabuf_allocator_try()) */
|
|
buf->display = gst_object_ref (self->display);
|
|
buffer_destroy = gst_va_buffer_surface_unref;
|
|
}
|
|
|
|
g_atomic_int_add (&buf->ref_count, 1);
|
|
gst_mini_object_set_qdata (GST_MINI_OBJECT (mem),
|
|
gst_va_buffer_surface_quark (), buf, buffer_destroy);
|
|
|
|
if (G_UNLIKELY (info)) {
|
|
GST_VIDEO_INFO_PLANE_OFFSET (&info->vinfo, i) =
|
|
GST_VIDEO_INFO_SIZE (&info->vinfo);
|
|
}
|
|
|
|
GST_LOG_OBJECT (self, "buffer %p: new dmabuf %d / surface %#x [%dx%d] "
|
|
"size %" G_GSIZE_FORMAT " drm mod %#" G_GINT64_MODIFIER "x",
|
|
buffer, fd, surface, GST_VIDEO_INFO_WIDTH (&self->info.vinfo),
|
|
GST_VIDEO_INFO_HEIGHT (&self->info.vinfo), size,
|
|
self->info.drm_modifier);
|
|
}
|
|
|
|
if (G_UNLIKELY (info)) {
|
|
if (desc.num_objects > 0) {
|
|
/* update drm modifier and format */
|
|
info->drm_modifier = desc.objects[0].drm_format_modifier;
|
|
info->drm_fourcc = gst_va_drm_fourcc_from_video_format
|
|
(GST_VIDEO_INFO_FORMAT (&self->info.vinfo));
|
|
}
|
|
|
|
GST_VIDEO_INFO_SIZE (&info->vinfo) = gst_buffer_get_size (buffer);
|
|
|
|
for (i = 0; i < desc.num_layers; i++) {
|
|
g_assert (desc.layers[i].num_planes == 1);
|
|
GST_VIDEO_INFO_PLANE_OFFSET (&info->vinfo, i) =
|
|
object_offset[desc.layers[i].object_index[0]] +
|
|
desc.layers[i].offset[0];
|
|
GST_VIDEO_INFO_PLANE_STRIDE (&info->vinfo, i) = desc.layers[i].pitch[0];
|
|
}
|
|
} else {
|
|
gst_va_memory_pool_surface_inc (&self->pool);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_allocator_setup_buffer:
|
|
* @allocator: a #GstAllocator
|
|
* @buffer: an empty #GstBuffer
|
|
*
|
|
* This function creates a new VASurfaceID and exposes its DMABufs,
|
|
* later it populates the @buffer with those DMABufs.
|
|
*
|
|
* Return: %TRUE if @buffer is populated correctly; %FALSE otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_dmabuf_allocator_setup_buffer (GstAllocator * allocator,
|
|
GstBuffer * buffer)
|
|
{
|
|
return gst_va_dmabuf_allocator_setup_buffer_full (allocator, buffer, NULL);
|
|
}
|
|
|
|
static VASurfaceID
|
|
gst_va_dmabuf_allocator_prepare_buffer_unlocked (GstVaDmabufAllocator * self,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstMemory *mems[GST_VIDEO_MAX_PLANES] = { 0, };
|
|
GstVaBufferSurface *buf;
|
|
gint i, j, idx;
|
|
|
|
mems[0] = gst_va_memory_pool_pop (&self->pool);
|
|
if (!mems[0])
|
|
return VA_INVALID_ID;
|
|
|
|
buf = gst_mini_object_get_qdata (GST_MINI_OBJECT (mems[0]),
|
|
gst_va_buffer_surface_quark ());
|
|
if (!buf)
|
|
return VA_INVALID_ID;
|
|
|
|
if (buf->surface == VA_INVALID_ID)
|
|
return VA_INVALID_ID;
|
|
|
|
for (idx = 1; idx < buf->n_mems; idx++) {
|
|
/* grab next memory from queue */
|
|
{
|
|
GstMemory *mem;
|
|
GstVaBufferSurface *pbuf;
|
|
|
|
mem = gst_va_memory_pool_peek (&self->pool);
|
|
if (!mem)
|
|
return VA_INVALID_ID;
|
|
|
|
pbuf = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
|
|
gst_va_buffer_surface_quark ());
|
|
if (!pbuf)
|
|
return VA_INVALID_ID;
|
|
|
|
if (pbuf->surface != buf->surface) {
|
|
GST_WARNING_OBJECT (self,
|
|
"expecting memory with surface %#x but got %#x: "
|
|
"possible memory interweaving", buf->surface, pbuf->surface);
|
|
return VA_INVALID_ID;
|
|
}
|
|
}
|
|
|
|
mems[idx] = gst_va_memory_pool_pop (&self->pool);
|
|
};
|
|
|
|
/* append memories */
|
|
for (i = 0; i < buf->n_mems; i++) {
|
|
gboolean found = FALSE;
|
|
|
|
/* find next memory to append */
|
|
for (j = 0; j < idx; j++) {
|
|
if (buf->mems[i] == mems[j]) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if not found, free all the popped memories and bail */
|
|
if (!found) {
|
|
if (!buf->display)
|
|
buf->display = gst_object_ref (self->display);
|
|
for (j = 0; j < idx; j++) {
|
|
gst_object_ref (buf->mems[j]->allocator);
|
|
GST_MINI_OBJECT (mems[j])->dispose = NULL;
|
|
gst_memory_unref (mems[j]);
|
|
}
|
|
return VA_INVALID_ID;
|
|
}
|
|
|
|
g_atomic_int_add (&buf->ref_mems_count, 1);
|
|
gst_object_ref (buf->mems[i]->allocator);
|
|
gst_buffer_append_memory (buffer, buf->mems[i]);
|
|
|
|
GST_LOG ("bufer %p: memory %p - dmabuf %d / surface %#x", buffer,
|
|
buf->mems[i], gst_dmabuf_memory_get_fd (buf->mems[i]),
|
|
gst_va_memory_get_surface (buf->mems[i]));
|
|
}
|
|
|
|
return buf->surface;
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_allocator_prepare_buffer:
|
|
* @allocator: a #GstAllocator
|
|
* @buffer: an empty #GstBuffer
|
|
*
|
|
* This method will populate @buffer with pooled VASurfaceID/DMABuf
|
|
* memories. It doesn't allocate new VASurfacesID.
|
|
*
|
|
* Returns: %TRUE if @buffer was populated correctly; %FALSE
|
|
* otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_dmabuf_allocator_prepare_buffer (GstAllocator * allocator,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstVaDmabufAllocator *self;
|
|
VASurfaceID surface;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_DMABUF_ALLOCATOR (allocator), FALSE);
|
|
|
|
self = GST_VA_DMABUF_ALLOCATOR (allocator);
|
|
|
|
GST_VA_MEMORY_POOL_LOCK (&self->pool);
|
|
surface = gst_va_dmabuf_allocator_prepare_buffer_unlocked (self, buffer);
|
|
GST_VA_MEMORY_POOL_UNLOCK (&self->pool);
|
|
|
|
return (surface != VA_INVALID_ID);
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_allocator_flush:
|
|
* @allocator: a #GstAllocator
|
|
*
|
|
* Removes all the memories in @allocator's pool.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
void
|
|
gst_va_dmabuf_allocator_flush (GstAllocator * allocator)
|
|
{
|
|
GstVaDmabufAllocator *self;
|
|
|
|
g_return_if_fail (GST_IS_VA_DMABUF_ALLOCATOR (allocator));
|
|
|
|
self = GST_VA_DMABUF_ALLOCATOR (allocator);
|
|
|
|
gst_va_memory_pool_flush (&self->pool, self->display);
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_allocator_try:
|
|
* @allocator: a #GstAllocator
|
|
*
|
|
* Try to allocate a test buffer in order to verify that the
|
|
* allocator's configuration is valid.
|
|
*
|
|
* Returns: %TRUE if the configuration is valid; %FALSE otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
static gboolean
|
|
gst_va_dmabuf_allocator_try (GstAllocator * allocator)
|
|
{
|
|
GstBuffer *buffer;
|
|
GstVaDmabufAllocator *self;
|
|
GstVideoInfoDmaDrm info;
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_DMABUF_ALLOCATOR (allocator), FALSE);
|
|
|
|
self = GST_VA_DMABUF_ALLOCATOR (allocator);
|
|
info = self->info;
|
|
|
|
buffer = gst_buffer_new ();
|
|
ret = gst_va_dmabuf_allocator_setup_buffer_full (allocator, buffer, &info);
|
|
gst_buffer_unref (buffer);
|
|
|
|
if (ret)
|
|
self->info = info;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_allocator_set_format:
|
|
* @allocator: a #GstAllocator
|
|
* @info: (in) (out caller-allocates) (not nullable): a #GstVideoInfoDmaDrm
|
|
* @usage_hint: VA usage hint
|
|
*
|
|
* Sets the configuration defined by @info and @usage_hint for
|
|
* @allocator, and it tries the configuration, if @allocator has not
|
|
* allocated memories yet.
|
|
*
|
|
* If @allocator has memory allocated already, and frame size, format
|
|
* and modifier in @info are the same as currently configured in
|
|
* @allocator, the rest of @info parameters are updated internally.
|
|
*
|
|
* Returns: %TRUE if the configuration is valid or updated; %FALSE if
|
|
* configuration is not valid or not updated.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_dmabuf_allocator_set_format (GstAllocator * allocator,
|
|
GstVideoInfoDmaDrm * info, guint usage_hint)
|
|
{
|
|
GstVaDmabufAllocator *self;
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_DMABUF_ALLOCATOR (allocator), FALSE);
|
|
g_return_val_if_fail (info, FALSE);
|
|
|
|
self = GST_VA_DMABUF_ALLOCATOR (allocator);
|
|
|
|
if (gst_va_memory_pool_surface_count (&self->pool) != 0) {
|
|
if (info->drm_modifier == self->info.drm_modifier
|
|
&& GST_VIDEO_INFO_FORMAT (&info->vinfo)
|
|
== GST_VIDEO_INFO_FORMAT (&self->info.vinfo)
|
|
&& GST_VIDEO_INFO_WIDTH (&info->vinfo)
|
|
== GST_VIDEO_INFO_WIDTH (&self->info.vinfo)
|
|
&& GST_VIDEO_INFO_HEIGHT (&info->vinfo)
|
|
== GST_VIDEO_INFO_HEIGHT (&self->info.vinfo)
|
|
&& usage_hint == self->usage_hint) {
|
|
*info = self->info; /* update callee info (offset & stride) */
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
self->usage_hint = usage_hint;
|
|
self->info = *info;
|
|
|
|
g_clear_pointer (&self->copy, gst_va_surface_copy_free);
|
|
|
|
ret = gst_va_dmabuf_allocator_try (allocator);
|
|
|
|
if (ret)
|
|
*info = self->info;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_allocator_get_format:
|
|
* @allocator: a #GstAllocator
|
|
* @info: (out) (optional): a #GstVideoInfoDmaDrm
|
|
* @usage_hint: (out) (optional): VA usage hint
|
|
*
|
|
* Gets current internal configuration of @allocator.
|
|
*
|
|
* Returns: %TRUE if @allocator is already configured; %FALSE
|
|
* otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_dmabuf_allocator_get_format (GstAllocator * allocator,
|
|
GstVideoInfoDmaDrm * info, guint * usage_hint)
|
|
{
|
|
GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (allocator);
|
|
|
|
if (GST_VIDEO_INFO_FORMAT (&self->info.vinfo) == GST_VIDEO_FORMAT_UNKNOWN)
|
|
return FALSE;
|
|
|
|
if (info)
|
|
*info = self->info;
|
|
if (usage_hint)
|
|
*usage_hint = self->usage_hint;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_is_fd_repeated (uintptr_t fds[GST_VIDEO_MAX_PLANES], guint cur, guint * prev)
|
|
{
|
|
guint i;
|
|
|
|
g_assert (cur <= GST_VIDEO_MAX_PLANES);
|
|
|
|
if (cur == 0)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < cur; i++) {
|
|
if (fds[i] == fds[cur]) {
|
|
if (prev)
|
|
*prev = i;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* gst_va_dmabuf_memories_setup:
|
|
* @display: a #GstVaDisplay
|
|
* @drm_info: a #GstVideoInfoDmaDrm
|
|
* @mem: (array fixed-size=4) (element-type GstMemory): Memories. One
|
|
* per plane.
|
|
* @fds: (array fixed-size=4) (element-type uintptr_t): array of
|
|
* DMABuf file descriptors.
|
|
* @offset: (array fixed-size=4) (element-type gsize): array of memory
|
|
* offsets.
|
|
* @usage_hint: VA usage hint.
|
|
*
|
|
* It imports the array of @mem, representing a single frame, into a
|
|
* VASurfaceID and it's attached into every @mem.
|
|
*
|
|
* Returns: %TRUE if frame is imported correctly into a VASurfaceID;
|
|
* %FALSE otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
/* XXX: use a surface pool to control the created surfaces */
|
|
gboolean
|
|
gst_va_dmabuf_memories_setup (GstVaDisplay * display,
|
|
GstVideoInfoDmaDrm * drm_info, GstMemory * mem[GST_VIDEO_MAX_PLANES],
|
|
uintptr_t fds[GST_VIDEO_MAX_PLANES], gsize offset[GST_VIDEO_MAX_PLANES],
|
|
guint usage_hint)
|
|
{
|
|
GstVideoFormat format;
|
|
GstVaBufferSurface *buf;
|
|
GstVideoInfo *info = &drm_info->vinfo;
|
|
/* *INDENT-OFF* */
|
|
VADRMPRIMESurfaceDescriptor desc = {
|
|
.width = GST_VIDEO_INFO_WIDTH (info),
|
|
.height = GST_VIDEO_INFO_HEIGHT (info),
|
|
/* GStreamer can only describe one PRIME layer */
|
|
.num_layers = 1,
|
|
};
|
|
/* *INDENT-ON* */
|
|
VASurfaceID surface;
|
|
guint32 fourcc, rt_format, drm_fourcc;
|
|
guint64 drm_modifier;
|
|
guint i, j, prev, n_planes;
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_DISPLAY (display), FALSE);
|
|
|
|
n_planes = GST_VIDEO_INFO_N_PLANES (info);
|
|
|
|
format = (drm_info->drm_fourcc == DRM_FORMAT_INVALID) ?
|
|
GST_VIDEO_INFO_FORMAT (info) :
|
|
gst_va_video_format_from_drm_fourcc (drm_info->drm_fourcc);
|
|
if (format == GST_VIDEO_FORMAT_UNKNOWN)
|
|
return FALSE;
|
|
|
|
drm_fourcc = (drm_info->drm_fourcc == DRM_FORMAT_INVALID) ?
|
|
gst_va_drm_fourcc_from_video_format (format) : drm_info->drm_fourcc;
|
|
if (drm_fourcc == 0)
|
|
return FALSE;
|
|
|
|
rt_format = gst_va_chroma_from_video_format (format);
|
|
if (rt_format == 0)
|
|
return FALSE;
|
|
|
|
fourcc = gst_va_fourcc_from_video_format (format);
|
|
if (fourcc == 0)
|
|
return FALSE;
|
|
|
|
drm_modifier = (drm_info->drm_modifier == DRM_FORMAT_MOD_INVALID) ?
|
|
DRM_FORMAT_MOD_LINEAR : drm_info->drm_modifier;
|
|
|
|
desc.fourcc = fourcc;
|
|
desc.layers[0].num_planes = n_planes;
|
|
desc.layers[0].drm_format = drm_fourcc;
|
|
|
|
for (i = j = 0; i < n_planes; i++) {
|
|
desc.layers[0].pitch[i] = GST_VIDEO_INFO_PLANE_STRIDE (info, i);
|
|
desc.layers[0].offset[i] = offset[i];
|
|
|
|
if (_is_fd_repeated (fds, i, &prev)) {
|
|
desc.layers[0].object_index[i] = prev;
|
|
continue;
|
|
}
|
|
|
|
desc.objects[j].fd = fds[i];
|
|
desc.objects[j].size = _get_fd_size (fds[i]);
|
|
desc.objects[j].drm_format_modifier = drm_modifier;
|
|
|
|
desc.layers[0].object_index[i] = j;
|
|
j++;
|
|
}
|
|
|
|
desc.num_objects = j;
|
|
|
|
ret = va_create_surfaces (display, rt_format, fourcc, desc.width, desc.height,
|
|
usage_hint, NULL, 0, &desc, &surface, 1);
|
|
if (!ret)
|
|
return FALSE;
|
|
|
|
GST_LOG_OBJECT (display, "Created surface %#x [%dx%d]", surface, desc.width,
|
|
desc.height);
|
|
|
|
buf = gst_va_buffer_surface_new (surface);
|
|
buf->display = gst_object_ref (display);
|
|
buf->n_mems = n_planes;
|
|
memcpy (buf->mems, mem, sizeof (buf->mems));
|
|
|
|
for (i = 0; i < n_planes; i++) {
|
|
g_atomic_int_add (&buf->ref_count, 1);
|
|
gst_mini_object_set_qdata (GST_MINI_OBJECT (mem[i]),
|
|
gst_va_buffer_surface_quark (), buf, gst_va_buffer_surface_unref);
|
|
GST_INFO_OBJECT (display, "setting surface %#x to dmabuf fd %d",
|
|
buf->surface, gst_dmabuf_memory_get_fd (mem[i]));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*===================== GstVaAllocator / GstVaMemory =========================*/
|
|
|
|
/**
|
|
* GstVaAllocator:
|
|
*
|
|
* A pooled memory allocator backed by VASurfaceID.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
typedef struct _GstVaAllocator GstVaAllocator;
|
|
typedef struct _GstVaAllocatorClass GstVaAllocatorClass;
|
|
|
|
struct _GstVaAllocator
|
|
{
|
|
GstAllocator parent;
|
|
|
|
GstVaDisplay *display;
|
|
|
|
gboolean use_derived;
|
|
GArray *surface_formats;
|
|
|
|
GstVideoFormat surface_format;
|
|
GstVideoFormat img_format;
|
|
guint32 fourcc;
|
|
guint32 rt_format;
|
|
|
|
GstVideoInfo info;
|
|
guint usage_hint;
|
|
|
|
guint32 hacks;
|
|
|
|
GstVaSurfaceCopy *copy;
|
|
|
|
GstVaMemoryPool pool;
|
|
};
|
|
|
|
struct _GstVaAllocatorClass
|
|
{
|
|
GstAllocatorClass parent_class;
|
|
};
|
|
|
|
typedef struct _GstVaMemory GstVaMemory;
|
|
struct _GstVaMemory
|
|
{
|
|
GstMemory mem;
|
|
|
|
VASurfaceID surface;
|
|
GstVideoFormat surface_format;
|
|
VAImage image;
|
|
gpointer mapped_data;
|
|
|
|
GstMapFlags prev_mapflags;
|
|
gint map_count;
|
|
|
|
gboolean is_derived;
|
|
gboolean is_dirty;
|
|
GMutex lock;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GstVaAllocator, gst_va_allocator, GST_TYPE_ALLOCATOR,
|
|
_init_debug_category ());
|
|
|
|
static gboolean _va_unmap (GstVaMemory * mem);
|
|
|
|
static void
|
|
gst_va_allocator_finalize (GObject * object)
|
|
{
|
|
GstVaAllocator *self = GST_VA_ALLOCATOR (object);
|
|
|
|
g_clear_pointer (&self->copy, gst_va_surface_copy_free);
|
|
gst_va_memory_pool_finalize (&self->pool);
|
|
g_clear_pointer (&self->surface_formats, g_array_unref);
|
|
gst_clear_object (&self->display);
|
|
|
|
G_OBJECT_CLASS (gst_va_allocator_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_va_allocator_dispose (GObject * object)
|
|
{
|
|
GstVaAllocator *self = GST_VA_ALLOCATOR (object);
|
|
|
|
gst_va_memory_pool_flush_unlocked (&self->pool, self->display);
|
|
if (gst_va_memory_pool_surface_count (&self->pool) != 0) {
|
|
GST_WARNING_OBJECT (self, "Surfaces leaked: %d",
|
|
gst_va_memory_pool_surface_count (&self->pool));
|
|
}
|
|
|
|
G_OBJECT_CLASS (gst_va_allocator_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
_va_free (GstAllocator * allocator, GstMemory * mem)
|
|
{
|
|
GstVaAllocator *self = GST_VA_ALLOCATOR (allocator);
|
|
GstVaMemory *va_mem = (GstVaMemory *) mem;
|
|
|
|
if (va_mem->mapped_data) {
|
|
g_warning (G_STRLOC ":%s: Freeing memory %p still mapped", G_STRFUNC,
|
|
va_mem);
|
|
_va_unmap (va_mem);
|
|
}
|
|
|
|
if (va_mem->surface != VA_INVALID_ID && mem->parent == NULL) {
|
|
GST_LOG_OBJECT (self, "Destroying surface %#x", va_mem->surface);
|
|
va_destroy_surfaces (self->display, &va_mem->surface, 1);
|
|
}
|
|
|
|
g_mutex_clear (&va_mem->lock);
|
|
|
|
g_free (va_mem);
|
|
}
|
|
|
|
static void
|
|
gst_va_allocator_class_init (GstVaAllocatorClass * klass)
|
|
{
|
|
GstAllocatorClass *allocator_class = GST_ALLOCATOR_CLASS (klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = gst_va_allocator_dispose;
|
|
object_class->finalize = gst_va_allocator_finalize;
|
|
allocator_class->free = _va_free;
|
|
}
|
|
|
|
static inline void
|
|
_clean_mem (GstVaMemory * mem)
|
|
{
|
|
memset (&mem->image, 0, sizeof (mem->image));
|
|
mem->image.image_id = VA_INVALID_ID;
|
|
mem->image.buf = VA_INVALID_ID;
|
|
|
|
mem->is_derived = FALSE;
|
|
mem->is_dirty = FALSE;
|
|
mem->prev_mapflags = 0;
|
|
mem->mapped_data = NULL;
|
|
}
|
|
|
|
static void
|
|
_reset_mem (GstVaMemory * mem, GstAllocator * allocator, gsize size)
|
|
{
|
|
_clean_mem (mem);
|
|
g_atomic_int_set (&mem->map_count, 0);
|
|
g_mutex_init (&mem->lock);
|
|
|
|
gst_memory_init (GST_MEMORY_CAST (mem), 0, allocator, NULL, size,
|
|
0 /* align */ , 0 /* offset */ , size);
|
|
}
|
|
|
|
/*
|
|
* HACK:
|
|
*
|
|
* This method should be defined as a public method of GstVaDisplay. But in
|
|
* order to backport this fix, it's kept locally.
|
|
*/
|
|
static gboolean
|
|
_gst_va_display_get_vendor_version (GstVaDisplay * display, guint * major,
|
|
guint * minor)
|
|
{
|
|
VADisplay dpy;
|
|
guint maj, min;
|
|
const char *vendor;
|
|
|
|
dpy = gst_va_display_get_va_dpy (display);
|
|
vendor = vaQueryVendorString (dpy);
|
|
if (vendor && sscanf (vendor, "Mesa Gallium driver %d.%d.", &maj, &min) == 2) {
|
|
*major = maj;
|
|
*minor = min;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
_is_old_mesa (GstVaAllocator * va_allocator)
|
|
{
|
|
guint major, minor;
|
|
|
|
if (!GST_VA_DISPLAY_IS_IMPLEMENTATION (va_allocator->display, MESA_GALLIUM))
|
|
return FALSE;
|
|
if (!_gst_va_display_get_vendor_version (va_allocator->display, &major,
|
|
&minor)) {
|
|
GST_WARNING ("Could not parse version from Mesa vendor string");
|
|
return FALSE;
|
|
}
|
|
if (major > 23)
|
|
return FALSE;
|
|
if (major == 23 && minor > 2)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static inline void
|
|
_update_info (GstVideoInfo * info, const VAImage * image)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < image->num_planes; i++) {
|
|
GST_VIDEO_INFO_PLANE_OFFSET (info, i) = image->offsets[i];
|
|
GST_VIDEO_INFO_PLANE_STRIDE (info, i) = image->pitches[i];
|
|
}
|
|
|
|
/* Don't update image size for one planed images since drivers might add extra
|
|
* bits which will drop wrong raw images with filesink, for example. Multiple
|
|
* plane images require video meta */
|
|
if (image->num_planes > 1)
|
|
GST_VIDEO_INFO_SIZE (info) = image->data_size;
|
|
}
|
|
|
|
static inline gboolean
|
|
_update_image_info (GstVaAllocator * va_allocator,
|
|
GstVaFeature feat_use_derived)
|
|
{
|
|
VASurfaceID surface;
|
|
VAImage image = {.image_id = VA_INVALID_ID, };
|
|
|
|
/* Create a test surface first */
|
|
if (!va_create_surfaces (va_allocator->display, va_allocator->rt_format,
|
|
va_allocator->fourcc, GST_VIDEO_INFO_WIDTH (&va_allocator->info),
|
|
GST_VIDEO_INFO_HEIGHT (&va_allocator->info), va_allocator->usage_hint,
|
|
NULL, 0, NULL, &surface, 1)) {
|
|
GST_ERROR_OBJECT (va_allocator, "Failed to create a test surface");
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (va_allocator, "Created surface %#x [%dx%d]", surface,
|
|
GST_VIDEO_INFO_WIDTH (&va_allocator->info),
|
|
GST_VIDEO_INFO_HEIGHT (&va_allocator->info));
|
|
|
|
#ifdef G_OS_WIN32
|
|
/* XXX: Derived image is problematic for D3D backend */
|
|
if (feat_use_derived != GST_VA_FEATURE_DISABLED) {
|
|
GST_INFO_OBJECT (va_allocator, "Disable image derive on Windows.");
|
|
feat_use_derived = GST_VA_FEATURE_DISABLED;
|
|
}
|
|
va_allocator->use_derived = FALSE;
|
|
#else
|
|
/* XXX: Derived in Mesa <23.3 can't use derived images for P010 format
|
|
* https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24381
|
|
*/
|
|
if (va_allocator->img_format == GST_VIDEO_FORMAT_P010_10LE
|
|
&& _is_old_mesa (va_allocator)) {
|
|
if (feat_use_derived != GST_VA_FEATURE_DISABLED) {
|
|
GST_INFO_OBJECT (va_allocator, "Disable image derive on old Mesa.");
|
|
feat_use_derived = GST_VA_FEATURE_DISABLED;
|
|
}
|
|
va_allocator->use_derived = FALSE;
|
|
}
|
|
#endif
|
|
/* Try derived first, but different formats can never derive */
|
|
if (feat_use_derived != GST_VA_FEATURE_DISABLED
|
|
&& va_allocator->surface_format == va_allocator->img_format) {
|
|
va_allocator->use_derived =
|
|
va_get_derive_image (va_allocator->display, surface, &image);
|
|
if (va_allocator->use_derived)
|
|
goto done;
|
|
image.image_id = VA_INVALID_ID; /* reset it */
|
|
}
|
|
|
|
if (feat_use_derived == GST_VA_FEATURE_ENABLED && !va_allocator->use_derived)
|
|
GST_WARNING_OBJECT (va_allocator, "Derived images are disabled.");
|
|
|
|
/* Then we try to create a image. */
|
|
if (!va_create_image (va_allocator->display, va_allocator->img_format,
|
|
GST_VIDEO_INFO_WIDTH (&va_allocator->info),
|
|
GST_VIDEO_INFO_HEIGHT (&va_allocator->info), &image)) {
|
|
va_destroy_surfaces (va_allocator->display, &surface, 1);
|
|
return FALSE;
|
|
}
|
|
|
|
done:
|
|
_update_info (&va_allocator->info, &image);
|
|
if (GST_VIDEO_INFO_SIZE (&va_allocator->info) > image.data_size) {
|
|
GST_WARNING_OBJECT (va_allocator,
|
|
"image size is lesser than the minimum required");
|
|
}
|
|
|
|
va_destroy_image (va_allocator->display, image.image_id);
|
|
va_destroy_surfaces (va_allocator->display, &surface, 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gpointer
|
|
_va_map_unlocked (GstVaMemory * mem, GstMapFlags flags)
|
|
{
|
|
GstAllocator *allocator = GST_MEMORY_CAST (mem)->allocator;
|
|
GstVaAllocator *va_allocator;
|
|
GstVaDisplay *display;
|
|
|
|
g_return_val_if_fail (mem->surface != VA_INVALID_ID, NULL);
|
|
g_return_val_if_fail (GST_IS_VA_ALLOCATOR (allocator), NULL);
|
|
|
|
if (g_atomic_int_get (&mem->map_count) > 0) {
|
|
if (!(mem->prev_mapflags & flags) || !mem->mapped_data)
|
|
return NULL;
|
|
else
|
|
goto success;
|
|
}
|
|
|
|
va_allocator = GST_VA_ALLOCATOR (allocator);
|
|
display = va_allocator->display;
|
|
|
|
if (flags & GST_MAP_WRITE) {
|
|
mem->is_dirty = TRUE;
|
|
} else { /* GST_MAP_READ only */
|
|
mem->is_dirty = FALSE;
|
|
}
|
|
|
|
if (flags & GST_MAP_VA) {
|
|
mem->mapped_data = &mem->surface;
|
|
goto success;
|
|
}
|
|
|
|
mem->is_derived = va_allocator->use_derived;
|
|
|
|
if (!va_ensure_image (display, mem->surface, &va_allocator->info, &mem->image,
|
|
va_allocator->use_derived))
|
|
return NULL;
|
|
|
|
if (!mem->is_derived) {
|
|
if (!va_get_image (display, mem->surface, &mem->image))
|
|
goto fail;
|
|
}
|
|
|
|
if (!va_map_buffer (display, mem->image.buf, flags, &mem->mapped_data))
|
|
goto fail;
|
|
|
|
success:
|
|
{
|
|
mem->prev_mapflags = flags;
|
|
g_atomic_int_add (&mem->map_count, 1);
|
|
return mem->mapped_data;
|
|
}
|
|
|
|
fail:
|
|
{
|
|
va_destroy_image (display, mem->image.image_id);
|
|
_clean_mem (mem);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static gpointer
|
|
_va_map (GstVaMemory * mem, gsize maxsize, GstMapFlags flags)
|
|
{
|
|
gpointer data;
|
|
|
|
g_mutex_lock (&mem->lock);
|
|
data = _va_map_unlocked (mem, flags);
|
|
g_mutex_unlock (&mem->lock);
|
|
|
|
return data;
|
|
}
|
|
|
|
static gboolean
|
|
_va_unmap_unlocked (GstVaMemory * mem)
|
|
{
|
|
GstAllocator *allocator = GST_MEMORY_CAST (mem)->allocator;
|
|
GstVaDisplay *display;
|
|
gboolean ret = TRUE;
|
|
|
|
if (!g_atomic_int_dec_and_test (&mem->map_count))
|
|
return TRUE;
|
|
|
|
if (mem->prev_mapflags & GST_MAP_VA)
|
|
goto bail;
|
|
|
|
display = GST_VA_ALLOCATOR (allocator)->display;
|
|
|
|
if (mem->image.image_id != VA_INVALID_ID) {
|
|
if (mem->is_dirty && !mem->is_derived) {
|
|
ret = va_put_image (display, mem->surface, &mem->image);
|
|
mem->is_dirty = FALSE;
|
|
}
|
|
/* XXX(victor): if is derived and is dirty, create another surface
|
|
* an replace it in mem */
|
|
}
|
|
|
|
ret &= va_unmap_buffer (display, mem->image.buf);
|
|
ret &= va_destroy_image (display, mem->image.image_id);
|
|
|
|
bail:
|
|
_clean_mem (mem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
_va_unmap (GstVaMemory * mem)
|
|
{
|
|
gboolean ret;
|
|
|
|
g_mutex_lock (&mem->lock);
|
|
ret = _va_unmap_unlocked (mem);
|
|
g_mutex_unlock (&mem->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstMemory *
|
|
_va_share (GstMemory * mem, gssize offset, gssize size)
|
|
{
|
|
GstVaMemory *vamem = (GstVaMemory *) mem;
|
|
GstVaMemory *sub;
|
|
GstMemory *parent;
|
|
|
|
GST_DEBUG ("%p: share %" G_GSSIZE_FORMAT ", %" G_GSIZE_FORMAT, mem, offset,
|
|
size);
|
|
|
|
/* find real parent */
|
|
if ((parent = vamem->mem.parent) == NULL)
|
|
parent = (GstMemory *) vamem;
|
|
|
|
if (size == -1)
|
|
size = mem->maxsize - offset;
|
|
|
|
sub = g_new (GstVaMemory, 1);
|
|
|
|
/* the shared memory is alwyas readonly */
|
|
gst_memory_init (GST_MEMORY_CAST (sub), GST_MINI_OBJECT_FLAGS (parent) |
|
|
GST_MINI_OBJECT_FLAG_LOCK_READONLY, vamem->mem.allocator, parent,
|
|
vamem->mem.maxsize, vamem->mem.align, vamem->mem.offset + offset, size);
|
|
|
|
sub->surface = vamem->surface;
|
|
sub->surface_format = vamem->surface_format;
|
|
|
|
_clean_mem (sub);
|
|
|
|
g_atomic_int_set (&sub->map_count, 0);
|
|
g_mutex_init (&sub->lock);
|
|
|
|
return GST_MEMORY_CAST (sub);
|
|
}
|
|
|
|
/* XXX(victor): deep copy implementation. */
|
|
static GstMemory *
|
|
_va_copy (GstMemory * mem, gssize offset, gssize size)
|
|
{
|
|
GstMemory *copy;
|
|
GstMapInfo sinfo, dinfo;
|
|
GstVaAllocator *va_allocator = GST_VA_ALLOCATOR (mem->allocator);
|
|
GstVaMemory *va_copy, *va_mem = (GstVaMemory *) mem;
|
|
gsize mem_size;
|
|
|
|
GST_DEBUG ("%p: copy %" G_GSSIZE_FORMAT ", %" G_GSIZE_FORMAT, mem, offset,
|
|
size);
|
|
|
|
{
|
|
GST_VA_MEMORY_POOL_LOCK (&va_allocator->pool);
|
|
copy = gst_va_memory_pool_pop (&va_allocator->pool);
|
|
GST_VA_MEMORY_POOL_UNLOCK (&va_allocator->pool);
|
|
|
|
if (!copy) {
|
|
copy = gst_va_allocator_alloc (mem->allocator);
|
|
if (!copy) {
|
|
GST_WARNING ("failed to allocate new memory");
|
|
return NULL;
|
|
}
|
|
} else {
|
|
gst_object_ref (mem->allocator);
|
|
}
|
|
}
|
|
|
|
va_copy = (GstVaMemory *) copy;
|
|
mem_size = gst_memory_get_sizes (mem, NULL, NULL);
|
|
|
|
if (size == -1)
|
|
size = mem_size > offset ? mem_size - offset : 0;
|
|
|
|
if (offset == 0 && size == mem_size) {
|
|
GstVaSurfaceCopy *copy_func;
|
|
|
|
copy_func = _ensure_surface_copy (&va_allocator->copy,
|
|
va_allocator->display, &va_allocator->info);
|
|
if (copy_func
|
|
&& gst_va_surface_copy (copy_func, va_copy->surface, va_mem->surface))
|
|
return copy;
|
|
}
|
|
|
|
if (!gst_memory_map (mem, &sinfo, GST_MAP_READ)) {
|
|
GST_WARNING ("failed to map memory to copy");
|
|
return NULL;
|
|
}
|
|
|
|
if (!gst_memory_map (copy, &dinfo, GST_MAP_WRITE)) {
|
|
GST_WARNING ("could not write map memory %p", copy);
|
|
gst_allocator_free (mem->allocator, copy);
|
|
gst_memory_unmap (mem, &sinfo);
|
|
return NULL;
|
|
}
|
|
|
|
memcpy (dinfo.data, sinfo.data + offset, size);
|
|
gst_memory_unmap (copy, &dinfo);
|
|
gst_memory_unmap (mem, &sinfo);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
gst_va_allocator_init (GstVaAllocator * self)
|
|
{
|
|
GstAllocator *allocator = GST_ALLOCATOR (self);
|
|
|
|
allocator->mem_type = GST_ALLOCATOR_VASURFACE;
|
|
allocator->mem_map = (GstMemoryMapFunction) _va_map;
|
|
allocator->mem_unmap = (GstMemoryUnmapFunction) _va_unmap;
|
|
allocator->mem_share = _va_share;
|
|
allocator->mem_copy = _va_copy;
|
|
|
|
gst_va_memory_pool_init (&self->pool);
|
|
|
|
GST_OBJECT_FLAG_SET (self, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
|
|
}
|
|
|
|
static gboolean
|
|
gst_va_memory_release (GstMiniObject * mini_object)
|
|
{
|
|
GstMemory *mem = GST_MEMORY_CAST (mini_object);
|
|
GstVaAllocator *self = GST_VA_ALLOCATOR (mem->allocator);
|
|
|
|
GST_LOG ("releasing %p: surface %#x", mem, gst_va_memory_get_surface (mem));
|
|
|
|
gst_va_memory_pool_push (&self->pool, mem);
|
|
|
|
/* Keep last in case we are holding on the last allocator ref */
|
|
gst_object_unref (mem->allocator);
|
|
|
|
/* don't call mini_object's free */
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_alloc:
|
|
* @allocator: a #GstAllocator
|
|
*
|
|
* Allocate a new VASurfaceID backed #GstMemory.
|
|
*
|
|
* Returns: a #GstMemory backed with a VASurfaceID; %NULL, otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
GstMemory *
|
|
gst_va_allocator_alloc (GstAllocator * allocator)
|
|
{
|
|
GstVaAllocator *self;
|
|
GstVaMemory *mem;
|
|
VASurfaceID surface;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_ALLOCATOR (allocator), NULL);
|
|
|
|
self = GST_VA_ALLOCATOR (allocator);
|
|
|
|
if (self->rt_format == 0) {
|
|
GST_ERROR_OBJECT (self, "Unknown fourcc or chroma format");
|
|
return NULL;
|
|
}
|
|
|
|
if (!va_create_surfaces (self->display, self->rt_format, self->fourcc,
|
|
GST_VIDEO_INFO_WIDTH (&self->info),
|
|
GST_VIDEO_INFO_HEIGHT (&self->info), self->usage_hint, NULL, 0, NULL,
|
|
&surface, 1))
|
|
return NULL;
|
|
|
|
mem = g_new (GstVaMemory, 1);
|
|
|
|
mem->surface = surface;
|
|
mem->surface_format = self->surface_format;
|
|
|
|
_reset_mem (mem, allocator, GST_VIDEO_INFO_SIZE (&self->info));
|
|
|
|
GST_MINI_OBJECT (mem)->dispose = gst_va_memory_release;
|
|
gst_va_memory_pool_surface_inc (&self->pool);
|
|
|
|
GST_LOG_OBJECT (self, "Created surface %#x [%dx%d]", mem->surface,
|
|
GST_VIDEO_INFO_WIDTH (&self->info), GST_VIDEO_INFO_HEIGHT (&self->info));
|
|
|
|
return GST_MEMORY_CAST (mem);
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_new:
|
|
* @display: a #GstVaDisplay
|
|
* @surface_formats: (element-type guint) (transfer full): a #GArray
|
|
* of valid #GstVideoFormat for surfaces in current VA context.
|
|
*
|
|
* Instanciate a new pooled #GstAllocator backed by VASurfaceID.
|
|
*
|
|
* Returns: a #GstVaDisplay
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
GstAllocator *
|
|
gst_va_allocator_new (GstVaDisplay * display, GArray * surface_formats)
|
|
{
|
|
GstVaAllocator *self;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_DISPLAY (display), NULL);
|
|
|
|
self = g_object_new (GST_TYPE_VA_ALLOCATOR, NULL);
|
|
self->display = gst_object_ref (display);
|
|
self->surface_formats = surface_formats;
|
|
gst_object_ref_sink (self);
|
|
|
|
return GST_ALLOCATOR (self);
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_setup_buffer:
|
|
* @allocator: a #GstAllocator
|
|
* @buffer: a #GstBuffer
|
|
*
|
|
* Populates an empty @buffer with a VASuface backed #GstMemory.
|
|
*
|
|
* Returns: %TRUE if @buffer is populated; %FALSE otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_allocator_setup_buffer (GstAllocator * allocator, GstBuffer * buffer)
|
|
{
|
|
GstMemory *mem = gst_va_allocator_alloc (allocator);
|
|
if (!mem)
|
|
return FALSE;
|
|
|
|
gst_buffer_append_memory (buffer, mem);
|
|
return TRUE;
|
|
}
|
|
|
|
static VASurfaceID
|
|
gst_va_allocator_prepare_buffer_unlocked (GstVaAllocator * self,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstMemory *mem;
|
|
VASurfaceID surface;
|
|
|
|
mem = gst_va_memory_pool_pop (&self->pool);
|
|
if (!mem)
|
|
return VA_INVALID_ID;
|
|
|
|
gst_object_ref (mem->allocator);
|
|
surface = gst_va_memory_get_surface (mem);
|
|
gst_buffer_append_memory (buffer, mem);
|
|
|
|
GST_LOG ("buffer %p: memory %p - surface %#x", buffer, mem, surface);
|
|
|
|
return surface;
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_prepare_buffer:
|
|
* @allocator: a #GstAllocator
|
|
* @buffer: an empty #GstBuffer
|
|
*
|
|
* This method will populate @buffer with pooled VASurfaceID
|
|
* memories. It doesn't allocate new VASurfacesID.
|
|
*
|
|
* Returns: %TRUE if @buffer was populated correctly; %FALSE
|
|
* otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_allocator_prepare_buffer (GstAllocator * allocator, GstBuffer * buffer)
|
|
{
|
|
GstVaAllocator *self;
|
|
VASurfaceID surface;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_ALLOCATOR (allocator), FALSE);
|
|
|
|
self = GST_VA_ALLOCATOR (allocator);
|
|
|
|
GST_VA_MEMORY_POOL_LOCK (&self->pool);
|
|
surface = gst_va_allocator_prepare_buffer_unlocked (self, buffer);
|
|
GST_VA_MEMORY_POOL_UNLOCK (&self->pool);
|
|
|
|
return (surface != VA_INVALID_ID);
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_flush:
|
|
* @allocator: a #GstAllocator
|
|
*
|
|
* Removes all the memories in @allocator's pool.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
void
|
|
gst_va_allocator_flush (GstAllocator * allocator)
|
|
{
|
|
GstVaAllocator *self;
|
|
|
|
g_return_if_fail (GST_IS_VA_ALLOCATOR (allocator));
|
|
|
|
self = GST_VA_ALLOCATOR (allocator);
|
|
|
|
gst_va_memory_pool_flush (&self->pool, self->display);
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_try:
|
|
* @allocator: a #GstAllocator
|
|
*
|
|
* Try to allocate a test buffer in order to verify that the
|
|
* allocator's configuration is valid.
|
|
*
|
|
* Returns: %TRUE if the configuration is valid; %FALSE otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
static gboolean
|
|
gst_va_allocator_try (GstAllocator * allocator, GstVaFeature feat_use_derived)
|
|
{
|
|
GstVaAllocator *self;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_ALLOCATOR (allocator), FALSE);
|
|
|
|
self = GST_VA_ALLOCATOR (allocator);
|
|
|
|
self->fourcc = 0;
|
|
self->rt_format = 0;
|
|
self->use_derived = FALSE;
|
|
self->img_format = GST_VIDEO_INFO_FORMAT (&self->info);
|
|
|
|
self->surface_format =
|
|
gst_va_video_surface_format_from_image_format (self->img_format,
|
|
self->surface_formats);
|
|
if (self->surface_format == GST_VIDEO_FORMAT_UNKNOWN) {
|
|
/* try a surface without fourcc but rt_format only */
|
|
self->fourcc = 0;
|
|
self->rt_format = gst_va_chroma_from_video_format (self->img_format);
|
|
} else {
|
|
if (G_LIKELY (!(self->hacks & GST_VA_HACK_SURFACE_NO_FOURCC)))
|
|
self->fourcc = gst_va_fourcc_from_video_format (self->surface_format);
|
|
self->rt_format = gst_va_chroma_from_video_format (self->surface_format);
|
|
}
|
|
|
|
if (self->rt_format == 0) {
|
|
GST_ERROR_OBJECT (allocator, "Unsupported image format: %s",
|
|
gst_video_format_to_string (self->img_format));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!_update_image_info (self, feat_use_derived)) {
|
|
GST_ERROR_OBJECT (allocator, "Failed to update allocator info");
|
|
return FALSE;
|
|
}
|
|
|
|
GST_INFO_OBJECT (self,
|
|
"va allocator info, surface format: %s, image format: %s, "
|
|
"use derived: %s, rt format: 0x%x, fourcc: %" GST_FOURCC_FORMAT,
|
|
(self->surface_format == GST_VIDEO_FORMAT_UNKNOWN) ? "unknown"
|
|
: gst_video_format_to_string (self->surface_format),
|
|
gst_video_format_to_string (self->img_format),
|
|
self->use_derived ? "true" : "false", self->rt_format,
|
|
GST_FOURCC_ARGS (self->fourcc));
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_set_format:
|
|
* @allocator: a #GstAllocator
|
|
* @info: (inout): a #GstVideoInfo
|
|
* @usage_hint: VA usage hint
|
|
* @feat_use_derived: a #GstVaFeature
|
|
*
|
|
* Sets the configuration defined by @info, @usage_hint and
|
|
* @use_derived for @allocator, and it tries the configuration, if
|
|
* @allocator has not allocated memories yet.
|
|
*
|
|
* If @allocator has memory allocated already, and frame size and
|
|
* format in @info are the same as currently configured in @allocator,
|
|
* the rest of @info parameters are updated internally.
|
|
*
|
|
* Returns: %TRUE if the configuration is valid or updated; %FALSE if
|
|
* configuration is not valid or not updated.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_allocator_set_format (GstAllocator * allocator, GstVideoInfo * info,
|
|
guint usage_hint, GstVaFeature feat_use_derived)
|
|
{
|
|
GstVaAllocator *self;
|
|
gboolean use_derived;
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_ALLOCATOR (allocator), FALSE);
|
|
g_return_val_if_fail (info, FALSE);
|
|
|
|
self = GST_VA_ALLOCATOR (allocator);
|
|
|
|
use_derived = feat_use_derived == GST_VA_FEATURE_AUTO ? self->use_derived
|
|
: feat_use_derived == GST_VA_FEATURE_DISABLED ? FALSE : TRUE;
|
|
|
|
if (gst_va_memory_pool_surface_count (&self->pool) != 0) {
|
|
if (GST_VIDEO_INFO_FORMAT (info) == GST_VIDEO_INFO_FORMAT (&self->info)
|
|
&& GST_VIDEO_INFO_WIDTH (info) == GST_VIDEO_INFO_WIDTH (&self->info)
|
|
&& GST_VIDEO_INFO_HEIGHT (info) == GST_VIDEO_INFO_HEIGHT (&self->info)
|
|
&& usage_hint == self->usage_hint && use_derived == self->use_derived) {
|
|
*info = self->info; /* update callee info (offset & stride) */
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
self->usage_hint = usage_hint;
|
|
self->info = *info;
|
|
|
|
g_clear_pointer (&self->copy, gst_va_surface_copy_free);
|
|
|
|
ret = gst_va_allocator_try (allocator, feat_use_derived);
|
|
if (ret)
|
|
*info = self->info;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_get_format:
|
|
* @allocator: a #GstAllocator
|
|
* @info: (out) (optional): a #GstVideoInfo
|
|
* @usage_hint: (out) (optional): VA usage hint
|
|
* @use_derived: (out) (optional): whether derived images are used for buffer
|
|
* mapping.
|
|
*
|
|
* Gets current internal configuration of @allocator.
|
|
*
|
|
* Returns: %TRUE if @allocator is already configured; %FALSE
|
|
* otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_allocator_get_format (GstAllocator * allocator, GstVideoInfo * info,
|
|
guint * usage_hint, gboolean * use_derived)
|
|
{
|
|
GstVaAllocator *self;
|
|
|
|
g_return_val_if_fail (GST_IS_VA_ALLOCATOR (allocator), FALSE);
|
|
self = GST_VA_ALLOCATOR (allocator);
|
|
|
|
if (GST_VIDEO_INFO_FORMAT (&self->info) == GST_VIDEO_FORMAT_UNKNOWN)
|
|
return FALSE;
|
|
|
|
if (info)
|
|
*info = self->info;
|
|
if (usage_hint)
|
|
*usage_hint = self->usage_hint;
|
|
if (use_derived)
|
|
*use_derived = self->use_derived;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_set_hacks: (skip)
|
|
* @allocator: a #GstAllocator
|
|
* @hacks: hacks id to set
|
|
*
|
|
* Internal method to set allocator specific logic changes.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
void
|
|
gst_va_allocator_set_hacks (GstAllocator * allocator, guint32 hacks)
|
|
{
|
|
GstVaAllocator *self;
|
|
|
|
g_return_if_fail (GST_IS_VA_ALLOCATOR (allocator));
|
|
self = GST_VA_ALLOCATOR (allocator);
|
|
|
|
self->hacks = hacks;
|
|
}
|
|
|
|
/**
|
|
* gst_va_allocator_peek_display:
|
|
* @allocator: a #GstAllocator
|
|
*
|
|
* Returns: (transfer none): the display which this
|
|
* @allocator belongs to. The reference of the display is unchanged.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
GstVaDisplay *
|
|
gst_va_allocator_peek_display (GstAllocator * allocator)
|
|
{
|
|
if (!allocator)
|
|
return NULL;
|
|
|
|
if (GST_IS_VA_DMABUF_ALLOCATOR (allocator)) {
|
|
return GST_VA_DMABUF_ALLOCATOR (allocator)->display;
|
|
} else if (GST_IS_VA_ALLOCATOR (allocator)) {
|
|
return GST_VA_ALLOCATOR (allocator)->display;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*============ Utilities =====================================================*/
|
|
|
|
/**
|
|
* gst_va_memory_get_surface: (skip)
|
|
* @mem: a #GstMemory
|
|
*
|
|
* Returns: (type guint): the VASurfaceID in @mem.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
VASurfaceID
|
|
gst_va_memory_get_surface (GstMemory * mem)
|
|
{
|
|
VASurfaceID surface = VA_INVALID_ID;
|
|
|
|
if (!mem->allocator)
|
|
return VA_INVALID_ID;
|
|
|
|
if (GST_IS_DMABUF_ALLOCATOR (mem->allocator)) {
|
|
GstVaBufferSurface *buf;
|
|
|
|
buf = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
|
|
gst_va_buffer_surface_quark ());
|
|
if (buf)
|
|
surface = buf->surface;
|
|
} else if (GST_IS_VA_ALLOCATOR (mem->allocator)) {
|
|
GstVaMemory *va_mem = (GstVaMemory *) mem;
|
|
surface = va_mem->surface;
|
|
}
|
|
|
|
return surface;
|
|
}
|
|
|
|
/**
|
|
* gst_va_memory_peek_display:
|
|
* @mem: a #GstMemory
|
|
*
|
|
* Returns: (transfer none): the display which
|
|
* this @mem belongs to. The reference of the display is unchanged.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
GstVaDisplay *
|
|
gst_va_memory_peek_display (GstMemory * mem)
|
|
{
|
|
GstAllocator *allocator;
|
|
|
|
if (!mem)
|
|
return NULL;
|
|
|
|
allocator = GST_MEMORY_CAST (mem)->allocator;
|
|
/* no allocator, not VA kind memory. */
|
|
if (!allocator)
|
|
return NULL;
|
|
|
|
return gst_va_allocator_peek_display (allocator);
|
|
}
|
|
|
|
/**
|
|
* gst_va_buffer_get_surface: (skip)
|
|
* @buffer: a #GstBuffer
|
|
*
|
|
* Returns: (type guint): the VASurfaceID in @buffer.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
VASurfaceID
|
|
gst_va_buffer_get_surface (GstBuffer * buffer)
|
|
{
|
|
GstMemory *mem;
|
|
|
|
mem = gst_buffer_peek_memory (buffer, 0);
|
|
if (!mem)
|
|
return VA_INVALID_ID;
|
|
|
|
return gst_va_memory_get_surface (mem);
|
|
}
|
|
|
|
/**
|
|
* gst_va_buffer_create_aux_surface:
|
|
* @buffer: a #GstBuffer
|
|
*
|
|
* Creates a new VASurfaceID with @buffer's allocator and attached it
|
|
* to it.
|
|
*
|
|
* *This method is used only by plugin's internal VA decoder.*
|
|
*
|
|
* Returns: %TRUE if the new VASurfaceID is attached to @buffer
|
|
* correctly; %FALSE, otherwise.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
gboolean
|
|
gst_va_buffer_create_aux_surface (GstBuffer * buffer)
|
|
{
|
|
GstMemory *mem;
|
|
VASurfaceID surface = VA_INVALID_ID;
|
|
GstVaDisplay *display = NULL;
|
|
GstVideoFormat format;
|
|
GstVaBufferSurface *surface_buffer;
|
|
|
|
mem = gst_buffer_peek_memory (buffer, 0);
|
|
if (!mem)
|
|
return FALSE;
|
|
|
|
/* Already created. */
|
|
surface_buffer = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
|
|
gst_va_buffer_aux_surface_quark ());
|
|
if (surface_buffer)
|
|
return TRUE;
|
|
|
|
if (!mem->allocator)
|
|
return FALSE;
|
|
|
|
if (GST_IS_VA_DMABUF_ALLOCATOR (mem->allocator)) {
|
|
GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (mem->allocator);
|
|
guint32 fourcc, rt_format;
|
|
|
|
format = GST_VIDEO_INFO_FORMAT (&self->info.vinfo);
|
|
fourcc = gst_va_fourcc_from_video_format (format);
|
|
rt_format = gst_va_chroma_from_video_format (format);
|
|
if (fourcc == 0 || rt_format == 0) {
|
|
GST_ERROR_OBJECT (self, "Unsupported format: %s",
|
|
gst_video_format_to_string (format));
|
|
return FALSE;
|
|
}
|
|
|
|
display = self->display;
|
|
if (!va_create_surfaces (self->display, rt_format, fourcc,
|
|
GST_VIDEO_INFO_WIDTH (&self->info.vinfo),
|
|
GST_VIDEO_INFO_HEIGHT (&self->info.vinfo), self->usage_hint, NULL,
|
|
0, NULL, &surface, 1))
|
|
return FALSE;
|
|
} else if (GST_IS_VA_ALLOCATOR (mem->allocator)) {
|
|
GstVaAllocator *self = GST_VA_ALLOCATOR (mem->allocator);
|
|
|
|
if (self->rt_format == 0) {
|
|
GST_ERROR_OBJECT (self, "Unknown fourcc or chroma format");
|
|
return FALSE;
|
|
}
|
|
|
|
display = self->display;
|
|
format = GST_VIDEO_INFO_FORMAT (&self->info);
|
|
if (!va_create_surfaces (self->display, self->rt_format, self->fourcc,
|
|
GST_VIDEO_INFO_WIDTH (&self->info),
|
|
GST_VIDEO_INFO_HEIGHT (&self->info), self->usage_hint, NULL, 0,
|
|
NULL, &surface, 1))
|
|
return FALSE;
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
if (!display || surface == VA_INVALID_ID)
|
|
return FALSE;
|
|
|
|
surface_buffer = gst_va_buffer_surface_new (surface);
|
|
surface_buffer->display = gst_object_ref (display);
|
|
g_atomic_int_add (&surface_buffer->ref_count, 1);
|
|
|
|
gst_mini_object_set_qdata (GST_MINI_OBJECT (mem),
|
|
gst_va_buffer_aux_surface_quark (), surface_buffer,
|
|
gst_va_buffer_surface_unref);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_va_buffer_get_aux_surface: (skip)
|
|
* @buffer: a #GstBuffer
|
|
*
|
|
* Returns: (type guint): the VASurfaceID attached to
|
|
* @buffer.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
VASurfaceID
|
|
gst_va_buffer_get_aux_surface (GstBuffer * buffer)
|
|
{
|
|
GstVaBufferSurface *surface_buffer;
|
|
GstMemory *mem;
|
|
|
|
mem = gst_buffer_peek_memory (buffer, 0);
|
|
if (!mem)
|
|
return VA_INVALID_ID;
|
|
|
|
surface_buffer = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
|
|
gst_va_buffer_aux_surface_quark ());
|
|
if (!surface_buffer)
|
|
return VA_INVALID_ID;
|
|
|
|
/* No one increments it, and its lifetime is the same with the
|
|
gstmemory itself */
|
|
g_assert (g_atomic_int_get (&surface_buffer->ref_count) == 1);
|
|
|
|
return surface_buffer->surface;
|
|
}
|
|
|
|
/**
|
|
* gst_va_buffer_peek_display:
|
|
* @buffer: a #GstBuffer
|
|
*
|
|
* Returns: (transfer none): the display which this
|
|
* @buffer belongs to. The reference of the display is unchanged.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
GstVaDisplay *
|
|
gst_va_buffer_peek_display (GstBuffer * buffer)
|
|
{
|
|
GstMemory *mem;
|
|
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
mem = gst_buffer_peek_memory (buffer, 0);
|
|
/* Buffer without mem, not VA kind memory. */
|
|
if (!mem)
|
|
return NULL;
|
|
|
|
return gst_va_memory_peek_display (mem);
|
|
}
|