]> git.sesse.net Git - vlc/blob - modules/video_output/ios.m
auhal: fixed 'Pause' in SPDIF mode
[vlc] / modules / video_output / ios.m
1 /*****************************************************************************
2  * ios.m: iOS X OpenGLES provider
3  *****************************************************************************
4  * Copyright (C) 2010-2013 VLC Authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Romain Goyet <romain.goyet at likid dot org>
8  *          Felix Paul Kühne <fkuehne at videolan dot org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #import <UIKit/UIKit.h>
30 #import <OpenGLES/ES1/gl.h>
31 #import <OpenGLES/ES1/glext.h>
32 #import <QuartzCore/QuartzCore.h>
33 #import <dlfcn.h>
34
35 #ifdef HAVE_CONFIG_H
36 # include "config.h"
37 #endif
38
39 #include <vlc_common.h>
40 #include <vlc_plugin.h>
41 #include <vlc_vout_display.h>
42 #include <vlc_opengl.h>
43
44 #include "opengl.h"
45
46 /**
47  * Forward declarations
48  */
49 static int Open(vlc_object_t *);
50 static void Close(vlc_object_t *);
51
52 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count);
53 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
54 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
55 static int Control (vout_display_t *vd, int query, va_list ap);
56
57 static void *OurGetProcAddress(vlc_gl_t *, const char *);
58
59 static int OpenglClean(vlc_gl_t *gl);
60 static void OpenglSwap(vlc_gl_t *gl);
61
62 /**
63  * Module declaration
64  */
65 vlc_module_begin ()
66     /* Will be loaded even without interface module. see voutgl.m */
67     set_shortname("iOS")
68     set_description( N_("iOS OpenGL ES video output (requires UIView)"))
69     set_category(CAT_VIDEO)
70     set_subcategory(SUBCAT_VIDEO_VOUT )
71     set_capability("vout display", 300)
72     set_callbacks(Open, Close)
73
74     add_shortcut("ios", "vout_ios")
75 vlc_module_end ()
76
77 @interface VLCOpenGLESVideoView : UIView
78 {
79     vout_display_t * _vd;
80     EAGLContext * _context;
81     GLuint _defaultFramebuffer;
82     GLuint _colorRenderbuffer;
83     BOOL _framebufferDirty;
84 }
85 - (id)initWithFrame:(CGRect)frame andVOutDisplay:(vout_display_t *)vd;
86 @property (readonly) EAGLContext * context;
87 @property (readonly) GLuint colorRenderbuffer;
88 - (void)setVoutDisplay:(vout_display_t *)vd;
89 - (void)cleanFramebuffer;
90 @end
91
92
93 struct vout_display_sys_t
94 {
95     VLCOpenGLESVideoView *glView;
96     UIView * container;
97
98     vlc_gl_t gl;
99     vout_display_opengl_t *vgl;
100
101     picture_pool_t *pool;
102     picture_t *current;
103     bool has_first_frame;
104 };
105
106 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
107 {
108     VLC_UNUSED(gl);
109
110     return dlsym(RTLD_DEFAULT, name);
111 }
112
113 // Called from vout thread
114 static int Open(vlc_object_t *this)
115 {
116     vout_display_t *vd = (vout_display_t *)this;
117     vout_display_sys_t *sys = calloc(1, sizeof(*sys));
118     NSAutoreleasePool *nsPool = nil;
119
120     if (!sys)
121         return VLC_ENOMEM;
122
123     vd->sys = sys;
124     sys->pool = NULL;
125     sys->gl.sys = NULL;
126
127     /* Get the drawable object */
128     UIView * container = (UIView *)var_CreateGetAddress(vd, "drawable-nsobject");
129
130     if (![container isKindOfClass:[UIView class]]) {
131         msg_Dbg(vd, "Container isn't an UIView, passing over.");
132         goto error;
133     }
134     vout_display_DeleteWindow(vd, NULL);
135
136     /* This will be released in Close(), on
137      * main thread, after we are done using it. */
138     sys->container = [container retain];
139
140     /* Get our main view*/
141     nsPool = [[NSAutoreleasePool alloc] init];
142
143     msg_Dbg(vd, "Creating VLCOpenGLESVideoView");
144     sys->glView = [[VLCOpenGLESVideoView alloc] initWithFrame:[container bounds] andVOutDisplay:vd];
145     if (!sys->glView)
146         goto error;
147
148     /* We don't wait, that means that we'll have to be careful about releasing
149      * container.
150      * That's why we'll release on main thread in Close(). */
151     [container performSelectorOnMainThread:@selector(addSubview:) withObject:sys->glView waitUntilDone:NO];
152
153     [nsPool drain];
154     nsPool = nil;
155
156     /* Initialize common OpenGL video display */
157     sys->gl.lock = OpenglClean; // We don't do locking, but sometimes we need to cleanup the framebuffer
158     sys->gl.unlock = NULL;
159     sys->gl.swap = OpenglSwap;
160         sys->gl.getProcAddress = OurGetProcAddress;
161     sys->gl.sys = sys;
162
163         sys->vgl = vout_display_opengl_New(&vd->fmt, NULL, &sys->gl);
164         if (!sys->vgl)
165     {
166         sys->gl.sys = NULL;
167         goto error;
168     }
169
170     /* */
171     vout_display_info_t info = vd->info;
172     info.has_pictures_invalid = false;
173
174     /* Setup vout_display_t once everything is fine */
175     vd->info = info;
176
177     vd->pool = Pool;
178     vd->prepare = PictureRender;
179     vd->display = PictureDisplay;
180     vd->control = Control;
181
182     /* */
183     CGRect bounds = sys->glView.layer.bounds;
184     CGFloat scaleFactor = sys->glView.contentScaleFactor;
185     /* we need to multiply the bounds dimensions by the scaleFactor to be save for Retina Displays */
186     vout_display_SendEventFullscreen (vd, false);
187     vout_display_SendEventDisplaySize (vd, bounds.size.width * scaleFactor, bounds.size.height * scaleFactor, false);
188
189     return VLC_SUCCESS;
190
191 error:
192     [nsPool release];
193     Close(this);
194     return VLC_EGENERIC;
195 }
196
197 // Called from vout thread as well
198 void Close(vlc_object_t *this)
199 {
200     vout_display_t *vd = (vout_display_t *)this;
201     vout_display_sys_t *sys = vd->sys;
202
203     [sys->glView setVoutDisplay:nil];
204
205     var_Destroy(vd, "drawable-nsobject");
206     /* release on main thread as explained in Open() */
207     [(id)sys->container performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
208     [sys->glView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
209
210     [sys->glView release];
211
212     if (sys->gl.sys != NULL)
213         vout_display_opengl_Delete(sys->vgl);
214
215     free (sys);
216 }
217
218 /*****************************************************************************
219  * vout display callbacks
220  *****************************************************************************/
221
222 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
223 {
224     vout_display_sys_t *sys = vd->sys;
225
226     if (!sys->pool)
227         sys->pool = vout_display_opengl_GetPool (sys->vgl, requested_count);
228     assert(sys->pool);
229     return sys->pool;
230 }
231
232 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
233 {
234     vout_display_sys_t *sys = vd->sys;
235
236     vout_display_opengl_Prepare( sys->vgl, pic, subpicture );
237 }
238
239 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
240 {
241     vout_display_sys_t *sys = vd->sys;
242     vout_display_opengl_Display(sys->vgl, &vd->fmt );
243     picture_Release (pic);
244     sys->has_first_frame = true;
245         (void)subpicture;
246 }
247
248 static int Control (vout_display_t *vd, int query, va_list ap)
249 {
250     vout_display_sys_t *sys = vd->sys;
251
252     switch (query)
253     {
254         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
255         {
256             [sys->glView performSelectorOnMainThread:@selector(layoutSubviews) withObject:nil waitUntilDone:NO];
257             return VLC_SUCCESS;
258         }
259         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
260         case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
261         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
262         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
263         case VOUT_DISPLAY_CHANGE_ZOOM:
264         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
265         {
266             return VLC_SUCCESS;
267         }
268         case VOUT_DISPLAY_HIDE_MOUSE:
269             return VLC_SUCCESS;
270
271         case VOUT_DISPLAY_GET_OPENGL:
272         {
273             vlc_gl_t **gl = va_arg (ap, vlc_gl_t **);
274             *gl = &sys->gl;
275             return VLC_SUCCESS;
276         }
277
278         case VOUT_DISPLAY_RESET_PICTURES:
279             assert (0);
280         default:
281             msg_Err (vd, "Unknown request in iOS vout display");
282             return VLC_EGENERIC;
283     }
284 }
285
286 /*****************************************************************************
287  * vout opengl callbacks
288  *****************************************************************************/
289
290 static int OpenglClean(vlc_gl_t *gl) {
291     vout_display_sys_t *sys = gl->sys;
292     [sys->glView cleanFramebuffer];
293     return 0;
294 }
295
296 static void OpenglSwap(vlc_gl_t *gl)
297 {
298     vout_display_sys_t *sys = gl->sys;
299     EAGLContext *context = [sys->glView context];
300     [context presentRenderbuffer:GL_RENDERBUFFER_OES];
301 }
302
303 /*****************************************************************************
304  * Our UIView object
305  * *ALL* OpenGL calls should happen in the render thread
306  *****************************************************************************/
307
308 @interface VLCOpenGLESVideoView (Private)
309 - (void)_createFramebuffer;
310 - (void)_updateViewportWithBackingWitdh:(GLuint)backingWidth andBackingHeight:(GLuint)backingHeight;
311 - (void)_destroyFramebuffer;
312 @end
313
314 @implementation VLCOpenGLESVideoView
315 @synthesize context=_context, colorRenderbuffer=_colorRenderbuffer;
316 #define VLCAssertMainThread() assert([[NSThread currentThread] isMainThread])
317
318 + (Class)layerClass {
319     return [CAEAGLLayer class];
320 }
321
322 /**
323  * Gets called by the Open() method.
324  */
325
326 - (id)initWithFrame:(CGRect)frame andVOutDisplay:(vout_display_t *)vd {
327     if (self = [super initWithFrame:frame]) {
328         _vd = vd;
329         CAEAGLLayer * eaglLayer = (CAEAGLLayer *)self.layer;
330
331         eaglLayer.opaque = TRUE;
332         eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
333 //                                        [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
334                                         kEAGLColorFormatRGB565, kEAGLDrawablePropertyColorFormat,
335                                         nil];
336
337         _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
338         NSAssert(_context && [EAGLContext setCurrentContext:_context], @"Creating context");
339
340         // This shouldn't need to be done on the main thread.
341         // Indeed, it works just fine from the render thread on iOS 3.2 to 4.1
342         // However, if you don't call it from the main thread, it doesn't work on iOS 4.2 beta 1
343         [self performSelectorOnMainThread:@selector(_createFramebuffer) withObject:nil waitUntilDone:YES];
344
345         _framebufferDirty = NO;
346
347         [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
348     }
349     return self;
350 }
351
352 - (void) dealloc
353 {
354     [_context release];
355     [super dealloc];
356 }
357
358 /**
359  * Gets called by the Close and Open methods.
360  * (Non main thread).
361  */
362 - (void)setVoutDisplay:(vout_display_t *)aVd
363 {
364     @synchronized(self) {
365         _vd = aVd;
366     }
367 }
368
369
370 /**
371  * Method called by UIKit when we have been resized
372  */
373 - (void)layoutSubviews {
374     // CAUTION : This is called from the main thread
375     _framebufferDirty = YES;
376 }
377
378 - (void)cleanFramebuffer {
379     if (_framebufferDirty) {
380         [self _destroyFramebuffer];
381         [self _createFramebuffer];
382         _framebufferDirty = NO;
383     }
384 }
385
386 /* we don't get the correct scale factor if we don't overwrite this method */
387 - (void) drawRect: (CGRect) rect
388 {
389 }
390
391 @end
392
393 @implementation VLCOpenGLESVideoView (Private)
394 - (void)_createFramebuffer {
395     msg_Dbg(_vd, "Creating framebuffer for layer %p with bounds (%.1f,%.1f,%.1f,%.1f)", self.layer, self.layer.bounds.origin.x, self.layer.bounds.origin.y, self.layer.bounds.size.width, self.layer.bounds.size.height);
396     [EAGLContext setCurrentContext:_context];
397     // Create default framebuffer object. The backing will be allocated for the current layer in -resizeFromLayer
398     glGenFramebuffersOES(1, &_defaultFramebuffer); // Generate one framebuffer, store it in _defaultFrameBuffer
399     glGenRenderbuffersOES(1, &_colorRenderbuffer);
400     glBindFramebufferOES(GL_FRAMEBUFFER_OES, _defaultFramebuffer);
401     glBindRenderbufferOES(GL_RENDERBUFFER_OES, _colorRenderbuffer);
402
403     // This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
404     // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
405     [_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
406     glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, _colorRenderbuffer);
407
408     GLint backingWidth, backingHeight;
409     glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
410     glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
411
412     if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
413         msg_Err(_vd, "Failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
414     }
415     [self _updateViewportWithBackingWitdh:backingWidth andBackingHeight:backingHeight];
416 }
417
418 - (void)_updateViewportWithBackingWitdh:(GLuint)backingWidth andBackingHeight:(GLuint)backingHeight {
419     msg_Dbg(_vd, "Reshaping to %dx%d", backingWidth, backingHeight);
420
421     CGFloat width = (CGFloat)backingWidth;
422     CGFloat height = (CGFloat)backingHeight;
423
424     GLint x = width, y = height;
425
426     if (_vd) {
427         CGFloat videoHeight = _vd->source.i_visible_height;
428         CGFloat videoWidth = _vd->source.i_visible_width;
429
430         GLint sarNum = _vd->source.i_sar_num;
431         GLint sarDen = _vd->source.i_sar_den;
432
433         if (height * videoWidth * sarNum < width * videoHeight * sarDen)
434         {
435             x = (height * videoWidth * sarNum) / (videoHeight * sarDen);
436             y = height;
437         }
438         else
439         {
440             x = width;
441             y = (width * videoHeight * sarDen) / (videoWidth * sarNum);
442         }
443
444         @synchronized (self)
445         {
446             vout_display_cfg_t cfg_tmp = *(_vd->cfg);
447             cfg_tmp.display.width  = width;
448             cfg_tmp.display.height = height;
449
450             vout_display_SendEventDisplaySize (_vd, width, height, false);
451         }
452     }
453
454     [EAGLContext setCurrentContext:_context];
455     glViewport((width - x) / 2, (height - y) / 2, x, y);
456 }
457
458 - (void)_destroyFramebuffer {
459     [EAGLContext setCurrentContext:_context];
460     glDeleteFramebuffersOES(1, &_defaultFramebuffer);
461     _defaultFramebuffer = 0;
462     glDeleteRenderbuffersOES(1, &_colorRenderbuffer);
463     _colorRenderbuffer = 0;
464 }
465 @end
466