You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

534 lines
17 KiB

/*****************************************************************************
* VLCOpenGLES2VideoView.m: iOS OpenGL ES provider through CAEAGLLayer
*****************************************************************************
* Copyright (C) 2001-2017 VLC authors and VideoLAN
* Copyright (C) 2021 Videolabs
*
* Authors: Pierre d'Herbemont <pdherbemont at videolan dot org>
* Felix Paul Kühne <fkuehne at videolan dot org>
* David Fuhrmann <david dot fuhrmann at googlemail dot com>
* Rémi Denis-Courmont
* Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
* Eric Petit <titer@m0k.org>
* Alexandre Janniaux <ajanni@videolabs.io>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
#import <QuartzCore/QuartzCore.h>
#import <dlfcn.h>
#ifdef HAVE_CONFIG_H
# import "config.h"
#endif
#import <vlc_common.h>
#import <vlc_threads.h>
#import <vlc_plugin.h>
#import <vlc_vout_display.h>
#import <vlc_opengl.h>
#import <vlc_dialog.h>
#import "../opengl/vout_helper.h"
#import "../opengl/gl_api.h"
@interface VLCOpenGLES2VideoView : UIView {
vlc_gl_t *_gl;
EAGLContext *_eaglContext;
EAGLContext *_previousEaglContext;
CAEAGLLayer *_layer;
vlc_mutex_t _mutex;
vlc_cond_t _gl_attached_wait;
BOOL _gl_attached;
BOOL _bufferNeedReset;
BOOL _appActive;
BOOL _eaglEnabled;
GLuint _renderBuffer;
GLuint _frameBuffer;
struct vlc_gl_api _api;
}
- (id)initWithFrame:(CGRect)frame gl:(vlc_gl_t*)gl;
- (BOOL)makeCurrent;
- (void)releaseCurrent;
- (void)presentRenderbuffer;
- (void)didMoveToWindow;
- (void)detachFromWindow;
@end
static void vlc_dispatch_sync(void (^block_function)())
{
CFRunLoopRef runloop = CFRunLoopGetMain();
__block vlc_sem_t performed;
vlc_sem_init(&performed, 0);
CFStringRef modes_cfstrings[] = {
kCFRunLoopCommonModes,
CFSTR("org.videolan.vlccore.window"),
};
CFArrayRef modes = CFArrayCreate(NULL, (const void **)modes_cfstrings,
ARRAY_SIZE(modes_cfstrings),
&kCFTypeArrayCallBacks);
/* NOTE: we're using CFRunLoopPerformBlock with a custom mode tag
* to avoid deadlocks between the window module (main thread) and the
* display module, which would happen when using dispatch_sycn here. */
CFRunLoopPerformBlock(runloop, modes, ^{
(block_function)();
vlc_sem_post(&performed);
});
CFRunLoopWakeUp(runloop);
vlc_sem_wait(&performed);
CFRelease(modes);
}
/*****************************************************************************
* vlc_gl_t callbacks
*****************************************************************************/
static void *GetSymbol(vlc_gl_t *gl, const char *name)
{
VLC_UNUSED(gl);
return dlsym(RTLD_DEFAULT, name);
}
static int MakeCurrent(vlc_gl_t *gl)
{
VLCOpenGLES2VideoView *view = (__bridge VLCOpenGLES2VideoView *)gl->sys;
if (![view makeCurrent])
return VLC_EGENERIC;
return VLC_SUCCESS;
}
static void ReleaseCurrent(vlc_gl_t *gl)
{
VLCOpenGLES2VideoView *view = (__bridge VLCOpenGLES2VideoView *)gl->sys;
[view releaseCurrent];
}
static void Swap(vlc_gl_t *gl)
{
VLCOpenGLES2VideoView *view = (__bridge VLCOpenGLES2VideoView *)gl->sys;
[view presentRenderbuffer];
}
static void Resize(vlc_gl_t *gl, unsigned width, unsigned height)
{
VLC_UNUSED(gl); VLC_UNUSED(width); VLC_UNUSED(height);
/* Use the parent frame size for now, resize is smoother and called
* automatically from the main thread queue. */
}
static void Close(vlc_gl_t *gl)
{
/* Transfer ownership back from VLC to ARC so that it can be released. */
VLCOpenGLES2VideoView *view = (__bridge_transfer VLCOpenGLES2VideoView*)gl->sys;
/* We need to detach because the superview has a reference to our view. */
[view detachFromWindow];
}
/*****************************************************************************
* Our UIView object
*****************************************************************************/
@implementation VLCOpenGLES2VideoView
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
- (id)initWithFrame:(CGRect)frame gl:(vlc_gl_t*)gl
{
_gl = gl;
_appActive = ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground);
if (unlikely(!_appActive))
return nil;
self = [super initWithFrame:frame];
if (!self)
return nil;
_eaglEnabled = YES;
_bufferNeedReset = YES;
vlc_mutex_init(&_mutex);
vlc_cond_init(&_gl_attached_wait);
/* The following creates a new OpenGL ES context with the API version we
* need. If there is already an active context created by another OpenGL
* provider we cache it and restore analog to the
* makeCurrent/releaseCurrent pattern used through-out the class */
_eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
_previousEaglContext = nil;
_layer = (CAEAGLLayer *)self.layer;
_layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
_layer.opaque = YES;
/* Resize is done accordingly to the parent frame directly. */
self.autoresizingMask = UIViewAutoresizingFlexibleWidth
| UIViewAutoresizingFlexibleHeight;
self.contentMode = UIViewContentModeScaleToFill;
/* Connect to the parent UIView which will contain this surface.
* Using a parent UIView makes it easier to handle window resize and
* still have full control over the display object, since layers don't
* need to draw anything to exist. */
if (![self attachToWindow: _gl->surface])
return nil;
/* Listen application state change because we cannot use OpenGL in the
* background. This should probably move to the vout_window reports in
* the future, which could even signal that we need to disable the whole
* display and potentially adapt playback for that. */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationStateChanged:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationStateChanged:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationStateChanged:)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationStateChanged:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
/* Setup the usual vlc_gl_t callbacks before loading the API since we need
* the get_proc_address symbol and a current context. */
static const struct vlc_gl_operations gl_ops =
{
.make_current = MakeCurrent,
.release_current = ReleaseCurrent,
.resize = Resize,
.swap = Swap,
.get_proc_address = GetSymbol,
.close = Close,
};
gl->ops = &gl_ops;
return self;
}
- (BOOL)attachToWindow:(vlc_window_t*)wnd
{
@try {
UIView *viewContainer = (__bridge UIView*)wnd->handle.nsobject;
/* get the object we will draw into */
if (unlikely(viewContainer == nil)) {
msg_Err(_gl, "provided view container is nil");
return NO;
}
if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
msg_Err(_gl, "void pointer not an ObjC object");
return NO;
}
if (unlikely(![viewContainer isKindOfClass:[UIView class]])) {
msg_Err(_gl, "passed ObjC object not of class UIView");
return NO;
}
/* Initial size setup */
self.frame = viewContainer.bounds;
[viewContainer addSubview:self];
return YES;
} @catch (NSException *exception) {
msg_Err(_gl, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
return NO;
}
}
- (void)detachFromWindow
{
/* Flush the OpenGL pipeline before leaving. */
vlc_mutex_lock(&_mutex);
if (_eaglEnabled)
[self flushEAGLLocked];
_eaglEnabled = NO;
vlc_mutex_unlock(&_mutex);
/* We can drop the context as fast as possible since we don't use it
* anymore. It avoids crashes with the context modifying destroyed
* resources when it is being deallocated in the main thread. */
_eaglContext = nil;
/* This cannot be a synchronous dispatch because player is usually running
* in the main thread and block the main thread unless we accept our fate
* and exit here. */
dispatch_async(dispatch_get_main_queue(), ^{
/* Remove the external references to the view so that
* dealloc can be called by ARC. */
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
assert(!_gl_attached);
[self removeFromSuperview];
});
}
- (void)didMoveToWindow
{
self.contentScaleFactor = self.window.screen.scale;
vlc_mutex_lock(&_mutex);
_bufferNeedReset = YES;
vlc_mutex_unlock(&_mutex);
}
- (BOOL)doResetBuffers:(vlc_gl_t *)gl
{
if (_frameBuffer != 0)
{
/* clear frame buffer */
glDeleteFramebuffers(1, &_frameBuffer);
_frameBuffer = 0;
}
if (_renderBuffer != 0)
{
/* clear render buffer */
glDeleteRenderbuffers(1, &_renderBuffer);
_renderBuffer = 0;
}
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
[_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_layer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
msg_Err(_gl, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
return NO;
}
return YES;
}
- (BOOL)makeCurrent
{
vlc_mutex_lock(&_mutex);
assert(!_gl_attached);
if (unlikely(!_appActive))
{
vlc_mutex_unlock(&_mutex);
return NO;
}
assert(_eaglEnabled);
_previousEaglContext = [EAGLContext currentContext];
assert(_eaglContext);
if (![EAGLContext setCurrentContext:_eaglContext])
{
vlc_mutex_unlock(&_mutex);
return NO;
}
BOOL resetBuffers = NO;
if (unlikely(_bufferNeedReset))
{
_bufferNeedReset = NO;
resetBuffers = YES;
}
_gl_attached = YES;
vlc_mutex_unlock(&_mutex);
if (resetBuffers && ![self doResetBuffers:_gl])
{
[self releaseCurrent];
return NO;
}
return YES;
}
- (void)releaseCurrent
{
vlc_mutex_lock(&_mutex);
assert(_gl_attached);
_gl_attached = NO;
[EAGLContext setCurrentContext:_previousEaglContext];
_previousEaglContext = nil;
vlc_cond_signal(&_gl_attached_wait);
vlc_mutex_unlock(&_mutex);
}
- (void)presentRenderbuffer
{
[_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
}
- (void)layoutSubviews
{
vlc_mutex_lock(&_mutex);
_bufferNeedReset = YES;
vlc_mutex_unlock(&_mutex);
}
- (void)flushEAGLLocked
{
assert(_eaglEnabled);
/* Ensure that all previously submitted commands are drained from the
* command buffer and are executed by OpenGL ES before moving to the
* background.*/
EAGLContext *previousEaglContext = [EAGLContext currentContext];
if ([EAGLContext setCurrentContext:_eaglContext])
glFinish();
[EAGLContext setCurrentContext:previousEaglContext];
}
- (void)applicationStateChanged:(NSNotification *)notification
{
vlc_mutex_lock(&_mutex);
if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification])
_appActive = NO;
else if ([[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification])
{
_appActive = NO;
/* Wait for the vout to unlock the eagl context before releasing
* it. */
while (_gl_attached && _eaglEnabled)
vlc_cond_wait(&_gl_attached_wait, &_mutex);
/* _eaglEnabled can change during the vlc_cond_wait
* as the mutex is unlocked during that, so this check
* has to be done after the vlc_cond_wait! */
if (_eaglEnabled) {
[self flushEAGLLocked];
_eaglEnabled = NO;
}
}
else if ([[notification name] isEqualToString:UIApplicationWillEnterForegroundNotification])
_eaglEnabled = YES;
else
{
assert([[notification name] isEqualToString:UIApplicationDidBecomeActiveNotification]);
_appActive = YES;
}
vlc_mutex_unlock(&_mutex);
}
- (void)updateConstraints
{
[super updateConstraints];
}
- (BOOL)isOpaque
{
return YES;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
/* Disable events for this view, as the vout_window view will be the one
* handling them. */
return nil;
}
@end
static int Open(vlc_gl_t *gl, unsigned width, unsigned height,
const struct vlc_gl_cfg *gl_cfg)
{
vlc_window_t *wnd = gl->surface;
if (gl_cfg->need_alpha)
{
msg_Err(gl, "Cannot support alpha yet");
return VLC_ENOTSUP;
}
/* We only support UIView container window. */
if (wnd->type != VLC_WINDOW_TYPE_NSOBJECT)
return VLC_EGENERIC;
@autoreleasepool {
/* NOTE: we're using CFRunLoopPerformBlock with the "vlc_runloop" tag
* to avoid deadlocks between the window module (main thread) and the
* display module, which would happen when using dispatch_sycn here. */
vlc_dispatch_sync(^{
gl->sys = (__bridge_retained void*)[[VLCOpenGLES2VideoView alloc]
initWithFrame:CGRectMake(0.,0.,width,height) gl:gl];
});
if (gl->sys == NULL)
{
msg_Err(gl, "Creating OpenGL ES 2 view failed");
return VLC_EGENERIC;
}
}
return VLC_SUCCESS;
}
vlc_module_begin ()
set_shortname (N_("CAEAGL"))
set_description (N_("CAEAGL provider for OpenGL"))
set_subcategory (SUBCAT_VIDEO_VOUT)
set_callback_opengl_es2 (Open, 50)
add_shortcut ("caeagl")
vlc_module_end ()