]> git.sesse.net Git - vlc/blob - modules/video_output/ios2.m
28369b4f68afc33326736921a701878e8d099571
[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     [sys->glESView resetBuffers];
343     return 0;
344 }
345
346 static void OpenglESSwap(vlc_gl_t *gl)
347 {
348     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
349     if (likely([sys->glESView isAppActive]))
350         [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
351 }
352
353 /*****************************************************************************
354  * Our UIView object
355  *****************************************************************************/
356 @implementation VLCOpenGLES2VideoView
357 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext, isAppActive = _appActive;
358
359 + (Class)layerClass
360 {
361     return [CAEAGLLayer class];
362 }
363
364 - (id)initWithFrame:(CGRect)frame
365 {
366     self = [super initWithFrame:frame]; // perform selector on main thread?
367
368     if (!self)
369         return nil;
370
371     CAEAGLLayer * layer = (CAEAGLLayer *)self.layer;
372     layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
373     layer.opaque = YES;
374
375     _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
376     if (!_eaglContext)
377         return nil;
378     [EAGLContext setCurrentContext:_eaglContext];
379
380     [self performSelectorOnMainThread:@selector(createBuffers) withObject:nil waitUntilDone:YES];
381     [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
382     [self setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
383
384     _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
385
386     return self;
387 }
388
389 - (void)dealloc
390 {
391     [[NSNotificationCenter defaultCenter] removeObserver:self];
392     [_eaglContext release];
393     [super dealloc];
394 }
395
396 /* we don't get the correct scale factor if we don't overwrite this method */
397 - (void)drawRect:(CGRect) rect
398 {
399     [super drawRect:rect];
400 }
401
402 - (void)createBuffers
403 {
404     /* make sure the current context is us */
405     [EAGLContext setCurrentContext:_eaglContext];
406
407     /* create render buffer */
408     glGenRenderbuffers(1, &_renderBuffer);
409     glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
410
411     /* create frame buffer */
412     glGenFramebuffers(1, &_frameBuffer);
413     glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
414
415     /* allocate storage for the pixels we are going to to draw to */
416     [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];
417
418     /* bind render buffer to frame buffer */
419     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
420
421     /* make sure that our shape is ok */
422     [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
423 }
424
425 - (void)destroyBuffers
426 {
427     /* re-set current context */
428     [EAGLContext setCurrentContext:_eaglContext];
429
430     /* clear frame buffer */
431     glDeleteFramebuffers(1, &_frameBuffer);
432     _frameBuffer = 0;
433
434     /* clear render buffer */
435     glDeleteRenderbuffers(1, &_renderBuffer);
436     _renderBuffer = 0;
437 }
438
439 - (void)resetBuffers
440 {
441     if (_bufferNeedReset) {
442         [self destroyBuffers];
443         [self createBuffers];
444         _bufferNeedReset = NO;
445     }
446 }
447
448 - (void)layoutSubviews
449 {
450     /* this method is called as soon as we are resized.
451      * so set a variable to re-create our buffers on the next clean event */
452     _bufferNeedReset = YES;
453 }
454
455 /**
456  * Method called by Cocoa when the view is resized.
457  */
458 - (void)reshape
459 {
460     assert([[NSThread currentThread] isMainThread]);
461
462     CGRect bounds;
463     bounds = [self bounds];
464
465     vout_display_place_t place;
466
467     @synchronized(self) {
468         if (_voutDisplay) {
469             vout_display_cfg_t cfg_tmp = *(_voutDisplay->cfg);
470             CGFloat scaleFactor = self.contentScaleFactor;
471
472             cfg_tmp.display.width  = bounds.size.width * scaleFactor;
473             cfg_tmp.display.height = bounds.size.height * scaleFactor;
474
475             vout_display_PlacePicture(&place, &_voutDisplay->source, &cfg_tmp, false);
476             _voutDisplay->sys->place = place;
477             vout_display_SendEventDisplaySize(_voutDisplay, bounds.size.width * scaleFactor, bounds.size.height * scaleFactor, _voutDisplay->cfg->is_fullscreen);
478         }
479     }
480
481     // x / y are top left corner, but we need the lower left one
482     glViewport(place.x, place.y, place.width, place.height);
483 }
484
485 - (void)applicationStateChanged:(NSNotification *)notification
486 {
487     if ([[notification name] isEqualToString: UIApplicationWillResignActiveNotification])
488         _appActive = NO;
489     else
490         _appActive = YES;
491 }
492
493 - (void)updateConstraints
494 {
495     [self reshape];
496     [super updateConstraints];
497 }
498
499 - (BOOL)isOpaque
500 {
501     return YES;
502 }
503
504 - (BOOL)acceptsFirstResponder
505 {
506     return YES;
507 }
508
509 @end