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