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