demux:ogg: no need to change the es_format_t before a copy
[vlc.git] / modules / video_output / ios.m
blobba54455128633899a514ab85c5dc1ffd6b855611
1 /*****************************************************************************
2  * ios.m: iOS OpenGL ES provider
3  *****************************************************************************
4  * Copyright (C) 2001-2017 VLC authors and VideoLAN
5  * $Id$
6  *
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>
10  *          Rémi Denis-Courmont
11  *          Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
12  *          Eric Petit <titer@m0k.org>
13  *
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.
18  *
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.
23  *
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 /*****************************************************************************
30  * Preamble
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>
38 #import <dlfcn.h>
40 #ifdef HAVE_CONFIG_H
41 # import "config.h"
42 #endif
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"
51 /**
52  * Forward declarations
53  */
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 *);
73 /**
74  * Module declaration
75  */
76 vlc_module_begin ()
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")
85 vlc_module_end ()
87 @interface VLCOpenGLES2VideoView : UIView {
88     vout_display_t *_voutDisplay;
89     EAGLContext *_eaglContext;
90     EAGLContext *_previousEaglContext;
91     GLuint _renderBuffer;
92     GLuint _frameBuffer;
94     BOOL _bufferNeedReset;
95     BOOL _appActive;
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;
109 - (void)lock;
110 - (void)unlock;
112 - (void)reshape;
113 @end
115 struct vout_display_sys_t
117     VLCOpenGLES2VideoView *glESView;
118     UIView *viewContainer;
119     UITapGestureRecognizer *tapRecognizer;
121     vlc_gl_t *gl;
122     vout_display_opengl_t *vgl;
124     picture_pool_t *picturePool;
126     vout_display_place_t place;
129 struct gl_sys
131     CVEAGLContext locked_ctx;
132     VLCOpenGLES2VideoView *glESView;
135 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
137     VLC_UNUSED(gl);
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))
147         return VLC_EGENERIC;
149     vout_display_sys_t *sys = calloc (1, sizeof(*sys));
151     if (!sys)
152         return VLC_ENOMEM;
154     vd->sys = sys;
155     sys->picturePool = NULL;
156     sys->gl = NULL;
158     @autoreleasepool {
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");
165             goto bailout;
166         }
168         [sys->glESView performSelectorOnMainThread:@selector(fetchViewContainer) withObject:nil waitUntilDone:YES];
169         if (!sys->viewContainer) {
170             msg_Err(vd, "Fetching view container failed");
171             goto bailout;
172         }
174         const vlc_fourcc_t *subpicture_chromas;
175         video_format_t fmt = vd->fmt;
177         sys->gl = vlc_object_create(this, sizeof(*sys->gl));
178         if (!sys->gl)
179             goto bailout;
181         struct gl_sys *glsys = sys->gl->sys = malloc(sizeof(struct gl_sys));
182         if (unlikely(!sys->gl->sys))
183             goto bailout;
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)
193             goto bailout;
194         sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
195                                            sys->gl, &vd->cfg->viewpoint);
196         vlc_gl_ReleaseCurrent(sys->gl);
197         if (!sys->vgl)
198             goto bailout;
200         /* */
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 */
207         vd->info = info;
209         vd->pool = PicturePool;
210         vd->prepare = PictureRender;
211         vd->display = PictureDisplay;
212         vd->control = Control;
213         vd->manage = NULL;
215         /* forward our dimensions to the vout core */
216         CGFloat scaleFactor;
217         CGSize viewSize;
218         @synchronized(sys->viewContainer) {
219             scaleFactor = sys->viewContainer.contentScaleFactor;
220             viewSize = sys->viewContainer.bounds.size;
221         }
222         vout_display_SendEventDisplaySize(vd, viewSize.width * scaleFactor, viewSize.height * scaleFactor);
224         /* */
225         [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
226                                                  selector:@selector(applicationStateChanged:)
227                                                      name:UIApplicationWillResignActiveNotification
228                                                    object:nil];
229         [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
230                                                  selector:@selector(applicationStateChanged:)
231                                                      name:UIApplicationDidBecomeActiveNotification
232                                                    object:nil];
233         [sys->glESView reshape];
234         return VLC_SUCCESS;
236     bailout:
237         Close(this);
238         return VLC_EGENERIC;
239     }
242 void Close (vlc_object_t *this)
244     vout_display_t *vd = (vout_display_t *)this;
245     vout_display_sys_t *sys = vd->sys;
247     @autoreleasepool {
248         if (sys->tapRecognizer) {
249             [sys->tapRecognizer.view removeGestureRecognizer:sys->tapRecognizer];
250             [sys->tapRecognizer release];
251         }
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];
259         }
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)
267                 {
268                     vlc_gl_MakeCurrent(sys->gl);
269                     vout_display_opengl_Delete(sys->vgl);
270                     vlc_gl_ReleaseCurrent(sys->gl);
271                 }
272             }
273             vlc_object_release(sys->gl);
274         }
276         [sys->glESView release];
278         free(sys);
279     }
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;
290     switch (query) {
291         case VOUT_DISPLAY_HIDE_MOUSE:
292             return VLC_EGENERIC;
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:
299         {
300             if (!vd->sys)
301                 return VLC_EGENERIC;
303             @autoreleasepool {
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 *);
310                     cfg = vd->cfg;
311                 } else {
312                     source = &vd->source;
313                     cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
314                 }
316                 /* we don't adapt anything here regardless of what the vout core
317                  * wants since we are not in a traditional desktop window */
318                 if (!cfg)
319                     return VLC_EGENERIC;
321                 vout_display_cfg_t cfg_tmp = *cfg;
322                 CGSize viewSize;
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) {
333                     sys->place = place;
334                 }
336                 if (sys->gl != NULL)
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);
342             }
343             return VLC_SUCCESS;
344         }
346         case VOUT_DISPLAY_CHANGE_VIEWPOINT:
347             if (sys->gl != NULL)
348                 return vout_display_opengl_SetViewpoint(sys->vgl,
349                     &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
350             else
351                 return VLC_EGENERIC;
353         case VOUT_DISPLAY_RESET_PICTURES:
354             vlc_assert_unreachable ();
355         default:
356             msg_Err(vd, "Unknown request %d", query);
357         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
358             return VLC_EGENERIC;
359     }
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)
367         {
368             vout_display_opengl_Display(sys->vgl, &vd->source);
369             vlc_gl_ReleaseCurrent(sys->gl);
370         }
371     }
373     picture_Release(pic);
375     if (subpicture)
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)
383     {
384         vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
385         vlc_gl_ReleaseCurrent(sys->gl);
386     }
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)
394     {
395         sys->picturePool = vout_display_opengl_GetPool(sys->vgl, requested_count);
396         vlc_gl_ReleaseCurrent(sys->gl);
397     }
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]);
412     return 0;
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 /*****************************************************************************
432  * Our UIView object
433  *****************************************************************************/
434 @implementation VLCOpenGLES2VideoView
435 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext, isAppActive = _appActive;
437 + (Class)layerClass
439     return [CAEAGLLayer class];
442 - (id)initWithFrame:(CGRect)frame voutDisplay:(vout_display_t *)vd
444     self = [super initWithFrame:frame];
446     if (!self)
447         return nil;
449     _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
450     if (unlikely(!_appActive))
451         return nil;
453     _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
455     if (unlikely(!_eaglContext))
456         return nil;
457     if (unlikely(![EAGLContext setCurrentContext:_eaglContext]))
458         return nil;
460     CAEAGLLayer *layer = (CAEAGLLayer *)self.layer;
461     layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
462     layer.opaque = YES;
464     _voutDisplay = vd;
466     [self createBuffers];
468     [self reshape];
469     [self setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
471     return self;
474 - (void)fetchViewContainer
476     @try {
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");
481             return;
482         }
484         [viewContainer retain];
486         @synchronized(viewContainer) {
487             if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
488                 msg_Err(_voutDisplay, "void pointer not an ObjC object");
489                 return;
490             }
492             if (![viewContainer isKindOfClass:[UIView class]]) {
493                 msg_Err(_voutDisplay, "passed ObjC object not of class UIView");
494                 return;
495             }
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;
504             [self reshape];
506             [sys->viewContainer performSelectorOnMainThread:@selector(addSubview:)
507                                                  withObject:self
508                                               waitUntilDone:YES];
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];
517                 }
518             }
519             sys->tapRecognizer.cancelsTouchesInView = NO;
520         }
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;
525     }
528 - (void)dealloc
531     [[NSNotificationCenter defaultCenter] removeObserver:self];
532     [_eaglContext release];
533     [super dealloc];
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) {
556         if (_voutDisplay)
557             msg_Err(_voutDisplay, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
558     }
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);
569     _frameBuffer = 0;
571     /* clear render buffer */
572     glDeleteRenderbuffers(1, &_renderBuffer);
573     _renderBuffer = 0;
574     [EAGLContext setCurrentContext:previousContext];
577 - (void)resetBuffers
579     if (unlikely(_bufferNeedReset)) {
580         [self destroyBuffers];
581         [self createBuffers];
582         _bufferNeedReset = NO;
583     }
586 - (void)lock
588     _previousEaglContext = [EAGLContext currentContext];
589     [EAGLContext setCurrentContext:_eaglContext];
592 - (void)unlock
594     [EAGLContext setCurrentContext:_previousEaglContext];
597 - (void)layoutSubviews
599     [self reshape];
601     _bufferNeedReset = YES;
604 - (void)reshape
606     EAGLContext *previousContext = [EAGLContext currentContext];
607     [EAGLContext setCurrentContext:_eaglContext];
609     CGSize viewSize = [self bounds].size;
611     vout_display_place_t place;
613     @synchronized (self) {
614         if (_voutDisplay) {
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);
625         }
626     }
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])
652         _appActive = NO;
653     else
654         _appActive = YES;
655     }
658 - (void)updateConstraints
660     [super updateConstraints];
661     [self reshape];
664 - (BOOL)isOpaque
666     return YES;
669 - (BOOL)acceptsFirstResponder
671     return YES;
674 @end