1 /*****************************************************************************
2 * ios2.m: iOS OpenGL ES 2 provider
3 *****************************************************************************
4 * Copyright (C) 2001-2014 VLC authors and VideoLAN
7 * Authors: Pierre d'Herbemont <pdherbemont at videolan dot org>
8 * Felix Paul Kühne <fkuehne at videolan dot org>
9 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
11 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
12 * Eric Petit <titer@m0k.org>
15 * This program is free software; you can redistribute it and/or modify it
16 * under the terms of the GNU Lesser General Public License as published by
17 * the Free Software Foundation; either version 2.1 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU Lesser General Public License for more details.
25 * You should have received a copy of the GNU Lesser General Public License
26 * along with this program; if not, write to the Free Software Foundation,
27 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
28 *****************************************************************************/
30 /*****************************************************************************
32 *****************************************************************************/
34 #import <UIKit/UIKit.h>
35 #import <OpenGLES/EAGL.h>
36 #import <OpenGLES/ES2/gl.h>
37 #import <QuartzCore/QuartzCore.h>
44 #include <vlc_common.h>
45 #include <vlc_plugin.h>
46 #include <vlc_vout_display.h>
47 #include <vlc_opengl.h>
48 #include <vlc_dialog.h>
52 * Forward declarations
54 static int Open(vlc_object_t *);
55 static void Close(vlc_object_t *);
57 static picture_pool_t* PicturePool(vout_display_t *vd, unsigned requested_count);
58 static void PictureRender(vout_display_t* vd, picture_t *pic, subpicture_t *subpicture);
59 static void PictureDisplay(vout_display_t* vd, picture_t *pic, subpicture_t *subpicture);
60 static int Control(vout_display_t* vd, int query, va_list ap);
62 static void *OurGetProcAddress(vlc_gl_t *, const char *);
64 static int OpenglESClean(vlc_gl_t* gl);
65 static void OpenglESSwap(vlc_gl_t* gl);
71 set_shortname("iOS vout")
72 set_description(N_("iOS OpenGL video output"))
73 set_category(CAT_VIDEO)
74 set_subcategory(SUBCAT_VIDEO_VOUT)
75 set_capability("vout display", 300)
76 set_callbacks(Open, Close)
78 add_shortcut("vout_ios2")
81 @interface VLCOpenGLES2VideoView : UIView {
82 vout_display_t *_voutDisplay;
83 EAGLContext *_eaglContext;
87 BOOL _bufferNeedReset;
90 @property (readwrite) vout_display_t* voutDisplay;
91 @property (readonly) EAGLContext* eaglContext;
92 @property (readonly) BOOL isAppActive;
94 - (void)createBuffers;
95 - (void)destroyBuffers;
99 struct vout_display_sys_t
101 VLCOpenGLES2VideoView *glESView;
102 UIView* viewContainer;
103 UITapGestureRecognizer *tapRecognizer;
106 vout_display_opengl_t *vgl;
108 picture_pool_t *picturePool;
109 bool has_first_frame;
111 vout_display_place_t place;
114 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
118 return dlsym(RTLD_DEFAULT, name);
121 static int Open(vlc_object_t *this)
123 vout_display_t *vd = (vout_display_t *)this;
124 vout_display_sys_t *sys = calloc (1, sizeof(*sys));
125 NSAutoreleasePool *autoreleasePool = nil;
131 sys->picturePool = NULL;
134 autoreleasePool = [[NSAutoreleasePool alloc] init];
136 /* get the object we will draw into */
137 UIView* viewContainer = var_CreateGetAddress (vd, "drawable-nsobject");
138 if (!viewContainer || ![viewContainer isKindOfClass:[UIView class]])
141 vout_display_DeleteWindow (vd, NULL);
143 /* This will be released in Close(), on
144 * main thread, after we are done using it. */
145 sys->viewContainer = [viewContainer retain];
147 /* setup the actual OpenGL ES view */
148 sys->glESView = [[VLCOpenGLES2VideoView alloc] initWithFrame:[viewContainer bounds]];
153 [sys->glESView setVoutDisplay:vd];
155 [sys->viewContainer performSelectorOnMainThread:@selector(addSubview:)
156 withObject:sys->glESView
159 /* add tap gesture recognizer for DVD menus and stuff */
160 sys->tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:sys->glESView
161 action:@selector(tapRecognized:)];
162 sys->tapRecognizer.numberOfTapsRequired = 2;
163 if (sys->viewContainer.window) {
164 if (sys->viewContainer.window.rootViewController) {
165 if (sys->viewContainer.window.rootViewController.view)
166 [sys->viewContainer.superview addGestureRecognizer:sys->tapRecognizer];
169 sys->tapRecognizer.cancelsTouchesInView = NO;
171 /* Initialize common OpenGL video display */
172 sys->gl.lock = OpenglESClean;
173 sys->gl.unlock = nil;
174 sys->gl.swap = OpenglESSwap;
175 sys->gl.getProcAddress = OurGetProcAddress;
177 const vlc_fourcc_t *subpicture_chromas;
178 video_format_t fmt = vd->fmt;
180 sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas, &sys->gl);
187 vout_display_info_t info = vd->info;
188 info.has_pictures_invalid = false;
189 info.has_event_thread = true;
190 info.subpicture_chromas = subpicture_chromas;
191 info.has_hide_mouse = false;
193 /* Setup vout_display_t once everything is fine */
196 vd->pool = PicturePool;
197 vd->prepare = PictureRender;
198 vd->display = PictureDisplay;
199 vd->control = Control;
201 /* forward our dimensions to the vout core */
202 CGSize viewSize = sys->viewContainer.frame.size;
203 vout_display_SendEventFullscreen(vd, false);
204 vout_display_SendEventDisplaySize(vd, (int)viewSize.width, (int)viewSize.height, false);
207 [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
208 selector:@selector(applicationStateChanged:)
209 name:UIApplicationWillResignActiveNotification
211 [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
212 selector:@selector(applicationStateChanged:)
213 name:UIApplicationDidBecomeActiveNotification
215 [sys->glESView performSelectorOnMainThread:@selector(reshape)
219 [autoreleasePool release];
223 [autoreleasePool release];
228 void Close (vlc_object_t *this)
230 vout_display_t *vd = (vout_display_t *)this;
231 vout_display_sys_t *sys = vd->sys;
233 if (sys->tapRecognizer) {
234 [sys->glESView removeGestureRecognizer:sys->tapRecognizer];
235 [sys->tapRecognizer release];
238 [sys->glESView setVoutDisplay:nil];
240 var_Destroy (vd, "drawable-nsobject");
241 [sys->viewContainer performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
242 [sys->glESView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
244 if (sys->gl.sys != NULL) {
245 msg_Dbg(this, "deleting display");
246 vout_display_opengl_Delete(sys->vgl);
249 [sys->glESView release];
254 /*****************************************************************************
255 * vout display callbacks
256 *****************************************************************************/
258 static int Control(vout_display_t *vd, int query, va_list ap)
260 vout_display_sys_t *sys = vd->sys;
263 case VOUT_DISPLAY_HIDE_MOUSE:
266 case VOUT_DISPLAY_CHANGE_FULLSCREEN:
267 case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
268 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
269 case VOUT_DISPLAY_CHANGE_ZOOM:
270 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
271 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
272 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
277 NSAutoreleasePool * autoreleasePool = [[NSAutoreleasePool alloc] init];
279 const vout_display_cfg_t *cfg;
280 const video_format_t *source;
282 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
283 source = (const video_format_t *)va_arg(ap, const video_format_t *);
286 source = &vd->source;
287 cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
290 /* we don't adapt anything here regardless of what the vout core
291 * wants since we are not in a traditional desktop window */
295 vout_display_cfg_t cfg_tmp = *cfg;
297 viewSize = [sys->glESView bounds].size;
299 /* on HiDPI displays, the point bounds don't equal the actual pixels */
300 CGFloat scaleFactor = sys->glESView.contentScaleFactor;
301 cfg_tmp.display.width = viewSize.width * scaleFactor;
302 cfg_tmp.display.height = viewSize.height * scaleFactor;
304 vout_display_place_t place;
305 vout_display_PlacePicture(&place, source, &cfg_tmp, false);
306 @synchronized (sys->glESView) {
310 [autoreleasePool release];
314 case VOUT_DISPLAY_GET_OPENGL:
316 vlc_gl_t **gl = va_arg(ap, vlc_gl_t **);
321 case VOUT_DISPLAY_RESET_PICTURES:
324 msg_Err(vd, "Unknown request %d", query);
329 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
331 vout_display_sys_t *sys = vd->sys;
332 sys->has_first_frame = true;
333 if (likely([sys->glESView isAppActive]))
334 vout_display_opengl_Display(sys->vgl, &vd->source);
336 picture_Release(pic);
339 subpicture_Delete(subpicture);
342 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
344 vout_display_sys_t *sys = vd->sys;
346 if (likely([sys->glESView isAppActive]))
347 vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
350 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
352 vout_display_sys_t *sys = vd->sys;
354 if (!sys->picturePool)
355 sys->picturePool = vout_display_opengl_GetPool(sys->vgl, requested_count);
356 assert(sys->picturePool);
357 return sys->picturePool;
360 /*****************************************************************************
361 * vout opengl callbacks
362 *****************************************************************************/
363 static int OpenglESClean(vlc_gl_t *gl)
365 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
366 if (likely([sys->glESView isAppActive]))
367 [sys->glESView resetBuffers];
371 static void OpenglESSwap(vlc_gl_t *gl)
373 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
374 if (likely([sys->glESView isAppActive]))
375 [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
378 /*****************************************************************************
380 *****************************************************************************/
381 @implementation VLCOpenGLES2VideoView
382 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext, isAppActive = _appActive;
386 return [CAEAGLLayer class];
389 - (id)initWithFrame:(CGRect)frame
391 self = [super initWithFrame:frame];
396 CAEAGLLayer * layer = (CAEAGLLayer *)self.layer;
397 layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
400 _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
403 [EAGLContext setCurrentContext:_eaglContext];
405 [self performSelectorOnMainThread:@selector(createBuffers) withObject:nil waitUntilDone:YES];
406 [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
407 [self setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
409 _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
416 [[NSNotificationCenter defaultCenter] removeObserver:self];
417 [_eaglContext release];
421 - (void)didMoveToWindow
423 self.contentScaleFactor = self.window.screen.scale;
424 _bufferNeedReset = YES;
427 - (void)createBuffers
429 /* make sure the current context is us */
430 [EAGLContext setCurrentContext:_eaglContext];
432 /* create render buffer */
433 glGenRenderbuffers(1, &_renderBuffer);
434 glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
436 /* create frame buffer */
437 glGenFramebuffers(1, &_frameBuffer);
438 glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
440 /* allocate storage for the pixels we are going to to draw to */
441 [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];
443 /* bind render buffer to frame buffer */
444 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
446 /* make sure that our shape is ok */
447 [self performSelectorOnMainThread:@selector(reshape) withObject:nil waitUntilDone:NO];
450 - (void)destroyBuffers
452 /* re-set current context */
453 [EAGLContext setCurrentContext:_eaglContext];
455 /* clear frame buffer */
456 glDeleteFramebuffers(1, &_frameBuffer);
459 /* clear render buffer */
460 glDeleteRenderbuffers(1, &_renderBuffer);
466 if (_bufferNeedReset) {
467 [self destroyBuffers];
468 [self createBuffers];
469 _bufferNeedReset = NO;
473 - (void)layoutSubviews
475 /* this method is called as soon as we are resized.
476 * so set a variable to re-create our buffers on the next clean event */
477 _bufferNeedReset = YES;
482 assert([[NSThread currentThread] isMainThread]);
484 [EAGLContext setCurrentContext:_eaglContext];
486 CGSize viewSize = [self bounds].size;
488 vout_display_place_t place;
490 @synchronized(self) {
492 vout_display_cfg_t cfg_tmp = *(_voutDisplay->cfg);
493 CGFloat scaleFactor = self.contentScaleFactor;
495 cfg_tmp.display.width = viewSize.width * scaleFactor;
496 cfg_tmp.display.height = viewSize.height * scaleFactor;
498 vout_display_PlacePicture(&place, &_voutDisplay->source, &cfg_tmp, false);
499 _voutDisplay->sys->place = place;
500 vout_display_SendEventDisplaySize(_voutDisplay, viewSize.width * scaleFactor,
501 viewSize.height * scaleFactor,
502 _voutDisplay->cfg->is_fullscreen);
506 // x / y are top left corner, but we need the lower left one
507 glViewport(place.x, place.y, place.width, place.height);
510 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
512 UIGestureRecognizerState state = [tapRecognizer state];
513 CGPoint touchPoint = [tapRecognizer locationInView:self];
514 CGFloat scaleFactor = self.contentScaleFactor;
515 vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay, ORIENT_NORMAL,
516 (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor,
517 &_voutDisplay->sys->place);
519 vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
520 vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
523 - (void)applicationStateChanged:(NSNotification *)notification
525 if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification]
526 || [[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification]
527 || [[notification name] isEqualToString:UIApplicationWillTerminateNotification])
533 - (void)updateConstraints
536 [super updateConstraints];
544 - (BOOL)acceptsFirstResponder