gstreamer/gst-libs/gst/gl/gstglbuffer.c
Matthew Waters 98249a57db gst: don't use volatile to mean atomic
volatile is not sufficient to provide atomic guarantees and real atomics
should be used instead.  GCC 11 has started warning about using volatile
with atomic operations.

https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1719

Discovered in https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/868

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1073>
2021-03-19 04:20:19 +00:00

494 lines
14 KiB
C

/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include "gstglbuffer.h"
#include "gstglcontext.h"
#include "gstglfuncs.h"
#include "gstglutils.h"
/**
* SECTION:gstglbuffer
* @title: GstGLBuffer
* @short_description: memory subclass for GL buffers
* @see_also: #GstMemory, #GstAllocator
*
* GstGLBuffer is a #GstMemory subclass providing support for the mapping of
* GL buffers.
*
* Data is uploaded or downloaded from the GPU as is necessary.
*/
/* Implementation notes:
*
* Currently does not take into account GLES2 differences (no mapbuffer)
*/
#define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
#define USING_OPENGL3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 3, 1))
#define USING_GLES(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES, 1, 0))
#define USING_GLES2(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 2, 0))
#define USING_GLES3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 3, 0))
/* compatibility definitions... */
#ifndef GL_MAP_READ_BIT
#define GL_MAP_READ_BIT 0x0001
#endif
#ifndef GL_MAP_WRITE_BIT
#define GL_MAP_WRITE_BIT 0x0002
#endif
#ifndef GL_COPY_READ_BUFFER
#define GL_COPY_READ_BUFFER 0x8F36
#endif
#ifndef GL_COPY_WRITE_BUFFER
#define GL_COPY_WRITE_BUFFER 0x8F37
#endif
GST_DEBUG_CATEGORY_STATIC (GST_CAT_GL_BUFFER);
#define GST_CAT_DEFUALT GST_CAT_GL_BUFFER
GST_DEFINE_MINI_OBJECT_TYPE (GstGLBuffer, gst_gl_buffer);
static GstAllocator *_gl_buffer_allocator;
static gboolean
_gl_buffer_create (GstGLBuffer * gl_mem, GError ** error)
{
const GstGLFuncs *gl = gl_mem->mem.context->gl_vtable;
gl->GenBuffers (1, &gl_mem->id);
gl->BindBuffer (gl_mem->target, gl_mem->id);
gl->BufferData (gl_mem->target, gl_mem->mem.mem.maxsize, NULL,
gl_mem->usage_hints);
gl->BindBuffer (gl_mem->target, 0);
return TRUE;
}
struct create_data
{
GstGLBuffer *mem;
gboolean result;
};
static void
_gl_buffer_init (GstGLBuffer * mem, GstAllocator * allocator,
GstMemory * parent, GstGLContext * context, guint gl_target, guint gl_usage,
const GstAllocationParams * params, gsize size)
{
mem->target = gl_target;
mem->usage_hints = gl_usage;
gst_gl_base_memory_init ((GstGLBaseMemory *) mem, allocator, parent, context,
params, size, NULL, NULL);
GST_CAT_DEBUG (GST_CAT_GL_BUFFER, "new GL buffer memory:%p size:%"
G_GSIZE_FORMAT, mem, mem->mem.mem.maxsize);
}
static GstGLBuffer *
_gl_buffer_new (GstAllocator * allocator, GstMemory * parent,
GstGLContext * context, guint gl_target, guint gl_usage,
const GstAllocationParams * params, gsize size)
{
GstGLBuffer *ret = g_new0 (GstGLBuffer, 1);
_gl_buffer_init (ret, allocator, parent, context, gl_target, gl_usage,
params, size);
return ret;
}
static gpointer
gst_gl_buffer_cpu_access (GstGLBuffer * mem, GstMapInfo * info, gsize size)
{
const GstGLFuncs *gl = mem->mem.context->gl_vtable;
gpointer data, ret;
if (!gst_gl_base_memory_alloc_data (GST_GL_BASE_MEMORY_CAST (mem)))
return NULL;
ret = mem->mem.data;
GST_CAT_LOG (GST_CAT_GL_BUFFER, "mapping id %d size %" G_GSIZE_FORMAT,
mem->id, size);
/* The extra data pointer indirection/memcpy is needed for coherent across
* concurrent map()'s in both GL and CPU */
if (GST_MEMORY_FLAG_IS_SET (mem, GST_GL_BASE_MEMORY_TRANSFER_NEED_DOWNLOAD)
&& (info->flags & GST_MAP_GL) == 0 && (info->flags & GST_MAP_READ) != 0) {
gl->BindBuffer (mem->target, mem->id);
if (gl->MapBufferRange) {
/* FIXME: optionally remove this with a flag and return the
* glMapBufferRange pointer (requires
* GL_ARB_buffer_storage/GL4/GL_COHERENT_BIT) */
guint gl_map_flags = GL_MAP_READ_BIT;
data = gl->MapBufferRange (mem->target, 0, size, gl_map_flags);
if (data)
memcpy (mem->mem.data, data, size);
gl->UnmapBuffer (mem->target);
ret = mem->mem.data;
} else if (gl->GetBufferSubData) {
gl->GetBufferSubData (mem->target, 0, size, mem->mem.data);
ret = mem->mem.data;
} else {
ret = NULL;
}
gl->BindBuffer (mem->target, 0);
}
return ret;
}
static void
gst_gl_buffer_upload_cpu_write (GstGLBuffer * mem, GstMapInfo * info,
gsize size)
{
const GstGLFuncs *gl = mem->mem.context->gl_vtable;
gpointer data;
if (!mem->mem.data)
/* no data pointer has been written */
return;
/* The extra data pointer indirection/memcpy is needed for coherent across
* concurrent map()'s in both GL and CPU */
/* FIXME: uploading potentially half-written data for libav pushing READWRITE
* mapped buffers */
if (GST_MEMORY_FLAG_IS_SET (mem, GST_GL_BASE_MEMORY_TRANSFER_NEED_UPLOAD)
|| (mem->mem.map_flags & GST_MAP_WRITE) != 0) {
gl->BindBuffer (mem->target, mem->id);
if (gl->MapBufferRange) {
/* FIXME: optionally remove this with a flag and return the
* glMapBufferRange pointer (requires
* GL_ARB_buffer_storage/GL4/GL_COHERENT_BIT) */
guint gl_map_flags = GL_MAP_WRITE_BIT;
data = gl->MapBufferRange (mem->target, 0, size, gl_map_flags);
if (data)
memcpy (data, mem->mem.data, size);
gl->UnmapBuffer (mem->target);
} else if (gl->BufferSubData) {
gl->BufferSubData (mem->target, 0, size, mem->mem.data);
}
gl->BindBuffer (mem->target, 0);
}
}
static gpointer
_gl_buffer_map (GstGLBuffer * mem, GstMapInfo * info, gsize size)
{
const GstGLFuncs *gl = mem->mem.context->gl_vtable;
if ((info->flags & GST_MAP_GL) != 0) {
if (info->flags & GST_MAP_READ) {
gst_gl_buffer_upload_cpu_write (mem, info, size);
}
gl->BindBuffer (mem->target, mem->id);
return &mem->id;
} else {
return gst_gl_buffer_cpu_access (mem, info, size);
}
return NULL;
}
static void
_gl_buffer_unmap (GstGLBuffer * mem, GstMapInfo * info)
{
const GstGLFuncs *gl = mem->mem.context->gl_vtable;
if ((info->flags & GST_MAP_GL) != 0) {
gl->BindBuffer (mem->target, 0);
}
/* XXX: optimistically transfer data */
}
/**
* gst_gl_buffer_copy_buffer_sub_data:
* @src: the source #GstGLBuffer
* @dest: the destination #GstGLBuffer
* @offset: the offset to copy from @src
* @size: the size to copy from @src
*
* Copies @src into @dest using glCopyBufferSubData().
*
* Returns: whether the copy operation succeeded
*
* Since: 1.8
*/
static gboolean
gst_gl_buffer_copy_buffer_sub_data (GstGLBuffer * src,
GstGLBuffer * dest, gssize offset, gssize size)
{
const GstGLFuncs *gl = src->mem.context->gl_vtable;
GstMapInfo sinfo, dinfo;
if (!gl->CopyBufferSubData)
/* This is GL(ES) 3.0+ only */
return FALSE;
if (!gst_memory_map ((GstMemory *) src, &sinfo, GST_MAP_READ | GST_MAP_GL)) {
GST_CAT_WARNING (GST_CAT_GL_BUFFER,
"failed to read map source memory %p", src);
return FALSE;
}
if (!gst_memory_map ((GstMemory *) dest, &dinfo, GST_MAP_WRITE | GST_MAP_GL)) {
GST_CAT_WARNING (GST_CAT_GL_BUFFER,
"failed to write map destination memory %p", dest);
gst_memory_unmap ((GstMemory *) src, &sinfo);
return FALSE;
}
gl->BindBuffer (GL_COPY_READ_BUFFER, src->id);
gl->BindBuffer (GL_COPY_WRITE_BUFFER, dest->id);
gl->CopyBufferSubData (GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER,
offset, 0, size);
gst_memory_unmap ((GstMemory *) src, &sinfo);
gst_memory_unmap ((GstMemory *) dest, &dinfo);
return TRUE;
}
static GstGLBuffer *
_gl_buffer_copy (GstGLBuffer * src, gssize offset, gssize size)
{
GstAllocator *allocator = src->mem.mem.allocator;
GstAllocationParams params = { 0, src->mem.mem.align, 0, 0 };
GstGLBuffer *dest = NULL;
dest = _gl_buffer_new (allocator, NULL, src->mem.context,
src->target, src->usage_hints, &params, src->mem.mem.maxsize);
/* If not doing a full copy, then copy to sysmem, the 2D represention of the
* texture would become wrong */
if (GST_MEMORY_FLAG_IS_SET (src, GST_GL_BASE_MEMORY_TRANSFER_NEED_UPLOAD)) {
if (!gst_gl_base_memory_memcpy (GST_GL_BASE_MEMORY_CAST (src),
GST_GL_BASE_MEMORY_CAST (dest), offset, size)) {
GST_CAT_WARNING (GST_CAT_GL_BUFFER, "Could not copy GL Buffer");
gst_memory_unref (GST_MEMORY_CAST (dest));
dest = NULL;
}
} else {
if (!gst_gl_buffer_copy_buffer_sub_data (src, dest, offset, size)) {
if (!gst_gl_base_memory_memcpy (GST_GL_BASE_MEMORY_CAST (src),
GST_GL_BASE_MEMORY_CAST (dest), offset, size)) {
GST_CAT_WARNING (GST_CAT_GL_BUFFER, "Could not copy GL Buffer");
gst_memory_unref (GST_MEMORY_CAST (dest));
dest = NULL;
}
}
}
return dest;
}
static GstMemory *
_gl_buffer_alloc (GstAllocator * allocator, gsize size,
GstAllocationParams * params)
{
g_critical ("Need to use gst_gl_base_memory_alloc() to allocate from "
"this allocator");
return NULL;
}
static void
_gl_buffer_destroy (GstGLBuffer * mem)
{
const GstGLFuncs *gl = mem->mem.context->gl_vtable;
gl->DeleteBuffers (1, &mem->id);
}
static void
_gst_gl_buffer_allocation_params_copy_data (GstGLBufferAllocationParams * src,
GstGLBufferAllocationParams * dest)
{
memset (dest, 0, sizeof (*dest));
gst_gl_allocation_params_copy_data (&src->parent, &dest->parent);
dest->gl_target = src->gl_target;
dest->gl_usage = src->gl_usage;
}
static void
_gst_gl_buffer_allocation_params_free_data (GstGLBufferAllocationParams *
params)
{
gst_gl_allocation_params_free_data (&params->parent);
}
G_DEFINE_BOXED_TYPE (GstGLBufferAllocationParams,
gst_gl_buffer_allocation_params,
(GBoxedCopyFunc) gst_gl_allocation_params_copy,
(GBoxedFreeFunc) gst_gl_allocation_params_free);
/**
* gst_gl_buffer_allocation_params_new:
* @context: a #GstGLContext
* @alloc_size: the size in bytes to allocate
* @alloc_params: (allow-none): the #GstAllocationParams for @tex_id
* @gl_target: the OpenGL target to allocate
* @gl_usage: the OpenGL usage hint to allocate with
*
* Returns: a new #GstGLBufferAllocationParams for allocating OpenGL buffer
* objects
*
* Since: 1.8
*/
GstGLBufferAllocationParams *
gst_gl_buffer_allocation_params_new (GstGLContext * context, gsize alloc_size,
const GstAllocationParams * alloc_params, guint gl_target, guint gl_usage)
{
GstGLBufferAllocationParams *params;
g_return_val_if_fail (GST_IS_GL_CONTEXT (context), NULL);
g_return_val_if_fail (alloc_size > 0, NULL);
params = g_new0 (GstGLBufferAllocationParams, 1);
if (!gst_gl_allocation_params_init (&params->parent, sizeof (*params),
GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_BUFFER |
GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_ALLOC,
(GstGLAllocationParamsCopyFunc)
_gst_gl_buffer_allocation_params_copy_data,
(GstGLAllocationParamsFreeFunc)
_gst_gl_buffer_allocation_params_free_data, context, alloc_size,
alloc_params, NULL, 0, NULL, NULL)) {
g_free (params);
return NULL;
}
params->gl_target = gl_target;
params->gl_usage = gl_usage;
return params;
}
static GstGLBuffer *
_gl_buffer_alloc_mem (GstGLBufferAllocator * allocator,
GstGLBufferAllocationParams * params)
{
guint alloc_flags = params->parent.alloc_flags;
g_return_val_if_fail (alloc_flags &
GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_BUFFER, NULL);
g_return_val_if_fail (alloc_flags & GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_ALLOC,
NULL);
return _gl_buffer_new (GST_ALLOCATOR (allocator), NULL,
params->parent.context, params->gl_target, params->gl_usage,
params->parent.alloc_params, params->parent.alloc_size);
}
G_DEFINE_TYPE (GstGLBufferAllocator, gst_gl_buffer_allocator,
GST_TYPE_GL_BASE_MEMORY_ALLOCATOR);
static void
gst_gl_buffer_allocator_class_init (GstGLBufferAllocatorClass * klass)
{
GstAllocatorClass *allocator_class = (GstAllocatorClass *) klass;
GstGLBaseMemoryAllocatorClass *gl_base;
gl_base = (GstGLBaseMemoryAllocatorClass *) klass;
gl_base->alloc = (GstGLBaseMemoryAllocatorAllocFunction) _gl_buffer_alloc_mem;
gl_base->create = (GstGLBaseMemoryAllocatorCreateFunction) _gl_buffer_create;
gl_base->map = (GstGLBaseMemoryAllocatorMapFunction) _gl_buffer_map;
gl_base->unmap = (GstGLBaseMemoryAllocatorUnmapFunction) _gl_buffer_unmap;
gl_base->copy = (GstGLBaseMemoryAllocatorCopyFunction) _gl_buffer_copy;
gl_base->destroy =
(GstGLBaseMemoryAllocatorDestroyFunction) _gl_buffer_destroy;
allocator_class->alloc = _gl_buffer_alloc;
}
static void
gst_gl_buffer_allocator_init (GstGLBufferAllocator * allocator)
{
GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
alloc->mem_type = GST_GL_BUFFER_ALLOCATOR_NAME;
GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
}
/**
* gst_gl_buffer_init_once:
*
* Initializes the GL Buffer allocator. It is safe to call this function
* multiple times. This must be called before any other #GstGLBuffer operation.
*
* Since: 1.8
*/
void
gst_gl_buffer_init_once (void)
{
static gsize _init = 0;
if (g_once_init_enter (&_init)) {
gst_gl_base_memory_init_once ();
GST_DEBUG_CATEGORY_INIT (GST_CAT_GL_BUFFER, "glbuffer", 0, "OpenGL Buffer");
_gl_buffer_allocator =
g_object_new (gst_gl_buffer_allocator_get_type (), NULL);
gst_object_ref_sink (_gl_buffer_allocator);
/* The allocator is never unreffed */
GST_OBJECT_FLAG_SET (_gl_buffer_allocator, GST_OBJECT_FLAG_MAY_BE_LEAKED);
gst_allocator_register (GST_GL_BUFFER_ALLOCATOR_NAME,
gst_object_ref (_gl_buffer_allocator));
g_once_init_leave (&_init, 1);
}
}
/**
* gst_is_gl_buffer:
* @mem:a #GstMemory
*
* Returns: whether the memory at @mem is a #GstGLBuffer
*
* Since: 1.8
*/
gboolean
gst_is_gl_buffer (GstMemory * mem)
{
return mem != NULL && mem->allocator != NULL &&
g_type_is_a (G_OBJECT_TYPE (mem->allocator),
GST_TYPE_GL_BUFFER_ALLOCATOR);
}