]> git.sesse.net Git - vlc/blob - modules/video_output/ios2.m
3efeb1207b2dab0d43bc418c18a8b4de88317341
[vlc] / modules / video_output / ios2.m
1 /*****************************************************************************
2  * ios2.m: iOS OpenGL ES 2 provider
3  *****************************************************************************
4  * Copyright (C) 2001-2014 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  *
15  * This program is free software; you can redistribute it and/or modify it
16  * under the terms of the GNU Lesser General Public License as published by
17  * the Free Software Foundation; either version 2.1 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23  * GNU Lesser General Public License for more details.
24  *
25  * You should have received a copy of the GNU Lesser General Public License
26  * along with this program; if not, write to the Free Software Foundation,
27  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
28  *****************************************************************************/
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33
34 #import <UIKit/UIKit.h>
35 #import <OpenGLES/EAGL.h>
36 #import <OpenGLES/ES2/gl.h>
37 #import <QuartzCore/QuartzCore.h>
38 #import <dlfcn.h>
39
40 #ifdef HAVE_CONFIG_H
41 # include "config.h"
42 #endif
43
44 #include <vlc_common.h>
45 #include <vlc_plugin.h>
46 #include <vlc_vout_display.h>
47 #include <vlc_opengl.h>
48 #include <vlc_dialog.h>
49 #include "opengl.h"
50
51 /**
52  * Forward declarations
53  */
54 static int Open(vlc_object_t *);
55 static void Close(vlc_object_t *);
56
57 static picture_pool_t* PicturePool(vout_display_t *vd, unsigned requested_count);
58 static void PictureRender(vout_display_t* vd, picture_t *pic, subpicture_t *subpicture);
59 static void PictureDisplay(vout_display_t* vd, picture_t *pic, subpicture_t *subpicture);
60 static int Control(vout_display_t* vd, int query, va_list ap);
61
62 static void *OurGetProcAddress(vlc_gl_t *, const char *);
63
64 static int OpenglESClean(vlc_gl_t* gl);
65 static void OpenglESSwap(vlc_gl_t* gl);
66
67 /**
68  * Module declaration
69  */
70 vlc_module_begin ()
71     set_shortname("iOS vout")
72     set_description(N_("iOS OpenGL video output"))
73     set_category(CAT_VIDEO)
74     set_subcategory(SUBCAT_VIDEO_VOUT)
75     set_capability("vout display", 300)
76     set_callbacks(Open, Close)
77
78     add_shortcut("vout_ios2")
79 vlc_module_end ()
80
81 @interface VLCOpenGLES2VideoView : UIView {
82     vout_display_t *_voutDisplay;
83     EAGLContext *_eaglContext;
84     GLuint _renderBuffer;
85     GLuint _frameBuffer;
86
87     BOOL _bufferNeedReset;
88     BOOL _appActive;
89 }
90 @property (readwrite) vout_display_t* voutDisplay;
91 @property (readonly) EAGLContext* eaglContext;
92 @property (readonly) BOOL isAppActive;
93
94 - (void)createBuffers;
95 - (void)destroyBuffers;
96 - (void)resetBuffers;
97 @end
98
99 struct vout_display_sys_t
100 {
101     VLCOpenGLES2VideoView *glESView;
102     UIView* viewContainer;
103     UITapGestureRecognizer *tapRecognizer;
104
105     vlc_gl_t gl;
106     vout_display_opengl_t *vgl;
107
108     picture_pool_t *picturePool;
109     bool has_first_frame;
110
111     vout_display_place_t place;
112 };
113
114 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
115 {
116     VLC_UNUSED(gl);
117
118     return dlsym(RTLD_DEFAULT, name);
119 }
120
121 static int Open(vlc_object_t *this)
122 {
123     vout_display_t *vd = (vout_display_t *)this;
124
125     if (vout_display_IsWindowed(vd))
126         return VLC_EGENERIC;
127
128     vout_display_sys_t *sys = calloc (1, sizeof(*sys));
129     NSAutoreleasePool *autoreleasePool = nil;
130
131     if (!sys)
132         return VLC_ENOMEM;
133
134     vd->sys = sys;
135     sys->picturePool = NULL;
136     sys->gl.sys = NULL;
137
138     autoreleasePool = [[NSAutoreleasePool alloc] init];
139
140     /* get the object we will draw into */
141     UIView* viewContainer = var_CreateGetAddress (vd, "drawable-nsobject");
142     if (!viewContainer || ![viewContainer isKindOfClass:[UIView class]])
143         goto bailout;
144
145     /* This will be released in Close(), on
146      * main thread, after we are done using it. */
147     sys->viewContainer = [viewContainer retain];
148
149     /* setup the actual OpenGL ES view */
150     sys->glESView = [[VLCOpenGLES2VideoView alloc] initWithFrame:[viewContainer bounds]];
151
152     if (!sys->glESView)
153         goto bailout;
154
155     [sys->glESView setVoutDisplay:vd];
156
157     [sys->viewContainer performSelectorOnMainThread:@selector(addSubview:)
158                                          withObject:sys->glESView
159                                       waitUntilDone:YES];
160
161     /* add tap gesture recognizer for DVD menus and stuff */
162     sys->tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:sys->glESView
163                                                                  action:@selector(tapRecognized:)];
164     sys->tapRecognizer.numberOfTapsRequired = 2;
165     if (sys->viewContainer.window) {
166         if (sys->viewContainer.window.rootViewController) {
167             if (sys->viewContainer.window.rootViewController.view)
168                 [sys->viewContainer.superview addGestureRecognizer:sys->tapRecognizer];
169         }
170     }
171     sys->tapRecognizer.cancelsTouchesInView = NO;
172
173     /* Initialize common OpenGL video display */
174     sys->gl.lock = OpenglESClean;
175     sys->gl.unlock = nil;
176     sys->gl.swap = OpenglESSwap;
177     sys->gl.getProcAddress = OurGetProcAddress;
178     sys->gl.sys = sys;
179     const vlc_fourcc_t *subpicture_chromas;
180     video_format_t fmt = vd->fmt;
181
182     sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas, &sys->gl);
183     if (!sys->vgl) {
184         sys->gl.sys = NULL;
185         goto bailout;
186     }
187
188     /* */
189     vout_display_info_t info = vd->info;
190     info.has_pictures_invalid = false;
191     info.has_event_thread = true;
192     info.subpicture_chromas = subpicture_chromas;
193     info.has_hide_mouse = false;
194
195     /* Setup vout_display_t once everything is fine */
196     vd->info = info;
197
198     vd->pool = PicturePool;
199     vd->prepare = PictureRender;
200     vd->display = PictureDisplay;
201     vd->control = Control;
202
203     /* forward our dimensions to the vout core */
204     CGSize viewSize = sys->viewContainer.frame.size;
205     vout_display_SendEventFullscreen(vd, false);
206     vout_display_SendEventDisplaySize(vd, (int)viewSize.width, (int)viewSize.height);
207
208     /* */
209     [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
210                                              selector:@selector(applicationStateChanged:)
211                                                  name:UIApplicationWillResignActiveNotification
212                                                object:nil];
213     [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
214                                              selector:@selector(applicationStateChanged:)
215                                                  name:UIApplicationDidBecomeActiveNotification
216                                                object:nil];
217     [sys->glESView performSelectorOnMainThread:@selector(reshape)
218                                     withObject:nil
219                                  waitUntilDone:YES];
220
221     [autoreleasePool release];
222     return VLC_SUCCESS;
223
224 bailout:
225     [autoreleasePool release];
226     Close(this);
227     return VLC_EGENERIC;
228 }
229
230 void Close (vlc_object_t *this)
231 {
232     vout_display_t *vd = (vout_display_t *)this;
233     vout_display_sys_t *sys = vd->sys;
234
235     if (sys->tapRecognizer) {
236         [sys->glESView removeGestureRecognizer:sys->tapRecognizer];
237         [sys->tapRecognizer release];
238     }
239
240     [sys->glESView setVoutDisplay:nil];
241
242     var_Destroy (vd, "drawable-nsobject");
243     [sys->viewContainer performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
244     [sys->glESView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
245
246     if (sys->gl.sys != NULL) {
247         msg_Dbg(this, "deleting display");
248         vout_display_opengl_Delete(sys->vgl);
249     }
250
251     [sys->glESView release];
252
253     free(sys);
254 }
255
256 /*****************************************************************************
257  * vout display callbacks
258  *****************************************************************************/
259
260 static int Control(vout_display_t *vd, int query, va_list ap)
261 {
262     vout_display_sys_t *sys = vd->sys;
263
264     switch (query) {
265         case VOUT_DISPLAY_HIDE_MOUSE:
266             return VLC_EGENERIC;
267
268         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
269         case VOUT_DISPLAY_CHANGE_ZOOM:
270         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
271         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
272         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
273         {
274             if (!vd->sys)
275                 return VLC_EGENERIC;
276
277             NSAutoreleasePool * autoreleasePool = [[NSAutoreleasePool alloc] init];
278
279             const vout_display_cfg_t *cfg;
280             const video_format_t *source;
281
282             if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
283                 source = (const video_format_t *)va_arg(ap, const video_format_t *);
284                 cfg = vd->cfg;
285             } else {
286                 source = &vd->source;
287                 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
288             }
289
290             /* we don't adapt anything here regardless of what the vout core
291              * wants since we are not in a traditional desktop window */
292             if (!cfg)
293                 return VLC_EGENERIC;
294
295             vout_display_cfg_t cfg_tmp = *cfg;
296             CGSize viewSize;
297             viewSize = [sys->glESView bounds].size;
298
299             /* on HiDPI displays, the point bounds don't equal the actual pixels */
300             CGFloat scaleFactor = sys->glESView.contentScaleFactor;
301             cfg_tmp.display.width = viewSize.width * scaleFactor;
302             cfg_tmp.display.height = viewSize.height * scaleFactor;
303
304             vout_display_place_t place;
305             vout_display_PlacePicture(&place, source, &cfg_tmp, false);
306             @synchronized (sys->glESView) {
307                 sys->place = place;
308             }
309
310             // x / y are top left corner, but we need the lower left one
311             if (query != VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
312                 glViewport(place.x, cfg_tmp.display.height - (place.y + place.height), place.width, place.height);
313
314             [autoreleasePool release];
315             return VLC_SUCCESS;
316         }
317
318         case VOUT_DISPLAY_RESET_PICTURES:
319             assert (0);
320         default:
321             msg_Err(vd, "Unknown request %d", query);
322         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
323             return VLC_EGENERIC;
324     }
325 }
326
327 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
328 {
329     vout_display_sys_t *sys = vd->sys;
330     sys->has_first_frame = true;
331     if (likely([sys->glESView isAppActive]))
332         vout_display_opengl_Display(sys->vgl, &vd->source);
333
334     picture_Release(pic);
335
336     if (subpicture)
337         subpicture_Delete(subpicture);
338 }
339
340 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
341 {
342     vout_display_sys_t *sys = vd->sys;
343
344     if (likely([sys->glESView isAppActive]))
345         vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
346 }
347
348 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
349 {
350     vout_display_sys_t *sys = vd->sys;
351
352     if (!sys->picturePool)
353         sys->picturePool = vout_display_opengl_GetPool(sys->vgl, requested_count);
354     assert(sys->picturePool);
355     return sys->picturePool;
356 }
357
358 /*****************************************************************************
359  * vout opengl callbacks
360  *****************************************************************************/
361 static int OpenglESClean(vlc_gl_t *gl)
362 {
363     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
364     if (likely([sys->glESView isAppActive]))
365         [sys->glESView resetBuffers];
366     return 0;
367 }
368
369 static void OpenglESSwap(vlc_gl_t *gl)
370 {
371     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
372     if (likely([sys->glESView isAppActive]))
373         [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
374 }
375
376 /*****************************************************************************
377  * Our UIView object
378  *****************************************************************************/
379 @implementation VLCOpenGLES2VideoView
380 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext, isAppActive = _appActive;
381
382 + (Class)layerClass
383 {
384     return [CAEAGLLayer class];
385 }
386
387 - (id)initWithFrame:(CGRect)frame
388 {
389     self = [super initWithFrame:frame];
390
391     if (!self)
392         return nil;
393
394     CAEAGLLayer * layer = (CAEAGLLayer *)self.layer;
395     layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
396     layer.opaque = YES;
397
398     _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
399     if (!_eaglContext)
400         return nil;
401     [EAGLContext setCurrentContext:_eaglContext];
402
403     [self performSelectorOnMainThread:@selector(createBuffers) withObject:nil waitUntilDone:YES];
404     [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
405     [self setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
406
407     _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
408
409     return self;
410 }
411
412 - (void)dealloc
413 {
414     [[NSNotificationCenter defaultCenter] removeObserver:self];
415     [_eaglContext release];
416     [super dealloc];
417 }
418
419 - (void)didMoveToWindow
420 {
421     self.contentScaleFactor = self.window.screen.scale;
422     _bufferNeedReset = YES;
423 }
424
425 - (void)createBuffers
426 {
427     /* make sure the current context is us */
428     [EAGLContext setCurrentContext:_eaglContext];
429
430     /* create render buffer */
431     glGenRenderbuffers(1, &_renderBuffer);
432     glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
433
434     /* create frame buffer */
435     glGenFramebuffers(1, &_frameBuffer);
436     glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
437
438     /* allocate storage for the pixels we are going to to draw to */
439     [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];
440
441     /* bind render buffer to frame buffer */
442     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
443
444     /* make sure that our shape is ok */
445     [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
446 }
447
448 - (void)destroyBuffers
449 {
450     /* re-set current context */
451     [EAGLContext setCurrentContext:_eaglContext];
452
453     /* clear frame buffer */
454     glDeleteFramebuffers(1, &_frameBuffer);
455     _frameBuffer = 0;
456
457     /* clear render buffer */
458     glDeleteRenderbuffers(1, &_renderBuffer);
459     _renderBuffer = 0;
460 }
461
462 - (void)resetBuffers
463 {
464     if (_bufferNeedReset) {
465         [self destroyBuffers];
466         [self createBuffers];
467         _bufferNeedReset = NO;
468     }
469 }
470
471 - (void)layoutSubviews
472 {
473     /* this method is called as soon as we are resized.
474      * so set a variable to re-create our buffers on the next clean event */
475     _bufferNeedReset = YES;
476 }
477
478 - (void)reshape
479 {
480     assert([[NSThread currentThread] isMainThread]);
481
482     [EAGLContext setCurrentContext:_eaglContext];
483
484     CGSize viewSize = [self bounds].size;
485
486     vout_display_place_t place;
487
488     @synchronized(self) {
489         if (_voutDisplay) {
490             vout_display_cfg_t cfg_tmp = *(_voutDisplay->cfg);
491             CGFloat scaleFactor = self.contentScaleFactor;
492
493             cfg_tmp.display.width  = viewSize.width * scaleFactor;
494             cfg_tmp.display.height = viewSize.height * scaleFactor;
495
496             vout_display_PlacePicture(&place, &_voutDisplay->source, &cfg_tmp, false);
497             _voutDisplay->sys->place = place;
498             vout_display_SendEventDisplaySize(_voutDisplay, viewSize.width * scaleFactor,
499                                               viewSize.height * scaleFactor);
500         }
501     }
502
503     // x / y are top left corner, but we need the lower left one
504     glViewport(place.x, place.y, place.width, place.height);
505 }
506
507 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
508 {
509     UIGestureRecognizerState state = [tapRecognizer state];
510     CGPoint touchPoint = [tapRecognizer locationInView:self];
511     CGFloat scaleFactor = self.contentScaleFactor;
512     vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay, ORIENT_NORMAL,
513                                                   (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor,
514                                                   &_voutDisplay->sys->place);
515
516     vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
517     vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
518 }
519
520 - (void)applicationStateChanged:(NSNotification *)notification
521 {
522     if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification]
523         || [[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification]
524         || [[notification name] isEqualToString:UIApplicationWillTerminateNotification])
525         _appActive = NO;
526     else
527         _appActive = YES;
528 }
529
530 - (void)updateConstraints
531 {
532     [self reshape];
533     [super updateConstraints];
534 }
535
536 - (BOOL)isOpaque
537 {
538     return YES;
539 }
540
541 - (BOOL)acceptsFirstResponder
542 {
543     return YES;
544 }
545
546 @end