1 /* xscreensaver, Copyright (c) 2006-2014 Jamie Zawinski <jwz@jwz.org>
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
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.
18 #import "XScreenSaverGLView.h"
19 #import "XScreenSaverConfigSheet.h"
20 #import "screenhackI.h"
21 #import "xlockmoreI.h"
26 # import <OpenGL/OpenGL.h>
29 /* used by the OpenGL screen savers
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);
38 @implementation XScreenSaverGLView
42 [super stopAnimation];
44 // Without this, the GL frame stays on screen when switching tabs
45 // in System Preferences.
48 [NSOpenGLContext clearCurrentContext];
49 # endif // !USE_IPHONE
51 clear_gl_error(); // This hack is defunct, don't let this linger.
55 // #### maybe this could/should just be on 'lockFocus' instead?
56 - (void) prepareContext
60 [EAGLContext setCurrentContext:ogl_ctx];
62 [ogl_ctx makeCurrentContext];
63 // check_gl_error ("makeCurrentContext");
70 - (void) resizeContext
74 [ogl_ctx setView:self];
75 # endif // !USE_IPHONE
79 - (NSOpenGLContext *) oglContext
86 /* With GL programs, drawing at full resolution isn't a problem.
88 - (CGFloat) hackedContentScaleFactor
90 return [self contentScaleFactor];
95 - (void) setOglContext: (NSOpenGLContext *) ctx
100 [EAGLContext setCurrentContext: ogl_ctx];
102 double s = [self hackedContentScaleFactor];
103 int w = s * [self bounds].size.width;
104 int h = s * [self bounds].size.height;
106 if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer);
107 if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
108 if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer);
110 glGenFramebuffersOES (1, &gl_framebuffer);
111 glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer);
113 glGenRenderbuffersOES (1, &gl_renderbuffer);
114 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
117 // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, w, h);
118 [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
119 fromDrawable:(CAEAGLLayer*)self.layer];
121 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
122 GL_RENDERBUFFER_OES, gl_renderbuffer);
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,
129 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
130 GL_RENDERBUFFER_OES, gl_depthbuffer);
132 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
134 case GL_FRAMEBUFFER_COMPLETE_OES:
136 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
137 NSAssert (0, @"framebuffer incomplete attachment");
139 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
140 NSAssert (0, @"framebuffer incomplete missing attachment");
142 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
143 NSAssert (0, @"framebuffer incomplete dimensions");
145 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
146 NSAssert (0, @"framebuffer incomplete formats");
148 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
149 NSAssert (0, @"framebuffer unsupported");
152 case GL_FRAMEBUFFER_UNDEFINED:
153 NSAssert (0, @"framebuffer undefined");
155 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
156 NSAssert (0, @"framebuffer incomplete draw buffer");
158 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
159 NSAssert (0, @"framebuffer incomplete read buffer");
161 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
162 NSAssert (0, @"framebuffer incomplete multisample");
164 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
165 NSAssert (0, @"framebuffer incomplete layer targets");
169 NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
173 check_gl_error ("setOglContext");
175 # endif // USE_IPHONE
177 [self resizeContext];
183 return [CAEAGLLayer class];
188 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
189 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
194 #ifdef USE_BACKBUFFER
201 - (void)drawRect:(NSRect)rect
206 - (void) animateOneFrame
209 UIGraphicsPushContext (backbuffer);
215 UIGraphicsPopContext();
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.
226 - (void) createBackbuffer:(CGSize)new_size
228 backbuffer_size = new_size;
231 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
234 backbuffer = CGBitmapContextCreate (NULL, w, h, // yup, only 8px x 8px.
236 kCGImageAlphaPremultipliedLast);
237 CGColorSpaceRelease (cs);
240 # endif // USE_BACKBUFFER
243 /* When changing the device orientation, leave the X11 Window and glViewport
244 in portrait configuration. OpenGL hacks examine current_device_rotation()
245 within the scene as needed.
247 - (BOOL)reshapeRotatedWindow
264 /* Utility functions...
268 // redefine NSAssert, etc. here since they don't work when not inside
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)
283 /* Called by OpenGL savers using the XLockmore API.
286 init_GL (ModeInfo *mi)
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];
298 NSOpenGLPixelFormatAttribute attrs[40];
300 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
301 attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
302 attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
304 if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
305 attrs[i++] = NSOpenGLPFADoubleBuffer;
307 Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample");
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.
318 // if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
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;
330 NSOpenGLPixelFormat *pixfmt =
331 [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
333 if (ms_p && !pixfmt) { // Retry without multisampling.
336 pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
339 NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
341 // #### Analyze says: "Potential leak of an object stored into pixfmt"
342 ctx = [[NSOpenGLContext alloc]
343 initWithFormat:pixfmt
345 // [pixfmt release]; // #### ???
348 // Sync refreshes to the vertical blanking interval
350 [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
351 // check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
353 // #### Analyze says: "Potential leak of an object stored into "ctx"
354 // But makeCurrentContext retains it (right?)
356 [ctx makeCurrentContext];
357 check_gl_error ("makeCurrentContext");
359 [view setOglContext:ctx];
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);
365 // glXSwapBuffers (mi->dpy, mi->window);
368 // Enable multi-threading, if possible. This runs most OpenGL commands
369 // and GPU management on a second CPU.
371 # ifndef kCGLCEMPEngine
372 # define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
374 CGLContextObj cctx = CGLGetCurrentContext();
375 CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
376 if (err != kCGLNoError) {
377 NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
381 check_gl_error ("init_GL");
385 EAGLContext *ogl_ctx = ctx;
389 get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer");
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.
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,
404 // Without this, the GL frame buffer is half the screen resolution!
405 eagl_layer.contentsScale = [UIScreen mainScreen].scale;
407 ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
412 [view setOglContext:ogl_ctx];
413 // #### Analyze says "Potential leak of an object stored into ogl_ctx"
415 check_gl_error ("OES_init");
419 # endif // USE_IPHONE
421 // I don't know why this is necessary, but it beats randomly having some
422 // textures be upside down.
424 glMatrixMode(GL_TEXTURE);
426 glMatrixMode(GL_PROJECTION);
428 glMatrixMode(GL_MODELVIEW);
432 // Caller expects a pointer to an opaque struct... which it dereferences.
433 // Don't ask me, it's historical...
434 static int blort = -1;
435 return (void *) &blort;
439 /* Copy the back buffer to the front buffer.
442 glXSwapBuffers (Display *dpy, Window window)
444 XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
445 NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
446 @"wrong view class: %@", view);
448 NSOpenGLContext *ctx = [view oglContext];
449 if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
450 #else /* USE_IPHONE */
452 #endif /* USE_IPHONE */
455 /* Does nothing - prepareContext already did the work.
458 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
463 /* clear away any lingering error codes */
465 clear_gl_error (void)
467 while (glGetError() != GL_NO_ERROR)
472 /* report a GL error. */
474 check_gl_error (const char *type)
479 switch ((i = glGetError())) {
480 case GL_NO_ERROR: return;
481 case GL_INVALID_ENUM: e = "invalid enum"; break;
482 case GL_INVALID_VALUE: e = "invalid value"; break;
483 case GL_INVALID_OPERATION: e = "invalid operation"; break;
484 case GL_STACK_OVERFLOW: e = "stack overflow"; break;
485 case GL_STACK_UNDERFLOW: e = "stack underflow"; break;
486 case GL_OUT_OF_MEMORY: e = "out of memory"; break;
487 #ifdef GL_TABLE_TOO_LARGE_EXT
488 case GL_TABLE_TOO_LARGE_EXT: e = "table too large"; break;
490 #ifdef GL_TEXTURE_TOO_LARGE_EXT
491 case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
494 e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
496 NSAssert2 (0, @"%s GL error: %s", type, e);