1 /*****************************************************************************
2 * voutgl.m: iOS X OpenGLES provider
3 *****************************************************************************
4 * Copyright (C) 2001-2009 the VideoLAN team
7 * Authors: Romain Goyet <romain.goyet at likid dot org>
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
28 #import <UIKit/UIKit.h>
29 #import <OpenGLES/ES1/gl.h>
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_vout_display.h>
38 #include <vlc_vout_opengl.h>
40 #define USE_OPENGL_ES 1
42 #include <QuartzCore/QuartzCore.h>
45 * Forward declarations
47 static int Open(vlc_object_t *);
48 static void Close(vlc_object_t *);
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);
55 static int OpenglClean(vout_opengl_t *gl);
56 static void OpenglSwap(vout_opengl_t *gl);
62 /* Will be loaded even without interface module. see voutgl.m */
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)
70 add_shortcut("ios", "vout_ios")
73 @interface VLCOpenGLESVideoView : UIView
76 EAGLContext * _context;
77 GLuint _defaultFramebuffer;
78 GLuint _colorRenderbuffer;
79 BOOL _framebufferDirty;
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;
89 struct vout_display_sys_t
91 VLCOpenGLESVideoView *glView;
95 vout_display_opengl_t vgl;
102 // Called from vout thread
103 static int Open(vlc_object_t *this)
105 vout_display_t *vd = (vout_display_t *)this;
106 vout_display_sys_t *sys = calloc(1, sizeof(*sys));
107 NSAutoreleasePool *nsPool = nil;
116 /* Get the drawable object */
117 UIView * container = (UIView *)var_CreateGetAddress(vd, "drawable-nsobject");
119 if (![container isKindOfClass:[UIView class]]) {
120 msg_Dbg(vd, "Container isn't an UIView, passing over.");
123 vout_display_DeleteWindow(vd, NULL);
125 /* This will be released in Close(), on
126 * main thread, after we are done using it. */
127 sys->container = [container retain];
129 /* Get our main view*/
130 nsPool = [[NSAutoreleasePool alloc] init];
132 msg_Dbg(vd, "Creating VLCOpenGLESVideoView");
133 sys->glView = [[VLCOpenGLESVideoView alloc] initWithFrame:[container bounds] andVOutDisplay:vd];
137 /* We don't wait, that means that we'll have to be careful about releasing
139 * That's why we'll release on main thread in Close(). */
140 [container performSelectorOnMainThread:@selector(addSubview:) withObject:sys->glView waitUntilDone:NO];
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;
151 if (vout_display_opengl_Init(&sys->vgl, &vd->fmt, &sys->gl))
158 vout_display_info_t info = vd->info;
159 info.has_pictures_invalid = false;
161 /* Setup vout_display_t once everything is fine */
165 vd->prepare = PictureRender;
166 vd->display = PictureDisplay;
167 vd->control = Control;
170 vout_display_SendEventFullscreen (vd, false);
171 vout_display_SendEventDisplaySize (vd, vd->source.i_visible_width, vd->source.i_visible_height, false);
181 // Called from vout thread as well
182 void Close(vlc_object_t *this)
184 vout_display_t *vd = (vout_display_t *)this;
185 vout_display_sys_t *sys = vd->sys;
187 [sys->glView setVoutDisplay:nil];
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];
194 [sys->glView release];
196 if (sys->gl.sys != NULL)
197 vout_display_opengl_Clean(&sys->vgl);
202 /*****************************************************************************
203 * vout display callbacks
204 *****************************************************************************/
206 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
208 vout_display_sys_t *sys = vd->sys;
209 VLC_UNUSED(requested_count);
212 sys->pool = vout_display_opengl_GetPool (&sys->vgl);
217 static void PictureRender(vout_display_t *vd, picture_t *pic)
219 vout_display_sys_t *sys = vd->sys;
221 vout_display_opengl_Prepare( &sys->vgl, pic );
224 static void PictureDisplay(vout_display_t *vd, picture_t *pic)
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;
232 static int Control (vout_display_t *vd, int query, va_list ap)
234 vout_display_sys_t *sys = vd->sys;
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:
248 case VOUT_DISPLAY_HIDE_MOUSE:
251 case VOUT_DISPLAY_GET_OPENGL:
253 vout_opengl_t **gl = va_arg (ap, vout_opengl_t **);
258 case VOUT_DISPLAY_RESET_PICTURES:
261 msg_Err (vd, "Unknown request in iOS vout display");
266 /*****************************************************************************
267 * vout opengl callbacks
268 *****************************************************************************/
270 static int OpenglClean(vout_opengl_t *gl) {
271 vout_display_sys_t *sys = gl->sys;
272 [sys->glView cleanFramebuffer];
276 static void OpenglSwap(vout_opengl_t *gl)
278 vout_display_sys_t *sys = gl->sys;
279 EAGLContext *context = [sys->glView context];
280 [context presentRenderbuffer:GL_RENDERBUFFER_OES];
283 /*****************************************************************************
285 * *ALL* OpenGL calls should happen in the render thread
286 *****************************************************************************/
288 @interface VLCOpenGLESVideoView (Private)
289 - (void)_createFramebuffer;
290 - (void)_updateViewportWithBackingWitdh:(GLuint)backingWidth andBackingHeight:(GLuint)backingHeight;
291 - (void)_destroyFramebuffer;
294 @implementation VLCOpenGLESVideoView
295 @synthesize context=_context, colorRenderbuffer=_colorRenderbuffer;
296 #define VLCAssertMainThread() assert([[NSThread currentThread] isMainThread])
298 + (Class)layerClass {
299 return [CAEAGLLayer class];
303 * Gets called by the Open() method.
306 - (id)initWithFrame:(CGRect)frame andVOutDisplay:(vout_display_t *)vd {
307 if (self = [super initWithFrame:frame]) {
309 CAEAGLLayer * eaglLayer = (CAEAGLLayer *)self.layer;
311 eaglLayer.opaque = TRUE;
312 eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
313 // [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
314 kEAGLColorFormatRGB565, kEAGLDrawablePropertyColorFormat,
317 _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
318 NSAssert(_context && [EAGLContext setCurrentContext:_context], @"Creating context");
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];
325 _framebufferDirty = NO;
327 [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
339 * Gets called by the Close and Open methods.
342 - (void)setVoutDisplay:(vout_display_t *)aVd
344 @synchronized(self) {
351 * Method called by UIKit when we have been resized
353 - (void)layoutSubviews {
354 // CAUTION : This is called from the main thread
355 _framebufferDirty = YES;
358 - (void)cleanFramebuffer {
359 if (_framebufferDirty) {
360 [self _destroyFramebuffer];
361 [self _createFramebuffer];
362 _framebufferDirty = NO;
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);
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);
383 GLuint backingWidth, backingHeight;
384 glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
385 glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
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));
390 [self _updateViewportWithBackingWitdh:backingWidth andBackingHeight:backingHeight];
393 - (void)_updateViewportWithBackingWitdh:(GLuint)backingWidth andBackingHeight:(GLuint)backingHeight {
394 msg_Dbg(_vd, "Reshaping to %dx%d", backingWidth, backingHeight);
396 CGFloat width = (CGFloat)backingWidth;
397 CGFloat height = (CGFloat)backingHeight;
399 GLint x = width, y = height;
402 CGFloat videoHeight = _vd->source.i_visible_height;
403 CGFloat videoWidth = _vd->source.i_visible_width;
405 GLint sarNum = _vd->source.i_sar_num;
406 GLint sarDen = _vd->source.i_sar_den;
408 if (height * videoWidth * sarNum < width * videoHeight * sarDen)
410 x = (height * videoWidth * sarNum) / (videoHeight * sarDen);
416 y = (width * videoHeight * sarDen) / (videoWidth * sarNum);
420 [EAGLContext setCurrentContext:_context];
421 glViewport((width - x) / 2, (height - y) / 2, x, y);
424 - (void)_destroyFramebuffer {
425 [EAGLContext setCurrentContext:_context];
426 glDeleteFramebuffersOES(1, &_defaultFramebuffer);
427 _defaultFramebuffer = 0;
428 glDeleteRenderbuffersOES(1, &_colorRenderbuffer);
429 _colorRenderbuffer = 0;