opengl: "-vvvv" to dump shaders
[vlc.git] / modules / video_output / ios.m
blob0740c2624ce63755b598dd03e4ddc4cf9a979d3c
1 /*****************************************************************************
2  * ios.m: iOS OpenGL ES provider
3  *****************************************************************************
4  * Copyright (C) 2001-2017 VLC authors and VideoLAN
5  * $Id$
6  *
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>
10  *          Rémi Denis-Courmont
11  *          Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
12  *          Eric Petit <titer@m0k.org>
13  *
14  * This program is free software; you can redistribute it and/or modify it
15  * under the terms of the GNU Lesser General Public License as published by
16  * the Free Software Foundation; either version 2.1 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU Lesser General Public License for more details.
23  *
24  * You should have received a copy of the GNU Lesser General Public License
25  * along with this program; if not, write to the Free Software Foundation,
26  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27  *****************************************************************************/
29 /*****************************************************************************
30  * Preamble
31  *****************************************************************************/
33 #import <UIKit/UIKit.h>
34 #import <OpenGLES/EAGL.h>
35 #import <OpenGLES/ES2/gl.h>
36 #import <OpenGLES/ES2/glext.h>
37 #import <QuartzCore/QuartzCore.h>
38 #import <dlfcn.h>
40 #ifdef HAVE_CONFIG_H
41 # import "config.h"
42 #endif
44 #import <vlc_common.h>
45 #import <vlc_plugin.h>
46 #import <vlc_vout_display.h>
47 #import <vlc_opengl.h>
48 #import <vlc_dialog.h>
49 #import "opengl/vout_helper.h"
51 /**
52  * Forward declarations
53  */
55 struct picture_sys_t {
56     CVPixelBufferRef pixelBuffer;
59 static int Open(vlc_object_t *);
60 static void Close(vlc_object_t *);
62 static picture_pool_t* PicturePool(vout_display_t *, unsigned);
63 static void PictureRender(vout_display_t *, picture_t *, subpicture_t *);
64 static void PictureDisplay(vout_display_t *, picture_t *, subpicture_t *);
65 static int Control(vout_display_t*, int, va_list);
67 static void *OurGetProcAddress(vlc_gl_t *, const char *);
69 static int OpenglESLock(vlc_gl_t *);
70 static void OpenglESSwap(vlc_gl_t *);
71 static void OpenglESUnlock(vlc_gl_t *);
73 /**
74  * Module declaration
75  */
76 vlc_module_begin ()
77     set_shortname("iOS vout")
78     set_description("iOS OpenGL video output")
79     set_category(CAT_VIDEO)
80     set_subcategory(SUBCAT_VIDEO_VOUT)
81     set_capability("vout display", 300)
82     set_callbacks(Open, Close)
84     add_shortcut("vout_ios2", "vout_ios")
85     add_glconv()
86 vlc_module_end ()
88 @interface VLCOpenGLES2VideoView : UIView {
89     vout_display_t *_voutDisplay;
90     EAGLContext *_eaglContext;
91     EAGLContext *_previousEaglContext;
92     GLuint _renderBuffer;
93     GLuint _frameBuffer;
95     BOOL _bufferNeedReset;
96     BOOL _appActive;
98 @property (readonly) GLuint renderBuffer;
99 @property (readonly) GLuint frameBuffer;
100 @property (readwrite) vout_display_t* voutDisplay;
101 @property (readonly) EAGLContext* eaglContext;
102 @property (readonly) BOOL isAppActive;
103 @property GLuint shaderProgram;
105 - (void)createBuffers;
106 - (void)destroyBuffers;
107 - (void)resetBuffers;
108 - (void)lock;
109 - (void)unlock;
111 - (void)reshape;
112 - (void)propagateDimensionsToVoutCore;
113 - (CGSize)viewSize;
114 @end
116 struct vout_display_sys_t
118     VLCOpenGLES2VideoView *glESView;
119     UIView *viewContainer;
120     UITapGestureRecognizer *tapRecognizer;
122     vlc_gl_t *gl;
123     vout_display_opengl_t *vgl;
125     picture_pool_t *picturePool;
127     vout_display_place_t place;
130 struct gl_sys
132     VLCOpenGLES2VideoView *glESView;
135 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
137     VLC_UNUSED(gl);
139     return dlsym(RTLD_DEFAULT, name);
142 static int Open(vlc_object_t *this)
144     vout_display_t *vd = (vout_display_t *)this;
146     if (vout_display_IsWindowed(vd))
147         return VLC_EGENERIC;
149     vout_display_sys_t *sys = vlc_obj_calloc (this, 1, sizeof(*sys));
151     if (!sys)
152         return VLC_ENOMEM;
154     vd->sys = sys;
155     sys->picturePool = NULL;
156     sys->gl = NULL;
158     var_Create(vd->obj.parent, "ios-eaglcontext", VLC_VAR_ADDRESS);
160     @autoreleasepool {
161         /* setup the actual OpenGL ES view */
162         [VLCOpenGLES2VideoView performSelectorOnMainThread:@selector(getNewView:)
163                                              withObject:[NSValue valueWithPointer:&sys->glESView]
164                                           waitUntilDone:YES];
165         [sys->glESView setVoutDisplay:vd];
167         if (!sys->glESView) {
168             msg_Err(vd, "Creating OpenGL ES 2 view failed");
169             goto bailout;
170         }
172         [sys->glESView performSelectorOnMainThread:@selector(fetchViewContainer) withObject:nil waitUntilDone:YES];
173         if (!sys->viewContainer) {
174             msg_Err(vd, "Fetching view container failed");
175             goto bailout;
176         }
178         const vlc_fourcc_t *subpicture_chromas;
179         video_format_t fmt = vd->fmt;
181         sys->gl = vlc_object_create(this, sizeof(*sys->gl));
182         if (!sys->gl)
183             goto bailout;
185         struct gl_sys *glsys = sys->gl->sys =
186             vlc_obj_alloc(this, 1, sizeof(struct gl_sys));
187         if (unlikely(!sys->gl->sys))
188             goto bailout;
189         glsys->glESView = sys->glESView;
190         /* Initialize common OpenGL video display */
191         sys->gl->makeCurrent = OpenglESLock;
192         sys->gl->releaseCurrent = OpenglESUnlock;
193         sys->gl->swap = OpenglESSwap;
194         sys->gl->getProcAddress = OurGetProcAddress;
196         if (vlc_gl_MakeCurrent(sys->gl) != VLC_SUCCESS)
197             goto bailout;
199         var_SetAddress(vd->obj.parent, "ios-eaglcontext", [sys->glESView eaglContext]);
201         sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
202                                            sys->gl, &vd->cfg->viewpoint);
203         vlc_gl_ReleaseCurrent(sys->gl);
204         if (!sys->vgl)
205             goto bailout;
207         /* */
208         vout_display_info_t info = vd->info;
209         info.has_pictures_invalid = false;
210         info.subpicture_chromas = subpicture_chromas;
212         /* Setup vout_display_t once everything is fine */
213         vd->info = info;
215         vd->pool = PicturePool;
216         vd->prepare = PictureRender;
217         vd->display = PictureDisplay;
218         vd->control = Control;
220         /* forward our dimensions to the vout core */
221         [sys->glESView performSelectorOnMainThread:@selector(propagateDimensionsToVoutCore) withObject:nil waitUntilDone:YES];
223         /* */
224         [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
225                                                  selector:@selector(applicationStateChanged:)
226                                                      name:UIApplicationWillResignActiveNotification
227                                                    object:nil];
228         [[NSNotificationCenter defaultCenter] addObserver:sys->glESView
229                                                  selector:@selector(applicationStateChanged:)
230                                                      name:UIApplicationDidBecomeActiveNotification
231                                                    object:nil];
232         [sys->glESView reshape];
233         return VLC_SUCCESS;
235     bailout:
236         Close(this);
237         return VLC_EGENERIC;
238     }
241 static void Close (vlc_object_t *this)
243     vout_display_t *vd = (vout_display_t *)this;
244     vout_display_sys_t *sys = vd->sys;
246     @autoreleasepool {
247         if (sys->tapRecognizer) {
248             [sys->tapRecognizer.view performSelectorOnMainThread:@selector(removeGestureRecognizer:) withObject:sys->tapRecognizer waitUntilDone:YES];
249             [sys->tapRecognizer release];
250         }
252         [sys->glESView setVoutDisplay:nil];
254         var_Destroy (vd, "drawable-nsobject");
255         @synchronized(sys->viewContainer) {
256             [sys->glESView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
257             [sys->viewContainer performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
258         }
259         sys->viewContainer = nil;
261         if (sys->gl != NULL) {
262             @synchronized (sys->glESView) {
263                 msg_Dbg(this, "deleting display");
265                 if (likely(sys->vgl))
266                 {
267                     vlc_gl_MakeCurrent(sys->gl);
268                     vout_display_opengl_Delete(sys->vgl);
269                     vlc_gl_ReleaseCurrent(sys->gl);
270                 }
271             }
272             vlc_object_release(sys->gl);
273         }
275         [sys->glESView release];
276     }
277     var_Destroy(vd->obj.parent, "ios-eaglcontext");
280 /*****************************************************************************
281  * vout display callbacks
282  *****************************************************************************/
284 static int Control(vout_display_t *vd, int query, va_list ap)
286     vout_display_sys_t *sys = vd->sys;
288     switch (query) {
289         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
290         case VOUT_DISPLAY_CHANGE_ZOOM:
291         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
292         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
293         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
294         {
295             if (!vd->sys)
296                 return VLC_EGENERIC;
298             @autoreleasepool {
299                 const vout_display_cfg_t *cfg;
301                 if (vlc_gl_MakeCurrent(sys->gl) != VLC_SUCCESS)
302                     return VLC_EGENERIC;
304                 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT ||
305                     query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
306                     cfg = vd->cfg;
307                 } else {
308                     cfg = (const vout_display_cfg_t*)va_arg(ap, const vout_display_cfg_t *);
309                 }
311                 /* we don't adapt anything here regardless of what the vout core
312                  * wants since we are not in a traditional desktop window */
313                 if (!cfg)
314                     return VLC_EGENERIC;
316                 vout_display_cfg_t cfg_tmp = *cfg;
317                 CGSize viewSize;
318                 viewSize = [sys->glESView viewSize];
320                 /* on HiDPI displays, the point bounds don't equal the actual pixels */
321                 CGFloat scaleFactor = sys->glESView.contentScaleFactor;
322                 cfg_tmp.display.width = viewSize.width * scaleFactor;
323                 cfg_tmp.display.height = viewSize.height * scaleFactor;
325                 vout_display_place_t place;
326                 vout_display_PlacePicture(&place, &vd->source, &cfg_tmp, false);
327                 @synchronized (sys->glESView) {
328                     sys->place = place;
329                 }
331                 vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
333                 // x / y are top left corner, but we need the lower left one
334                 if (query != VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
335                     glViewport(place.x, cfg_tmp.display.height - (place.y + place.height), place.width, place.height);
336                 vlc_gl_ReleaseCurrent(sys->gl);
337             }
338             return VLC_SUCCESS;
339         }
341         case VOUT_DISPLAY_CHANGE_VIEWPOINT:
342             return vout_display_opengl_SetViewpoint(sys->vgl,
343                 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
345         case VOUT_DISPLAY_RESET_PICTURES:
346             vlc_assert_unreachable ();
347         default:
348             msg_Err(vd, "Unknown request %d", query);
349             return VLC_EGENERIC;
350     }
353 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
355     vout_display_sys_t *sys = vd->sys;
356     @synchronized (sys->glESView) {
357         if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
358         {
359             vout_display_opengl_Display(sys->vgl, &vd->source);
360             vlc_gl_ReleaseCurrent(sys->gl);
361         }
362     }
364     picture_Release(pic);
366     if (subpicture)
367         subpicture_Delete(subpicture);
370 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
372     vout_display_sys_t *sys = vd->sys;
373     if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
374     {
375         vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
376         vlc_gl_ReleaseCurrent(sys->gl);
377     }
380 static picture_pool_t *PicturePool(vout_display_t *vd, unsigned requested_count)
382     vout_display_sys_t *sys = vd->sys;
384     if (!sys->picturePool && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
385     {
386         sys->picturePool = vout_display_opengl_GetPool(sys->vgl, requested_count);
387         vlc_gl_ReleaseCurrent(sys->gl);
388     }
389     return sys->picturePool;
392 /*****************************************************************************
393  * vout opengl callbacks
394  *****************************************************************************/
395 static int OpenglESLock(vlc_gl_t *gl)
397     struct gl_sys *sys = gl->sys;
399     if (unlikely(![sys->glESView isAppActive]))
400         return VLC_EGENERIC;
402     [sys->glESView lock];
403     [sys->glESView resetBuffers];
404     return VLC_SUCCESS;
407 static void OpenglESUnlock(vlc_gl_t *gl)
409     struct gl_sys *sys = gl->sys;
411     [sys->glESView unlock];
414 static void OpenglESSwap(vlc_gl_t *gl)
416     struct gl_sys *sys = gl->sys;
418     if (likely([sys->glESView isAppActive]))
419         [[sys->glESView eaglContext] presentRenderbuffer:GL_RENDERBUFFER];
423 /*****************************************************************************
424  * Our UIView object
425  *****************************************************************************/
426 @implementation VLCOpenGLES2VideoView
427 @synthesize voutDisplay = _voutDisplay, eaglContext = _eaglContext, isAppActive = _appActive;
429 + (Class)layerClass
431     return [CAEAGLLayer class];
434 + (void)getNewView:(NSValue *)value
436     id *ret = [value pointerValue];
437     *ret = [[self alloc] initWithFrame:CGRectMake(0.,0.,320.,240.)];
440 - (id)initWithFrame:(CGRect)frame
442     self = [super initWithFrame:frame];
444     if (!self)
445         return nil;
447     _appActive = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive);
448     if (unlikely(!_appActive))
449         return nil;
451     /* the following creates a new OpenGL ES context with the API version we
452      * need if there is already an active context created by another OpenGL
453      * provider we cache it and restore analog to the lock/unlock pattern used
454      * through-out the class */
455     _previousEaglContext = [EAGLContext currentContext];
457     _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
459     if (unlikely(!_eaglContext))
460         return nil;
461     if (unlikely(![EAGLContext setCurrentContext:_eaglContext]))
462         return nil;
464     CAEAGLLayer *layer = (CAEAGLLayer *)self.layer;
465     layer.drawableProperties = [NSDictionary dictionaryWithObject:kEAGLColorFormatRGBA8 forKey: kEAGLDrawablePropertyColorFormat];
466     layer.opaque = YES;
468     self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
470     [self unlock];
472     return self;
475 - (void)setVoutDisplay:(vout_display_t *)vd
477     _voutDisplay = vd;
479     [self createBuffers];
481     [self reshape];
484 - (vout_display_t *)voutDisplay
486     return _voutDisplay;
489 - (void)fetchViewContainer
491     @try {
492         /* get the object we will draw into */
493         UIView *viewContainer = var_CreateGetAddress (_voutDisplay, "drawable-nsobject");
494         if (unlikely(viewContainer == nil)) {
495             msg_Err(_voutDisplay, "provided view container is nil");
496             return;
497         }
499         if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
500             msg_Err(_voutDisplay, "void pointer not an ObjC object");
501             return;
502         }
504         [viewContainer retain];
506         @synchronized(viewContainer) {
507             if (![viewContainer isKindOfClass:[UIView class]]) {
508                 msg_Err(_voutDisplay, "passed ObjC object not of class UIView");
509                 return;
510             }
512             vout_display_sys_t *sys = _voutDisplay->sys;
514             /* This will be released in Close(), on
515              * main thread, after we are done using it. */
516             sys->viewContainer = viewContainer;
518             self.frame = viewContainer.bounds;
519             [self reshape];
521             [sys->viewContainer performSelectorOnMainThread:@selector(addSubview:)
522                                                  withObject:self
523                                               waitUntilDone:YES];
525             /* add tap gesture recognizer for DVD menus and stuff */
526             sys->tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
527                                                                          action:@selector(tapRecognized:)];
528             if (sys->viewContainer.window) {
529                 if (sys->viewContainer.window.rootViewController) {
530                     if (sys->viewContainer.window.rootViewController.view)
531                         [sys->viewContainer.superview addGestureRecognizer:sys->tapRecognizer];
532                 }
533             }
534             sys->tapRecognizer.cancelsTouchesInView = NO;
535         }
536     } @catch (NSException *exception) {
537         msg_Err(_voutDisplay, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
538         vout_display_sys_t *sys = _voutDisplay->sys;
539         sys->viewContainer = nil;
540     }
543 - (void)dealloc
545     [[NSNotificationCenter defaultCenter] removeObserver:self];
546     [_eaglContext release];
547     [super dealloc];
550 - (void)didMoveToWindow
552     self.contentScaleFactor = self.window.screen.scale;
553     _bufferNeedReset = YES;
556 - (void)createBuffers
558     if (![NSThread isMainThread])
559     {
560         [self performSelectorOnMainThread:@selector(createBuffers)
561                                                  withObject:nil
562                                               waitUntilDone:YES];
563         return;
564     }
566     if (unlikely(!_appActive)) {
567         return;
568     }
570     [self lock];
572     glDisable(GL_DEPTH_TEST);
574     glGenFramebuffers(1, &_frameBuffer);
575     glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
577     glGenRenderbuffers(1, &_renderBuffer);
578     glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
580     [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
582     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
583     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
584         if (_voutDisplay)
585             msg_Err(_voutDisplay, "Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
586     }
588     [self unlock];
591 - (void)destroyBuffers
593     if (![NSThread isMainThread])
594     {
595         [self performSelectorOnMainThread:@selector(destroyBuffers)
596                                                  withObject:nil
597                                               waitUntilDone:YES];
598         return;
599     }
601     [self lock];
603     /* clear frame buffer */
604     glDeleteFramebuffers(1, &_frameBuffer);
605     _frameBuffer = 0;
607     /* clear render buffer */
608     glDeleteRenderbuffers(1, &_renderBuffer);
609     _renderBuffer = 0;
611     [self unlock];
614 - (void)resetBuffers
616     if (unlikely(_bufferNeedReset)) {
617         [self destroyBuffers];
618         [self createBuffers];
619         _bufferNeedReset = NO;
620     }
623 - (void)lock
625     _previousEaglContext = [EAGLContext currentContext];
626     [EAGLContext setCurrentContext:_eaglContext];
629 - (void)unlock
631     [EAGLContext setCurrentContext:_previousEaglContext];
634 - (void)layoutSubviews
636     [self reshape];
638     _bufferNeedReset = YES;
641 - (void)reshape
643     if (![NSThread isMainThread])
644     {
645         [self performSelectorOnMainThread:@selector(reshape)
646                                                  withObject:nil
647                                               waitUntilDone:YES];
648         return;
649     }
651     [self lock];
653     CGSize viewSize = [self bounds].size;
654     CGFloat scaleFactor = self.contentScaleFactor;
655     vout_display_place_t place;
657     if (_voutDisplay) {
658         vout_display_cfg_t cfg_tmp = *(_voutDisplay->cfg);
660         cfg_tmp.display.width  = viewSize.width * scaleFactor;
661         cfg_tmp.display.height = viewSize.height * scaleFactor;
663         vout_display_PlacePicture(&place, &_voutDisplay->source, &cfg_tmp, false);
664         _voutDisplay->sys->place = place;
665         vout_display_SendEventDisplaySize(_voutDisplay, viewSize.width * scaleFactor,
666                                           viewSize.height * scaleFactor);
667     }
669     // x / y are top left corner, but we need the lower left one
670     glViewport(place.x, place.y, place.width, place.height);
671     [self unlock];
674 - (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
676     UIGestureRecognizerState state = [tapRecognizer state];
677     CGPoint touchPoint = [tapRecognizer locationInView:self];
678     CGFloat scaleFactor = self.contentScaleFactor;
679     vout_display_SendMouseMovedDisplayCoordinates(_voutDisplay, ORIENT_NORMAL,
680                                                   (int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor,
681                                                   &_voutDisplay->sys->place);
683     vout_display_SendEventMousePressed(_voutDisplay, MOUSE_BUTTON_LEFT);
684     vout_display_SendEventMouseReleased(_voutDisplay, MOUSE_BUTTON_LEFT);
687 - (void)applicationStateChanged:(NSNotification *)notification
689     @synchronized (self) {
690     if ([[notification name] isEqualToString:UIApplicationWillResignActiveNotification]
691         || [[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification]
692         || [[notification name] isEqualToString:UIApplicationWillTerminateNotification])
693         _appActive = NO;
694     else
695         _appActive = YES;
696     }
699 - (void)updateConstraints
701     [super updateConstraints];
702     [self reshape];
705 - (BOOL)isOpaque
707     return YES;
710 - (BOOL)acceptsFirstResponder
712     return YES;
715 - (void)propagateDimensionsToVoutCore
717     CGFloat scaleFactor;
718     CGSize viewSize;
719     @synchronized(_voutDisplay->sys->viewContainer) {
720         scaleFactor = _voutDisplay->sys->viewContainer.contentScaleFactor;
721         viewSize = _voutDisplay->sys->viewContainer.bounds.size;
722     }
723     vout_display_SendEventDisplaySize(_voutDisplay, viewSize.width * scaleFactor, viewSize.height * scaleFactor);
726 - (void)mainThreadContentScaleFactor:(NSNumber *)scaleFactor
728     id *ret = [scaleFactor pointerValue];
729     *ret = [[NSNumber alloc] initWithFloat:[super contentScaleFactor]];
732 - (CGFloat)contentScaleFactor
734     if ([NSThread isMainThread]) {
735         return [super contentScaleFactor];
736     }
738     NSNumber *scaleFactor;
739     [self performSelectorOnMainThread:@selector(mainThreadContentScaleFactor:)
740                                          withObject:[NSValue valueWithPointer:&scaleFactor]
741                                       waitUntilDone:YES];
742     CGFloat ret = [scaleFactor floatValue];
743     [scaleFactor release];
744     return ret;
747 - (void)mainThreadViewBounds:(NSValue *)viewBoundsString
749     id *ret = [viewBoundsString pointerValue];
750     *ret = [NSStringFromCGRect([super bounds]) retain];
753 - (CGSize)viewSize
755     if ([NSThread isMainThread]) {
756         return self.bounds.size;
757     }
759     NSString *viewBoundsString;
760     [self performSelectorOnMainThread:@selector(mainThreadViewBounds:)
761                                          withObject:[NSValue valueWithPointer:&viewBoundsString]
762                                       waitUntilDone:YES];
763     CGRect bounds = CGRectFromString(viewBoundsString);
764     [viewBoundsString release];
765     return bounds.size;
768 @end