From http://www.jwz.org/xscreensaver/xscreensaver-5.18.tar.gz
[xscreensaver] / OSX / XScreenSaverGLView.m
1 /* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
2 *
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation.  No representations are made about the suitability of this
8 * software for any purpose.  It is provided "as is" without express or 
9 * implied warranty.
10 */
11
12 /* This is a subclass of Apple's ScreenSaverView that knows how to run
13    xscreensaver programs without X11 via the dark magic of the "jwxyz"
14    library.  In xscreensaver terminology, this is the replacement for
15    the "screenhack.c" module.
16  */
17
18 #import "XScreenSaverGLView.h"
19 #import "XScreenSaverConfigSheet.h"
20 #import "screenhackI.h"
21 #import "xlockmoreI.h"
22
23 #ifdef USE_IPHONE
24 # include "jwzgles.h"
25 #else
26 # import <OpenGL/OpenGL.h>
27 #endif
28
29 /* used by the OpenGL screen savers
30  */
31 extern GLXContext *init_GL (ModeInfo *);
32 extern void glXSwapBuffers (Display *, Window);
33 extern void glXMakeCurrent (Display *, Window, GLXContext);
34 extern void clear_gl_error (void);
35 extern void check_gl_error (const char *type);
36
37
38 @implementation XScreenSaverGLView
39
40 - (void)stopAnimation
41 {
42   [super stopAnimation];
43   
44   // Without this, the GL frame stays on screen when switching tabs
45   // in System Preferences.
46   //
47 # ifndef USE_IPHONE
48   [NSOpenGLContext clearCurrentContext];
49 # endif // !USE_IPHONE
50
51   clear_gl_error();     // This hack is defunct, don't let this linger.
52 }
53
54
55 // #### maybe this could/should just be on 'lockFocus' instead?
56 - (void) prepareContext
57 {
58   if (ogl_ctx) {
59 #ifdef USE_IPHONE
60     [EAGLContext setCurrentContext:ogl_ctx];
61 #else  // !USE_IPHONE
62     [ogl_ctx makeCurrentContext];
63 //    check_gl_error ("makeCurrentContext");
64     [ogl_ctx update];
65 #endif // !USE_IPHONE
66   }
67 }
68
69
70 - (void) resizeContext
71 {
72 # ifndef USE_IPHONE
73   if (ogl_ctx) 
74     [ogl_ctx setView:self];
75 # endif // !USE_IPHONE
76 }
77
78
79 - (NSOpenGLContext *) oglContext
80 {
81   return ogl_ctx;
82 }
83
84
85 - (void) setOglContext: (NSOpenGLContext *) ctx
86 {
87   ogl_ctx = ctx;
88
89 # ifdef USE_IPHONE
90   [EAGLContext setCurrentContext: ogl_ctx];
91
92   double s = self.contentScaleFactor;
93   int w = s * [self frame].size.width;
94   int h = s * [self frame].size.height;
95
96   if (gl_framebuffer)  glDeleteFramebuffersOES  (1, &gl_framebuffer);
97   if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
98   if (gl_depthbuffer)  glDeleteRenderbuffersOES (1, &gl_depthbuffer);
99
100   glGenFramebuffersOES  (1, &gl_framebuffer);
101   glBindFramebufferOES  (GL_FRAMEBUFFER_OES,  gl_framebuffer);
102
103   glGenRenderbuffersOES (1, &gl_renderbuffer);
104   glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
105
106 // redundant?
107 //   glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, w, h);
108   [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
109            fromDrawable:(CAEAGLLayer*)self.layer];
110
111   glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES,  GL_COLOR_ATTACHMENT0_OES,
112                                 GL_RENDERBUFFER_OES, gl_renderbuffer);
113
114   glGenRenderbuffersOES (1, &gl_depthbuffer);
115   // renderbufferStorage: must be called before this.
116   glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_depthbuffer);
117   glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES,
118                             w, h);
119   glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES,  GL_DEPTH_ATTACHMENT_OES, 
120                                 GL_RENDERBUFFER_OES, gl_depthbuffer);
121
122   int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
123   switch (err) {
124   case GL_FRAMEBUFFER_COMPLETE_OES:
125     break;
126   case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
127     NSAssert (0, @"framebuffer incomplete attachment");
128     break;
129   case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
130     NSAssert (0, @"framebuffer incomplete missing attachment");
131     break;
132   case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
133     NSAssert (0, @"framebuffer incomplete dimensions");
134     break;
135   case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
136     NSAssert (0, @"framebuffer incomplete formats");
137     break;
138   case GL_FRAMEBUFFER_UNSUPPORTED_OES:
139     NSAssert (0, @"framebuffer unsupported");
140     break;
141 /*
142   case GL_FRAMEBUFFER_UNDEFINED:
143     NSAssert (0, @"framebuffer undefined");
144     break;
145   case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
146     NSAssert (0, @"framebuffer incomplete draw buffer");
147     break;
148   case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
149     NSAssert (0, @"framebuffer incomplete read buffer");
150     break;
151   case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
152     NSAssert (0, @"framebuffer incomplete multisample");
153     break;
154   case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
155     NSAssert (0, @"framebuffer incomplete layer targets");
156     break;
157  */
158   default:
159     NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
160     break;
161   }
162
163   check_gl_error ("setOglContext");
164
165 # endif // USE_IPHONE
166
167   [self resizeContext];
168 }
169
170 #ifdef USE_IPHONE
171 + (Class) layerClass
172 {
173     return [CAEAGLLayer class];
174 }
175
176
177 /* On MacOS:   drawRect does nothing, and animateOneFrame renders.
178    On iOS GL:  drawRect does nothing, and animateOneFrame renders.
179    On iOS X11: drawRect renders, and animateOneFrame marks the view dirty.
180  */
181 - (void)drawRect:(NSRect)rect
182 {
183 }
184
185
186 - (void) animateOneFrame
187 {
188   UIGraphicsPushContext (backbuffer);
189   [self render_x11];
190   UIGraphicsPopContext();
191 }
192
193
194 /* The backbuffer isn't actually used for GL programs, but it needs to
195    be there for X11 calls to not error out.  However, nothing done with
196    X11 calls will ever show up!  It all gets written into the backbuffer
197    and discarded.  That's ok, though, because mostly it's just calls to
198    XClearWindow and housekeeping stuff like that.  So we make a tiny one.
199  */
200 - (void) createBackbuffer
201 {
202   // Don't resize the X11 window to match rotation. 
203   // Rotation and scaling are handled in GL.
204   //
205   NSRect f = [self frame];
206   double s = self.contentScaleFactor;
207   backbuffer_size.width  = (int) (s * f.size.width);
208   backbuffer_size.height = (int) (s * f.size.height);
209
210   if (! backbuffer) {
211     CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
212     int w = 8;
213     int h = 8;
214     backbuffer = CGBitmapContextCreate (NULL, w, h,
215                                         8, w*4, cs,
216                                         kCGImageAlphaPremultipliedLast);
217     CGColorSpaceRelease (cs);
218   }
219 }
220
221
222 - (void) swapBuffers
223 {
224   glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
225   [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
226 }
227 # endif // USE_IPHONE
228
229
230 - (void)dealloc {
231   // ogl_ctx
232   // gl_framebuffer
233   // gl_renderbuffer
234   // gl_depthbuffer
235   [super dealloc];
236 }
237
238 @end
239
240
241 /* Utility functions...
242  */
243
244
245 // redefine NSAssert, etc. here since they don't work when not inside
246 // an ObjC method.
247
248 #undef NSAssert
249 #undef NSAssert1
250 #undef NSAssert2
251 #define NSASS(S) \
252   jwxyz_abort ("%s", [(S) cStringUsingEncoding:NSUTF8StringEncoding])
253 #define NSAssert(CC,S)      do { if (!(CC)) { NSASS((S)); }} while(0)
254 #define NSAssert1(CC,S,A)   do { if (!(CC)) { \
255   NSASS(([NSString stringWithFormat: S, A])); }} while(0)
256 #define NSAssert2(CC,S,A,B) do { if (!(CC)) { \
257   NSASS(([NSString stringWithFormat: S, A, B])); }} while(0)
258
259
260 /* Called by OpenGL savers using the XLockmore API.
261  */
262 GLXContext *
263 init_GL (ModeInfo *mi)
264 {
265   Window win = mi->window;
266   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
267   NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
268              @"wrong view class: %@", view);
269   NSOpenGLContext *ctx = [view oglContext];
270
271 # ifndef USE_IPHONE
272
273   if (!ctx) {
274
275     NSOpenGLPixelFormatAttribute attrs[40];
276     int i = 0;
277     attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
278     attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
279     attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
280
281     if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
282       attrs[i++] = NSOpenGLPFADoubleBuffer;
283
284     Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample");
285
286     /* Sometimes, turning on multisampling kills performance.  At one point,
287        I thought the answer was, "only run multisampling on one screen, and
288        leave it turned off on other screens".  That's what this code does,
289        but it turns out, that solution is insufficient.  I can't really tell
290        what causes poor performance with multisampling, but it's not
291        predictable.  Without changing the code, some times a given saver will
292        perform fine with multisampling on, and other times it will perform
293        very badly.  Without multisampling, they always perform fine.
294      */
295 //  if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
296 //    ms_p = 0;
297
298     if (ms_p) {
299       attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
300       attrs[i++] = NSOpenGLPFASamples;       attrs[i++] = 6;
301       // Don't really understand what this means:
302       // attrs[i++] = NSOpenGLPFANoRecovery;
303     }
304
305     attrs[i] = 0;
306
307     NSOpenGLPixelFormat *pixfmt = 
308       [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
309
310     if (ms_p && !pixfmt) {   // Retry without multisampling.
311       i -= 2;
312       attrs[i] = 0;
313       pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
314     }
315
316     NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
317
318     ctx = [[NSOpenGLContext alloc] 
319             initWithFormat:pixfmt
320               shareContext:nil];
321 //    [pixfmt release]; // #### ???
322   }
323
324   // Sync refreshes to the vertical blanking interval
325   GLint r = 1;
326   [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
327 //  check_gl_error ("NSOpenGLCPSwapInterval");  // SEGV sometimes. Too early?
328
329   // #### "Build and Analyze" says that ctx leaks, because it doesn't
330   //      seem to realize that makeCurrentContext retains it (right?)
331   //      Not sure what to do to make this warning go away.
332
333   [ctx makeCurrentContext];
334   check_gl_error ("makeCurrentContext");
335
336   [view setOglContext:ctx];
337
338   // Clear frame buffer ASAP, else there are bits left over from other apps.
339   glClearColor (0, 0, 0, 1);
340   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
341 //  glFinish ();
342 //  glXSwapBuffers (mi->dpy, mi->window);
343
344
345   // Enable multi-threading, if possible.  This runs most OpenGL commands
346   // and GPU management on a second CPU.
347   {
348 #   ifndef  kCGLCEMPEngine
349 #    define kCGLCEMPEngine 313  // Added in MacOS 10.4.8 + XCode 2.4.
350 #   endif
351     CGLContextObj cctx = CGLGetCurrentContext();
352     CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
353     if (err != kCGLNoError) {
354       NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
355     }
356   }
357
358   check_gl_error ("init_GL");
359
360 # else  // USE_IPHONE
361
362   EAGLContext *ogl_ctx = ctx;
363   if (!ogl_ctx) {
364
365     Bool dbuf_p = 
366       get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer");
367
368     /* There seems to be no way to actually turn off double-buffering in
369        EAGLContext (e.g., no way to draw to the front buffer directly)
370        but if we turn on "retained backing" for non-buffering apps like
371        "pipes", at least the back buffer isn't auto-cleared on them.
372      */
373     CAEAGLLayer *eagl_layer = (CAEAGLLayer *) view.layer;
374     eagl_layer.opaque = TRUE;
375     eagl_layer.drawableProperties = 
376       [NSDictionary dictionaryWithObjectsAndKeys:
377       kEAGLColorFormatRGBA8,             kEAGLDrawablePropertyColorFormat,
378       [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking,
379       nil];
380
381     // Without this, the GL frame buffer is half the screen resolution!
382     eagl_layer.contentsScale = [UIScreen mainScreen].scale;
383
384     ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
385   }
386
387   if (!ogl_ctx)
388     return 0;
389   [view setOglContext:ogl_ctx];
390
391   check_gl_error ("OES_init");
392
393 # endif // USE_IPHONE
394
395   // Caller expects a pointer to an opaque struct...  which it dereferences.
396   // Don't ask me, it's historical...
397   static int blort = -1;
398   return (void *) &blort;
399 }
400
401
402 /* Copy the back buffer to the front buffer.
403  */
404 void
405 glXSwapBuffers (Display *dpy, Window window)
406 {
407   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
408   NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
409              @"wrong view class: %@", view);
410 #ifndef USE_IPHONE
411   NSOpenGLContext *ctx = [view oglContext];
412   if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
413 #else /* USE_IPHONE */
414   [view swapBuffers];
415 #endif /* USE_IPHONE */
416 }
417
418 /* Does nothing - prepareContext already did the work.
419  */
420 void
421 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
422 {
423 }
424
425
426 /* clear away any lingering error codes */
427 void
428 clear_gl_error (void)
429 {
430   while (glGetError() != GL_NO_ERROR)
431     ;
432 }
433
434
435 /* report a GL error. */
436 void
437 check_gl_error (const char *type)
438 {
439   char buf[100];
440   GLenum i;
441   const char *e;
442   switch ((i = glGetError())) {
443     case GL_NO_ERROR: return;
444     case GL_INVALID_ENUM:          e = "invalid enum";      break;
445     case GL_INVALID_VALUE:         e = "invalid value";     break;
446     case GL_INVALID_OPERATION:     e = "invalid operation"; break;
447     case GL_STACK_OVERFLOW:        e = "stack overflow";    break;
448     case GL_STACK_UNDERFLOW:       e = "stack underflow";   break;
449     case GL_OUT_OF_MEMORY:         e = "out of memory";     break;
450 #ifdef GL_TABLE_TOO_LARGE_EXT
451     case GL_TABLE_TOO_LARGE_EXT:   e = "table too large";   break;
452 #endif
453 #ifdef GL_TEXTURE_TOO_LARGE_EXT
454     case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
455 #endif
456     default:
457       e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
458   }
459   NSAssert2 (0, @"%s GL error: %s", type, e);
460 }