gstreamer/gst-libs/gst/gl/gstglframebuffer.c
Matthew Waters 6a37bf9bb9 gl: Don't restore the viewport on function exit
Doing so involves retrieving the current viewport from OpenGL which as
with any glGet operation, is expensive.

This means that the various sinks need to reset the viewport on draw.

In the process, fix resizing on cocoa.
2019-03-08 17:49:05 +11:00

571 lines
15 KiB
C

/*
* 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
static void gst_gl_framebuffer_finalize (GObject * object);
struct _GstGLFramebufferPrivate
{
guint effective_width;
guint effective_height;
};
#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,
G_ADD_PRIVATE (GstGLFramebuffer) DEBUG_INIT);
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_OBJECT_CLASS (klass)->finalize = gst_gl_framebuffer_finalize;
}
static void
gst_gl_framebuffer_init (GstGLFramebuffer * fb)
{
fb->priv = gst_gl_framebuffer_get_instance_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)
{
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->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);
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;
}