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;
96 BOOL _bufferNeedReset;
98 BOOL _placeInvalidated;
100 UIView *_viewContainer;
101 UITapGestureRecognizer *_tapRecognizer;
103 /* Written from MT, read locked from vout */
104 vout_display_place_t _place;
106 CGFloat _scaleFactor;
108 /* Written from vout, read locked from MT */
109 vout_display_cfg_t _cfg;
111 @property (readonly) GLuint renderBuffer;
112 @property (readonly) GLuint frameBuffer;
113 @property (readonly) EAGLContext* eaglContext;
114 @property GLuint shaderProgram;
116 - (id)initWithFrameAndVd:(CGRect)frame withVd:(vout_display_t*)vd;
117 - (void)cleanAndRelease;
118 - (void)createBuffers;
119 - (void)destroyBuffers;
120 - (BOOL)makeCurrent:(EAGLContext **)previousEaglContext;
121 - (BOOL)makeCurrentWithGL:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl;
122 - (void)releaseCurrent:(EAGLContext *)previousEaglContext;
124 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl;
125 - (void)getPlaceLocked:(vout_display_place_t *)place;
128 struct vout_display_sys_t
130 VLCOpenGLES2VideoView *glESView;
134 picture_pool_t *picturePool;
139 VLCOpenGLES2VideoView *glESView;
140 vout_display_opengl_t *vgl;
141 EAGLContext *previousEaglContext;
144 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
148 return dlsym(RTLD_DEFAULT, name);
151 static int Open(vlc_object_t *this)
153 vout_display_t *vd = (vout_display_t *)this;
155 if (vout_display_IsWindowed(vd))
158 vout_display_sys_t *sys = vlc_obj_calloc (this, 1, sizeof(*sys));
164 sys->picturePool = NULL;
167 var_Create(vd->obj.parent, "ios-eaglcontext", VLC_VAR_ADDRESS);
170 /* setup the actual OpenGL ES view */
172 [VLCOpenGLES2VideoView performSelectorOnMainThread:@selector(getNewView:)
173 withObject:[NSArray arrayWithObjects:
174 [NSValue valueWithPointer:&sys->glESView],
175 [NSValue valueWithPointer:vd], nil]
177 if (!sys->glESView) {
178 msg_Err(vd, "Creating OpenGL ES 2 view failed");
179 var_Destroy(vd->obj.parent, "ios-eaglcontext");
183 const vlc_fourcc_t *subpicture_chromas;
184 video_format_t fmt = vd->fmt;
186 sys->gl = vlc_object_create(this, sizeof(*sys->gl));
190 struct gl_sys *glsys = sys->gl->sys =
191 vlc_obj_malloc(this, sizeof(struct gl_sys));
192 if (unlikely(!sys->gl->sys))
194 glsys->glESView = sys->glESView;
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 var_SetAddress(vd->obj.parent, "ios-eaglcontext", [sys->glESView eaglContext]);
207 vout_display_opengl_t *vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
208 sys->gl, &vd->cfg->viewpoint);
209 vlc_gl_ReleaseCurrent(sys->gl);
215 vout_display_info_t info = vd->info;
216 info.has_pictures_invalid = false;
217 info.subpicture_chromas = subpicture_chromas;
219 /* Setup vout_display_t once everything is fine */
222 vd->pool = PicturePool;
223 vd->prepare = PictureRender;
224 vd->display = PictureDisplay;
225 vd->control = Control;
228 [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
229 selector:@selector(applicationStateChanged:)
230 name:UIApplicationWillResignActiveNotification
232 [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
233 selector:@selector(applicationStateChanged:)
234 name:UIApplicationDidBecomeActiveNotification
244 static void Close (vlc_object_t *this)
246 vout_display_t *vd = (vout_display_t *)this;
247 vout_display_sys_t *sys = vd->sys;
250 var_Destroy (vd, "drawable-nsobject");
252 if (sys->gl != NULL) {
253 struct gl_sys *glsys = sys->gl->sys;
254 msg_Dbg(this, "deleting display");
256 if (likely(glsys->vgl))
258 vlc_gl_MakeCurrent(sys->gl);
259 vout_display_opengl_Delete(glsys->vgl);
260 vlc_gl_ReleaseCurrent(sys->gl);
262 vlc_object_release(sys->gl);
265 [sys->glESView cleanAndRelease];
267 var_Destroy(vd->obj.parent, "ios-eaglcontext");
270 /*****************************************************************************
271 * vout display callbacks
272 *****************************************************************************/
274 static int Control(vout_display_t *vd, int query, va_list ap)
276 vout_display_sys_t *sys = vd->sys;
277 struct gl_sys *glsys = sys->gl->sys;
280 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
281 case VOUT_DISPLAY_CHANGE_ZOOM:
282 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
283 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
284 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
286 const vout_display_cfg_t *cfg;
288 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT ||
289 query == VOUT_DISPLAY_CHANGE_SOURCE_CROP)
292 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
296 [sys->glESView updateVoutCfg:cfg withVGL:glsys->vgl];
301 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
302 return vout_display_opengl_SetViewpoint(glsys->vgl,
303 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
305 case VOUT_DISPLAY_RESET_PICTURES:
306 vlc_assert_unreachable ();
308 msg_Err(vd, "Unknown request %d", query);
313 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
315 vout_display_sys_t *sys = vd->sys;
316 struct gl_sys *glsys = sys->gl->sys;
318 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
320 vout_display_opengl_Display(glsys->vgl, &vd->source);
321 vlc_gl_ReleaseCurrent(sys->gl);
324 picture_Release(pic);
327 subpicture_Delete(subpicture);
330 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
332 vout_display_sys_t *sys = vd->sys;
333 struct gl_sys *glsys = sys->gl->sys;
335 if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
337 vout_display_opengl_Prepare(glsys->vgl, pic, subpicture);
338 vlc_gl_ReleaseCurrent(sys->gl);
342 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
344 vout_display_sys_t *sys = vd->sys;
345 struct gl_sys *glsys = sys->gl->sys;
347 if (!sys->picturePool && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
349 sys->picturePool = vout_display_opengl_GetPool(glsys->vgl, requested_count);
350 vlc_gl_ReleaseCurrent(sys->gl);
352 return sys->picturePool;
355 /*****************************************************************************
356 * vout opengl callbacks
357 *****************************************************************************/
358 static int GLESMakeCurrent(vlc_gl_t *gl)
360 struct gl_sys *sys = gl->sys;
362 if (![sys->glESView makeCurrentWithGL:&sys->previousEaglContext withGL:gl])
367 static void GLESReleaseCurrent(vlc_gl_t *gl)
369 struct gl_sys *sys = gl->sys;
371 [sys->glESView releaseCurrent:sys->previousEaglContext];
374 static void GLESSwap(vlc_gl_t *gl)
376 struct gl_sys *sys = gl->sys;
378 [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
382 /*****************************************************************************
384 *****************************************************************************/
385 @implementation VLCOpenGLES2VideoView
386 @synthesize eaglContext = _eaglContext;
390 return [CAEAGLLayer class];
393 + (void)getNewView:(NSArray *)value
395 id *ret = [[value objectAtIndex:0] pointerValue];
396 vout_display_t *vd = [[value objectAtIndex:1] pointerValue];
397 *ret = [[self alloc] initWithFrameAndVd:CGRectMake(0.,0.,320.,240.) withVd:vd];
400 - (id)initWithFrameAndVd:(CGRect)frame withVd:(vout_display_t*)vd
402 self = [super initWithFrame:frame];
407 _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
408 if (unlikely(!_appActive))
412 _cfg = *_voutDisplay->cfg;
414 vlc_mutex_init(&_mutex);
416 /* the following creates a new OpenGL ES context with the API version we
417 * need if there is already an active context created by another OpenGL
418 * provider we cache it and restore analog to the
419 * makeCurrent/releaseCurrent pattern used through-out the class */
420 EAGLContext *previousEaglContext = [EAGLContext currentContext];
422 _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
424 if (unlikely(!_eaglContext)
425 || unlikely(![EAGLContext setCurrentContext:_eaglContext]))
427 vlc_mutex_destroy(&_mutex);
431 CAEAGLLayer *layer = (CAEAGLLayer *)self.layer;
432 layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
435 self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
437 [self releaseCurrent:previousEaglContext];
439 [self createBuffers];
441 if (![self fetchViewContainer])
443 vlc_mutex_destroy(&_mutex);
444 [_eaglContext release];
451 - (BOOL)fetchViewContainer
454 /* get the object we will draw into */
455 UIView *viewContainer = var_CreateGetAddress (_voutDisplay, "drawable-nsobject");
456 if (unlikely(viewContainer == nil)) {
457 msg_Err(_voutDisplay, "provided view container is nil");
461 if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
462 msg_Err(_voutDisplay, "void pointer not an ObjC object");
466 [viewContainer retain];
468 if (![viewContainer isKindOfClass:[UIView class]]) {
469 msg_Err(_voutDisplay, "passed ObjC object not of class UIView");
473 /* This will be released in Close(), on
474 * main thread, after we are done using it. */
475 _viewContainer = viewContainer;
477 self.frame = viewContainer.bounds;
480 [_viewContainer addSubview:self];
482 /* add tap gesture recognizer for DVD menus and stuff */
483 _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
484 action:@selector(tapRecognized:)];
485 if (_viewContainer.window
486 && _viewContainer.window.rootViewController
487 && _viewContainer.window.rootViewController.view)
488 [_viewContainer.superview addGestureRecognizer:_tapRecognizer];
489 _tapRecognizer.cancelsTouchesInView = NO;
491 } @catch (NSException *exception) {
492 msg_Err(_voutDisplay, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
493 vout_display_sys_t *sys = _voutDisplay->sys;
495 [_tapRecognizer release];
500 - (void)cleanAndRelease
502 if (![NSThread isMainThread])
504 vlc_mutex_lock(&_mutex);
506 vlc_mutex_unlock(&_mutex);
508 [self performSelectorOnMainThread:@selector(cleanAndRelease)
514 [[NSNotificationCenter defaultCenter] removeObserver:self];
516 [_tapRecognizer.view removeGestureRecognizer:_tapRecognizer];
517 [_tapRecognizer release];
519 [self removeFromSuperview];
520 [_viewContainer release];
522 [_eaglContext release];
528 vlc_mutex_destroy(&_mutex);
532 - (void)didMoveToWindow
534 self.contentScaleFactor = self.window.screen.scale;
536 vlc_mutex_lock(&_mutex);
537 _bufferNeedReset = YES;
538 vlc_mutex_unlock(&_mutex);
541 - (void)createBuffers
543 if (![NSThread isMainThread])
545 [self performSelectorOnMainThread:@selector(createBuffers)
551 EAGLContext *previousEaglContext;
552 if (![self makeCurrent:&previousEaglContext])
555 glDisable(GL_DEPTH_TEST);
557 glGenFramebuffers(1, &_frameBuffer);
558 glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
560 glGenRenderbuffers(1, &_renderBuffer);
561 glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
563 [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
565 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
566 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
567 msg_Err(_voutDisplay, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
569 [self releaseCurrent:previousEaglContext];
572 - (void)destroyBuffers
574 if (![NSThread isMainThread])
576 [self performSelectorOnMainThread:@selector(destroyBuffers)
582 EAGLContext *previousEaglContext;
583 if (![self makeCurrent:&previousEaglContext])
586 /* clear frame buffer */
587 glDeleteFramebuffers(1, &_frameBuffer);
590 /* clear render buffer */
591 glDeleteRenderbuffers(1, &_renderBuffer);
594 [self releaseCurrent:previousEaglContext];
597 - (BOOL)makeCurrentWithGL:(EAGLContext **)previousEaglContext withGL:(vlc_gl_t *)gl
599 vlc_mutex_lock(&_mutex);
601 if (unlikely(!_appActive))
603 vlc_mutex_unlock(&_mutex);
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 glViewport(_place.x, _place.y, _place.width, _place.height);
634 vlc_mutex_unlock(&_mutex);
638 [self destroyBuffers];
639 [self createBuffers];
644 - (BOOL)makeCurrent:(EAGLContext **)previousEaglContext
646 return [self makeCurrentWithGL:previousEaglContext withGL:nil];
649 - (void)releaseCurrent:(EAGLContext *)previousEaglContext
651 [EAGLContext setCurrentContext:previousEaglContext];
654 - (void)layoutSubviews
658 vlc_mutex_lock(&_mutex);
659 _bufferNeedReset = YES;
660 vlc_mutex_unlock(&_mutex);
663 - (void)getPlaceLocked:(vout_display_place_t *)place
665 assert(_voutDisplay);
666 vout_display_cfg_t cfg = _cfg;
668 cfg.display.width = _viewSize.width * _scaleFactor;
669 cfg.display.height = _viewSize.height * _scaleFactor;
671 vout_display_PlacePicture(place, &_voutDisplay->source, &cfg, false);
676 assert([NSThread isMainThread]);
678 vlc_mutex_lock(&_mutex);
681 vlc_mutex_unlock(&_mutex);
684 _viewSize = [self bounds].size;
685 _scaleFactor = self.contentScaleFactor;
687 vout_display_place_t place;
688 [self getPlaceLocked: &place];
690 if (memcmp(&place, &_place, sizeof(vout_display_place_t)) != 0)
692 _placeInvalidated = YES;
696 vout_display_SendEventDisplaySize(_voutDisplay, _viewSize.width * _scaleFactor,
697 _viewSize.height * _scaleFactor);
699 vlc_mutex_unlock(&_mutex);
702 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
704 vlc_mutex_lock(&_mutex);
707 vlc_mutex_unlock(&_mutex);
711 UIGestureRecognizerState state = [tapRecognizer state];
712 CGPoint touchPoint = [tapRecognizer locationInView:self];
713 CGFloat scaleFactor = self.contentScaleFactor;
714 vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay, ORIENT_NORMAL,
715 (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor,
718 vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
719 vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
721 vlc_mutex_unlock(&_mutex);
724 - (void)updateVoutCfg:(const vout_display_cfg_t *)cfg withVGL:(vout_display_opengl_t *)vgl
726 if (memcmp(&_cfg, cfg, sizeof(vout_display_cfg_t)) == 0)
729 vlc_mutex_lock(&_mutex);
732 vout_display_place_t place;
733 [self getPlaceLocked: &place];
734 vout_display_opengl_SetWindowAspectRatio(vgl, (float)place.width / place.height);
736 vlc_mutex_unlock(&_mutex);
738 [self performSelectorOnMainThread:@selector(setNeedsUpdateConstraints)
743 - (void)applicationStateChanged:(NSNotification *)notification
745 vlc_mutex_lock(&_mutex);
747 if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification]
748 || [[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification]
749 || [[notification name] isEqualToString:UIApplicationWillTerminateNotification])
754 vlc_mutex_unlock(&_mutex);
757 - (void)updateConstraints
759 [super updateConstraints];
768 - (BOOL)acceptsFirstResponder