gstreamer/gst-libs/gst/gl/eagl/gstglcontext_eagl.m
Matthew Waters 7066c849e4 glcontext: add support for influencing the backing configuration
New API:
- gst_gl_context_get_config()
- gst_gl_context_request_config()

A GL context configuration is a GstStructure that has some well-known
names for common values that can also be extended in platform-specific
ways if necessary.

Wrapped OpenGL contexts may be able to retrieve the GL context
configuration depending on the platform.  If that information is
available, GstGLContext will attempt to create an context that matches
the shared OpenGL context config unless gst_gl_context_request_config()
has been called.

A new environment variable 'GST_GL_CONFIG' will be read to influence the
configuration chosen.  The environment variable will only be used as a
fallback if gst_gl_context_request_config() has not been called.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/809>
2021-05-13 16:44:28 +10:00

471 lines
14 KiB
Objective-C

/*
* GStreamer
* Copyright (C) 2014 Sebastian Dröge <sebastian@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
#import <OpenGLES/EAGL.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#include <OpenGLES/ES2/gl.h>
#include "gstglcontext_eagl.h"
#include "../gstglcontext_private.h"
#include "gstglios_utils.h"
#define GST_GL_CONTEXT_EAGL_CONTEXT(obj) \
((__bridge EAGLContext *)(obj->priv->eagl_context))
#define GST_GL_CONTEXT_EAGL_LAYER(obj) \
((__bridge CAEAGLLayer *)(obj->priv->eagl_layer))
#define GST_CAT_DEFAULT gst_gl_context_debug
static gboolean gst_gl_context_eagl_create_context (GstGLContext * context,
GstGLAPI gl_api, GstGLContext * other_context, GError ** error);
static void gst_gl_context_eagl_destroy_context (GstGLContext * context);
static gboolean gst_gl_context_eagl_choose_format (GstGLContext * context,
GError ** error);
static guintptr gst_gl_context_eagl_get_gl_context (GstGLContext * window);
static gboolean gst_gl_context_eagl_activate (GstGLContext * context,
gboolean activate);
static void gst_gl_context_eagl_swap_buffers (GstGLContext * context);
static GstGLAPI gst_gl_context_eagl_get_gl_api (GstGLContext * context);
static GstGLPlatform gst_gl_context_eagl_get_gl_platform (GstGLContext *
context);
GstStructure *gst_gl_context_eagl_get_config (GstGLContext * context);
struct _GstGLContextEaglPrivate
{
gpointer eagl_context;
/* Used if we render to a window */
gpointer eagl_layer;
GLuint framebuffer;
GLuint color_renderbuffer;
GLuint depth_renderbuffer;
};
G_DEFINE_TYPE_WITH_PRIVATE (GstGLContextEagl, gst_gl_context_eagl,
GST_TYPE_GL_CONTEXT);
static void
gst_gl_context_eagl_class_init (GstGLContextEaglClass * klass)
{
GstGLContextClass *context_class;
context_class = (GstGLContextClass *) klass;
context_class->destroy_context =
GST_DEBUG_FUNCPTR (gst_gl_context_eagl_destroy_context);
context_class->create_context =
GST_DEBUG_FUNCPTR (gst_gl_context_eagl_create_context);
context_class->choose_format =
GST_DEBUG_FUNCPTR (gst_gl_context_eagl_choose_format);
context_class->get_gl_context =
GST_DEBUG_FUNCPTR (gst_gl_context_eagl_get_gl_context);
context_class->activate = GST_DEBUG_FUNCPTR (gst_gl_context_eagl_activate);
context_class->swap_buffers =
GST_DEBUG_FUNCPTR (gst_gl_context_eagl_swap_buffers);
context_class->get_gl_api =
GST_DEBUG_FUNCPTR (gst_gl_context_eagl_get_gl_api);
context_class->get_gl_platform =
GST_DEBUG_FUNCPTR (gst_gl_context_eagl_get_gl_platform);
context_class->get_config = GST_DEBUG_FUNCPTR (gst_gl_context_eagl_get_config);
}
static void
gst_gl_context_eagl_init (GstGLContextEagl * context)
{
context->priv = gst_gl_context_eagl_get_instance_private (context);
}
/* Must be called in the gl thread */
GstGLContextEagl *
gst_gl_context_eagl_new (GstGLDisplay * display)
{
GstGLContextEagl *context;
/* there isn't actually a display type for eagl yet? */
context = g_object_new (GST_TYPE_GL_CONTEXT_EAGL, NULL);
gst_object_ref_sink (context);
return context;
}
enum EAGLFormat
{
FORMAT_RGBA8 = 1,
FORMAT_RGB565,
};
static GstStructure *
layer_config_to_structure (GstGLContextEagl *eagl, CAEAGLLayer * layer)
{
GstStructure *ret;
NSDictionary *drawableProps = [layer drawableProperties];
NSString *color_format;
enum EAGLFormat eagl_format = FORMAT_RGBA8;
ret = gst_structure_new (GST_GL_CONFIG_STRUCTURE_NAME,
GST_GL_CONFIG_STRUCTURE_SET_ARGS(PLATFORM, GstGLPlatform, GST_GL_PLATFORM_EAGL),
NULL);
color_format = [drawableProps objectForKey:kEAGLDrawablePropertyColorFormat];
if (!color_format)
color_format = [layer contentsFormat];
if (!color_format) {
GST_WARNING_OBJECT (eagl, "Could not retrieve color format from layer %p", layer);
goto failure;
}
if (color_format == kEAGLColorFormatRGBA8 || color_format == kCAContentsFormatRGBA8Uint) {
eagl_format = FORMAT_RGBA8;
} else if (color_format == kEAGLColorFormatRGB565) {
eagl_format = FORMAT_RGB565;
} else {
GST_WARNING_OBJECT (eagl, "unknown drawable format: %s", [color_format UTF8String]);
goto failure;
}
/* XXX: defaults chosen by _update_layer() */
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(DEPTH_SIZE, int, 16), NULL);
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(STENCIL_SIZE, int, 0), NULL);
switch (eagl_format) {
case FORMAT_RGBA8:
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(RED_SIZE, int, 8), NULL);
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(GREEN_SIZE, int, 8), NULL);
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(BLUE_SIZE, int, 8), NULL);
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(ALPHA_SIZE, int, 8), NULL);
break;
case FORMAT_RGB565:
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(RED_SIZE, int, 5), NULL);
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(GREEN_SIZE, int, 6), NULL);
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(BLUE_SIZE, int, 5), NULL);
gst_structure_set (ret, GST_GL_CONFIG_STRUCTURE_SET_ARGS(ALPHA_SIZE, int, 0), NULL);
break;
default:
GST_WARNING_OBJECT (eagl, "Unhandled format!");
goto failure;
}
return ret;
failure:
gst_structure_free (ret);
return NULL;
}
void
gst_gl_context_eagl_resize (GstGLContextEagl * eagl_context)
{
int width, height;
glBindRenderbuffer (GL_RENDERBUFFER, eagl_context->priv->color_renderbuffer);
[GST_GL_CONTEXT_EAGL_CONTEXT(eagl_context)
renderbufferStorage:GL_RENDERBUFFER
fromDrawable:GST_GL_CONTEXT_EAGL_LAYER(eagl_context)];
glGetRenderbufferParameteriv (GL_RENDERBUFFER,
GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv (GL_RENDERBUFFER,
GL_RENDERBUFFER_HEIGHT, &height);
glBindRenderbuffer (GL_RENDERBUFFER, eagl_context->priv->depth_renderbuffer);
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width,
height);
}
static void
gst_gl_context_eagl_release_layer (GstGLContext * context)
{
GstGLContextEagl *context_eagl;
context_eagl = GST_GL_CONTEXT_EAGL (context);
if (context_eagl->priv->eagl_layer) {
gst_gl_context_eagl_activate (context, TRUE);
[GST_GL_CONTEXT_EAGL_CONTEXT(context_eagl)
renderbufferStorage:GL_RENDERBUFFER
fromDrawable:nil];
glDeleteFramebuffers (1, &context_eagl->priv->framebuffer);
context_eagl->priv->framebuffer = 0;
glDeleteRenderbuffers (1, &context_eagl->priv->depth_renderbuffer);
context_eagl->priv->depth_renderbuffer = 0;
glDeleteRenderbuffers (1, &context_eagl->priv->color_renderbuffer);
context_eagl->priv->color_renderbuffer = 0;
CFRelease (context_eagl->priv->eagl_layer);
context_eagl->priv->eagl_layer = NULL;
gst_gl_context_eagl_activate (context, FALSE);
}
}
void
gst_gl_context_eagl_update_layer (GstGLContext * context, gpointer layer)
{
GLuint framebuffer;
GLuint color_renderbuffer;
GLuint depth_renderbuffer;
GLint width;
GLint height;
CAEAGLLayer *eagl_layer;
GLenum status;
GstGLContextEagl *context_eagl = GST_GL_CONTEXT_EAGL (context);
GstGLContextEaglPrivate *priv = context_eagl->priv;
GstGLWindow *window = gst_gl_context_get_window (context);
GstStructure *fmt;
if (!layer || !gst_gl_window_get_window_handle (window)) {
GST_INFO_OBJECT (context, "window handle not set yet, not updating layer");
goto out;
}
if (priv->eagl_layer)
gst_gl_context_eagl_release_layer (context);
eagl_layer = (__bridge CAEAGLLayer *) layer;
[EAGLContext setCurrentContext:GST_GL_CONTEXT_EAGL_CONTEXT(context_eagl)];
/* Allocate framebuffer */
glGenFramebuffers (1, &framebuffer);
glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
/* Allocate color render buffer */
glGenRenderbuffers (1, &color_renderbuffer);
glBindRenderbuffer (GL_RENDERBUFFER, color_renderbuffer);
[GST_GL_CONTEXT_EAGL_CONTEXT(context_eagl) renderbufferStorage: GL_RENDERBUFFER fromDrawable:eagl_layer];
glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, color_renderbuffer);
/* Get renderbuffer width/height */
glGetRenderbufferParameteriv (GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH,
&width);
glGetRenderbufferParameteriv (GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT,
&height);
/* allocate depth render buffer */
glGenRenderbuffers (1, &depth_renderbuffer);
glBindRenderbuffer (GL_RENDERBUFFER, depth_renderbuffer);
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width,
height);
glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, depth_renderbuffer);
/* check creation status */
status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
GST_ERROR ("Failed to make complete framebuffer object %x", status);
goto out;
}
glBindRenderbuffer (GL_RENDERBUFFER, 0);
glBindFramebuffer (GL_FRAMEBUFFER, 0);
priv->eagl_layer = (__bridge_retained gpointer) eagl_layer;
priv->framebuffer = framebuffer;
priv->color_renderbuffer = color_renderbuffer;
priv->depth_renderbuffer = depth_renderbuffer;
fmt = layer_config_to_structure (context_eagl, eagl_layer);
if (fmt) {
GST_DEBUG_OBJECT (context_eagl, "chosen config %" GST_PTR_FORMAT, fmt);
gst_structure_free (fmt);
}
out:
if (window)
gst_object_unref (window);
if (layer)
CFRelease (layer);
}
static gboolean
gst_gl_context_eagl_create_context (GstGLContext * context, GstGLAPI gl_api,
GstGLContext * other_context, GError ** error)
{
GstGLContextEagl *context_eagl = GST_GL_CONTEXT_EAGL (context);
GstGLContextEaglPrivate *priv = context_eagl->priv;
GstGLWindow *window;
GstGLWindowEagl *window_eagl;
gpointer layer;
EAGLSharegroup *share_group;
if (other_context) {
EAGLContext *external_gl_context = (__bridge EAGLContext *)(void *)
gst_gl_context_get_gl_context (other_context);
share_group = [external_gl_context sharegroup];
} else {
share_group = nil;
}
priv->eagl_context = (__bridge_retained gpointer)[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 sharegroup:share_group];
if (!priv->eagl_context) {
priv->eagl_context = (__bridge_retained gpointer)[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:share_group];
}
if (!priv->eagl_context) {
g_set_error_literal (error, GST_GL_CONTEXT_ERROR,
GST_GL_CONTEXT_ERROR_CREATE_CONTEXT,
"Failed to create OpenGL ES context");
return FALSE;
}
priv->eagl_layer = NULL;
priv->framebuffer = 0;
priv->color_renderbuffer = 0;
priv->depth_renderbuffer = 0;
GST_INFO_OBJECT (context, "context created, updating layer");
window = gst_gl_context_get_window (context);
if (!window) {
g_set_error_literal (error, GST_GL_CONTEXT_ERROR,
GST_GL_CONTEXT_ERROR_CREATE_CONTEXT,
"No window to render into");
return FALSE;
}
window_eagl = GST_GL_WINDOW_EAGL (window);
layer = gst_gl_window_eagl_get_layer (window_eagl);
gst_gl_context_eagl_update_layer (context, layer);
gst_object_unref (window);
return TRUE;
}
static void
gst_gl_context_eagl_destroy_context (GstGLContext * context)
{
GstGLContextEagl *context_eagl;
context_eagl = GST_GL_CONTEXT_EAGL (context);
if (!context_eagl->priv->eagl_context)
return;
gst_gl_context_eagl_release_layer (context);
CFRelease(context_eagl->priv->eagl_context);
context_eagl->priv->eagl_context = NULL;
}
static gboolean
gst_gl_context_eagl_choose_format (GstGLContext * context, GError ** error)
{
return TRUE;
}
static guintptr
gst_gl_context_eagl_get_gl_context (GstGLContext * context)
{
return (guintptr) GST_GL_CONTEXT_EAGL (context)->priv->eagl_context;
}
void
gst_gl_context_eagl_prepare_draw (GstGLContextEagl * context)
{
if (!context->priv->eagl_layer)
return;
glBindFramebuffer (GL_FRAMEBUFFER, context->priv->framebuffer);
glBindRenderbuffer (GL_RENDERBUFFER, context->priv->color_renderbuffer);
}
void
gst_gl_context_eagl_finish_draw (GstGLContextEagl * context)
{
if (!context->priv->eagl_layer)
return;
glBindRenderbuffer (GL_RENDERBUFFER, 0);
glBindFramebuffer (GL_FRAMEBUFFER, 0);
}
static void
gst_gl_context_eagl_swap_buffers (GstGLContext * context)
{
GstGLContextEagl *context_eagl;
context_eagl = GST_GL_CONTEXT_EAGL (context);
if (!context_eagl->priv->eagl_layer)
return;
[GST_GL_CONTEXT_EAGL_CONTEXT(context_eagl) presentRenderbuffer:GL_RENDERBUFFER];
}
static gboolean
gst_gl_context_eagl_activate (GstGLContext * context, gboolean activate)
{
GstGLContextEagl *context_eagl;
context_eagl = GST_GL_CONTEXT_EAGL (context);
if (activate) {
EAGLContext *cur_ctx =[EAGLContext currentContext];
if (cur_ctx == context_eagl->priv->eagl_context) {
GST_DEBUG ("Already attached the context to thread %p", g_thread_self ());
return TRUE;
}
GST_DEBUG ("Attaching context to thread %p", g_thread_self ());
if ([EAGLContext setCurrentContext:GST_GL_CONTEXT_EAGL_CONTEXT(context_eagl)] == NO) {
GST_ERROR ("Couldn't make context current");
return FALSE;
}
} else {
GST_DEBUG ("Detaching context from thread %p", g_thread_self ());
if ([EAGLContext setCurrentContext:nil] == NO) {
GST_ERROR ("Couldn't unbind context");
return FALSE;
}
}
return TRUE;
}
static GstGLAPI
gst_gl_context_eagl_get_gl_api (GstGLContext * context)
{
return GST_GL_API_GLES2;
}
static GstGLPlatform
gst_gl_context_eagl_get_gl_platform (GstGLContext * context)
{
return GST_GL_PLATFORM_EAGL;
}
guintptr
gst_gl_context_eagl_get_current_context (void)
{
return (guintptr) [EAGLContext currentContext];
}
GstStructure *
gst_gl_context_eagl_get_config (GstGLContext * context)
{
GstGLContextEagl *eagl = GST_GL_CONTEXT_EAGL (context);
if (!eagl->priv->eagl_layer)
return NULL;
return layer_config_to_structure (eagl, (__bridge CAEAGLLayer *) eagl->priv->eagl_layer);
}