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 NSSize ssize = [[[UIScreen mainScreen] currentMode] size];
91 NSSize bsize = [self bounds].size;
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));
101 - (void) setOglContext: (NSOpenGLContext *) ctx
106 [EAGLContext setCurrentContext: ogl_ctx];
108 double s = [self hackedContentScaleFactor];
109 int w = s * [self bounds].size.width;
110 int h = s * [self bounds].size.height;
112 if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer);
113 if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
114 if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer);
116 glGenFramebuffersOES (1, &gl_framebuffer);
117 glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer);
119 glGenRenderbuffersOES (1, &gl_renderbuffer);
120 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
123 // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, w, h);
124 [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
125 fromDrawable:(CAEAGLLayer*)self.layer];
127 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
128 GL_RENDERBUFFER_OES, gl_renderbuffer);
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,
135 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
136 GL_RENDERBUFFER_OES, gl_depthbuffer);
138 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
140 case GL_FRAMEBUFFER_COMPLETE_OES:
142 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
143 NSAssert (0, @"framebuffer incomplete attachment");
145 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
146 NSAssert (0, @"framebuffer incomplete missing attachment");
148 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
149 NSAssert (0, @"framebuffer incomplete dimensions");
151 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
152 NSAssert (0, @"framebuffer incomplete formats");
154 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
155 NSAssert (0, @"framebuffer unsupported");
158 case GL_FRAMEBUFFER_UNDEFINED:
159 NSAssert (0, @"framebuffer undefined");
161 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
162 NSAssert (0, @"framebuffer incomplete draw buffer");
164 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
165 NSAssert (0, @"framebuffer incomplete read buffer");
167 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
168 NSAssert (0, @"framebuffer incomplete multisample");
170 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
171 NSAssert (0, @"framebuffer incomplete layer targets");
175 NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
179 check_gl_error ("setOglContext");
181 # endif // USE_IPHONE
183 [self resizeContext];
189 return [CAEAGLLayer class];
194 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
195 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
200 #ifdef USE_BACKBUFFER
207 - (void)drawRect:(NSRect)rect
212 - (void) animateOneFrame
215 UIGraphicsPushContext (backbuffer);
221 UIGraphicsPopContext();
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.
232 - (void) createBackbuffer:(CGSize)new_size
234 backbuffer_size = new_size;
237 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
240 backbuffer = CGBitmapContextCreate (NULL, w, h, // yup, only 8px x 8px.
242 kCGImageAlphaPremultipliedLast);
243 CGColorSpaceRelease (cs);
246 # endif // USE_BACKBUFFER
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.
253 - (BOOL)reshapeRotatedWindow
270 /* Utility functions...
274 // redefine NSAssert, etc. here since they don't work when not inside
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)
289 /* Called by OpenGL savers using the XLockmore API.
292 init_GL (ModeInfo *mi)
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];
304 NSOpenGLPixelFormatAttribute attrs[40];
306 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
307 attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
308 attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
310 if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
311 attrs[i++] = NSOpenGLPFADoubleBuffer;
313 Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample");
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.
324 // if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
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;
336 NSOpenGLPixelFormat *pixfmt =
337 [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
339 if (ms_p && !pixfmt) { // Retry without multisampling.
342 pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
345 NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
347 // #### Analyze says: "Potential leak of an object stored into pixfmt"
348 ctx = [[NSOpenGLContext alloc]
349 initWithFormat:pixfmt
351 // [pixfmt release]; // #### ???
354 // Sync refreshes to the vertical blanking interval
356 [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
357 // check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
359 // #### Analyze says: "Potential leak of an object stored into "ctx"
360 // But makeCurrentContext retains it (right?)
362 [ctx makeCurrentContext];
363 check_gl_error ("makeCurrentContext");
365 [view setOglContext:ctx];
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);
371 // glXSwapBuffers (mi->dpy, mi->window);
374 // Enable multi-threading, if possible. This runs most OpenGL commands
375 // and GPU management on a second CPU.
377 # ifndef kCGLCEMPEngine
378 # define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
380 CGLContextObj cctx = CGLGetCurrentContext();
381 CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
382 if (err != kCGLNoError) {
383 NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
387 check_gl_error ("init_GL");
391 EAGLContext *ogl_ctx = ctx;
395 get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer");
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.
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,
410 // Without this, the GL frame buffer is half the screen resolution!
411 eagl_layer.contentsScale = [UIScreen mainScreen].scale;
413 ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
418 [view setOglContext:ogl_ctx];
419 // #### Analyze says "Potential leak of an object stored into ogl_ctx"
421 check_gl_error ("OES_init");
425 # endif // USE_IPHONE
427 // I don't know why this is necessary, but it beats randomly having some
428 // textures be upside down.
430 glMatrixMode(GL_TEXTURE);
432 glMatrixMode(GL_PROJECTION);
434 glMatrixMode(GL_MODELVIEW);
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;
445 /* Copy the back buffer to the front buffer.
448 glXSwapBuffers (Display *dpy, Window window)
450 XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
451 NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
452 @"wrong view class: %@", view);
454 NSOpenGLContext *ctx = [view oglContext];
455 if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
456 #else /* USE_IPHONE */
458 #endif /* USE_IPHONE */
461 /* Does nothing - prepareContext already did the work.
464 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
469 /* clear away any lingering error codes */
471 clear_gl_error (void)
473 while (glGetError() != GL_NO_ERROR)
478 /* report a GL error. */
480 check_gl_error (const char *type)
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;
496 #ifdef GL_TEXTURE_TOO_LARGE_EXT
497 case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
500 e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
502 NSAssert2 (0, @"%s GL error: %s", type, e);