1 /*****************************************************************************
2 * ios.m: iOS OpenGL ES provider
3 *****************************************************************************
4 * Copyright (C) 2001-2017 VLC authors and VideoLAN
6 * Authors: Pierre d'Herbemont <pdherbemont at videolan dot org>
7 * Felix Paul Kühne <fkuehne at videolan dot org>
8 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
10 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
11 * Eric Petit <titer@m0k.org>
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation; either version 2.1 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with this program; if not, write to the Free Software Foundation,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
28 /*****************************************************************************
30 *****************************************************************************/
32 #import <UIKit/UIKit.h>
33 #import <OpenGLES/EAGL.h>
34 #import <OpenGLES/ES2/gl.h>
35 #import <OpenGLES/ES2/glext.h>
36 #import <QuartzCore/QuartzCore.h>
43 #import <vlc_common.h>
44 #import <vlc_plugin.h>
45 #import <vlc_vout_display.h>
46 #import <vlc_opengl.h>
47 #import <vlc_dialog.h>
48 #import "opengl/vout_helper.h"
51 * Forward declarations
53 static int Open(vout_display_t *vd, const vout_display_cfg_t *cfg,
54 video_format_t *fmt, vlc_video_context *context);
55 static void Close(vout_display_t *vd);
57 static void PictureRender(vout_display_t *, picture_t *, subpicture_t *, vlc_tick_t);
58 static void PictureDisplay(vout_display_t *, picture_t *);
59 static int Control(vout_display_t*, int, va_list);
61 static void *OurGetProcAddress(vlc_gl_t *, const char *);
63 static int GLESMakeCurrent(vlc_gl_t *);
64 static void GLESSwap(vlc_gl_t *);
65 static void GLESReleaseCurrent(vlc_gl_t *);
71 set_shortname("iOS vout")
72 set_description("iOS OpenGL video output")
73 set_category(CAT_VIDEO)
74 set_subcategory(SUBCAT_VIDEO_VOUT)
75 set_callback_display(Open, 300)
77 add_shortcut("vout_ios2", "vout_ios")
81 @interface VLCOpenGLES2VideoView : UIView {
82 vout_display_t *_voutDisplay;
83 EAGLContext *_eaglContext;
87 vlc_cond_t _gl_attached_wait;
90 BOOL _bufferNeedReset;
93 BOOL _placeInvalidated;
95 UIView *_viewContainer;
96 UITapGestureRecognizer *_tapRecognizer;
98 /* Written from MT, read locked from vout */
99 vout_display_place_t _place;
101 CGFloat _scaleFactor;
103 /* Written from vout, read locked from MT */
104 vout_display_cfg_t _cfg;
107 - (id)initWithFrame:(CGRect)frame andVD:(vout_display_t*)vd;
108 - (void)cleanAndRelease:(BOOL)flushed;
109 - (BOOL)makeCurrent:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl;
110 - (void)releaseCurrent:(EAGLContext *)previousEaglContext;
111 - (void)presentRenderbuffer;
113 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl;
114 - (void)getPlaceLocked:(vout_display_place_t *)place;
117 struct vout_display_sys_t
119 VLCOpenGLES2VideoView *glESView;
123 vout_window_t *embed;
128 VLCOpenGLES2VideoView *glESView;
129 vout_display_opengl_t *vgl;
132 EAGLContext *previousEaglContext;
135 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
139 return dlsym(RTLD_DEFAULT, name);
142 static int Open(vout_display_t *vd, const vout_display_cfg_t *cfg,
143 video_format_t *fmt, vlc_video_context *context)
145 if (vout_display_cfg_IsWindowed(cfg))
148 vout_display_sys_t *sys = vlc_obj_calloc(VLC_OBJECT(vd), 1, sizeof(*sys));
156 var_Create(vlc_object_parent(vd), "ios-eaglcontext", VLC_VAR_ADDRESS);
159 /* setup the actual OpenGL ES view */
161 [VLCOpenGLES2VideoView performSelectorOnMainThread:@selector(getNewView:)
162 withObject:[NSArray arrayWithObjects:
163 [NSValue valueWithPointer:&sys->glESView],
164 [NSValue valueWithPointer:vd], nil]
166 if (!sys->glESView) {
167 msg_Err(vd, "Creating OpenGL ES 2 view failed");
168 var_Destroy(vlc_object_parent(vd), "ios-eaglcontext");
172 const vlc_fourcc_t *subpicture_chromas;
174 sys->embed = cfg->window;
175 sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
179 struct gl_sys *glsys = sys->gl->sys =
180 vlc_obj_malloc(VLC_OBJECT(vd), sizeof(struct gl_sys));
181 if (unlikely(!sys->gl->sys))
183 glsys->glESView = sys->glESView;
185 glsys->renderBuffer = glsys->frameBuffer = 0;
187 /* Initialize common OpenGL video display */
188 sys->gl->makeCurrent = GLESMakeCurrent;
189 sys->gl->releaseCurrent = GLESReleaseCurrent;
190 sys->gl->swap = GLESSwap;
191 sys->gl->getProcAddress = OurGetProcAddress;
193 if (vlc_gl_MakeCurrent(sys->gl) != VLC_SUCCESS)
196 vout_display_opengl_t *vgl = vout_display_opengl_New(fmt, &subpicture_chromas,
197 sys->gl, &cfg->viewpoint,
199 vlc_gl_ReleaseCurrent(sys->gl);
204 /* Setup vout_display_t once everything is fine */
205 vd->info.subpicture_chromas = subpicture_chromas;
207 vd->prepare = PictureRender;
208 vd->display = PictureDisplay;
209 vd->control = Control;
220 static void Close(vout_display_t *vd)
222 vout_display_sys_t *sys = vd->sys;
226 if (sys->gl != NULL) {
227 struct gl_sys *glsys = sys->gl->sys;
228 msg_Dbg(vd, "deleting display");
230 if (likely(glsys->vgl))
232 int ret = vlc_gl_MakeCurrent(sys->gl);
233 vout_display_opengl_Delete(glsys->vgl);
234 if (ret == VLC_SUCCESS)
236 vlc_gl_ReleaseCurrent(sys->gl);
240 vlc_object_delete(sys->gl);
243 [sys->glESView cleanAndRelease:flushed];
245 var_Destroy(vlc_object_parent(vd), "ios-eaglcontext");
248 /*****************************************************************************
249 * vout display callbacks
250 *****************************************************************************/
252 static int Control(vout_display_t *vd, int query, va_list ap)
254 vout_display_sys_t *sys = vd->sys;
255 struct gl_sys *glsys = sys->gl->sys;
258 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
259 case VOUT_DISPLAY_CHANGE_ZOOM:
260 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
261 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
262 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
264 const vout_display_cfg_t *cfg =
265 va_arg(ap, const vout_display_cfg_t *);
269 [sys->glESView updateVoutCfg:cfg withVGL:glsys->vgl];
274 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
275 return vout_display_opengl_SetViewpoint(glsys->vgl,
276 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
278 case VOUT_DISPLAY_RESET_PICTURES:
279 vlc_assert_unreachable ();
281 msg_Err(vd, "Unknown request %d", query);
286 static void PictureDisplay(vout_display_t *vd, picture_t *pic)
288 vout_display_sys_t *sys = vd->sys;
289 struct gl_sys *glsys = sys->gl->sys;
292 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
294 vout_display_opengl_Display(glsys->vgl, &vd->source);
295 vlc_gl_ReleaseCurrent(sys->gl);
299 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture,
303 vout_display_sys_t *sys = vd->sys;
304 struct gl_sys *glsys = sys->gl->sys;
306 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
308 vout_display_opengl_Prepare(glsys->vgl, pic, subpicture);
309 vlc_gl_ReleaseCurrent(sys->gl);
313 /*****************************************************************************
314 * vout opengl callbacks
315 *****************************************************************************/
316 static int GLESMakeCurrent(vlc_gl_t *gl)
318 struct gl_sys *sys = gl->sys;
320 if (![sys->glESView makeCurrent:&sys->previousEaglContext withGL:gl])
325 static void GLESReleaseCurrent(vlc_gl_t *gl)
327 struct gl_sys *sys = gl->sys;
329 [sys->glESView releaseCurrent:sys->previousEaglContext];
332 static void GLESSwap(vlc_gl_t *gl)
334 struct gl_sys *sys = gl->sys;
336 [sys->glESView presentRenderbuffer];
340 /*****************************************************************************
342 *****************************************************************************/
343 @implementation VLCOpenGLES2VideoView
347 return [CAEAGLLayer class];
350 + (void)getNewView:(NSArray *)value
352 id *ret = [[value objectAtIndex:0] pointerValue];
353 vout_display_t *vd = [[value objectAtIndex:1] pointerValue];
354 *ret = [[self alloc] initWithFrame:CGRectMake(0.,0.,320.,240.) andVD:vd];
357 - (id)initWithFrame:(CGRect)frame andVD:(vout_display_t*)vd
359 _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
360 if (unlikely(!_appActive))
363 self = [super initWithFrame:frame];
368 _bufferNeedReset = YES;
370 _cfg = *_voutDisplay->cfg;
372 vlc_mutex_init(&_mutex);
373 vlc_cond_init(&_gl_attached_wait);
376 /* the following creates a new OpenGL ES context with the API version we
377 * need if there is already an active context created by another OpenGL
378 * provider we cache it and restore analog to the
379 * makeCurrent/releaseCurrent pattern used through-out the class */
380 EAGLContext *previousEaglContext = [EAGLContext currentContext];
382 _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
384 if (unlikely(!_eaglContext)
385 || unlikely(![EAGLContext setCurrentContext:_eaglContext]))
387 [_eaglContext release];
391 [self releaseCurrent:previousEaglContext];
393 /* Set "ios-eaglcontext" to be used by cvpx fitlers/glconv */
394 var_SetAddress(vlc_object_parent(_voutDisplay), "ios-eaglcontext", _eaglContext);
396 _layer = (CAEAGLLayer *)self.layer;
397 _layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
400 self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
402 if (![self fetchViewContainer])
404 [_eaglContext release];
410 [[NSNotificationCenter defaultCenter] addObserver:self
411 selector:@selector(applicationStateChanged:)
412 name:UIApplicationWillResignActiveNotification
414 [[NSNotificationCenter defaultCenter] addObserver:self
415 selector:@selector(applicationStateChanged:)
416 name:UIApplicationDidBecomeActiveNotification
418 [[NSNotificationCenter defaultCenter] addObserver:self
419 selector:@selector(applicationStateChanged:)
420 name:UIApplicationDidEnterBackgroundNotification
422 [[NSNotificationCenter defaultCenter] addObserver:self
423 selector:@selector(applicationStateChanged:)
424 name:UIApplicationWillEnterForegroundNotification
430 - (BOOL)fetchViewContainer
433 /* get the object we will draw into */
434 UIView *viewContainer = var_InheritAddress (_voutDisplay, "drawable-nsobject");
435 if (unlikely(viewContainer == nil)) {
436 msg_Err(_voutDisplay, "provided view container is nil");
440 if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
441 msg_Err(_voutDisplay, "void pointer not an ObjC object");
445 [viewContainer retain];
447 if (![viewContainer isKindOfClass:[UIView class]]) {
448 msg_Err(_voutDisplay, "passed ObjC object not of class UIView");
452 /* This will be released in Close(), on
453 * main thread, after we are done using it. */
454 _viewContainer = viewContainer;
456 self.frame = viewContainer.bounds;
459 [_viewContainer addSubview:self];
461 /* add tap gesture recognizer for DVD menus and stuff */
462 _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
463 action:@selector(tapRecognized:)];
464 if (_viewContainer.window
465 && _viewContainer.window.rootViewController
466 && _viewContainer.window.rootViewController.view)
467 [_viewContainer.superview addGestureRecognizer:_tapRecognizer];
468 _tapRecognizer.cancelsTouchesInView = NO;
470 } @catch (NSException *exception) {
471 msg_Err(_voutDisplay, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
472 vout_display_sys_t *sys = _voutDisplay->sys;
474 [_tapRecognizer release];
479 - (void)cleanAndReleaseFromMainThread
481 [[NSNotificationCenter defaultCenter] removeObserver:self];
483 [_tapRecognizer.view removeGestureRecognizer:_tapRecognizer];
484 [_tapRecognizer release];
486 [self removeFromSuperview];
487 [_viewContainer release];
489 assert(!_gl_attached);
490 [_eaglContext release];
494 - (void)cleanAndRelease:(BOOL)flushed
496 vlc_mutex_lock(&_mutex);
497 if (_eaglEnabled && !flushed)
498 [self flushEAGLLocked];
501 vlc_mutex_unlock(&_mutex);
503 [self performSelectorOnMainThread:@selector(cleanAndReleaseFromMainThread)
510 vlc_mutex_destroy(&_mutex);
511 vlc_cond_destroy(&_gl_attached_wait);
515 - (void)didMoveToWindow
517 self.contentScaleFactor = self.window.screen.scale;
519 vlc_mutex_lock(&_mutex);
520 _bufferNeedReset = YES;
521 vlc_mutex_unlock(&_mutex);
524 - (BOOL)doResetBuffers:(vlc_gl_t *)gl
526 struct gl_sys *glsys = gl->sys;
528 if (glsys->frameBuffer != 0)
530 /* clear frame buffer */
531 glDeleteFramebuffers(1, &glsys->frameBuffer);
532 glsys->frameBuffer = 0;
535 if (glsys->renderBuffer != 0)
537 /* clear render buffer */
538 glDeleteRenderbuffers(1, &glsys->renderBuffer);
539 glsys->renderBuffer = 0;
542 glDisable(GL_DEPTH_TEST);
544 glGenFramebuffers(1, &glsys->frameBuffer);
545 glBindFramebuffer(GL_FRAMEBUFFER, glsys->frameBuffer);
547 glGenRenderbuffers(1, &glsys->renderBuffer);
548 glBindRenderbuffer(GL_RENDERBUFFER, glsys->renderBuffer);
550 [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_layer];
552 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, glsys->renderBuffer);
553 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
555 msg_Err(_voutDisplay, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
561 - (BOOL)makeCurrent:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl
563 vlc_mutex_lock(&_mutex);
564 assert(!_gl_attached);
566 if (unlikely(!_appActive))
568 vlc_mutex_unlock(&_mutex);
572 assert(_eaglEnabled);
573 *previousEaglContext = [EAGLContext currentContext];
575 if (![EAGLContext setCurrentContext:_eaglContext])
577 vlc_mutex_unlock(&_mutex);
581 BOOL resetBuffers = NO;
585 struct gl_sys *glsys = gl->sys;
587 if (unlikely(_bufferNeedReset))
589 _bufferNeedReset = NO;
592 if (unlikely(_placeInvalidated && glsys->vgl))
594 _placeInvalidated = NO;
596 vout_display_place_t place;
597 [self getPlaceLocked: &place];
598 vout_display_opengl_SetWindowAspectRatio(glsys->vgl, (float)place.width / place.height);
600 // x / y are top left corner, but we need the lower left one
601 vout_display_opengl_Viewport(glsys->vgl, _place.x, _place.y, _place.width, _place.height);
607 vlc_mutex_unlock(&_mutex);
609 if (resetBuffers && ![self doResetBuffers:gl])
611 [self releaseCurrent:*previousEaglContext];
617 - (void)releaseCurrent:(EAGLContext *)previousEaglContext
619 [EAGLContext setCurrentContext:previousEaglContext];
621 vlc_mutex_lock(&_mutex);
622 assert(_gl_attached);
624 vlc_cond_signal(&_gl_attached_wait);
625 vlc_mutex_unlock(&_mutex);
628 - (void)presentRenderbuffer
630 [_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
633 - (void)layoutSubviews
637 vlc_mutex_lock(&_mutex);
638 _bufferNeedReset = YES;
639 vlc_mutex_unlock(&_mutex);
642 - (void)getPlaceLocked:(vout_display_place_t *)place
644 assert(_voutDisplay);
645 vout_display_cfg_t cfg = _cfg;
647 cfg.display.width = _viewSize.width * _scaleFactor;
648 cfg.display.height = _viewSize.height * _scaleFactor;
650 vout_display_PlacePicture(place, &_voutDisplay->source, &cfg);
655 assert([NSThread isMainThread]);
657 vlc_mutex_lock(&_mutex);
660 vlc_mutex_unlock(&_mutex);
663 _viewSize = [self bounds].size;
664 _scaleFactor = self.contentScaleFactor;
666 vout_display_place_t place;
667 [self getPlaceLocked: &place];
669 if (memcmp(&place, &_place, sizeof(vout_display_place_t)) != 0)
671 _placeInvalidated = YES;
675 vlc_mutex_unlock(&_mutex);
678 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
680 vlc_mutex_lock(&_mutex);
683 vlc_mutex_unlock(&_mutex);
687 UIGestureRecognizerState state = [tapRecognizer state];
688 CGPoint touchPoint = [tapRecognizer locationInView:self];
689 CGFloat scaleFactor = self.contentScaleFactor;
690 vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay,
691 (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor);
693 vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
694 vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
696 vlc_mutex_unlock(&_mutex);
699 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl
701 if (memcmp(&_cfg, cfg, sizeof(vout_display_cfg_t)) == 0)
704 vlc_mutex_lock(&_mutex);
707 vout_display_place_t place;
708 [self getPlaceLocked: &place];
709 vout_display_opengl_SetWindowAspectRatio(vgl, (float)place.width / place.height);
711 vlc_mutex_unlock(&_mutex);
713 [self performSelectorOnMainThread:@selector(setNeedsUpdateConstraints)
718 - (void)flushEAGLLocked
720 assert(_eaglEnabled);
722 /* Ensure that all previously submitted commands are drained from the
723 * command buffer and are executed by OpenGL ES before moving to the
725 EAGLContext *previousEaglContext = [EAGLContext currentContext];
726 if ([EAGLContext setCurrentContext:_eaglContext])
728 [EAGLContext setCurrentContext:previousEaglContext];
731 - (void)applicationStateChanged:(NSNotification *)notification
733 vlc_mutex_lock(&_mutex);
735 if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification])
737 else if ([[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification])
741 /* Wait for the vout to unlock the eagl context before releasing
743 while (_gl_attached && _eaglEnabled)
744 vlc_cond_wait(&_gl_attached_wait, &_mutex);
746 /* _eaglEnabled can change during the vlc_cond_wait
747 * as the mutex is unlocked during that, so this check
748 * has to be done after the vlc_cond_wait! */
750 [self flushEAGLLocked];
754 else if ([[notification name] isEqualToString:UIApplicationWillEnterForegroundNotification])
758 assert([[notification name] isEqualToString:UIApplicationDidBecomeActiveNotification]);
762 vlc_mutex_unlock(&_mutex);
765 - (void)updateConstraints
767 [super updateConstraints];
776 - (BOOL)acceptsFirstResponder