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
55 struct picture_sys_t {
56 CVPixelBufferRef pixelBuffer;
59 static int Open(vlc_object_t *);
60 static void Close(vlc_object_t *);
62 static picture_pool_t* PicturePool(vout_display_t *, unsigned);
63 static void PictureRender(vout_display_t *, picture_t *, subpicture_t *);
64 static void PictureDisplay(vout_display_t *, picture_t *, subpicture_t *);
65 static int Control(vout_display_t*, int, va_list);
67 static void *OurGetProcAddress(vlc_gl_t *, const char *);
69 static int GLESMakeCurrent(vlc_gl_t *);
70 static void GLESSwap(vlc_gl_t *);
71 static void GLESReleaseCurrent(vlc_gl_t *);
77 set_shortname("iOS vout")
78 set_description("iOS OpenGL video output")
79 set_category(CAT_VIDEO)
80 set_subcategory(SUBCAT_VIDEO_VOUT)
81 set_capability("vout display", 300)
82 set_callbacks(Open, Close)
84 add_shortcut("vout_ios2", "vout_ios")
88 @interface VLCOpenGLES2VideoView : UIView {
89 vout_display_t *_voutDisplay;
90 EAGLContext *_eaglContext;
94 vlc_cond_t _gl_attached_wait;
97 BOOL _bufferNeedReset;
100 BOOL _placeInvalidated;
102 UIView *_viewContainer;
103 UITapGestureRecognizer *_tapRecognizer;
105 /* Written from MT, read locked from vout */
106 vout_display_place_t _place;
108 CGFloat _scaleFactor;
110 /* Written from vout, read locked from MT */
111 vout_display_cfg_t _cfg;
114 - (id)initWithFrameAndVd:(CGRect)frame withVd:(vout_display_t*)vd;
115 - (void)cleanAndRelease:(BOOL)flushed;
116 - (BOOL)makeCurrentWithGL:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl;
117 - (void)releaseCurrent:(EAGLContext *)previousEaglContext;
118 - (void)presentRenderbuffer;
120 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl;
121 - (void)getPlaceLocked:(vout_display_place_t *)place;
124 struct vout_display_sys_t
126 VLCOpenGLES2VideoView *glESView;
130 picture_pool_t *picturePool;
135 VLCOpenGLES2VideoView *glESView;
136 vout_display_opengl_t *vgl;
139 EAGLContext *previousEaglContext;
142 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
146 return dlsym(RTLD_DEFAULT, name);
149 static int Open(vlc_object_t *this)
151 vout_display_t *vd = (vout_display_t *)this;
153 if (vout_display_IsWindowed(vd))
156 vout_display_sys_t *sys = vlc_obj_calloc (this, 1, sizeof(*sys));
162 sys->picturePool = NULL;
165 var_Create(vd->obj.parent, "ios-eaglcontext", VLC_VAR_ADDRESS);
168 /* setup the actual OpenGL ES view */
170 [VLCOpenGLES2VideoView performSelectorOnMainThread:@selector(getNewView:)
171 withObject:[NSArray arrayWithObjects:
172 [NSValue valueWithPointer:&sys->glESView],
173 [NSValue valueWithPointer:vd], nil]
175 if (!sys->glESView) {
176 msg_Err(vd, "Creating OpenGL ES 2 view failed");
177 var_Destroy(vd->obj.parent, "ios-eaglcontext");
181 const vlc_fourcc_t *subpicture_chromas;
182 video_format_t fmt = vd->fmt;
184 sys->gl = vlc_object_create(this, sizeof(*sys->gl));
188 struct gl_sys *glsys = sys->gl->sys =
189 vlc_obj_malloc(this, sizeof(struct gl_sys));
190 if (unlikely(!sys->gl->sys))
192 glsys->glESView = sys->glESView;
194 glsys->renderBuffer = glsys->frameBuffer = 0;
196 /* Initialize common OpenGL video display */
197 sys->gl->makeCurrent = GLESMakeCurrent;
198 sys->gl->releaseCurrent = GLESReleaseCurrent;
199 sys->gl->swap = GLESSwap;
200 sys->gl->getProcAddress = OurGetProcAddress;
202 if (vlc_gl_MakeCurrent(sys->gl) != VLC_SUCCESS)
205 vout_display_opengl_t *vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
206 sys->gl, &vd->cfg->viewpoint);
207 vlc_gl_ReleaseCurrent(sys->gl);
213 vout_display_info_t info = vd->info;
214 info.has_pictures_invalid = false;
215 info.subpicture_chromas = subpicture_chromas;
217 /* Setup vout_display_t once everything is fine */
220 vd->pool = PicturePool;
221 vd->prepare = PictureRender;
222 vd->display = PictureDisplay;
223 vd->control = Control;
233 static void Close (vlc_object_t *this)
235 vout_display_t *vd = (vout_display_t *)this;
236 vout_display_sys_t *sys = vd->sys;
240 if (sys->gl != NULL) {
241 struct gl_sys *glsys = sys->gl->sys;
242 msg_Dbg(this, "deleting display");
244 if (likely(glsys->vgl))
246 int ret = vlc_gl_MakeCurrent(sys->gl);
247 vout_display_opengl_Delete(glsys->vgl);
248 if (ret == VLC_SUCCESS)
250 vlc_gl_ReleaseCurrent(sys->gl);
254 vlc_object_release(sys->gl);
257 [sys->glESView cleanAndRelease:flushed];
259 var_Destroy(vd->obj.parent, "ios-eaglcontext");
262 /*****************************************************************************
263 * vout display callbacks
264 *****************************************************************************/
266 static int Control(vout_display_t *vd, int query, va_list ap)
268 vout_display_sys_t *sys = vd->sys;
269 struct gl_sys *glsys = sys->gl->sys;
272 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
273 case VOUT_DISPLAY_CHANGE_ZOOM:
274 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
275 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
276 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
278 const vout_display_cfg_t *cfg;
280 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT ||
281 query == VOUT_DISPLAY_CHANGE_SOURCE_CROP)
284 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
288 [sys->glESView updateVoutCfg:cfg withVGL:glsys->vgl];
293 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
294 return vout_display_opengl_SetViewpoint(glsys->vgl,
295 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
297 case VOUT_DISPLAY_RESET_PICTURES:
298 vlc_assert_unreachable ();
300 msg_Err(vd, "Unknown request %d", query);
305 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
307 vout_display_sys_t *sys = vd->sys;
308 struct gl_sys *glsys = sys->gl->sys;
310 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
312 vout_display_opengl_Display(glsys->vgl, &vd->source);
313 vlc_gl_ReleaseCurrent(sys->gl);
316 picture_Release(pic);
319 subpicture_Delete(subpicture);
322 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
324 vout_display_sys_t *sys = vd->sys;
325 struct gl_sys *glsys = sys->gl->sys;
327 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
329 vout_display_opengl_Prepare(glsys->vgl, pic, subpicture);
330 vlc_gl_ReleaseCurrent(sys->gl);
334 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
336 vout_display_sys_t *sys = vd->sys;
337 struct gl_sys *glsys = sys->gl->sys;
339 if (!sys->picturePool && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
341 sys->picturePool = vout_display_opengl_GetPool(glsys->vgl, requested_count);
342 vlc_gl_ReleaseCurrent(sys->gl);
344 return sys->picturePool;
347 /*****************************************************************************
348 * vout opengl callbacks
349 *****************************************************************************/
350 static int GLESMakeCurrent(vlc_gl_t *gl)
352 struct gl_sys *sys = gl->sys;
354 if (![sys->glESView makeCurrentWithGL:&sys->previousEaglContext withGL:gl])
359 static void GLESReleaseCurrent(vlc_gl_t *gl)
361 struct gl_sys *sys = gl->sys;
363 [sys->glESView releaseCurrent:sys->previousEaglContext];
366 static void GLESSwap(vlc_gl_t *gl)
368 struct gl_sys *sys = gl->sys;
370 [sys->glESView presentRenderbuffer];
374 /*****************************************************************************
376 *****************************************************************************/
377 @implementation VLCOpenGLES2VideoView
381 return [CAEAGLLayer class];
384 + (void)getNewView:(NSArray *)value
386 id *ret = [[value objectAtIndex:0] pointerValue];
387 vout_display_t *vd = [[value objectAtIndex:1] pointerValue];
388 *ret = [[self alloc] initWithFrameAndVd:CGRectMake(0.,0.,320.,240.) withVd:vd];
391 - (id)initWithFrameAndVd:(CGRect)frame withVd:(vout_display_t*)vd
393 _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
394 if (unlikely(!_appActive))
397 self = [super initWithFrame:frame];
402 _bufferNeedReset = YES;
404 _cfg = *_voutDisplay->cfg;
406 vlc_mutex_init(&_mutex);
407 vlc_cond_init(&_gl_attached_wait);
410 /* the following creates a new OpenGL ES context with the API version we
411 * need if there is already an active context created by another OpenGL
412 * provider we cache it and restore analog to the
413 * makeCurrent/releaseCurrent pattern used through-out the class */
414 EAGLContext *previousEaglContext = [EAGLContext currentContext];
416 _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
418 if (unlikely(!_eaglContext)
419 || unlikely(![EAGLContext setCurrentContext:_eaglContext]))
422 [_eaglContext release];
423 vlc_mutex_destroy(&_mutex);
424 vlc_cond_destroy(&_gl_attached_wait);
428 [self releaseCurrent:previousEaglContext];
430 /* Set "ios-eaglcontext" to be used by cvpx fitlers/glconv */
431 var_SetAddress(_voutDisplay->obj.parent, "ios-eaglcontext", _eaglContext);
433 _layer = (CAEAGLLayer *)self.layer;
434 _layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
437 self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
439 if (![self fetchViewContainer])
441 vlc_mutex_destroy(&_mutex);
442 vlc_cond_destroy(&_gl_attached_wait);
443 [_eaglContext release];
449 [[NSNotificationCenter defaultCenter] addObserver:self
450 selector:@selector(applicationStateChanged:)
451 name:UIApplicationWillResignActiveNotification
453 [[NSNotificationCenter defaultCenter] addObserver:self
454 selector:@selector(applicationStateChanged:)
455 name:UIApplicationDidBecomeActiveNotification
457 [[NSNotificationCenter defaultCenter] addObserver:self
458 selector:@selector(applicationStateChanged:)
459 name:UIApplicationDidEnterBackgroundNotification
461 [[NSNotificationCenter defaultCenter] addObserver:self
462 selector:@selector(applicationStateChanged:)
463 name:UIApplicationWillEnterForegroundNotification
469 - (BOOL)fetchViewContainer
472 /* get the object we will draw into */
473 UIView *viewContainer = var_InheritAddress (_voutDisplay, "drawable-nsobject");
474 if (unlikely(viewContainer == nil)) {
475 msg_Err(_voutDisplay, "provided view container is nil");
479 if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
480 msg_Err(_voutDisplay, "void pointer not an ObjC object");
484 [viewContainer retain];
486 if (![viewContainer isKindOfClass:[UIView class]]) {
487 msg_Err(_voutDisplay, "passed ObjC object not of class UIView");
491 /* This will be released in Close(), on
492 * main thread, after we are done using it. */
493 _viewContainer = viewContainer;
495 self.frame = viewContainer.bounds;
498 [_viewContainer addSubview:self];
500 /* add tap gesture recognizer for DVD menus and stuff */
501 _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
502 action:@selector(tapRecognized:)];
503 if (_viewContainer.window
504 && _viewContainer.window.rootViewController
505 && _viewContainer.window.rootViewController.view)
506 [_viewContainer.superview addGestureRecognizer:_tapRecognizer];
507 _tapRecognizer.cancelsTouchesInView = NO;
509 } @catch (NSException *exception) {
510 msg_Err(_voutDisplay, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
511 vout_display_sys_t *sys = _voutDisplay->sys;
513 [_tapRecognizer release];
518 - (void)cleanAndReleaseFromMainThread
520 [[NSNotificationCenter defaultCenter] removeObserver:self];
522 [_tapRecognizer.view removeGestureRecognizer:_tapRecognizer];
523 [_tapRecognizer release];
525 [self removeFromSuperview];
526 [_viewContainer release];
528 assert(!_gl_attached);
529 [_eaglContext release];
533 - (void)cleanAndRelease:(BOOL)flushed
535 vlc_mutex_lock(&_mutex);
536 if (_eaglEnabled && !flushed)
537 [self flushEAGLLocked];
540 vlc_mutex_unlock(&_mutex);
542 [self performSelectorOnMainThread:@selector(cleanAndReleaseFromMainThread)
549 vlc_mutex_destroy(&_mutex);
550 vlc_cond_destroy(&_gl_attached_wait);
554 - (void)didMoveToWindow
556 self.contentScaleFactor = self.window.screen.scale;
558 vlc_mutex_lock(&_mutex);
559 _bufferNeedReset = YES;
560 vlc_mutex_unlock(&_mutex);
563 - (BOOL)doResetBuffers:(vlc_gl_t *)gl
565 struct gl_sys *glsys = gl->sys;
567 if (glsys->frameBuffer != 0)
569 /* clear frame buffer */
570 glDeleteFramebuffers(1, &glsys->frameBuffer);
571 glsys->frameBuffer = 0;
574 if (glsys->renderBuffer != 0)
576 /* clear render buffer */
577 glDeleteRenderbuffers(1, &glsys->renderBuffer);
578 glsys->renderBuffer = 0;
581 glDisable(GL_DEPTH_TEST);
583 glGenFramebuffers(1, &glsys->frameBuffer);
584 glBindFramebuffer(GL_FRAMEBUFFER, glsys->frameBuffer);
586 glGenRenderbuffers(1, &glsys->renderBuffer);
587 glBindRenderbuffer(GL_RENDERBUFFER, glsys->renderBuffer);
589 [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_layer];
591 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, glsys->renderBuffer);
592 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
594 msg_Err(_voutDisplay, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
600 - (BOOL)makeCurrentWithGL:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl
602 vlc_mutex_lock(&_mutex);
603 assert(!_gl_attached);
605 if (unlikely(!_appActive))
607 vlc_mutex_unlock(&_mutex);
610 assert(_eaglEnabled);
612 *previousEaglContext = [EAGLContext currentContext];
614 BOOL success = [EAGLContext setCurrentContext:_eaglContext];
615 BOOL resetBuffers = NO;
617 if (success && gl != NULL)
619 struct gl_sys *glsys = gl->sys;
621 if (unlikely(_bufferNeedReset))
623 _bufferNeedReset = NO;
626 if (unlikely(_placeInvalidated && glsys->vgl))
628 _placeInvalidated = NO;
630 vout_display_place_t place;
631 [self getPlaceLocked: &place];
632 vout_display_opengl_SetWindowAspectRatio(glsys->vgl, (float)place.width / place.height);
634 // x / y are top left corner, but we need the lower left one
635 vout_display_opengl_Viewport(glsys->vgl, _place.x, _place.y, _place.width, _place.height);
642 vlc_mutex_unlock(&_mutex);
644 if (resetBuffers && ![self doResetBuffers:gl])
646 [self releaseCurrent:*previousEaglContext];
652 - (void)releaseCurrent:(EAGLContext *)previousEaglContext
654 [EAGLContext setCurrentContext:previousEaglContext];
656 vlc_mutex_lock(&_mutex);
657 assert(_gl_attached);
659 vlc_mutex_unlock(&_mutex);
660 vlc_cond_signal(&_gl_attached_wait);
663 - (void)presentRenderbuffer
665 [_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
668 - (void)layoutSubviews
672 vlc_mutex_lock(&_mutex);
673 _bufferNeedReset = YES;
674 vlc_mutex_unlock(&_mutex);
677 - (void)getPlaceLocked:(vout_display_place_t *)place
679 assert(_voutDisplay);
680 vout_display_cfg_t cfg = _cfg;
682 cfg.display.width = _viewSize.width * _scaleFactor;
683 cfg.display.height = _viewSize.height * _scaleFactor;
685 vout_display_PlacePicture(place, &_voutDisplay->source, &cfg, false);
690 assert([NSThread isMainThread]);
692 vlc_mutex_lock(&_mutex);
695 vlc_mutex_unlock(&_mutex);
698 _viewSize = [self bounds].size;
699 _scaleFactor = self.contentScaleFactor;
701 vout_display_place_t place;
702 [self getPlaceLocked: &place];
704 if (memcmp(&place, &_place, sizeof(vout_display_place_t)) != 0)
706 _placeInvalidated = YES;
710 vout_display_SendEventDisplaySize(_voutDisplay, _viewSize.width * _scaleFactor,
711 _viewSize.height * _scaleFactor);
713 vlc_mutex_unlock(&_mutex);
716 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
718 vlc_mutex_lock(&_mutex);
721 vlc_mutex_unlock(&_mutex);
725 UIGestureRecognizerState state = [tapRecognizer state];
726 CGPoint touchPoint = [tapRecognizer locationInView:self];
727 CGFloat scaleFactor = self.contentScaleFactor;
728 vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay, ORIENT_NORMAL,
729 (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor,
732 vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
733 vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
735 vlc_mutex_unlock(&_mutex);
738 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl
740 if (memcmp(&_cfg, cfg, sizeof(vout_display_cfg_t)) == 0)
743 vlc_mutex_lock(&_mutex);
746 vout_display_place_t place;
747 [self getPlaceLocked: &place];
748 vout_display_opengl_SetWindowAspectRatio(vgl, (float)place.width / place.height);
750 vlc_mutex_unlock(&_mutex);
752 [self performSelectorOnMainThread:@selector(setNeedsUpdateConstraints)
757 - (void)flushEAGLLocked
759 assert(_eaglEnabled);
761 /* Ensure that all previously submitted commands are drained from the
762 * command buffer and are executed by OpenGL ES before moving to the
764 EAGLContext *previousEaglContext = [EAGLContext currentContext];
765 if ([EAGLContext setCurrentContext:_eaglContext])
770 [EAGLContext setCurrentContext:previousEaglContext];
773 - (void)applicationStateChanged:(NSNotification *)notification
775 vlc_mutex_lock(&_mutex);
777 if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification])
779 else if ([[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification])
785 /* Wait for the vout to unlock the eagl context before releasing
788 vlc_cond_wait(&_gl_attached_wait, &_mutex);
790 [self flushEAGLLocked];
794 else if ([[notification name] isEqualToString:UIApplicationWillEnterForegroundNotification])
798 assert([[notification name] isEqualToString:UIApplicationDidBecomeActiveNotification]);
802 vlc_mutex_unlock(&_mutex);
805 - (void)updateConstraints
807 [super updateConstraints];
816 - (BOOL)acceptsFirstResponder