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