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