| /* |
| * GStreamer |
| * Copyright (C) 2013 Matthew Waters <ystreet00@gmail.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:gstglframebuffer |
| * @short_description: OpenGL framebuffer abstraction |
| * @title: GstGLFramebuffer |
| * @see_also: #GstGLBaseMemory, #GstGLMemory, #GstGLContext |
| * |
| * A #GstGLFramebuffer represents and holds an OpenGL framebuffer object with |
| * it's associated attachments. |
| * |
| * A #GstGLFramebuffer can be created with gst_gl_framebuffer_new() or |
| * gst_gl_framebuffer_new_with_default_depth() and bound with |
| * gst_gl_framebuffer_bind(). Other resources can be bound with |
| * gst_gl_framebuffer_attach() |
| * |
| * Note: OpenGL framebuffers are not shareable resources so cannot be used |
| * between multiple OpenGL contexts. |
| * |
| * Since: 1.10 |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstglframebuffer.h" |
| |
| #include "gstglcontext.h" |
| #include "gstglfuncs.h" |
| #include "gstglmemory.h" |
| #include "gstglrenderbuffer.h" |
| |
| #ifndef GL_FRAMEBUFFER_UNDEFINED |
| #define GL_FRAMEBUFFER_UNDEFINED 0x8219 |
| #endif |
| #ifndef GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT |
| #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 |
| #endif |
| #ifndef GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT |
| #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 |
| #endif |
| #ifndef GL_FRAMEBUFFER_UNSUPPORTED |
| #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD |
| #endif |
| #ifndef GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS |
| #define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9 |
| #endif |
| |
| #ifndef GL_DEPTH_STENCIL_ATTACHMENT |
| #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A |
| #endif |
| |
| #ifndef GL_READ_FRAMEBUFFER |
| #define GL_READ_FRAMEBUFFER 0x8CA8 |
| #endif |
| #ifndef GL_DRAW_FRAMEBUFFER |
| #define GL_DRAW_FRAMEBUFFER 0x8CA9 |
| #endif |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_gl_framebuffer_debug); |
| #define GST_CAT_DEFAULT gst_gl_framebuffer_debug |
| |
| #define DEBUG_INIT \ |
| GST_DEBUG_CATEGORY_INIT (gst_gl_framebuffer_debug, "glframebuffer", 0, "GL Framebuffer"); |
| |
| G_DEFINE_TYPE_WITH_CODE (GstGLFramebuffer, gst_gl_framebuffer, GST_TYPE_OBJECT, |
| DEBUG_INIT); |
| |
| #define GST_GL_FRAMEBUFFER_GET_PRIVATE(o) \ |
| (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_TYPE_GL_FRAMEBUFFER, GstGLFramebufferPrivate)) |
| |
| static void gst_gl_framebuffer_finalize (GObject * object); |
| |
| struct _GstGLFramebufferPrivate |
| { |
| guint effective_width; |
| guint effective_height; |
| }; |
| |
| struct fbo_attachment |
| { |
| guint attachment_point; |
| GstGLBaseMemory *mem; |
| }; |
| |
| static void |
| _fbo_attachment_init (struct fbo_attachment *attach, guint point, |
| GstGLBaseMemory * mem) |
| { |
| attach->attachment_point = point; |
| attach->mem = (GstGLBaseMemory *) gst_memory_ref (GST_MEMORY_CAST (mem)); |
| } |
| |
| static void |
| _fbo_attachment_unset (struct fbo_attachment *attach) |
| { |
| if (!attach) |
| return; |
| |
| if (attach->mem) |
| gst_memory_unref (GST_MEMORY_CAST (attach->mem)); |
| attach->mem = NULL; |
| } |
| |
| static void |
| gst_gl_framebuffer_class_init (GstGLFramebufferClass * klass) |
| { |
| g_type_class_add_private (klass, sizeof (GstGLFramebufferPrivate)); |
| |
| G_OBJECT_CLASS (klass)->finalize = gst_gl_framebuffer_finalize; |
| } |
| |
| static void |
| gst_gl_framebuffer_init (GstGLFramebuffer * fb) |
| { |
| fb->priv = GST_GL_FRAMEBUFFER_GET_PRIVATE (fb); |
| |
| fb->attachments = |
| g_array_new (FALSE, FALSE, (sizeof (struct fbo_attachment))); |
| g_array_set_clear_func (fb->attachments, |
| (GDestroyNotify) _fbo_attachment_unset); |
| } |
| |
| static void |
| _delete_fbo_gl (GstGLContext * context, GstGLFramebuffer * fb) |
| { |
| const GstGLFuncs *gl = context->gl_vtable; |
| |
| if (fb->fbo_id) |
| gl->DeleteFramebuffers (1, &fb->fbo_id); |
| fb->fbo_id = 0; |
| } |
| |
| static void |
| gst_gl_framebuffer_finalize (GObject * object) |
| { |
| GstGLFramebuffer *fb = GST_GL_FRAMEBUFFER (object); |
| |
| if (fb->context) { |
| if (fb->fbo_id) |
| gst_gl_context_thread_add (fb->context, |
| (GstGLContextThreadFunc) _delete_fbo_gl, fb); |
| |
| gst_object_unref (fb->context); |
| fb->context = NULL; |
| } |
| |
| if (fb->attachments) |
| g_array_free (fb->attachments, TRUE); |
| fb->attachments = NULL; |
| |
| G_OBJECT_CLASS (gst_gl_framebuffer_parent_class)->finalize (object); |
| } |
| |
| /** |
| * gst_gl_framebuffer_new: |
| * @context: a #GstGLContext |
| * |
| * Returns: (transfer full): a new #GstGLFramebuffer |
| * |
| * Since: 1.10 |
| */ |
| GstGLFramebuffer * |
| gst_gl_framebuffer_new (GstGLContext * context) |
| { |
| GstGLFramebuffer *fb; |
| const GstGLFuncs *gl; |
| |
| g_return_val_if_fail (GST_IS_GL_CONTEXT (context), NULL); |
| g_return_val_if_fail (gst_gl_context_get_current () == context, NULL); |
| |
| gl = context->gl_vtable; |
| |
| if (!gl->GenFramebuffers) { |
| GST_ERROR_OBJECT (context, "Framebuffers are not supported!"); |
| return NULL; |
| } |
| |
| fb = g_object_new (GST_TYPE_GL_FRAMEBUFFER, NULL); |
| fb->context = gst_object_ref (context); |
| gl->GenFramebuffers (1, &fb->fbo_id); |
| gst_object_ref_sink (fb); |
| |
| return fb; |
| } |
| |
| /** |
| * gst_gl_framebuffer_new_with_default_depth: |
| * @context: a #GstGLContext |
| * @width: width for the depth buffer |
| * @height: for the depth buffer |
| * |
| * Returns: a new #GstGLFramebuffer with a depth buffer of @width and @height |
| * |
| * Since: 1.10 |
| */ |
| GstGLFramebuffer * |
| gst_gl_framebuffer_new_with_default_depth (GstGLContext * context, guint width, |
| guint height) |
| { |
| GstGLFramebuffer *fb = gst_gl_framebuffer_new (context); |
| GstGLBaseMemoryAllocator *render_alloc; |
| GstGLAllocationParams *params; |
| GstGLBaseMemory *renderbuffer; |
| guint attach_point, attach_type; |
| |
| if (!fb) |
| return NULL; |
| |
| if (gst_gl_context_get_gl_api (fb->context) & (GST_GL_API_OPENGL | |
| GST_GL_API_OPENGL3)) { |
| attach_point = GL_DEPTH_STENCIL_ATTACHMENT; |
| attach_type = GST_GL_DEPTH24_STENCIL8; |
| } else if (gst_gl_context_get_gl_api (fb->context) & GST_GL_API_GLES2) { |
| attach_point = GL_DEPTH_ATTACHMENT; |
| attach_type = GST_GL_DEPTH_COMPONENT16; |
| } else { |
| g_assert_not_reached (); |
| return NULL; |
| } |
| |
| render_alloc = (GstGLBaseMemoryAllocator *) |
| gst_allocator_find (GST_GL_RENDERBUFFER_ALLOCATOR_NAME); |
| params = (GstGLAllocationParams *) |
| gst_gl_renderbuffer_allocation_params_new (context, NULL, attach_type, |
| width, height); |
| |
| renderbuffer = gst_gl_base_memory_alloc (render_alloc, params); |
| gst_gl_allocation_params_free (params); |
| gst_object_unref (render_alloc); |
| |
| gst_gl_framebuffer_bind (fb); |
| gst_gl_framebuffer_attach (fb, attach_point, renderbuffer); |
| gst_gl_context_clear_framebuffer (fb->context); |
| gst_memory_unref (GST_MEMORY_CAST (renderbuffer)); |
| |
| return fb; |
| } |
| |
| /** |
| * gst_gl_framebuffer_draw_to_texture: |
| * @fb: a #GstGLFramebuffer |
| * @mem: the #GstGLMemory to draw to |
| * @func: (scope call): the function to run |
| * @user_data: data to pass to @func |
| * |
| * Perform the steps necessary to have the output of a glDraw* command in |
| * @func update the contents of @mem. |
| * |
| * Returns: the result of executing @func |
| * |
| * Since: 1.10 |
| */ |
| gboolean |
| gst_gl_framebuffer_draw_to_texture (GstGLFramebuffer * fb, GstGLMemory * mem, |
| GstGLFramebufferFunc func, gpointer user_data) |
| { |
| GLint viewport_dim[4] = { 0 }; |
| const GstGLFuncs *gl; |
| gboolean ret; |
| |
| g_return_val_if_fail (GST_IS_GL_FRAMEBUFFER (fb), FALSE); |
| g_return_val_if_fail (gst_is_gl_memory (GST_MEMORY_CAST (mem)), FALSE); |
| |
| gl = fb->context->gl_vtable; |
| |
| GST_TRACE_OBJECT (fb, "drawing to texture %u, dimensions %ix%i", mem->tex_id, |
| gst_gl_memory_get_texture_width (mem), |
| gst_gl_memory_get_texture_height (mem)); |
| |
| gst_gl_framebuffer_bind (fb); |
| gst_gl_framebuffer_attach (fb, GL_COLOR_ATTACHMENT0, (GstGLBaseMemory *) mem); |
| |
| gl->GetIntegerv (GL_VIEWPORT, viewport_dim); |
| gl->Viewport (0, 0, fb->priv->effective_width, fb->priv->effective_height); |
| if (gst_gl_context_get_gl_api (fb->context) & (GST_GL_API_OPENGL | |
| GST_GL_API_OPENGL3)) |
| gl->DrawBuffer (GL_COLOR_ATTACHMENT0); |
| |
| ret = func (user_data); |
| |
| if (gst_gl_context_get_gl_api (fb->context) & (GST_GL_API_OPENGL | |
| GST_GL_API_OPENGL3)) |
| gl->DrawBuffer (GL_COLOR_ATTACHMENT0); |
| gl->Viewport (viewport_dim[0], viewport_dim[1], viewport_dim[2], |
| viewport_dim[3]); |
| gst_gl_context_clear_framebuffer (fb->context); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_gl_framebuffer_bind: |
| * @fb: a #GstGLFramebuffer |
| * |
| * Bind @fb into the current thread |
| * |
| * Since: 1.10 |
| */ |
| void |
| gst_gl_framebuffer_bind (GstGLFramebuffer * fb) |
| { |
| const GstGLFuncs *gl; |
| |
| g_return_if_fail (GST_IS_GL_FRAMEBUFFER (fb)); |
| g_return_if_fail (gst_gl_context_get_current () == fb->context); |
| g_return_if_fail (fb->fbo_id != 0); |
| |
| gl = fb->context->gl_vtable; |
| |
| gl->BindFramebuffer (GL_FRAMEBUFFER, fb->fbo_id); |
| } |
| |
| /** |
| * gst_gl_context_clear_framebuffer: |
| * @context: a #GstGLContext |
| * |
| * Unbind the current framebuffer |
| * |
| * Since: 1.10 |
| */ |
| void |
| gst_gl_context_clear_framebuffer (GstGLContext * context) |
| { |
| const GstGLFuncs *gl; |
| |
| g_return_if_fail (GST_IS_GL_CONTEXT (context)); |
| |
| gl = context->gl_vtable; |
| |
| gl->BindFramebuffer (GL_FRAMEBUFFER, 0); |
| } |
| |
| static void |
| _update_effective_dimensions (GstGLFramebuffer * fb) |
| { |
| int i; |
| guint min_width = -1, min_height = -1; |
| |
| /* remove the previous attachment */ |
| for (i = 0; i < fb->attachments->len; i++) { |
| struct fbo_attachment *attach; |
| int width, height; |
| |
| attach = &g_array_index (fb->attachments, struct fbo_attachment, i); |
| |
| if (gst_is_gl_memory (GST_MEMORY_CAST (attach->mem))) { |
| GstGLMemory *mem = (GstGLMemory *) attach->mem; |
| |
| width = gst_gl_memory_get_texture_width (mem); |
| height = gst_gl_memory_get_texture_height (mem); |
| } else if (gst_is_gl_renderbuffer (GST_MEMORY_CAST (attach->mem))) { |
| GstGLRenderbuffer *mem = (GstGLRenderbuffer *) attach->mem; |
| |
| width = mem->width; |
| height = mem->height; |
| } else { |
| g_assert_not_reached (); |
| } |
| |
| if (width < min_width) |
| min_width = width; |
| if (height < min_height) |
| min_height = height; |
| } |
| |
| fb->priv->effective_width = min_width; |
| fb->priv->effective_height = min_height; |
| } |
| |
| static gboolean |
| _is_valid_attachment_point (guint attachment_point) |
| { |
| /* all 31 possible color attachments */ |
| if (attachment_point >= 0x8CE0 && attachment_point <= 0x8CFF) |
| return TRUE; |
| |
| /* depth-stencil attachment */ |
| if (attachment_point == 0x821A) |
| return TRUE; |
| |
| /* depth attachment */ |
| if (attachment_point == 0x8D00) |
| return TRUE; |
| |
| /* stencil attachment */ |
| if (attachment_point == 0x8D20) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| static void |
| _attach_gl_memory (GstGLFramebuffer * fb, guint attachment_point, |
| GstGLMemory * mem) |
| { |
| struct fbo_attachment attach; |
| const GstGLFuncs *gl = fb->context->gl_vtable; |
| guint gl_target = gst_gl_texture_target_to_gl (mem->tex_target); |
| |
| gst_gl_framebuffer_bind (fb); |
| |
| gl->FramebufferTexture2D (GL_FRAMEBUFFER, attachment_point, gl_target, |
| mem->tex_id, 0); |
| |
| _fbo_attachment_init (&attach, attachment_point, (GstGLBaseMemory *) mem); |
| fb->attachments = g_array_append_val (fb->attachments, attach); |
| } |
| |
| static void |
| _attach_renderbuffer (GstGLFramebuffer * fb, guint attachment_point, |
| GstGLRenderbuffer * rb) |
| { |
| struct fbo_attachment attach; |
| const GstGLFuncs *gl = fb->context->gl_vtable; |
| |
| gst_gl_framebuffer_bind (fb); |
| gl->BindRenderbuffer (GL_RENDERBUFFER, rb->renderbuffer_id); |
| |
| gl->FramebufferRenderbuffer (GL_FRAMEBUFFER, attachment_point, |
| GL_RENDERBUFFER, rb->renderbuffer_id); |
| |
| _fbo_attachment_init (&attach, attachment_point, (GstGLBaseMemory *) rb); |
| fb->attachments = g_array_append_val (fb->attachments, attach); |
| } |
| |
| /** |
| * gst_gl_framebuffer_attach: |
| * @fb: a #GstGLFramebuffer |
| * @attachment_point: the OpenGL attachment point to bind @mem to |
| * @mem: the memory object to bind to @attachment_point |
| * |
| * attach @mem to @attachment_point |
| * |
| * Since: 1.10 |
| */ |
| void |
| gst_gl_framebuffer_attach (GstGLFramebuffer * fb, guint attachment_point, |
| GstGLBaseMemory * mem) |
| { |
| int i; |
| |
| g_return_if_fail (GST_IS_GL_FRAMEBUFFER (fb)); |
| g_return_if_fail (gst_gl_context_get_current () == fb->context); |
| g_return_if_fail (_is_valid_attachment_point (attachment_point)); |
| |
| /* remove the previous attachment */ |
| for (i = 0; i < fb->attachments->len; i++) { |
| struct fbo_attachment *attach; |
| |
| attach = &g_array_index (fb->attachments, struct fbo_attachment, i); |
| |
| if (attach->attachment_point == attachment_point) { |
| g_array_remove_index_fast (fb->attachments, i); |
| break; |
| } |
| } |
| |
| if (gst_is_gl_memory (GST_MEMORY_CAST (mem))) { |
| _attach_gl_memory (fb, attachment_point, (GstGLMemory *) mem); |
| } else if (gst_is_gl_renderbuffer (GST_MEMORY_CAST (mem))) { |
| _attach_renderbuffer (fb, attachment_point, (GstGLRenderbuffer *) mem); |
| } else { |
| g_assert_not_reached (); |
| return; |
| } |
| |
| _update_effective_dimensions (fb); |
| } |
| |
| /** |
| * gst_gl_framebuffer_get_effective_dimensions: |
| * @fb: a #GstGLFramebuffer |
| * @width: (out) (allow-none): output width |
| * @height: (out) (allow-none): output height |
| * |
| * Retreive the effective dimensions from the current attachments attached to |
| * @fb. |
| * |
| * Since: 1.10 |
| */ |
| void |
| gst_gl_framebuffer_get_effective_dimensions (GstGLFramebuffer * fb, |
| guint * width, guint * height) |
| { |
| g_return_if_fail (GST_IS_GL_FRAMEBUFFER (fb)); |
| |
| if (width) |
| *width = fb->priv->effective_width; |
| if (height) |
| *height = fb->priv->effective_height; |
| } |
| |
| /** |
| * gst_gl_context_check_framebuffer_status: |
| * @context: a #GstGLContext |
| * @fbo_target: the GL value of the framebuffer target, GL_FRAMEBUFFER, |
| * GL_READ_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER |
| * |
| * Returns: whether whether the current framebuffer is complete |
| * |
| * Since: 1.10 |
| */ |
| gboolean |
| gst_gl_context_check_framebuffer_status (GstGLContext * context, |
| guint fbo_target) |
| { |
| g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE); |
| |
| if (fbo_target != GL_FRAMEBUFFER && fbo_target != GL_READ_FRAMEBUFFER |
| && fbo_target != GL_DRAW_FRAMEBUFFER) { |
| GST_ERROR_OBJECT (context, "fbo target is invalid"); |
| return FALSE; |
| } |
| |
| switch (context->gl_vtable->CheckFramebufferStatus (fbo_target)) { |
| case GL_FRAMEBUFFER_COMPLETE: |
| return TRUE; |
| break; |
| case GL_FRAMEBUFFER_UNSUPPORTED: |
| GST_WARNING_OBJECT (context, "GL_FRAMEBUFFER_UNSUPPORTED"); |
| break; |
| case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: |
| GST_WARNING_OBJECT (context, "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); |
| break; |
| case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: |
| GST_WARNING_OBJECT (context, |
| "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); |
| break; |
| case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: |
| GST_WARNING_OBJECT (context, "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"); |
| break; |
| #if GST_GL_HAVE_OPENGL |
| case GL_FRAMEBUFFER_UNDEFINED: |
| GST_WARNING_OBJECT (context, "GL_FRAMEBUFFER_UNDEFINED"); |
| break; |
| #endif |
| default: |
| GST_WARNING_OBJECT (context, "Unknown FBO error"); |
| break; |
| } |
| |
| return FALSE; |
| } |
| |
| /** |
| * gst_gl_framebuffer_get_id: |
| * @fb: a #GstGLFramebuffer |
| * |
| * Returns: the OpenGL id for @fb |
| * |
| * Since: 1.10 |
| */ |
| guint |
| gst_gl_framebuffer_get_id (GstGLFramebuffer * fb) |
| { |
| g_return_val_if_fail (GST_IS_GL_FRAMEBUFFER (fb), 0); |
| |
| return fb->fbo_id; |
| } |