]> git.sesse.net Git - vlc/blob - modules/video_output/ios2.m
vout_ios: use a broader background definition (refs #9431)
[vlc] / modules / video_output / ios2.m
1 /*****************************************************************************
2  * ios2.m: iOS OpenGL ES 2 provider
3  *****************************************************************************
4  * Copyright (C) 2001-2013 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
104     vlc_gl_t gl;
105     vout_display_opengl_t *vgl;
106
107     picture_pool_t *picturePool;
108     bool has_first_frame;
109
110     vout_display_place_t place;
111 };
112
113 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
114 {
115     VLC_UNUSED(gl);
116
117     return dlsym(RTLD_DEFAULT, name);
118 }
119
120 static int Open(vlc_object_t *this)
121 {
122     vout_display_t *vd = (vout_display_t *)this;
123     vout_display_sys_t *sys = calloc (1, sizeof(*sys));
124     NSAutoreleasePool *autoreleasePool = nil;
125
126     if (!sys)
127         return VLC_ENOMEM;
128
129     vd->sys = sys;
130     sys->picturePool = NULL;
131     sys->gl.sys = NULL;
132
133     autoreleasePool = [[NSAutoreleasePool alloc] init];
134
135     /* get the object we will draw into */
136     UIView* viewContainer = var_CreateGetAddress (vd, "drawable-nsobject");
137     if (!viewContainer || ![viewContainer isKindOfClass:[UIView class]])
138         goto bailout;
139
140     vout_display_DeleteWindow (vd, NULL);
141
142     /* This will be released in Close(), on
143      * main thread, after we are done using it. */
144     sys->viewContainer = [viewContainer retain];
145
146     /* setup the actual OpenGL ES view */
147     sys->glESView = [[VLCOpenGLES2VideoView alloc] initWithFrame:[viewContainer bounds]];
148
149     if (!sys->glESView)
150         goto bailout;
151
152     [sys->glESView setVoutDisplay:vd];
153
154     [sys->viewContainer performSelectorOnMainThread:@selector(addSubview:) withObject:sys->glESView waitUntilDone:YES];
155
156     /* Initialize common OpenGL video display */
157     sys->gl.lock = OpenglESClean;
158     sys->gl.unlock = nil;
159     sys->gl.swap = OpenglESSwap;
160     sys->gl.getProcAddress = OurGetProcAddress;
161     sys->gl.sys = sys;
162     const vlc_fourcc_t *subpicture_chromas;
163     video_format_t fmt = vd->fmt;
164
165     sys->vgl = vout_display_opengl_New (&vd->fmt, &subpicture_chromas, &sys->gl);
166     if (!sys->vgl) {
167         sys->gl.sys = NULL;
168         goto bailout;
169     }
170
171     /* */
172     vout_display_info_t info = vd->info;
173     info.has_pictures_invalid = false;
174     info.has_event_thread = true;
175     info.subpicture_chromas = subpicture_chromas;
176
177     /* Setup vout_display_t once everything is fine */
178     vd->info = info;
179
180     vd->pool = PicturePool;
181     vd->prepare = PictureRender;
182     vd->display = PictureDisplay;
183     vd->control = Control;
184
185     /* */
186     [[NSNotificationCenter defaultCenter] addObserver:sys->glESView selector:@selector(applicationStateChanged:) name:UIApplicationWillResignActiveNotification object:nil];
187     [[NSNotificationCenter defaultCenter] addObserver:sys->glESView selector:@selector(applicationStateChanged:) name:UIApplicationDidBecomeActiveNotification object:nil];
188     [sys->glESView performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:YES];
189
190     [autoreleasePool release];
191     return VLC_SUCCESS;
192
193 bailout:
194     [autoreleasePool release];
195     Close(this);
196     return VLC_EGENERIC;
197 }
198
199 void Close (vlc_object_t *this)
200 {
201     vout_display_t *vd = (vout_display_t *)this;
202     vout_display_sys_t *sys = vd->sys;
203
204     [sys->glESView setVoutDisplay:nil];
205
206     var_Destroy (vd, "drawable-nsobject");
207     [sys->viewContainer performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
208     [sys->glESView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
209
210     if (sys->gl.sys != NULL) {
211         msg_Dbg(this, "deleting display");
212         vout_display_opengl_Delete(sys->vgl);
213     }
214
215     [sys->glESView release];
216
217     free(sys);
218 }
219
220 /*****************************************************************************
221  * vout display callbacks
222  *****************************************************************************/
223
224 static int Control(vout_display_t *vd, int query, va_list ap)
225 {
226     vout_display_sys_t *sys = vd->sys;
227
228     switch (query) {
229         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
230         case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
231         case VOUT_DISPLAY_HIDE_MOUSE:
232             return VLC_SUCCESS;
233         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
234         case VOUT_DISPLAY_CHANGE_ZOOM:
235         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
236         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
237         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
238         {
239             if (!vd->sys)
240                 return VLC_EGENERIC;
241
242             NSAutoreleasePool * autoreleasePool = [[NSAutoreleasePool alloc] init];
243
244             const vout_display_cfg_t *cfg;
245             const video_format_t *source;
246             bool is_forced = false;
247
248             if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
249                 source = (const video_format_t *)va_arg(ap, const video_format_t *);
250                 cfg = vd->cfg;
251             } else {
252                 source = &vd->source;
253                 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
254                 if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
255                     is_forced = (bool)va_arg(ap, int);
256             }
257
258             if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE && is_forced
259                 && (cfg->display.width != vd->cfg->display.width
260                     || cfg->display.height != vd->cfg->display.height))
261                 return VLC_EGENERIC;
262
263             /* we always use our current frame here, because we have some size constraints
264              in the ui vout provider */
265             vout_display_cfg_t cfg_tmp = *cfg;
266             CGRect bounds;
267             bounds = [sys->glESView bounds];
268
269             /* on HiDPI displays, the point bounds don't equal the actual pixel based bounds */
270             CGFloat scaleFactor = sys->glESView.contentScaleFactor;
271             cfg_tmp.display.width = bounds.size.width * scaleFactor;
272             cfg_tmp.display.height = bounds.size.height * scaleFactor;
273
274             vout_display_place_t place;
275             vout_display_PlacePicture(&place, source, &cfg_tmp, false);
276             @synchronized (sys->glESView) {
277                 sys->place = place;
278             }
279
280             /* For resize, we call glViewport in reshape and not here.
281              This has the positive side effect that we avoid erratic sizing as we animate every resize. */
282             if (query != VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
283                 // x / y are top left corner, but we need the lower left one
284                 glViewport(place.x, cfg_tmp.display.height - (place.y + place.height), place.width, place.height);
285
286             [autoreleasePool release];
287             return VLC_SUCCESS;
288         }
289
290         case VOUT_DISPLAY_GET_OPENGL:
291         {
292             vlc_gl_t **gl = va_arg(ap, vlc_gl_t **);
293             *gl = &sys->gl;
294             return VLC_SUCCESS;
295         }
296
297         case VOUT_DISPLAY_RESET_PICTURES:
298             assert (0);
299         default:
300             msg_Err(vd, "Unknown request %i in iOS ES 2 vout display", query);
301             return VLC_EGENERIC;
302     }
303 }
304
305 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
306 {
307     vout_display_sys_t *sys = vd->sys;
308     sys->has_first_frame = true;
309     if (likely([sys->glESView isAppActive]))
310         vout_display_opengl_Display(sys->vgl, &vd->source);
311
312     picture_Release(pic);
313
314     if (subpicture)
315         subpicture_Delete(subpicture);
316 }
317
318 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
319 {
320     vout_display_sys_t *sys = vd->sys;
321
322     if (likely([sys->glESView isAppActive]))
323         vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
324 }
325
326 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
327 {
328     vout_display_sys_t *sys = vd->sys;
329
330     if (!sys->picturePool)
331         sys->picturePool = vout_display_opengl_GetPool(sys->vgl, requested_count);
332     assert(sys->picturePool);
333     return sys->picturePool;
334 }
335
336 /*****************************************************************************
337  * vout opengl callbacks
338  *****************************************************************************/
339 static int OpenglESClean(vlc_gl_t *gl)
340 {
341     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
342     if (likely([sys->glESView isAppActive]))
343         [sys->glESView resetBuffers];
344     return 0;
345 }
346
347 static void OpenglESSwap(vlc_gl_t *gl)
348 {
349     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
350     if (likely([sys->glESView isAppActive]))
351         [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
352 }
353
354 /*****************************************************************************
355  * Our UIView object
356  *****************************************************************************/
357 @implementation VLCOpenGLES2VideoView
358 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext, isAppActive = _appActive;
359
360 + (Class)layerClass
361 {
362     return [CAEAGLLayer class];
363 }
364
365 - (id)initWithFrame:(CGRect)frame
366 {
367     self = [super initWithFrame:frame]; // perform selector on main thread?
368
369     if (!self)
370         return nil;
371
372     CAEAGLLayer * layer = (CAEAGLLayer *)self.layer;
373     layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
374     layer.opaque = YES;
375
376     _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
377     if (!_eaglContext)
378         return nil;
379     [EAGLContext setCurrentContext:_eaglContext];
380
381     [self performSelectorOnMainThread:@selector(createBuffers) withObject:nil waitUntilDone:YES];
382     [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
383     [self setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
384
385     _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
386
387     return self;
388 }
389
390 - (void)dealloc
391 {
392     [[NSNotificationCenter defaultCenter] removeObserver:self];
393     [_eaglContext release];
394     [super dealloc];
395 }
396
397 - (void)didMoveToWindow
398 {
399     self.contentScaleFactor = self.window.screen.scale;
400     _bufferNeedReset = YES;
401 }
402
403 - (void)createBuffers
404 {
405     /* make sure the current context is us */
406     [EAGLContext setCurrentContext:_eaglContext];
407
408     /* create render buffer */
409     glGenRenderbuffers(1, &_renderBuffer);
410     glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
411
412     /* create frame buffer */
413     glGenFramebuffers(1, &_frameBuffer);
414     glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
415
416     /* allocate storage for the pixels we are going to to draw to */
417     [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];
418
419     /* bind render buffer to frame buffer */
420     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
421
422     /* make sure that our shape is ok */
423     [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
424 }
425
426 - (void)destroyBuffers
427 {
428     /* re-set current context */
429     [EAGLContext setCurrentContext:_eaglContext];
430
431     /* clear frame buffer */
432     glDeleteFramebuffers(1, &_frameBuffer);
433     _frameBuffer = 0;
434
435     /* clear render buffer */
436     glDeleteRenderbuffers(1, &_renderBuffer);
437     _renderBuffer = 0;
438 }
439
440 - (void)resetBuffers
441 {
442     if (_bufferNeedReset) {
443         [self destroyBuffers];
444         [self createBuffers];
445         _bufferNeedReset = NO;
446     }
447 }
448
449 - (void)layoutSubviews
450 {
451     /* this method is called as soon as we are resized.
452      * so set a variable to re-create our buffers on the next clean event */
453     _bufferNeedReset = YES;
454 }
455
456 /**
457  * Method called by Cocoa when the view is resized.
458  */
459 - (void)reshape
460 {
461     assert([[NSThread currentThread] isMainThread]);
462
463     CGRect bounds;
464     bounds = [self bounds];
465
466     vout_display_place_t place;
467
468     @synchronized(self) {
469         if (_voutDisplay) {
470             vout_display_cfg_t cfg_tmp = *(_voutDisplay->cfg);
471             CGFloat scaleFactor = self.contentScaleFactor;
472
473             cfg_tmp.display.width  = bounds.size.width * scaleFactor;
474             cfg_tmp.display.height = bounds.size.height * scaleFactor;
475
476             vout_display_PlacePicture(&place, &_voutDisplay->source, &cfg_tmp, false);
477             _voutDisplay->sys->place = place;
478             vout_display_SendEventDisplaySize(_voutDisplay, bounds.size.width * scaleFactor, bounds.size.height * scaleFactor, _voutDisplay->cfg->is_fullscreen);
479         }
480     }
481
482     // x / y are top left corner, but we need the lower left one
483     glViewport(place.x, place.y, place.width, place.height);
484 }
485
486 - (void)applicationStateChanged:(NSNotification *)notification
487 {
488     if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification] || [[o_notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification] || [[o_notification name] isEqualToString:UIApplicationWillTerminateNotification])
489         _appActive = NO;
490     else
491         _appActive = YES;
492 }
493
494 - (void)updateConstraints
495 {
496     [self reshape];
497     [super updateConstraints];
498 }
499
500 - (BOOL)isOpaque
501 {
502     return YES;
503 }
504
505 - (BOOL)acceptsFirstResponder
506 {
507     return YES;
508 }
509
510 @end