| /* |
| * 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 |
| |
| 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, |
| 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, |
| 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, ¶ms, 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 (¶ms->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, |
| 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 (¶ms->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 volatile 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); |
| } |