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