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