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 *, mtime_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,
321 vout_display_sys_t *sys = vd->sys;
322 struct gl_sys *glsys = sys->gl->sys;
324 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
326 vout_display_opengl_Prepare(glsys->vgl, pic, subpicture);
327 vlc_gl_ReleaseCurrent(sys->gl);
331 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
333 vout_display_sys_t *sys = vd->sys;
334 struct gl_sys *glsys = sys->gl->sys;
336 if (!sys->picturePool && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
338 sys->picturePool = vout_display_opengl_GetPool(glsys->vgl, requested_count);
339 vlc_gl_ReleaseCurrent(sys->gl);
341 return sys->picturePool;
344 /*****************************************************************************
345 * vout opengl callbacks
346 *****************************************************************************/
347 static int GLESMakeCurrent(vlc_gl_t *gl)
349 struct gl_sys *sys = gl->sys;
351 if (![sys->glESView makeCurrentWithGL:&sys->previousEaglContext withGL:gl])
356 static void GLESReleaseCurrent(vlc_gl_t *gl)
358 struct gl_sys *sys = gl->sys;
360 [sys->glESView releaseCurrent:sys->previousEaglContext];
363 static void GLESSwap(vlc_gl_t *gl)
365 struct gl_sys *sys = gl->sys;
367 [sys->glESView presentRenderbuffer];
371 /*****************************************************************************
373 *****************************************************************************/
374 @implementation VLCOpenGLES2VideoView
378 return [CAEAGLLayer class];
381 + (void)getNewView:(NSArray *)value
383 id *ret = [[value objectAtIndex:0] pointerValue];
384 vout_display_t *vd = [[value objectAtIndex:1] pointerValue];
385 *ret = [[self alloc] initWithFrameAndVd:CGRectMake(0.,0.,320.,240.) withVd:vd];
388 - (id)initWithFrameAndVd:(CGRect)frame withVd:(vout_display_t*)vd
390 _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
391 if (unlikely(!_appActive))
394 self = [super initWithFrame:frame];
399 _bufferNeedReset = YES;
401 _cfg = *_voutDisplay->cfg;
403 vlc_mutex_init(&_mutex);
404 vlc_cond_init(&_gl_attached_wait);
407 /* the following creates a new OpenGL ES context with the API version we
408 * need if there is already an active context created by another OpenGL
409 * provider we cache it and restore analog to the
410 * makeCurrent/releaseCurrent pattern used through-out the class */
411 EAGLContext *previousEaglContext = [EAGLContext currentContext];
413 _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
415 if (unlikely(!_eaglContext)
416 || unlikely(![EAGLContext setCurrentContext:_eaglContext]))
419 [_eaglContext release];
420 vlc_mutex_destroy(&_mutex);
421 vlc_cond_destroy(&_gl_attached_wait);
425 [self releaseCurrent:previousEaglContext];
427 /* Set "ios-eaglcontext" to be used by cvpx fitlers/glconv */
428 var_SetAddress(_voutDisplay->obj.parent, "ios-eaglcontext", _eaglContext);
430 _layer = (CAEAGLLayer *)self.layer;
431 _layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
434 self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
436 if (![self fetchViewContainer])
438 vlc_mutex_destroy(&_mutex);
439 vlc_cond_destroy(&_gl_attached_wait);
440 [_eaglContext release];
446 [[NSNotificationCenter defaultCenter] addObserver:self
447 selector:@selector(applicationStateChanged:)
448 name:UIApplicationWillResignActiveNotification
450 [[NSNotificationCenter defaultCenter] addObserver:self
451 selector:@selector(applicationStateChanged:)
452 name:UIApplicationDidBecomeActiveNotification
454 [[NSNotificationCenter defaultCenter] addObserver:self
455 selector:@selector(applicationStateChanged:)
456 name:UIApplicationDidEnterBackgroundNotification
458 [[NSNotificationCenter defaultCenter] addObserver:self
459 selector:@selector(applicationStateChanged:)
460 name:UIApplicationWillEnterForegroundNotification
466 - (BOOL)fetchViewContainer
469 /* get the object we will draw into */
470 UIView *viewContainer = var_InheritAddress (_voutDisplay, "drawable-nsobject");
471 if (unlikely(viewContainer == nil)) {
472 msg_Err(_voutDisplay, "provided view container is nil");
476 if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
477 msg_Err(_voutDisplay, "void pointer not an ObjC object");
481 [viewContainer retain];
483 if (![viewContainer isKindOfClass:[UIView class]]) {
484 msg_Err(_voutDisplay, "passed ObjC object not of class UIView");
488 /* This will be released in Close(), on
489 * main thread, after we are done using it. */
490 _viewContainer = viewContainer;
492 self.frame = viewContainer.bounds;
495 [_viewContainer addSubview:self];
497 /* add tap gesture recognizer for DVD menus and stuff */
498 _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
499 action:@selector(tapRecognized:)];
500 if (_viewContainer.window
501 && _viewContainer.window.rootViewController
502 && _viewContainer.window.rootViewController.view)
503 [_viewContainer.superview addGestureRecognizer:_tapRecognizer];
504 _tapRecognizer.cancelsTouchesInView = NO;
506 } @catch (NSException *exception) {
507 msg_Err(_voutDisplay, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
508 vout_display_sys_t *sys = _voutDisplay->sys;
510 [_tapRecognizer release];
515 - (void)cleanAndReleaseFromMainThread
517 [[NSNotificationCenter defaultCenter] removeObserver:self];
519 [_tapRecognizer.view removeGestureRecognizer:_tapRecognizer];
520 [_tapRecognizer release];
522 [self removeFromSuperview];
523 [_viewContainer release];
525 assert(!_gl_attached);
526 [_eaglContext release];
530 - (void)cleanAndRelease:(BOOL)flushed
532 vlc_mutex_lock(&_mutex);
533 if (_eaglEnabled && !flushed)
534 [self flushEAGLLocked];
537 vlc_mutex_unlock(&_mutex);
539 [self performSelectorOnMainThread:@selector(cleanAndReleaseFromMainThread)
546 vlc_mutex_destroy(&_mutex);
547 vlc_cond_destroy(&_gl_attached_wait);
551 - (void)didMoveToWindow
553 self.contentScaleFactor = self.window.screen.scale;
555 vlc_mutex_lock(&_mutex);
556 _bufferNeedReset = YES;
557 vlc_mutex_unlock(&_mutex);
560 - (BOOL)doResetBuffers:(vlc_gl_t *)gl
562 struct gl_sys *glsys = gl->sys;
564 if (glsys->frameBuffer != 0)
566 /* clear frame buffer */
567 glDeleteFramebuffers(1, &glsys->frameBuffer);
568 glsys->frameBuffer = 0;
571 if (glsys->renderBuffer != 0)
573 /* clear render buffer */
574 glDeleteRenderbuffers(1, &glsys->renderBuffer);
575 glsys->renderBuffer = 0;
578 glDisable(GL_DEPTH_TEST);
580 glGenFramebuffers(1, &glsys->frameBuffer);
581 glBindFramebuffer(GL_FRAMEBUFFER, glsys->frameBuffer);
583 glGenRenderbuffers(1, &glsys->renderBuffer);
584 glBindRenderbuffer(GL_RENDERBUFFER, glsys->renderBuffer);
586 [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_layer];
588 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, glsys->renderBuffer);
589 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
591 msg_Err(_voutDisplay, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
597 - (BOOL)makeCurrentWithGL:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl
599 vlc_mutex_lock(&_mutex);
600 assert(!_gl_attached);
602 if (unlikely(!_appActive))
604 vlc_mutex_unlock(&_mutex);
607 assert(_eaglEnabled);
609 *previousEaglContext = [EAGLContext currentContext];
611 BOOL success = [EAGLContext setCurrentContext:_eaglContext];
612 BOOL resetBuffers = NO;
614 if (success && gl != NULL)
616 struct gl_sys *glsys = gl->sys;
618 if (unlikely(_bufferNeedReset))
620 _bufferNeedReset = NO;
623 if (unlikely(_placeInvalidated && glsys->vgl))
625 _placeInvalidated = NO;
627 vout_display_place_t place;
628 [self getPlaceLocked: &place];
629 vout_display_opengl_SetWindowAspectRatio(glsys->vgl, (float)place.width / place.height);
631 // x / y are top left corner, but we need the lower left one
632 vout_display_opengl_Viewport(glsys->vgl, _place.x, _place.y, _place.width, _place.height);
639 vlc_mutex_unlock(&_mutex);
641 if (resetBuffers && ![self doResetBuffers:gl])
643 [self releaseCurrent:*previousEaglContext];
649 - (void)releaseCurrent:(EAGLContext *)previousEaglContext
651 [EAGLContext setCurrentContext:previousEaglContext];
653 vlc_mutex_lock(&_mutex);
654 assert(_gl_attached);
656 vlc_mutex_unlock(&_mutex);
657 vlc_cond_signal(&_gl_attached_wait);
660 - (void)presentRenderbuffer
662 [_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
665 - (void)layoutSubviews
669 vlc_mutex_lock(&_mutex);
670 _bufferNeedReset = YES;
671 vlc_mutex_unlock(&_mutex);
674 - (void)getPlaceLocked:(vout_display_place_t *)place
676 assert(_voutDisplay);
677 vout_display_cfg_t cfg = _cfg;
679 cfg.display.width = _viewSize.width * _scaleFactor;
680 cfg.display.height = _viewSize.height * _scaleFactor;
682 vout_display_PlacePicture(place, &_voutDisplay->source, &cfg, false);
687 assert([NSThread isMainThread]);
689 vlc_mutex_lock(&_mutex);
692 vlc_mutex_unlock(&_mutex);
695 _viewSize = [self bounds].size;
696 _scaleFactor = self.contentScaleFactor;
698 vout_display_place_t place;
699 [self getPlaceLocked: &place];
701 if (memcmp(&place, &_place, sizeof(vout_display_place_t)) != 0)
703 _placeInvalidated = YES;
707 vout_display_SendEventDisplaySize(_voutDisplay, _viewSize.width * _scaleFactor,
708 _viewSize.height * _scaleFactor);
710 vlc_mutex_unlock(&_mutex);
713 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
715 vlc_mutex_lock(&_mutex);
718 vlc_mutex_unlock(&_mutex);
722 UIGestureRecognizerState state = [tapRecognizer state];
723 CGPoint touchPoint = [tapRecognizer locationInView:self];
724 CGFloat scaleFactor = self.contentScaleFactor;
725 vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay, ORIENT_NORMAL,
726 (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor,
729 vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
730 vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
732 vlc_mutex_unlock(&_mutex);
735 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl
737 if (memcmp(&_cfg, cfg, sizeof(vout_display_cfg_t)) == 0)
740 vlc_mutex_lock(&_mutex);
743 vout_display_place_t place;
744 [self getPlaceLocked: &place];
745 vout_display_opengl_SetWindowAspectRatio(vgl, (float)place.width / place.height);
747 vlc_mutex_unlock(&_mutex);
749 [self performSelectorOnMainThread:@selector(setNeedsUpdateConstraints)
754 - (void)flushEAGLLocked
756 assert(_eaglEnabled);
758 /* Ensure that all previously submitted commands are drained from the
759 * command buffer and are executed by OpenGL ES before moving to the
761 EAGLContext *previousEaglContext = [EAGLContext currentContext];
762 if ([EAGLContext setCurrentContext:_eaglContext])
767 [EAGLContext setCurrentContext:previousEaglContext];
770 - (void)applicationStateChanged:(NSNotification *)notification
772 vlc_mutex_lock(&_mutex);
774 if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification])
776 else if ([[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification])
782 /* Wait for the vout to unlock the eagl context before releasing
785 vlc_cond_wait(&_gl_attached_wait, &_mutex);
787 [self flushEAGLLocked];
791 else if ([[notification name] isEqualToString:UIApplicationWillEnterForegroundNotification])
795 assert([[notification name] isEqualToString:UIApplicationDidBecomeActiveNotification]);
799 vlc_mutex_unlock(&_mutex);
802 - (void)updateConstraints
804 [super updateConstraints];
813 - (BOOL)acceptsFirstResponder