1 /*****************************************************************************
2 * ios.m: iOS X OpenGLES provider
3 *****************************************************************************
4 * Copyright (C) 2010-2013 VLC Authors and VideoLAN
7 * Authors: Romain Goyet <romain.goyet at likid dot org>
8 * Felix Paul Kühne <fkuehne at videolan dot org>
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.
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.
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 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
29 #import <UIKit/UIKit.h>
30 #import <OpenGLES/ES1/gl.h>
31 #import <OpenGLES/ES1/glext.h>
32 #import <QuartzCore/QuartzCore.h>
39 #include <vlc_common.h>
40 #include <vlc_plugin.h>
41 #include <vlc_vout_display.h>
42 #include <vlc_opengl.h>
47 * Forward declarations
49 static int Open(vlc_object_t *);
50 static void Close(vlc_object_t *);
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);
57 static void *OurGetProcAddress(vlc_gl_t *, const char *);
59 static int OpenglClean(vlc_gl_t *gl);
60 static void OpenglSwap(vlc_gl_t *gl);
66 /* Will be loaded even without interface module. see voutgl.m */
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)
74 add_shortcut("ios", "vout_ios")
77 @interface VLCOpenGLESVideoView : UIView
80 EAGLContext * _context;
81 GLuint _defaultFramebuffer;
82 GLuint _colorRenderbuffer;
83 BOOL _framebufferDirty;
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;
93 struct vout_display_sys_t
95 VLCOpenGLESVideoView *glView;
99 vout_display_opengl_t *vgl;
101 picture_pool_t *pool;
103 bool has_first_frame;
106 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
110 return dlsym(RTLD_DEFAULT, name);
113 // Called from vout thread
114 static int Open(vlc_object_t *this)
116 vout_display_t *vd = (vout_display_t *)this;
117 vout_display_sys_t *sys = calloc(1, sizeof(*sys));
118 NSAutoreleasePool *nsPool = nil;
127 /* Get the drawable object */
128 UIView * container = (UIView *)var_CreateGetAddress(vd, "drawable-nsobject");
130 if (![container isKindOfClass:[UIView class]]) {
131 msg_Dbg(vd, "Container isn't an UIView, passing over.");
134 vout_display_DeleteWindow(vd, NULL);
136 /* This will be released in Close(), on
137 * main thread, after we are done using it. */
138 sys->container = [container retain];
140 /* Get our main view*/
141 nsPool = [[NSAutoreleasePool alloc] init];
143 msg_Dbg(vd, "Creating VLCOpenGLESVideoView");
144 sys->glView = [[VLCOpenGLESVideoView alloc] initWithFrame:[container bounds] andVOutDisplay:vd];
148 /* We don't wait, that means that we'll have to be careful about releasing
150 * That's why we'll release on main thread in Close(). */
151 [container performSelectorOnMainThread:@selector(addSubview:) withObject:sys->glView waitUntilDone:NO];
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;
163 sys->vgl = vout_display_opengl_New(&vd->fmt, NULL, &sys->gl);
171 vout_display_info_t info = vd->info;
172 info.has_pictures_invalid = false;
174 /* Setup vout_display_t once everything is fine */
178 vd->prepare = PictureRender;
179 vd->display = PictureDisplay;
180 vd->control = Control;
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);
197 // Called from vout thread as well
198 void Close(vlc_object_t *this)
200 vout_display_t *vd = (vout_display_t *)this;
201 vout_display_sys_t *sys = vd->sys;
203 [sys->glView setVoutDisplay:nil];
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];
210 [sys->glView release];
212 if (sys->gl.sys != NULL)
213 vout_display_opengl_Delete(sys->vgl);
218 /*****************************************************************************
219 * vout display callbacks
220 *****************************************************************************/
222 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
224 vout_display_sys_t *sys = vd->sys;
227 sys->pool = vout_display_opengl_GetPool (sys->vgl, requested_count);
232 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
234 vout_display_sys_t *sys = vd->sys;
236 vout_display_opengl_Prepare( sys->vgl, pic, subpicture );
239 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
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;
248 static int Control (vout_display_t *vd, int query, va_list ap)
250 vout_display_sys_t *sys = vd->sys;
254 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
256 [sys->glView performSelectorOnMainThread:@selector(layoutSubviews) withObject:nil waitUntilDone:NO];
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:
268 case VOUT_DISPLAY_HIDE_MOUSE:
271 case VOUT_DISPLAY_GET_OPENGL:
273 vlc_gl_t **gl = va_arg (ap, vlc_gl_t **);
278 case VOUT_DISPLAY_RESET_PICTURES:
281 msg_Err (vd, "Unknown request in iOS vout display");
286 /*****************************************************************************
287 * vout opengl callbacks
288 *****************************************************************************/
290 static int OpenglClean(vlc_gl_t *gl) {
291 vout_display_sys_t *sys = gl->sys;
292 [sys->glView cleanFramebuffer];
296 static void OpenglSwap(vlc_gl_t *gl)
298 vout_display_sys_t *sys = gl->sys;
299 EAGLContext *context = [sys->glView context];
300 [context presentRenderbuffer:GL_RENDERBUFFER_OES];
303 /*****************************************************************************
305 * *ALL* OpenGL calls should happen in the render thread
306 *****************************************************************************/
308 @interface VLCOpenGLESVideoView (Private)
309 - (void)_createFramebuffer;
310 - (void)_updateViewportWithBackingWitdh:(GLuint)backingWidth andBackingHeight:(GLuint)backingHeight;
311 - (void)_destroyFramebuffer;
314 @implementation VLCOpenGLESVideoView
315 @synthesize context=_context, colorRenderbuffer=_colorRenderbuffer;
316 #define VLCAssertMainThread() assert([[NSThread currentThread] isMainThread])
318 + (Class)layerClass {
319 return [CAEAGLLayer class];
323 * Gets called by the Open() method.
326 - (id)initWithFrame:(CGRect)frame andVOutDisplay:(vout_display_t *)vd {
327 if (self = [super initWithFrame:frame]) {
329 CAEAGLLayer * eaglLayer = (CAEAGLLayer *)self.layer;
331 eaglLayer.opaque = TRUE;
332 eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
333 // [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
334 kEAGLColorFormatRGB565, kEAGLDrawablePropertyColorFormat,
337 _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
338 NSAssert(_context && [EAGLContext setCurrentContext:_context], @"Creating context");
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];
345 _framebufferDirty = NO;
347 [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
359 * Gets called by the Close and Open methods.
362 - (void)setVoutDisplay:(vout_display_t *)aVd
364 @synchronized(self) {
371 * Method called by UIKit when we have been resized
373 - (void)layoutSubviews {
374 // CAUTION : This is called from the main thread
375 _framebufferDirty = YES;
378 - (void)cleanFramebuffer {
379 if (_framebufferDirty) {
380 [self _destroyFramebuffer];
381 [self _createFramebuffer];
382 _framebufferDirty = NO;
386 /* we don't get the correct scale factor if we don't overwrite this method */
387 - (void) drawRect: (CGRect) rect
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);
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);
408 GLint backingWidth, backingHeight;
409 glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
410 glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
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));
415 [self _updateViewportWithBackingWitdh:backingWidth andBackingHeight:backingHeight];
418 - (void)_updateViewportWithBackingWitdh:(GLuint)backingWidth andBackingHeight:(GLuint)backingHeight {
419 msg_Dbg(_vd, "Reshaping to %dx%d", backingWidth, backingHeight);
421 CGFloat width = (CGFloat)backingWidth;
422 CGFloat height = (CGFloat)backingHeight;
424 GLint x = width, y = height;
427 CGFloat videoHeight = _vd->source.i_visible_height;
428 CGFloat videoWidth = _vd->source.i_visible_width;
430 GLint sarNum = _vd->source.i_sar_num;
431 GLint sarDen = _vd->source.i_sar_den;
433 if (height * videoWidth * sarNum < width * videoHeight * sarDen)
435 x = (height * videoWidth * sarNum) / (videoHeight * sarDen);
441 y = (width * videoHeight * sarDen) / (videoWidth * sarNum);
446 vout_display_cfg_t cfg_tmp = *(_vd->cfg);
447 cfg_tmp.display.width = width;
448 cfg_tmp.display.height = height;
450 vout_display_SendEventDisplaySize (_vd, width, height, false);
454 [EAGLContext setCurrentContext:_context];
455 glViewport((width - x) / 2, (height - y) / 2, x, y);
458 - (void)_destroyFramebuffer {
459 [EAGLContext setCurrentContext:_context];
460 glDeleteFramebuffersOES(1, &_defaultFramebuffer);
461 _defaultFramebuffer = 0;
462 glDeleteRenderbuffersOES(1, &_colorRenderbuffer);
463 _colorRenderbuffer = 0;