/* * GStreamer * Copyright (C) 2015 Matthew Waters * * 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 #include "gstglbuffer.h" #include "gstglutils.h" /** * SECTION: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 */ } 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); } 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, NULL, NULL, 0)) { 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) { } /** * 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. */ 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_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 */ 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); }