]> git.sesse.net Git - vlc/blob - modules/video_output/ios2.m
goom: fix error path
[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: Derk-Jan Hartman <hartman at videolan dot org>
8  *          Eric Petit <titer@m0k.org>
9  *          Benjamin Pracht <bigben at videolan dot org>
10  *          Damien Fouilleul <damienf at videolan dot org>
11  *          Pierre d'Herbemont <pdherbemont at videolan dot org>
12  *          Felix Paul Kühne <fkuehne at videolan dot org>
13  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
14  *          Rémi Denis-Courmont
15  *          Juho Vähä-Herttua <juhovh at iki dot fi>
16  *          Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
17  *
18  * This program is free software; you can redistribute it and/or modify it
19  * under the terms of the GNU Lesser General Public License as published by
20  * the Free Software Foundation; either version 2.1 of the License, or
21  * (at your option) any later version.
22  *
23  * This program is distributed in the hope that it will be useful,
24  * but WITHOUT ANY WARRANTY; without even the implied warranty of
25  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26  * GNU Lesser General Public License for more details.
27  *
28  * You should have received a copy of the GNU Lesser General Public License
29  * along with this program; if not, write to the Free Software Foundation,
30  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
31  *****************************************************************************/
32
33 /*****************************************************************************
34  * Preamble
35  *****************************************************************************/
36
37 #import <UIKit/UIKit.h>
38 #import <OpenGLES/EAGL.h>
39 #import <OpenGLES/ES2/gl.h>
40 #import <QuartzCore/QuartzCore.h>
41 #import <dlfcn.h>
42
43 #ifdef HAVE_CONFIG_H
44 # include "config.h"
45 #endif
46
47 #include <vlc_common.h>
48 #include <vlc_plugin.h>
49 #include <vlc_vout_display.h>
50 #include <vlc_opengl.h>
51 #include <vlc_dialog.h>
52 #include "opengl.h"
53
54 /**
55  * Forward declarations
56  */
57 static int Open (vlc_object_t *);
58 static void Close (vlc_object_t *);
59
60 static picture_pool_t* PicturePool(vout_display_t *vd, unsigned requested_count);
61 static void PictureRender(vout_display_t* vd, picture_t *pic, subpicture_t *subpicture);
62 static void PictureDisplay(vout_display_t* vd, picture_t *pic, subpicture_t *subpicture);
63 static int Control(vout_display_t* vd, int query, va_list ap);
64
65 static void *OurGetProcAddress(vlc_gl_t *, const char *);
66
67 static int OpenglESClean(vlc_gl_t* gl);
68 static void OpenglESSwap(vlc_gl_t* gl);
69
70 /**
71  * Module declaration
72  */
73 vlc_module_begin ()
74     set_shortname ("iOS vout")
75     set_description (N_("iOS OpenGL video output"))
76     set_category (CAT_VIDEO)
77     set_subcategory (SUBCAT_VIDEO_VOUT)
78     set_capability ("vout display", 300)
79     set_callbacks (Open, Close)
80
81     add_shortcut ("vout_ios2")
82 vlc_module_end ()
83
84 @interface VLCOpenGLES2VideoView : UIView
85 {
86     vout_display_t *_voutDisplay;
87     EAGLContext *_eaglContext;
88     GLuint _renderBuffer;
89     GLuint _frameBuffer;
90
91     BOOL _bufferNeedReset;
92 }
93 @property (readwrite) vout_display_t* voutDisplay;
94 @property (readonly) EAGLContext* eaglContext;
95
96 - (void)createBuffers;
97 - (void)destroyBuffers;
98 - (void)resetBuffers;
99
100 @end
101
102
103 struct vout_display_sys_t
104 {
105     VLCOpenGLES2VideoView *glESView;
106     UIView* viewContainer;
107
108     vlc_gl_t gl;
109     vout_display_opengl_t *vgl;
110
111     picture_pool_t *picturePool;
112     picture_t *current;
113     bool has_first_frame;
114
115     vout_display_place_t place;
116 };
117
118
119 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
120 {
121     VLC_UNUSED(gl);
122
123     return dlsym(RTLD_DEFAULT, name);
124 }
125
126 static int Open(vlc_object_t *this)
127 {
128     vout_display_t *vd = (vout_display_t *)this;
129     vout_display_sys_t *sys = calloc (1, sizeof(*sys));
130     NSAutoreleasePool *autoreleasePool = nil;
131
132     if (!sys)
133         return VLC_ENOMEM;
134
135     vd->sys = sys;
136     sys->picturePool = NULL;
137     sys->gl.sys = NULL;
138
139     autoreleasePool = [[NSAutoreleasePool alloc] init];
140
141     /* get the object we will draw into */
142     UIView* viewContainer = var_CreateGetAddress (vd, "drawable-nsobject");
143     if (!viewContainer || ![viewContainer isKindOfClass:[UIView class]])
144         goto bailout;
145
146     vout_display_DeleteWindow (vd, NULL);
147
148     /* This will be released in Close(), on
149      * main thread, after we are done using it. */
150     sys->viewContainer = [viewContainer retain];
151
152     /* setup the actual OpenGL ES view */
153     sys->glESView = [[VLCOpenGLES2VideoView alloc] initWithFrame:[viewContainer bounds]];
154
155     if (!sys->glESView)
156         goto bailout;
157
158     [sys->glESView setVoutDisplay:vd];
159
160     [sys->viewContainer performSelectorOnMainThread:@selector(addSubview:) withObject:sys->glESView waitUntilDone:YES];
161
162     /* Initialize common OpenGL video display */
163     sys->gl.lock = OpenglESClean;
164     sys->gl.unlock = nil;
165     sys->gl.swap = OpenglESSwap;
166     sys->gl.getProcAddress = OurGetProcAddress;
167     sys->gl.sys = sys;
168     const vlc_fourcc_t *subpicture_chromas;
169     video_format_t fmt = vd->fmt;
170
171     sys->vgl = vout_display_opengl_New (&vd->fmt, &subpicture_chromas, &sys->gl);
172     if (!sys->vgl) {
173         sys->gl.sys = NULL;
174         goto bailout;
175     }
176
177     /* */
178     vout_display_info_t info = vd->info;
179     info.has_pictures_invalid = false;
180     info.has_event_thread = true;
181     info.subpicture_chromas = subpicture_chromas;
182
183     /* Setup vout_display_t once everything is fine */
184     vd->info = info;
185
186     vd->pool = PicturePool;
187     vd->prepare = PictureRender;
188     vd->display = PictureDisplay;
189     vd->control = Control;
190
191     /* */
192     [sys->glESView performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:YES];
193
194     [autoreleasePool release];
195     return VLC_SUCCESS;
196
197 bailout:
198     [autoreleasePool release];
199     Close(this);
200     return VLC_EGENERIC;
201 }
202
203 void Close (vlc_object_t *this)
204 {
205     vout_display_t *vd = (vout_display_t *)this;
206     vout_display_sys_t *sys = vd->sys;
207
208     [sys->glESView setVoutDisplay:nil];
209
210     var_Destroy (vd, "drawable-nsobject");
211     [sys->viewContainer performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
212     [sys->glESView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
213
214     if (sys->gl.sys != NULL) {
215         msg_Dbg(this, "deleting display");
216         vout_display_opengl_Delete(sys->vgl);
217     }
218
219     [sys->glESView release];
220
221     free(sys);
222 }
223
224 /*****************************************************************************
225  * vout display callbacks
226  *****************************************************************************/
227
228 static int Control(vout_display_t *vd, int query, va_list ap)
229 {
230     vout_display_sys_t *sys = vd->sys;
231
232     switch (query)
233     {
234         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
235         case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
236         case VOUT_DISPLAY_HIDE_MOUSE:
237             return VLC_SUCCESS;
238         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
239         case VOUT_DISPLAY_CHANGE_ZOOM:
240         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
241         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
242         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
243         {
244             if (!vd->sys)
245                 return VLC_EGENERIC;
246
247             NSAutoreleasePool * autoreleasePool = [[NSAutoreleasePool alloc] init];
248
249             const vout_display_cfg_t *cfg;
250             const video_format_t *source;
251             bool is_forced = false;
252
253             if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
254                 source = (const video_format_t *)va_arg(ap, const video_format_t *);
255                 cfg = vd->cfg;
256             } else {
257                 source = &vd->source;
258                 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
259                 if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
260                     is_forced = (bool)va_arg(ap, int);
261             }
262
263             if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE && is_forced
264                 && (cfg->display.width != vd->cfg->display.width
265                     || cfg->display.height != vd->cfg->display.height))
266                 return VLC_EGENERIC;
267
268             /* we always use our current frame here, because we have some size constraints
269              in the ui vout provider */
270             vout_display_cfg_t cfg_tmp = *cfg;
271             CGRect bounds;
272             bounds = [sys->glESView bounds];
273
274             /* on HiDPI displays, the point bounds don't equal the actual pixel based bounds */
275             CGFloat scaleFactor = sys->glESView.contentScaleFactor;
276             cfg_tmp.display.width = bounds.size.width * scaleFactor;
277             cfg_tmp.display.height = bounds.size.height * scaleFactor;
278
279             vout_display_place_t place;
280             vout_display_PlacePicture(&place, source, &cfg_tmp, false);
281             @synchronized (sys->glESView) {
282                 sys->place = place;
283             }
284
285             /* For resize, we call glViewport in reshape and not here.
286              This has the positive side effect that we avoid erratic sizing as we animate every resize. */
287             if (query != VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
288                 // x / y are top left corner, but we need the lower left one
289                 glViewport(place.x, cfg_tmp.display.height - (place.y + place.height), place.width, place.height);
290
291             [autoreleasePool release];
292             return VLC_SUCCESS;
293         }
294
295         case VOUT_DISPLAY_GET_OPENGL:
296         {
297             vlc_gl_t **gl = va_arg(ap, vlc_gl_t **);
298             *gl = &sys->gl;
299             return VLC_SUCCESS;
300         }
301
302         case VOUT_DISPLAY_RESET_PICTURES:
303             assert (0);
304         default:
305             msg_Err(vd, "Unknown request %i in iOS ES 2 vout display", query);
306             return VLC_EGENERIC;
307     }
308 }
309
310 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
311 {
312     vout_display_sys_t *sys = vd->sys;
313     sys->has_first_frame = true;
314     vout_display_opengl_Display(sys->vgl, &vd->source);
315
316     picture_Release(pic);
317
318     if (subpicture)
319         subpicture_Delete(subpicture);
320 }
321
322 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
323 {
324
325     vout_display_sys_t *sys = vd->sys;
326
327     vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
328 }
329
330 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
331 {
332     vout_display_sys_t *sys = vd->sys;
333
334     if (!sys->picturePool)
335         sys->picturePool = vout_display_opengl_GetPool(sys->vgl, requested_count);
336     assert(sys->picturePool);
337     return sys->picturePool;
338 }
339
340 /*****************************************************************************
341  * vout opengl callbacks
342  *****************************************************************************/
343 static int OpenglESClean(vlc_gl_t *gl)
344 {
345     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
346     [sys->glESView resetBuffers];
347     return 0;
348 }
349
350 static void OpenglESSwap(vlc_gl_t *gl)
351 {
352     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
353     [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
354 }
355
356 /*****************************************************************************
357  * Our UIView object
358  *****************************************************************************/
359 @implementation VLCOpenGLES2VideoView
360 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext;
361
362 + (Class)layerClass
363 {
364     return [CAEAGLLayer class];
365 }
366
367 - (id)initWithFrame:(CGRect)frame
368 {
369     self = [super initWithFrame:frame]; // perform selector on main thread?
370
371     if (!self)
372         return nil;
373
374     CAEAGLLayer * layer = (CAEAGLLayer *)self.layer;
375     layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
376     layer.opaque = YES;
377
378     _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
379     if (!_eaglContext)
380         return nil;
381     [EAGLContext setCurrentContext:_eaglContext];
382
383     [self performSelectorOnMainThread:@selector(createBuffers) withObject:nil waitUntilDone:YES];
384     [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
385     [self setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
386
387     return self;
388 }
389
390 - (void)dealloc
391 {
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         NSLog(@"actually resetting buffers");
443         [self destroyBuffers];
444         [self createBuffers];
445         _bufferNeedReset = NO;
446     }
447 }
448
449 - (void)layoutSubviews
450 {
451     NSLog(@"layoutSubviews");
452     /* this method is called as soon as we are resized.
453      * so set a variable to re-create our buffers on the next clean event */
454     _bufferNeedReset = YES;
455 }
456
457 /**
458  * Method called by Cocoa when the view is resized.
459  */
460 - (void)reshape
461 {
462     assert([[NSThread currentThread] isMainThread]);
463
464     CGRect bounds;
465     bounds = [self bounds];
466
467     vout_display_place_t place;
468
469     @synchronized(self) {
470         if (_voutDisplay) {
471             vout_display_cfg_t cfg_tmp = *(_voutDisplay->cfg);
472             CGFloat scaleFactor = self.contentScaleFactor;
473
474             cfg_tmp.display.width  = bounds.size.width * scaleFactor;
475             cfg_tmp.display.height = bounds.size.height * scaleFactor;
476
477             vout_display_PlacePicture(&place, &_voutDisplay->source, &cfg_tmp, false);
478             _voutDisplay->sys->place = place;
479             vout_display_SendEventDisplaySize(_voutDisplay, bounds.size.width * scaleFactor, bounds.size.height * scaleFactor, _voutDisplay->cfg->is_fullscreen);
480         }
481     }
482
483     // x / y are top left corner, but we need the lower left one
484     glViewport(place.x, place.y, place.width, place.height);
485 }
486
487 - (void)updateConstraints
488 {
489     [self reshape];
490     [super updateConstraints];
491 }
492
493 - (BOOL)isOpaque
494 {
495     return YES;
496 }
497
498 - (BOOL)acceptsFirstResponder
499 {
500     return YES;
501 }
502
503 @end