1 /*****************************************************************************
2 * ios.m: iOS OpenGL ES provider
3 *****************************************************************************
4 * Copyright (C) 2001-2017 VLC authors and VideoLAN
7 * Authors: Pierre d'Herbemont <pdherbemont at videolan dot org>
8 * Felix Paul Kühne <fkuehne at videolan dot org>
9 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
11 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
12 * Eric Petit <titer@m0k.org>
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU Lesser General Public License as published by
16 * the Free Software Foundation; either version 2.1 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Lesser General Public License for more details.
24 * You should have received a copy of the GNU Lesser General Public License
25 * along with this program; if not, write to the Free Software Foundation,
26 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27 *****************************************************************************/
29 /*****************************************************************************
31 *****************************************************************************/
33 #import <UIKit/UIKit.h>
34 #import <OpenGLES/EAGL.h>
35 #import <OpenGLES/ES2/gl.h>
36 #import <OpenGLES/ES2/glext.h>
37 #import <QuartzCore/QuartzCore.h>
44 #import <vlc_common.h>
45 #import <vlc_plugin.h>
46 #import <vlc_vout_display.h>
47 #import <vlc_opengl.h>
48 #import <vlc_dialog.h>
49 #import "opengl/vout_helper.h"
52 * Forward declarations
54 static int Open(vlc_object_t *);
55 static void Close(vlc_object_t *);
57 static picture_pool_t* PicturePool(vout_display_t *, unsigned);
58 static void PictureRender(vout_display_t *, picture_t *, subpicture_t *);
59 static void PictureDisplay(vout_display_t *, picture_t *, subpicture_t *);
60 static int Control(vout_display_t*, int, va_list);
62 static void *OurGetProcAddress(vlc_gl_t *, const char *);
64 static int GLESMakeCurrent(vlc_gl_t *);
65 static void GLESSwap(vlc_gl_t *);
66 static void GLESReleaseCurrent(vlc_gl_t *);
72 set_shortname("iOS vout")
73 set_description("iOS OpenGL video output")
74 set_category(CAT_VIDEO)
75 set_subcategory(SUBCAT_VIDEO_VOUT)
76 set_capability("vout display", 300)
77 set_callbacks(Open, Close)
79 add_shortcut("vout_ios2", "vout_ios")
83 @interface VLCOpenGLES2VideoView : UIView {
84 vout_display_t *_voutDisplay;
85 EAGLContext *_eaglContext;
89 vlc_cond_t _gl_attached_wait;
92 BOOL _bufferNeedReset;
95 BOOL _placeInvalidated;
97 UIView *_viewContainer;
98 UITapGestureRecognizer *_tapRecognizer;
100 /* Written from MT, read locked from vout */
101 vout_display_place_t _place;
103 CGFloat _scaleFactor;
105 /* Written from vout, read locked from MT */
106 vout_display_cfg_t _cfg;
109 - (id)initWithFrameAndVd:(CGRect)frame withVd:(vout_display_t*)vd;
110 - (void)cleanAndRelease:(BOOL)flushed;
111 - (BOOL)makeCurrentWithGL:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl;
112 - (void)releaseCurrent:(EAGLContext *)previousEaglContext;
113 - (void)presentRenderbuffer;
115 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl;
116 - (void)getPlaceLocked:(vout_display_place_t *)place;
119 struct vout_display_sys_t
121 VLCOpenGLES2VideoView *glESView;
125 picture_pool_t *picturePool;
130 VLCOpenGLES2VideoView *glESView;
131 vout_display_opengl_t *vgl;
134 EAGLContext *previousEaglContext;
137 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
141 return dlsym(RTLD_DEFAULT, name);
144 static int Open(vlc_object_t *this)
146 vout_display_t *vd = (vout_display_t *)this;
148 if (vout_display_IsWindowed(vd))
151 vout_display_sys_t *sys = vlc_obj_calloc (this, 1, sizeof(*sys));
157 sys->picturePool = NULL;
160 var_Create(vd->obj.parent, "ios-eaglcontext", VLC_VAR_ADDRESS);
163 /* setup the actual OpenGL ES view */
165 [VLCOpenGLES2VideoView performSelectorOnMainThread:@selector(getNewView:)
166 withObject:[NSArray arrayWithObjects:
167 [NSValue valueWithPointer:&sys->glESView],
168 [NSValue valueWithPointer:vd], nil]
170 if (!sys->glESView) {
171 msg_Err(vd, "Creating OpenGL ES 2 view failed");
172 var_Destroy(vd->obj.parent, "ios-eaglcontext");
176 const vlc_fourcc_t *subpicture_chromas;
177 video_format_t fmt = vd->fmt;
179 sys->gl = vlc_object_create(this, sizeof(*sys->gl));
183 struct gl_sys *glsys = sys->gl->sys =
184 vlc_obj_malloc(this, sizeof(struct gl_sys));
185 if (unlikely(!sys->gl->sys))
187 glsys->glESView = sys->glESView;
189 glsys->renderBuffer = glsys->frameBuffer = 0;
191 /* Initialize common OpenGL video display */
192 sys->gl->makeCurrent = GLESMakeCurrent;
193 sys->gl->releaseCurrent = GLESReleaseCurrent;
194 sys->gl->swap = GLESSwap;
195 sys->gl->getProcAddress = OurGetProcAddress;
197 if (vlc_gl_MakeCurrent(sys->gl) != VLC_SUCCESS)
200 vout_display_opengl_t *vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
201 sys->gl, &vd->cfg->viewpoint);
202 vlc_gl_ReleaseCurrent(sys->gl);
208 vout_display_info_t info = vd->info;
209 info.has_pictures_invalid = false;
210 info.subpicture_chromas = subpicture_chromas;
212 /* Setup vout_display_t once everything is fine */
215 vd->pool = PicturePool;
216 vd->prepare = PictureRender;
217 vd->display = PictureDisplay;
218 vd->control = Control;
228 static void Close (vlc_object_t *this)
230 vout_display_t *vd = (vout_display_t *)this;
231 vout_display_sys_t *sys = vd->sys;
235 if (sys->gl != NULL) {
236 struct gl_sys *glsys = sys->gl->sys;
237 msg_Dbg(this, "deleting display");
239 if (likely(glsys->vgl))
241 int ret = vlc_gl_MakeCurrent(sys->gl);
242 vout_display_opengl_Delete(glsys->vgl);
243 if (ret == VLC_SUCCESS)
245 vlc_gl_ReleaseCurrent(sys->gl);
249 vlc_object_release(sys->gl);
252 [sys->glESView cleanAndRelease:flushed];
254 var_Destroy(vd->obj.parent, "ios-eaglcontext");
257 /*****************************************************************************
258 * vout display callbacks
259 *****************************************************************************/
261 static int Control(vout_display_t *vd, int query, va_list ap)
263 vout_display_sys_t *sys = vd->sys;
264 struct gl_sys *glsys = sys->gl->sys;
267 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
268 case VOUT_DISPLAY_CHANGE_ZOOM:
269 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
270 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
271 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
273 const vout_display_cfg_t *cfg;
275 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT ||
276 query == VOUT_DISPLAY_CHANGE_SOURCE_CROP)
279 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
283 [sys->glESView updateVoutCfg:cfg withVGL:glsys->vgl];
288 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
289 return vout_display_opengl_SetViewpoint(glsys->vgl,
290 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
292 case VOUT_DISPLAY_RESET_PICTURES:
293 vlc_assert_unreachable ();
295 msg_Err(vd, "Unknown request %d", query);
300 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
302 vout_display_sys_t *sys = vd->sys;
303 struct gl_sys *glsys = sys->gl->sys;
305 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
307 vout_display_opengl_Display(glsys->vgl, &vd->source);
308 vlc_gl_ReleaseCurrent(sys->gl);
311 picture_Release(pic);
314 subpicture_Delete(subpicture);
317 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
319 vout_display_sys_t *sys = vd->sys;
320 struct gl_sys *glsys = sys->gl->sys;
322 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
324 vout_display_opengl_Prepare(glsys->vgl, pic, subpicture);
325 vlc_gl_ReleaseCurrent(sys->gl);
329 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
331 vout_display_sys_t *sys = vd->sys;
332 struct gl_sys *glsys = sys->gl->sys;
334 if (!sys->picturePool && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
336 sys->picturePool = vout_display_opengl_GetPool(glsys->vgl, requested_count);
337 vlc_gl_ReleaseCurrent(sys->gl);
339 return sys->picturePool;
342 /*****************************************************************************
343 * vout opengl callbacks
344 *****************************************************************************/
345 static int GLESMakeCurrent(vlc_gl_t *gl)
347 struct gl_sys *sys = gl->sys;
349 if (![sys->glESView makeCurrentWithGL:&sys->previousEaglContext withGL:gl])
354 static void GLESReleaseCurrent(vlc_gl_t *gl)
356 struct gl_sys *sys = gl->sys;
358 [sys->glESView releaseCurrent:sys->previousEaglContext];
361 static void GLESSwap(vlc_gl_t *gl)
363 struct gl_sys *sys = gl->sys;
365 [sys->glESView presentRenderbuffer];
369 /*****************************************************************************
371 *****************************************************************************/
372 @implementation VLCOpenGLES2VideoView
376 return [CAEAGLLayer class];
379 + (void)getNewView:(NSArray *)value
381 id *ret = [[value objectAtIndex:0] pointerValue];
382 vout_display_t *vd = [[value objectAtIndex:1] pointerValue];
383 *ret = [[self alloc] initWithFrameAndVd:CGRectMake(0.,0.,320.,240.) withVd:vd];
386 - (id)initWithFrameAndVd:(CGRect)frame withVd:(vout_display_t*)vd
388 _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
389 if (unlikely(!_appActive))
392 self = [super initWithFrame:frame];
397 _bufferNeedReset = YES;
399 _cfg = *_voutDisplay->cfg;
401 vlc_mutex_init(&_mutex);
402 vlc_cond_init(&_gl_attached_wait);
405 /* the following creates a new OpenGL ES context with the API version we
406 * need if there is already an active context created by another OpenGL
407 * provider we cache it and restore analog to the
408 * makeCurrent/releaseCurrent pattern used through-out the class */
409 EAGLContext *previousEaglContext = [EAGLContext currentContext];
411 _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
413 if (unlikely(!_eaglContext)
414 || unlikely(![EAGLContext setCurrentContext:_eaglContext]))
417 [_eaglContext release];
418 vlc_mutex_destroy(&_mutex);
419 vlc_cond_destroy(&_gl_attached_wait);
423 [self releaseCurrent:previousEaglContext];
425 /* Set "ios-eaglcontext" to be used by cvpx fitlers/glconv */
426 var_SetAddress(_voutDisplay->obj.parent, "ios-eaglcontext", _eaglContext);
428 _layer = (CAEAGLLayer *)self.layer;
429 _layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
432 self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
434 if (![self fetchViewContainer])
436 vlc_mutex_destroy(&_mutex);
437 vlc_cond_destroy(&_gl_attached_wait);
438 [_eaglContext release];
444 [[NSNotificationCenter defaultCenter] addObserver:self
445 selector:@selector(applicationStateChanged:)
446 name:UIApplicationWillResignActiveNotification
448 [[NSNotificationCenter defaultCenter] addObserver:self
449 selector:@selector(applicationStateChanged:)
450 name:UIApplicationDidBecomeActiveNotification
452 [[NSNotificationCenter defaultCenter] addObserver:self
453 selector:@selector(applicationStateChanged:)
454 name:UIApplicationDidEnterBackgroundNotification
456 [[NSNotificationCenter defaultCenter] addObserver:self
457 selector:@selector(applicationStateChanged:)
458 name:UIApplicationWillEnterForegroundNotification
464 - (BOOL)fetchViewContainer
467 /* get the object we will draw into */
468 UIView *viewContainer = var_InheritAddress (_voutDisplay, "drawable-nsobject");
469 if (unlikely(viewContainer == nil)) {
470 msg_Err(_voutDisplay, "provided view container is nil");
474 if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
475 msg_Err(_voutDisplay, "void pointer not an ObjC object");
479 [viewContainer retain];
481 if (![viewContainer isKindOfClass:[UIView class]]) {
482 msg_Err(_voutDisplay, "passed ObjC object not of class UIView");
486 /* This will be released in Close(), on
487 * main thread, after we are done using it. */
488 _viewContainer = viewContainer;
490 self.frame = viewContainer.bounds;
493 [_viewContainer addSubview:self];
495 /* add tap gesture recognizer for DVD menus and stuff */
496 _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
497 action:@selector(tapRecognized:)];
498 if (_viewContainer.window
499 && _viewContainer.window.rootViewController
500 && _viewContainer.window.rootViewController.view)
501 [_viewContainer.superview addGestureRecognizer:_tapRecognizer];
502 _tapRecognizer.cancelsTouchesInView = NO;
504 } @catch (NSException *exception) {
505 msg_Err(_voutDisplay, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
506 vout_display_sys_t *sys = _voutDisplay->sys;
508 [_tapRecognizer release];
513 - (void)cleanAndReleaseFromMainThread
515 [[NSNotificationCenter defaultCenter] removeObserver:self];
517 [_tapRecognizer.view removeGestureRecognizer:_tapRecognizer];
518 [_tapRecognizer release];
520 [self removeFromSuperview];
521 [_viewContainer release];
523 assert(!_gl_attached);
524 [_eaglContext release];
528 - (void)cleanAndRelease:(BOOL)flushed
530 vlc_mutex_lock(&_mutex);
531 if (_eaglEnabled && !flushed)
532 [self flushEAGLLocked];
535 vlc_mutex_unlock(&_mutex);
537 [self performSelectorOnMainThread:@selector(cleanAndReleaseFromMainThread)
544 vlc_mutex_destroy(&_mutex);
545 vlc_cond_destroy(&_gl_attached_wait);
549 - (void)didMoveToWindow
551 self.contentScaleFactor = self.window.screen.scale;
553 vlc_mutex_lock(&_mutex);
554 _bufferNeedReset = YES;
555 vlc_mutex_unlock(&_mutex);
558 - (BOOL)doResetBuffers:(vlc_gl_t *)gl
560 struct gl_sys *glsys = gl->sys;
562 if (glsys->frameBuffer != 0)
564 /* clear frame buffer */
565 glDeleteFramebuffers(1, &glsys->frameBuffer);
566 glsys->frameBuffer = 0;
569 if (glsys->renderBuffer != 0)
571 /* clear render buffer */
572 glDeleteRenderbuffers(1, &glsys->renderBuffer);
573 glsys->renderBuffer = 0;
576 glDisable(GL_DEPTH_TEST);
578 glGenFramebuffers(1, &glsys->frameBuffer);
579 glBindFramebuffer(GL_FRAMEBUFFER, glsys->frameBuffer);
581 glGenRenderbuffers(1, &glsys->renderBuffer);
582 glBindRenderbuffer(GL_RENDERBUFFER, glsys->renderBuffer);
584 [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_layer];
586 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, glsys->renderBuffer);
587 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
589 msg_Err(_voutDisplay, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
595 - (BOOL)makeCurrentWithGL:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl
597 vlc_mutex_lock(&_mutex);
598 assert(!_gl_attached);
600 if (unlikely(!_appActive))
602 vlc_mutex_unlock(&_mutex);
605 assert(_eaglEnabled);
607 *previousEaglContext = [EAGLContext currentContext];
609 BOOL success = [EAGLContext setCurrentContext:_eaglContext];
610 BOOL resetBuffers = NO;
612 if (success && gl != NULL)
614 struct gl_sys *glsys = gl->sys;
616 if (unlikely(_bufferNeedReset))
618 _bufferNeedReset = NO;
621 if (unlikely(_placeInvalidated && glsys->vgl))
623 _placeInvalidated = NO;
625 vout_display_place_t place;
626 [self getPlaceLocked: &place];
627 vout_display_opengl_SetWindowAspectRatio(glsys->vgl, (float)place.width / place.height);
629 // x / y are top left corner, but we need the lower left one
630 vout_display_opengl_Viewport(glsys->vgl, _place.x, _place.y, _place.width, _place.height);
637 vlc_mutex_unlock(&_mutex);
639 if (resetBuffers && ![self doResetBuffers:gl])
641 [self releaseCurrent:*previousEaglContext];
647 - (void)releaseCurrent:(EAGLContext *)previousEaglContext
649 [EAGLContext setCurrentContext:previousEaglContext];
651 vlc_mutex_lock(&_mutex);
652 assert(_gl_attached);
654 vlc_mutex_unlock(&_mutex);
655 vlc_cond_signal(&_gl_attached_wait);
658 - (void)presentRenderbuffer
660 [_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
663 - (void)layoutSubviews
667 vlc_mutex_lock(&_mutex);
668 _bufferNeedReset = YES;
669 vlc_mutex_unlock(&_mutex);
672 - (void)getPlaceLocked:(vout_display_place_t *)place
674 assert(_voutDisplay);
675 vout_display_cfg_t cfg = _cfg;
677 cfg.display.width = _viewSize.width * _scaleFactor;
678 cfg.display.height = _viewSize.height * _scaleFactor;
680 vout_display_PlacePicture(place, &_voutDisplay->source, &cfg, false);
685 assert([NSThread isMainThread]);
687 vlc_mutex_lock(&_mutex);
690 vlc_mutex_unlock(&_mutex);
693 _viewSize = [self bounds].size;
694 _scaleFactor = self.contentScaleFactor;
696 vout_display_place_t place;
697 [self getPlaceLocked: &place];
699 if (memcmp(&place, &_place, sizeof(vout_display_place_t)) != 0)
701 _placeInvalidated = YES;
705 vout_display_SendEventDisplaySize(_voutDisplay, _viewSize.width * _scaleFactor,
706 _viewSize.height * _scaleFactor);
708 vlc_mutex_unlock(&_mutex);
711 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
713 vlc_mutex_lock(&_mutex);
716 vlc_mutex_unlock(&_mutex);
720 UIGestureRecognizerState state = [tapRecognizer state];
721 CGPoint touchPoint = [tapRecognizer locationInView:self];
722 CGFloat scaleFactor = self.contentScaleFactor;
723 vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay, ORIENT_NORMAL,
724 (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor,
727 vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
728 vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
730 vlc_mutex_unlock(&_mutex);
733 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl
735 if (memcmp(&_cfg, cfg, sizeof(vout_display_cfg_t)) == 0)
738 vlc_mutex_lock(&_mutex);
741 vout_display_place_t place;
742 [self getPlaceLocked: &place];
743 vout_display_opengl_SetWindowAspectRatio(vgl, (float)place.width / place.height);
745 vlc_mutex_unlock(&_mutex);
747 [self performSelectorOnMainThread:@selector(setNeedsUpdateConstraints)
752 - (void)flushEAGLLocked
754 assert(_eaglEnabled);
756 /* Ensure that all previously submitted commands are drained from the
757 * command buffer and are executed by OpenGL ES before moving to the
759 EAGLContext *previousEaglContext = [EAGLContext currentContext];
760 if ([EAGLContext setCurrentContext:_eaglContext])
765 [EAGLContext setCurrentContext:previousEaglContext];
768 - (void)applicationStateChanged:(NSNotification *)notification
770 vlc_mutex_lock(&_mutex);
772 if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification])
774 else if ([[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification])
780 /* Wait for the vout to unlock the eagl context before releasing
783 vlc_cond_wait(&_gl_attached_wait, &_mutex);
785 [self flushEAGLLocked];
789 else if ([[notification name] isEqualToString:UIApplicationWillEnterForegroundNotification])
793 assert([[notification name] isEqualToString:UIApplicationDidBecomeActiveNotification]);
797 vlc_mutex_unlock(&_mutex);
800 - (void)updateConstraints
802 [super updateConstraints];
811 - (BOOL)acceptsFirstResponder