mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-09 05:38:58 +00:00
349 lines
11 KiB
Objective-C
349 lines
11 KiB
Objective-C
/*
|
|
* GStreamer EGL/GLES Sink Adaptation for IOS
|
|
* Copyright (C) 2013 Collabora Ltd.
|
|
* @author: Thiago Santos <thiago.sousa.santos@collabora.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* Alternatively, the contents of this file may be used under the
|
|
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
|
|
* which case the following provisions apply instead of the ones
|
|
* mentioned above:
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#import <OpenGLES/EAGL.h>
|
|
#import <QuartzCore/QuartzCore.h>
|
|
#import <UIKit/UIKit.h>
|
|
#include <OpenGLES/ES2/gl.h>
|
|
|
|
#include "gstegladaptation.h"
|
|
|
|
#define GST_CAT_DEFAULT egladaption_debug
|
|
|
|
struct _GstEaglContext
|
|
{
|
|
EAGLContext *eagl_context;
|
|
GLuint framebuffer;
|
|
GLuint color_renderbuffer;
|
|
GLuint depth_renderbuffer;
|
|
|
|
UIView *window;
|
|
UIView *used_window;
|
|
};
|
|
|
|
static gboolean
|
|
gst_egl_adaptation_update_surface (GstEglAdaptationContext * ctx);
|
|
|
|
void
|
|
gst_egl_adaptation_init (GstEglAdaptationContext * ctx)
|
|
{
|
|
ctx->eaglctx = g_new0 (GstEaglContext, 1);
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_deinit (GstEglAdaptationContext * ctx)
|
|
{
|
|
g_free (ctx->eaglctx);
|
|
}
|
|
|
|
gboolean
|
|
gst_egl_adaptation_init_display (GstEglAdaptationContext * ctx)
|
|
{
|
|
/* NOP - the display should be initialized by the application */
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_bind_API (GstEglAdaptationContext * ctx)
|
|
{
|
|
/* NOP */
|
|
}
|
|
|
|
gboolean
|
|
gst_egl_adaptation_create_native_window (GstEglAdaptationContext * ctx, gint width, gint height, gpointer * own_window_data)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_destroy_native_window (GstEglAdaptationContext * ctx, gpointer * own_window_data)
|
|
{
|
|
}
|
|
|
|
gboolean
|
|
gst_egl_adaptation_create_egl_context (GstEglAdaptationContext * ctx)
|
|
{
|
|
__block EAGLContext *context;
|
|
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
EAGLContext *cur_ctx = [EAGLContext currentContext];
|
|
if (cur_ctx) {
|
|
context = cur_ctx;
|
|
} else {
|
|
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
|
|
if (context == nil) {
|
|
GST_ERROR_OBJECT (ctx->element, "Failed to create EAGL GLES2 context");
|
|
}
|
|
}
|
|
});
|
|
|
|
ctx->eaglctx->eagl_context = context;
|
|
if (context == nil)
|
|
return FALSE;
|
|
|
|
/* EAGL needs the context to be set here to allow surface creation */
|
|
return gst_egl_adaptation_context_make_current (ctx, TRUE);
|
|
}
|
|
|
|
gboolean
|
|
gst_egl_adaptation_context_make_current (GstEglAdaptationContext * ctx,
|
|
gboolean bind)
|
|
{
|
|
__block EAGLContext *ctx_to_set = nil;
|
|
if (bind && ctx->eaglctx->eagl_context) {
|
|
EAGLContext *cur_ctx = [EAGLContext currentContext];
|
|
|
|
if (cur_ctx == ctx->eaglctx->eagl_context) {
|
|
GST_DEBUG_OBJECT (ctx->element,
|
|
"Already attached the context to thread %p", g_thread_self ());
|
|
return TRUE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (ctx->element, "Attaching context to thread %p",
|
|
g_thread_self ());
|
|
if ([EAGLContext setCurrentContext: ctx->eaglctx->eagl_context] == NO) {
|
|
got_gl_error ("setCurrentContext");
|
|
GST_ERROR_OBJECT (ctx->element, "Couldn't bind context");
|
|
return FALSE;
|
|
}
|
|
ctx_to_set = ctx->eaglctx->eagl_context;
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
[EAGLContext setCurrentContext: ctx_to_set];
|
|
});
|
|
|
|
} else {
|
|
GST_DEBUG_OBJECT (ctx->element, "Detaching context from thread %p",
|
|
g_thread_self ());
|
|
if ([EAGLContext setCurrentContext: nil] == NO) {
|
|
got_gl_error ("setCurrentContext");
|
|
GST_ERROR_OBJECT (ctx->element, "Couldn't unbind context");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_egl_adaptation_create_surface (GstEglAdaptationContext * ctx)
|
|
{
|
|
__block GLuint framebuffer;
|
|
__block GLuint colorRenderbuffer;
|
|
__block GLint width;
|
|
__block GLint height;
|
|
__block GLuint depthRenderbuffer;
|
|
__block GLenum status;
|
|
__block CAEAGLLayer *eaglLayer = (CAEAGLLayer *)[ctx->eaglctx->window layer];
|
|
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
|
|
if (ctx->eaglctx->framebuffer) {
|
|
framebuffer = ctx->eaglctx->framebuffer;
|
|
} else {
|
|
/* Allocate framebuffer */
|
|
glGenFramebuffers(1, &framebuffer);
|
|
}
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
|
|
/* Allocate color render buffer */
|
|
glGenRenderbuffers(1, &colorRenderbuffer);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
|
|
[ctx->eaglctx->eagl_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer];
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
GL_RENDERBUFFER, colorRenderbuffer);
|
|
|
|
/* Get renderbuffer width/height */
|
|
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
|
|
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
|
|
|
|
/* allocate depth render buffer */
|
|
glGenRenderbuffers(1, &depthRenderbuffer);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
|
GL_RENDERBUFFER, depthRenderbuffer);
|
|
});
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
/* check creation status */
|
|
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
if(status != GL_FRAMEBUFFER_COMPLETE) {
|
|
NSLog(@"failed to make complete framebuffer object %x", status);
|
|
return FALSE;
|
|
}
|
|
|
|
ctx->eaglctx->framebuffer = framebuffer;
|
|
ctx->eaglctx->color_renderbuffer = colorRenderbuffer;
|
|
ctx->eaglctx->depth_renderbuffer = colorRenderbuffer;
|
|
ctx->surface_width = width;
|
|
ctx->surface_height = height;
|
|
glBindRenderbuffer(GL_RENDERBUFFER, ctx->eaglctx->color_renderbuffer);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
_gst_egl_choose_config (GstEglAdaptationContext * ctx, gboolean try_only, gint * num_configs)
|
|
{
|
|
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)[ctx->eaglctx->window layer];
|
|
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
|
|
kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
|
|
nil];
|
|
[eaglLayer setOpaque:YES];
|
|
[eaglLayer setDrawableProperties:dict];
|
|
|
|
if (num_configs)
|
|
*num_configs = 1;
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_query_buffer_preserved (GstEglAdaptationContext * ctx)
|
|
{
|
|
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)[ctx->eaglctx->window layer];
|
|
NSDictionary *dict = [eaglLayer drawableProperties];
|
|
|
|
ctx->buffer_preserved = FALSE;
|
|
if ([dict objectForKey: kEAGLDrawablePropertyRetainedBacking] != nil) {
|
|
NSNumber *n = [dict objectForKey: kEAGLDrawablePropertyRetainedBacking];
|
|
ctx->buffer_preserved = [n boolValue] != NO;
|
|
} else {
|
|
GST_DEBUG_OBJECT (ctx->element, "No information about buffer preserving in layer properties");
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_query_par (GstEglAdaptationContext * ctx)
|
|
{
|
|
/* TODO how can we check this? */
|
|
ctx->pixel_aspect_ratio_n = 1;
|
|
ctx->pixel_aspect_ratio_d = 1;
|
|
}
|
|
|
|
gboolean
|
|
gst_egl_adaptation_update_surface_dimensions (GstEglAdaptationContext *
|
|
ctx)
|
|
{
|
|
CAEAGLLayer *layer = (CAEAGLLayer *)[ctx->eaglctx->window layer];
|
|
CGSize size = layer.frame.size;
|
|
|
|
if (size.width != ctx->surface_width || size.height != ctx->surface_height) {
|
|
ctx->surface_width = size.width;
|
|
ctx->surface_height = size.height;
|
|
GST_INFO_OBJECT (ctx->element, "Got surface of %dx%d pixels",
|
|
(gint) size.width, (gint) size.height);
|
|
if (!gst_egl_adaptation_update_surface (ctx)) {
|
|
GST_WARNING_OBJECT (ctx->element, "Failed to update surface "
|
|
"to new dimensions");
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_init_exts (GstEglAdaptationContext * ctx)
|
|
{
|
|
const gchar *extensions = (const gchar *) glGetString(GL_EXTENSIONS);
|
|
NSString *extensionsString = NULL;
|
|
|
|
if (extensions) {
|
|
extensionsString= [NSString stringWithCString:extensions encoding: NSASCIIStringEncoding];
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (ctx->element, "Available GL extensions: %s\n",
|
|
GST_STR_NULL ([extensionsString UTF8String]));
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_destroy_surface (GstEglAdaptationContext * ctx)
|
|
{
|
|
if (ctx->eaglctx->framebuffer) {
|
|
glDeleteFramebuffers (1, &ctx->eaglctx->framebuffer);
|
|
ctx->eaglctx->framebuffer = 0;
|
|
ctx->have_surface = FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_egl_adaptation_update_surface (GstEglAdaptationContext * ctx)
|
|
{
|
|
glBindFramebuffer(GL_FRAMEBUFFER, ctx->eaglctx->framebuffer);
|
|
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
|
GL_RENDERBUFFER, 0);
|
|
glDeleteRenderbuffers(1, &ctx->eaglctx->depth_renderbuffer);
|
|
|
|
glBindRenderbuffer (GL_RENDERBUFFER, 0);
|
|
glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
GL_RENDERBUFFER, 0);
|
|
glDeleteRenderbuffers(1, &ctx->eaglctx->color_renderbuffer);
|
|
|
|
return gst_egl_adaptation_create_surface (ctx);
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_destroy_context (GstEglAdaptationContext * ctx)
|
|
{
|
|
if (ctx->eaglctx->eagl_context) {
|
|
/* Do not release/dealloc as it seems that EAGL expects to do all
|
|
* the cleanup by itself when a new context replaces the old one */
|
|
ctx->eaglctx->eagl_context = NULL;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gst_egl_adaptation_context_swap_buffers (GstEglAdaptationContext * ctx)
|
|
{
|
|
[ctx->eaglctx->eagl_context presentRenderbuffer:GL_RENDERBUFFER];
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gst_egl_adaptation_set_window (GstEglAdaptationContext * ctx, guintptr window)
|
|
{
|
|
ctx->eaglctx->window = (UIView *) window;
|
|
}
|