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 OpenglESLock(vlc_gl_t *);
70 static void OpenglESSwap(vlc_gl_t *);
71 static void OpenglESUnlock(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")
87 @interface VLCOpenGLES2VideoView : UIView {
88 vout_display_t *_voutDisplay;
89 EAGLContext *_eaglContext;
90 EAGLContext *_previousEaglContext;
94 BOOL _bufferNeedReset;
97 @property (readonly) GLuint renderBuffer;
98 @property (readonly) GLuint frameBuffer;
99 @property (readwrite) vout_display_t* voutDisplay;
100 @property (readonly) EAGLContext* eaglContext;
101 @property (readonly) BOOL isAppActive;
102 @property GLuint shaderProgram;
104 - (id)initWithFrame:(CGRect)frame voutDisplay:(vout_display_t *)vd;
106 - (void)createBuffers;
107 - (void)destroyBuffers;
108 - (void)resetBuffers;
115 struct vout_display_sys_t
117 VLCOpenGLES2VideoView *glESView;
118 UIView *viewContainer;
119 UITapGestureRecognizer *tapRecognizer;
122 vout_display_opengl_t *vgl;
124 picture_pool_t *picturePool;
126 vout_display_place_t place;
131 CVEAGLContext locked_ctx;
132 VLCOpenGLES2VideoView *glESView;
135 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
139 return dlsym(RTLD_DEFAULT, name);
142 static int Open(vlc_object_t *this)
144 vout_display_t *vd = (vout_display_t *)this;
146 if (vout_display_IsWindowed(vd))
149 vout_display_sys_t *sys = calloc (1, sizeof(*sys));
155 sys->picturePool = NULL;
159 /* setup the actual OpenGL ES view */
160 sys->glESView = [[VLCOpenGLES2VideoView alloc] initWithFrame:CGRectMake(0.,0.,320.,240.) voutDisplay:vd];
161 sys->glESView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
163 if (!sys->glESView) {
164 msg_Err(vd, "Creating OpenGL ES 2 view failed");
168 [sys->glESView performSelectorOnMainThread:@selector(fetchViewContainer) withObject:nil waitUntilDone:YES];
169 if (!sys->viewContainer) {
170 msg_Err(vd, "Fetching view container failed");
174 const vlc_fourcc_t *subpicture_chromas;
175 video_format_t fmt = vd->fmt;
177 sys->gl = vlc_object_create(this, sizeof(*sys->gl));
181 struct gl_sys *glsys = sys->gl->sys = malloc(sizeof(struct gl_sys));
182 if (unlikely(!sys->gl->sys))
184 glsys->locked_ctx = NULL;
185 glsys->glESView = sys->glESView;
186 /* Initialize common OpenGL video display */
187 sys->gl->makeCurrent = OpenglESLock;
188 sys->gl->releaseCurrent = OpenglESUnlock;
189 sys->gl->swap = OpenglESSwap;
190 sys->gl->getProcAddress = OurGetProcAddress;
192 if (vlc_gl_MakeCurrent(sys->gl) != VLC_SUCCESS)
194 sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
195 sys->gl, &vd->cfg->viewpoint);
196 vlc_gl_ReleaseCurrent(sys->gl);
201 vout_display_info_t info = vd->info;
202 info.has_pictures_invalid = false;
203 info.subpicture_chromas = subpicture_chromas;
204 info.has_hide_mouse = false;
206 /* Setup vout_display_t once everything is fine */
209 vd->pool = PicturePool;
210 vd->prepare = PictureRender;
211 vd->display = PictureDisplay;
212 vd->control = Control;
215 /* forward our dimensions to the vout core */
218 @synchronized(sys->viewContainer) {
219 scaleFactor = sys->viewContainer.contentScaleFactor;
220 viewSize = sys->viewContainer.bounds.size;
222 vout_display_SendEventDisplaySize(vd, viewSize.width * scaleFactor, viewSize.height * scaleFactor);
225 [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
226 selector:@selector(applicationStateChanged:)
227 name:UIApplicationWillResignActiveNotification
229 [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
230 selector:@selector(applicationStateChanged:)
231 name:UIApplicationDidBecomeActiveNotification
233 [sys->glESView reshape];
242 void Close (vlc_object_t *this)
244 vout_display_t *vd = (vout_display_t *)this;
245 vout_display_sys_t *sys = vd->sys;
248 if (sys->tapRecognizer) {
249 [sys->tapRecognizer.view removeGestureRecognizer:sys->tapRecognizer];
250 [sys->tapRecognizer release];
253 [sys->glESView setVoutDisplay:nil];
255 var_Destroy (vd, "drawable-nsobject");
256 @synchronized(sys->viewContainer) {
257 [sys->glESView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
258 [sys->viewContainer performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
260 sys->viewContainer = nil;
262 if (sys->gl != NULL) {
263 @synchronized (sys->glESView) {
264 msg_Dbg(this, "deleting display");
266 if (likely([sys->glESView isAppActive]) && sys->vgl)
268 vlc_gl_MakeCurrent(sys->gl);
269 vout_display_opengl_Delete(sys->vgl);
270 vlc_gl_ReleaseCurrent(sys->gl);
273 vlc_object_release(sys->gl);
276 [sys->glESView release];
282 /*****************************************************************************
283 * vout display callbacks
284 *****************************************************************************/
286 static int Control(vout_display_t *vd, int query, va_list ap)
288 vout_display_sys_t *sys = vd->sys;
291 case VOUT_DISPLAY_HIDE_MOUSE:
294 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
295 case VOUT_DISPLAY_CHANGE_ZOOM:
296 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
297 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
298 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
304 const vout_display_cfg_t *cfg;
305 const video_format_t *source;
307 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT ||
308 query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
309 source = (const video_format_t *)va_arg(ap, const video_format_t *);
312 source = &vd->source;
313 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
316 /* we don't adapt anything here regardless of what the vout core
317 * wants since we are not in a traditional desktop window */
321 vout_display_cfg_t cfg_tmp = *cfg;
323 viewSize = [sys->glESView bounds].size;
325 /* on HiDPI displays, the point bounds don't equal the actual pixels */
326 CGFloat scaleFactor = sys->glESView.contentScaleFactor;
327 cfg_tmp.display.width = viewSize.width * scaleFactor;
328 cfg_tmp.display.height = viewSize.height * scaleFactor;
330 vout_display_place_t place;
331 vout_display_PlacePicture(&place, source, &cfg_tmp, false);
332 @synchronized (sys->glESView) {
337 vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
339 // x / y are top left corner, but we need the lower left one
340 if (query != VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
341 glViewport(place.x, cfg_tmp.display.height - (place.y + place.height), place.width, place.height);
346 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
348 return vout_display_opengl_SetViewpoint(sys->vgl,
349 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
353 case VOUT_DISPLAY_RESET_PICTURES:
354 vlc_assert_unreachable ();
356 msg_Err(vd, "Unknown request %d", query);
357 case VOUT_DISPLAY_CHANGE_FULLSCREEN:
362 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
364 vout_display_sys_t *sys = vd->sys;
365 @synchronized (sys->glESView) {
366 if (likely([sys->glESView isAppActive]) && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
368 vout_display_opengl_Display(sys->vgl, &vd->source);
369 vlc_gl_ReleaseCurrent(sys->gl);
373 picture_Release(pic);
376 subpicture_Delete(subpicture);
379 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
381 vout_display_sys_t *sys = vd->sys;
382 if (likely([sys->glESView isAppActive]) && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
384 vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
385 vlc_gl_ReleaseCurrent(sys->gl);
389 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
391 vout_display_sys_t *sys = vd->sys;
393 if (!sys->picturePool && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
395 sys->picturePool = vout_display_opengl_GetPool(sys->vgl, requested_count);
396 vlc_gl_ReleaseCurrent(sys->gl);
398 return sys->picturePool;
401 /*****************************************************************************
402 * vout opengl callbacks
403 *****************************************************************************/
404 static int OpenglESLock(vlc_gl_t *gl)
406 struct gl_sys *sys = gl->sys;
408 [sys->glESView lock];
409 if (likely([sys->glESView isAppActive]))
410 [sys->glESView resetBuffers];
411 sys->locked_ctx = (__bridge CVEAGLContext) ((__bridge void *) [sys->glESView eaglContext]);
415 static void OpenglESUnlock(vlc_gl_t *gl)
417 struct gl_sys *sys = gl->sys;
419 [sys->glESView unlock];
422 static void OpenglESSwap(vlc_gl_t *gl)
424 struct gl_sys *sys = gl->sys;
426 if (likely([sys->glESView isAppActive]))
427 [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
431 /*****************************************************************************
433 *****************************************************************************/
434 @implementation VLCOpenGLES2VideoView
435 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext, isAppActive = _appActive;
439 return [CAEAGLLayer class];
442 - (id)initWithFrame:(CGRect)frame voutDisplay:(vout_display_t *)vd
444 self = [super initWithFrame:frame];
449 _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
450 if (unlikely(!_appActive))
453 _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
455 if (unlikely(!_eaglContext))
457 if (unlikely(![EAGLContext setCurrentContext:_eaglContext]))
460 CAEAGLLayer *layer = (CAEAGLLayer *)self.layer;
461 layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
466 [self createBuffers];
469 [self setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
474 - (void)fetchViewContainer
477 /* get the object we will draw into */
478 UIView *viewContainer = var_CreateGetAddress (_voutDisplay, "drawable-nsobject");
479 if (unlikely(viewContainer == nil)) {
480 msg_Err(_voutDisplay, "provided view container is nil");
484 [viewContainer retain];
486 @synchronized(viewContainer) {
487 if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
488 msg_Err(_voutDisplay, "void pointer not an ObjC object");
492 if (![viewContainer isKindOfClass:[UIView class]]) {
493 msg_Err(_voutDisplay, "passed ObjC object not of class UIView");
497 vout_display_sys_t *sys = _voutDisplay->sys;
499 /* This will be released in Close(), on
500 * main thread, after we are done using it. */
501 sys->viewContainer = viewContainer;
503 self.frame = viewContainer.bounds;
506 [sys->viewContainer performSelectorOnMainThread:@selector(addSubview:)
510 /* add tap gesture recognizer for DVD menus and stuff */
511 sys->tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
512 action:@selector(tapRecognized:)];
513 if (sys->viewContainer.window) {
514 if (sys->viewContainer.window.rootViewController) {
515 if (sys->viewContainer.window.rootViewController.view)
516 [sys->viewContainer.superview addGestureRecognizer:sys->tapRecognizer];
519 sys->tapRecognizer.cancelsTouchesInView = NO;
521 } @catch (NSException *exception) {
522 msg_Err(_voutDisplay, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
523 vout_display_sys_t *sys = _voutDisplay->sys;
524 sys->viewContainer = nil;
531 [[NSNotificationCenter defaultCenter] removeObserver:self];
532 [_eaglContext release];
536 - (void)didMoveToWindow
538 self.contentScaleFactor = self.window.screen.scale;
539 _bufferNeedReset = YES;
542 - (void)createBuffers
544 glDisable(GL_DEPTH_TEST);
546 glGenFramebuffers(1, &_frameBuffer);
547 glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
549 glGenRenderbuffers(1, &_renderBuffer);
550 glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
552 [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
554 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
555 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
557 msg_Err(_voutDisplay, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
561 - (void)destroyBuffers
563 /* re-set current context */
564 EAGLContext *previousContext = [EAGLContext currentContext];
565 [EAGLContext setCurrentContext:_eaglContext];
567 /* clear frame buffer */
568 glDeleteFramebuffers(1, &_frameBuffer);
571 /* clear render buffer */
572 glDeleteRenderbuffers(1, &_renderBuffer);
574 [EAGLContext setCurrentContext:previousContext];
579 if (unlikely(_bufferNeedReset)) {
580 [self destroyBuffers];
581 [self createBuffers];
582 _bufferNeedReset = NO;
588 _previousEaglContext = [EAGLContext currentContext];
589 [EAGLContext setCurrentContext:_eaglContext];
594 [EAGLContext setCurrentContext:_previousEaglContext];
597 - (void)layoutSubviews
601 _bufferNeedReset = YES;
606 EAGLContext *previousContext = [EAGLContext currentContext];
607 [EAGLContext setCurrentContext:_eaglContext];
609 CGSize viewSize = [self bounds].size;
611 vout_display_place_t place;
613 @synchronized (self) {
615 vout_display_cfg_t cfg_tmp = *(_voutDisplay->cfg);
616 CGFloat scaleFactor = self.contentScaleFactor;
618 cfg_tmp.display.width = viewSize.width * scaleFactor;
619 cfg_tmp.display.height = viewSize.height * scaleFactor;
621 vout_display_PlacePicture(&place, &_voutDisplay->source, &cfg_tmp, false);
622 _voutDisplay->sys->place = place;
623 vout_display_SendEventDisplaySize(_voutDisplay, viewSize.width * scaleFactor,
624 viewSize.height * scaleFactor);
628 // x / y are top left corner, but we need the lower left one
629 glViewport(place.x, place.y, place.width, place.height);
630 [EAGLContext setCurrentContext:previousContext];
633 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
635 UIGestureRecognizerState state = [tapRecognizer state];
636 CGPoint touchPoint = [tapRecognizer locationInView:self];
637 CGFloat scaleFactor = self.contentScaleFactor;
638 vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay, ORIENT_NORMAL,
639 (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor,
640 &_voutDisplay->sys->place);
642 vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
643 vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
646 - (void)applicationStateChanged:(NSNotification *)notification
648 @synchronized (self) {
649 if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification]
650 || [[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification]
651 || [[notification name] isEqualToString:UIApplicationWillTerminateNotification])
658 - (void)updateConstraints
660 [super updateConstraints];
669 - (BOOL)acceptsFirstResponder