3d3aeeaba4b438fb84d0b57eeeaac642e4b79533
[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 #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   NSRect f = [self bounds];
237   backbuffer_size.width  = (int) (s * f.size.width);
238   backbuffer_size.height = (int) (s * f.size.height);
239
240   if (! backbuffer) {
241     CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
242     int w = 8;
243     int h = 8;
244     backbuffer = CGBitmapContextCreate (NULL, w, h,
245                                         8, w*4, cs,
246                                         kCGImageAlphaPremultipliedLast);
247     CGColorSpaceRelease (cs);
248   }
249 }
250 # endif // USE_IPHONE
251
252
253 - (void)dealloc {
254   // ogl_ctx
255   // gl_framebuffer
256   // gl_renderbuffer
257   // gl_depthbuffer
258   [super dealloc];
259 }
260
261 @end
262
263
264 /* Utility functions...
265  */
266
267
268 // redefine NSAssert, etc. here since they don't work when not inside
269 // an ObjC method.
270
271 #undef NSAssert
272 #undef NSAssert1
273 #undef NSAssert2
274 #define NSASS(S) \
275   jwxyz_abort ("%s", [(S) cStringUsingEncoding:NSUTF8StringEncoding])
276 #define NSAssert(CC,S)      do { if (!(CC)) { NSASS((S)); }} while(0)
277 #define NSAssert1(CC,S,A)   do { if (!(CC)) { \
278   NSASS(([NSString stringWithFormat: S, A])); }} while(0)
279 #define NSAssert2(CC,S,A,B) do { if (!(CC)) { \
280   NSASS(([NSString stringWithFormat: S, A, B])); }} while(0)
281
282
283 /* Called by OpenGL savers using the XLockmore API.
284  */
285 GLXContext *
286 init_GL (ModeInfo *mi)
287 {
288   Window win = mi->window;
289   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
290   NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
291              @"wrong view class: %@", view);
292   NSOpenGLContext *ctx = [view oglContext];
293
294 # ifndef USE_IPHONE
295
296   if (!ctx) {
297
298     NSOpenGLPixelFormatAttribute attrs[40];
299     int i = 0;
300     attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
301     attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
302     attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
303
304     if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
305       attrs[i++] = NSOpenGLPFADoubleBuffer;
306
307     Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample");
308
309     /* Sometimes, turning on multisampling kills performance.  At one point,
310        I thought the answer was, "only run multisampling on one screen, and
311        leave it turned off on other screens".  That's what this code does,
312        but it turns out, that solution is insufficient.  I can't really tell
313        what causes poor performance with multisampling, but it's not
314        predictable.  Without changing the code, some times a given saver will
315        perform fine with multisampling on, and other times it will perform
316        very badly.  Without multisampling, they always perform fine.
317      */
318 //  if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
319 //    ms_p = 0;
320
321     if (ms_p) {
322       attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
323       attrs[i++] = NSOpenGLPFASamples;       attrs[i++] = 6;
324       // Don't really understand what this means:
325       // attrs[i++] = NSOpenGLPFANoRecovery;
326     }
327
328     attrs[i] = 0;
329
330     NSOpenGLPixelFormat *pixfmt = 
331       [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
332
333     if (ms_p && !pixfmt) {   // Retry without multisampling.
334       i -= 2;
335       attrs[i] = 0;
336       pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
337     }
338
339     NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
340
341     ctx = [[NSOpenGLContext alloc] 
342             initWithFormat:pixfmt
343               shareContext:nil];
344 //    [pixfmt release]; // #### ???
345   }
346
347   // Sync refreshes to the vertical blanking interval
348   GLint r = 1;
349   [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
350 //  check_gl_error ("NSOpenGLCPSwapInterval");  // SEGV sometimes. Too early?
351
352   // #### "Build and Analyze" says that ctx leaks, because it doesn't
353   //      seem to realize that makeCurrentContext retains it (right?)
354   //      Not sure what to do to make this warning go away.
355
356   [ctx makeCurrentContext];
357   check_gl_error ("makeCurrentContext");
358
359   [view setOglContext:ctx];
360
361   // Clear frame buffer ASAP, else there are bits left over from other apps.
362   glClearColor (0, 0, 0, 1);
363   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
364 //  glFinish ();
365 //  glXSwapBuffers (mi->dpy, mi->window);
366
367
368   // Enable multi-threading, if possible.  This runs most OpenGL commands
369   // and GPU management on a second CPU.
370   {
371 #   ifndef  kCGLCEMPEngine
372 #    define kCGLCEMPEngine 313  // Added in MacOS 10.4.8 + XCode 2.4.
373 #   endif
374     CGLContextObj cctx = CGLGetCurrentContext();
375     CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
376     if (err != kCGLNoError) {
377       NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
378     }
379   }
380
381   check_gl_error ("init_GL");
382
383 # else  // USE_IPHONE
384
385   EAGLContext *ogl_ctx = ctx;
386   if (!ogl_ctx) {
387
388     Bool dbuf_p = 
389       get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer");
390
391     /* There seems to be no way to actually turn off double-buffering in
392        EAGLContext (e.g., no way to draw to the front buffer directly)
393        but if we turn on "retained backing" for non-buffering apps like
394        "pipes", at least the back buffer isn't auto-cleared on them.
395      */
396     CAEAGLLayer *eagl_layer = (CAEAGLLayer *) view.layer;
397     eagl_layer.opaque = TRUE;
398     eagl_layer.drawableProperties = 
399       [NSDictionary dictionaryWithObjectsAndKeys:
400       kEAGLColorFormatRGBA8,             kEAGLDrawablePropertyColorFormat,
401       [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking,
402       nil];
403
404     // Without this, the GL frame buffer is half the screen resolution!
405     eagl_layer.contentsScale = [UIScreen mainScreen].scale;
406
407     ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
408   }
409
410   if (!ogl_ctx)
411     return 0;
412   [view setOglContext:ogl_ctx];
413
414   check_gl_error ("OES_init");
415
416 # endif // USE_IPHONE
417
418   // Caller expects a pointer to an opaque struct...  which it dereferences.
419   // Don't ask me, it's historical...
420   static int blort = -1;
421   return (void *) &blort;
422 }
423
424
425 /* Copy the back buffer to the front buffer.
426  */
427 void
428 glXSwapBuffers (Display *dpy, Window window)
429 {
430   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
431   NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
432              @"wrong view class: %@", view);
433 #ifndef USE_IPHONE
434   NSOpenGLContext *ctx = [view oglContext];
435   if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
436 #else /* USE_IPHONE */
437   [view swapBuffers];
438 #endif /* USE_IPHONE */
439 }
440
441 /* Does nothing - prepareContext already did the work.
442  */
443 void
444 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
445 {
446 }
447
448
449 /* clear away any lingering error codes */
450 void
451 clear_gl_error (void)
452 {
453   while (glGetError() != GL_NO_ERROR)
454     ;
455 }
456
457
458 /* report a GL error. */
459 void
460 check_gl_error (const char *type)
461 {
462   char buf[100];
463   GLenum i;
464   const char *e;
465   switch ((i = glGetError())) {
466     case GL_NO_ERROR: return;
467     case GL_INVALID_ENUM:          e = "invalid enum";      break;
468     case GL_INVALID_VALUE:         e = "invalid value";     break;
469     case GL_INVALID_OPERATION:     e = "invalid operation"; break;
470     case GL_STACK_OVERFLOW:        e = "stack overflow";    break;
471     case GL_STACK_UNDERFLOW:       e = "stack underflow";   break;
472     case GL_OUT_OF_MEMORY:         e = "out of memory";     break;
473 #ifdef GL_TABLE_TOO_LARGE_EXT
474     case GL_TABLE_TOO_LARGE_EXT:   e = "table too large";   break;
475 #endif
476 #ifdef GL_TEXTURE_TOO_LARGE_EXT
477     case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
478 #endif
479     default:
480       e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
481   }
482   NSAssert2 (0, @"%s GL error: %s", type, e);
483 }