1 /*****************************************************************************
2 * ios2.m: iOS OpenGL ES 2 provider
3 *****************************************************************************
4 * Copyright (C) 2001-2013 VLC authors and VideoLAN
7 * Authors: Derk-Jan Hartman <hartman at videolan dot org>
8 * Eric Petit <titer@m0k.org>
9 * Benjamin Pracht <bigben at videolan dot org>
10 * Damien Fouilleul <damienf at videolan dot org>
11 * Pierre d'Herbemont <pdherbemont at videolan dot org>
12 * Felix Paul Kühne <fkuehne at videolan dot org>
13 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
15 * Juho Vähä-Herttua <juhovh at iki dot fi>
16 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
18 * This program is free software; you can redistribute it and/or modify it
19 * under the terms of the GNU Lesser General Public License as published by
20 * the Free Software Foundation; either version 2.1 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU Lesser General Public License for more details.
28 * You should have received a copy of the GNU Lesser General Public License
29 * along with this program; if not, write to the Free Software Foundation,
30 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
31 *****************************************************************************/
33 /*****************************************************************************
35 *****************************************************************************/
37 #import <UIKit/UIKit.h>
38 #import <OpenGLES/EAGL.h>
39 #import <OpenGLES/ES2/gl.h>
40 #import <QuartzCore/QuartzCore.h>
47 #include <vlc_common.h>
48 #include <vlc_plugin.h>
49 #include <vlc_vout_display.h>
50 #include <vlc_opengl.h>
51 #include <vlc_dialog.h>
55 * Forward declarations
57 static int Open (vlc_object_t *);
58 static void Close (vlc_object_t *);
60 static picture_pool_t* PicturePool(vout_display_t *vd, unsigned requested_count);
61 static void PictureRender(vout_display_t* vd, picture_t *pic, subpicture_t *subpicture);
62 static void PictureDisplay(vout_display_t* vd, picture_t *pic, subpicture_t *subpicture);
63 static int Control(vout_display_t* vd, int query, va_list ap);
65 static void *OurGetProcAddress(vlc_gl_t *, const char *);
67 static int OpenglESClean(vlc_gl_t* gl);
68 static void OpenglESSwap(vlc_gl_t* gl);
74 set_shortname ("iOS vout")
75 set_description (N_("iOS OpenGL video output"))
76 set_category (CAT_VIDEO)
77 set_subcategory (SUBCAT_VIDEO_VOUT)
78 set_capability ("vout display", 300)
79 set_callbacks (Open, Close)
81 add_shortcut ("vout_ios2")
84 @interface VLCOpenGLES2VideoView : UIView
86 vout_display_t *_voutDisplay;
87 EAGLContext *_eaglContext;
91 BOOL _bufferNeedReset;
93 @property (readwrite) vout_display_t* voutDisplay;
94 @property (readonly) EAGLContext* eaglContext;
96 - (void)createBuffers;
97 - (void)destroyBuffers;
103 struct vout_display_sys_t
105 VLCOpenGLES2VideoView *glESView;
106 UIView* viewContainer;
109 vout_display_opengl_t *vgl;
111 picture_pool_t *picturePool;
113 bool has_first_frame;
115 vout_display_place_t place;
119 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
123 return dlsym(RTLD_DEFAULT, name);
126 static int Open(vlc_object_t *this)
128 vout_display_t *vd = (vout_display_t *)this;
129 vout_display_sys_t *sys = calloc (1, sizeof(*sys));
130 NSAutoreleasePool *autoreleasePool = nil;
136 sys->picturePool = NULL;
139 autoreleasePool = [[NSAutoreleasePool alloc] init];
141 /* get the object we will draw into */
142 UIView* viewContainer = var_CreateGetAddress (vd, "drawable-nsobject");
143 if (!viewContainer || ![viewContainer isKindOfClass:[UIView class]])
146 vout_display_DeleteWindow (vd, NULL);
148 /* This will be released in Close(), on
149 * main thread, after we are done using it. */
150 sys->viewContainer = [viewContainer retain];
152 /* setup the actual OpenGL ES view */
153 sys->glESView = [[VLCOpenGLES2VideoView alloc] initWithFrame:[viewContainer bounds]];
158 [sys->glESView setVoutDisplay:vd];
160 [sys->viewContainer performSelectorOnMainThread:@selector(addSubview:) withObject:sys->glESView waitUntilDone:YES];
162 /* Initialize common OpenGL video display */
163 sys->gl.lock = OpenglESClean;
164 sys->gl.unlock = nil;
165 sys->gl.swap = OpenglESSwap;
166 sys->gl.getProcAddress = OurGetProcAddress;
168 const vlc_fourcc_t *subpicture_chromas;
169 video_format_t fmt = vd->fmt;
171 sys->vgl = vout_display_opengl_New (&vd->fmt, &subpicture_chromas, &sys->gl);
178 vout_display_info_t info = vd->info;
179 info.has_pictures_invalid = false;
180 info.has_event_thread = true;
181 info.subpicture_chromas = subpicture_chromas;
183 /* Setup vout_display_t once everything is fine */
186 vd->pool = PicturePool;
187 vd->prepare = PictureRender;
188 vd->display = PictureDisplay;
189 vd->control = Control;
192 [sys->glESView performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:YES];
194 [autoreleasePool release];
198 [autoreleasePool release];
203 void Close (vlc_object_t *this)
205 vout_display_t *vd = (vout_display_t *)this;
206 vout_display_sys_t *sys = vd->sys;
208 [sys->glESView setVoutDisplay:nil];
210 var_Destroy (vd, "drawable-nsobject");
211 [sys->viewContainer performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
212 [sys->glESView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
214 if (sys->gl.sys != NULL) {
215 msg_Dbg(this, "deleting display");
216 vout_display_opengl_Delete(sys->vgl);
219 [sys->glESView release];
224 /*****************************************************************************
225 * vout display callbacks
226 *****************************************************************************/
228 static int Control(vout_display_t *vd, int query, va_list ap)
230 vout_display_sys_t *sys = vd->sys;
234 case VOUT_DISPLAY_CHANGE_FULLSCREEN:
235 case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
236 case VOUT_DISPLAY_HIDE_MOUSE:
238 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
239 case VOUT_DISPLAY_CHANGE_ZOOM:
240 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
241 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
242 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
247 NSAutoreleasePool * autoreleasePool = [[NSAutoreleasePool alloc] init];
249 const vout_display_cfg_t *cfg;
250 const video_format_t *source;
251 bool is_forced = false;
253 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
254 source = (const video_format_t *)va_arg(ap, const video_format_t *);
257 source = &vd->source;
258 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
259 if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
260 is_forced = (bool)va_arg(ap, int);
263 if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE && is_forced
264 && (cfg->display.width != vd->cfg->display.width
265 || cfg->display.height != vd->cfg->display.height))
268 /* we always use our current frame here, because we have some size constraints
269 in the ui vout provider */
270 vout_display_cfg_t cfg_tmp = *cfg;
272 bounds = [sys->glESView bounds];
274 /* on HiDPI displays, the point bounds don't equal the actual pixel based bounds */
275 CGFloat scaleFactor = sys->glESView.contentScaleFactor;
276 cfg_tmp.display.width = bounds.size.width * scaleFactor;
277 cfg_tmp.display.height = bounds.size.height * scaleFactor;
279 vout_display_place_t place;
280 vout_display_PlacePicture(&place, source, &cfg_tmp, false);
281 @synchronized (sys->glESView) {
285 /* For resize, we call glViewport in reshape and not here.
286 This has the positive side effect that we avoid erratic sizing as we animate every resize. */
287 if (query != VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
288 // x / y are top left corner, but we need the lower left one
289 glViewport(place.x, cfg_tmp.display.height - (place.y + place.height), place.width, place.height);
291 [autoreleasePool release];
295 case VOUT_DISPLAY_GET_OPENGL:
297 vlc_gl_t **gl = va_arg(ap, vlc_gl_t **);
302 case VOUT_DISPLAY_RESET_PICTURES:
305 msg_Err(vd, "Unknown request %i in iOS ES 2 vout display", query);
310 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
312 vout_display_sys_t *sys = vd->sys;
313 sys->has_first_frame = true;
314 vout_display_opengl_Display(sys->vgl, &vd->source);
316 picture_Release(pic);
319 subpicture_Delete(subpicture);
322 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
325 vout_display_sys_t *sys = vd->sys;
327 vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
330 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
332 vout_display_sys_t *sys = vd->sys;
334 if (!sys->picturePool)
335 sys->picturePool = vout_display_opengl_GetPool(sys->vgl, requested_count);
336 assert(sys->picturePool);
337 return sys->picturePool;
340 /*****************************************************************************
341 * vout opengl callbacks
342 *****************************************************************************/
343 static int OpenglESClean(vlc_gl_t *gl)
345 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
346 [sys->glESView resetBuffers];
350 static void OpenglESSwap(vlc_gl_t *gl)
352 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
353 [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
356 /*****************************************************************************
358 *****************************************************************************/
359 @implementation VLCOpenGLES2VideoView
360 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext;
364 return [CAEAGLLayer class];
367 - (id)initWithFrame:(CGRect)frame
369 self = [super initWithFrame:frame]; // perform selector on main thread?
374 CAEAGLLayer * layer = (CAEAGLLayer *)self.layer;
375 layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
378 _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
381 [EAGLContext setCurrentContext:_eaglContext];
383 [self performSelectorOnMainThread:@selector(createBuffers) withObject:nil waitUntilDone:YES];
384 [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
385 [self setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
392 [_eaglContext release];
396 /* we don't get the correct scale factor if we don't overwrite this method */
397 - (void)drawRect:(CGRect) rect
399 [super drawRect:rect];
402 - (void)createBuffers
404 /* make sure the current context is us */
405 [EAGLContext setCurrentContext:_eaglContext];
407 /* create render buffer */
408 glGenRenderbuffers(1, &_renderBuffer);
409 glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
411 /* create frame buffer */
412 glGenFramebuffers(1, &_frameBuffer);
413 glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
415 /* allocate storage for the pixels we are going to to draw to */
416 [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];
418 /* bind render buffer to frame buffer */
419 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
421 /* make sure that our shape is ok */
422 [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
425 - (void)destroyBuffers
427 /* re-set current context */
428 [EAGLContext setCurrentContext:_eaglContext];
430 /* clear frame buffer */
431 glDeleteFramebuffers(1, &_frameBuffer);
434 /* clear render buffer */
435 glDeleteRenderbuffers(1, &_renderBuffer);
441 if (_bufferNeedReset) {
442 NSLog(@"actually resetting buffers");
443 [self destroyBuffers];
444 [self createBuffers];
445 _bufferNeedReset = NO;
449 - (void)layoutSubviews
451 NSLog(@"layoutSubviews");
452 /* this method is called as soon as we are resized.
453 * so set a variable to re-create our buffers on the next clean event */
454 _bufferNeedReset = YES;
458 * Method called by Cocoa when the view is resized.
462 assert([[NSThread currentThread] isMainThread]);
465 bounds = [self bounds];
467 vout_display_place_t place;
469 @synchronized(self) {
471 vout_display_cfg_t cfg_tmp = *(_voutDisplay->cfg);
472 CGFloat scaleFactor = self.contentScaleFactor;
474 cfg_tmp.display.width = bounds.size.width * scaleFactor;
475 cfg_tmp.display.height = bounds.size.height * scaleFactor;
477 vout_display_PlacePicture(&place, &_voutDisplay->source, &cfg_tmp, false);
478 _voutDisplay->sys->place = place;
479 vout_display_SendEventDisplaySize(_voutDisplay, bounds.size.width * scaleFactor, bounds.size.height * scaleFactor, _voutDisplay->cfg->is_fullscreen);
483 // x / y are top left corner, but we need the lower left one
484 glViewport(place.x, place.y, place.width, place.height);
487 - (void)updateConstraints
490 [super updateConstraints];
498 - (BOOL)acceptsFirstResponder