1 /* xscreensaver, Copyright (c) 2006-2012 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
53 // #### maybe this could/should just be on 'lockFocus' instead?
54 - (void) prepareContext
58 [EAGLContext setCurrentContext:ogl_ctx];
60 [ogl_ctx makeCurrentContext];
61 // check_gl_error ("makeCurrentContext");
68 - (void) resizeContext
72 [ogl_ctx setView:self];
73 # endif // !USE_IPHONE
77 - (NSOpenGLContext *) oglContext
83 - (void) setOglContext: (NSOpenGLContext *) ctx
88 [EAGLContext setCurrentContext: ogl_ctx];
90 double s = self.contentScaleFactor;
91 int w = s * [self frame].size.width;
92 int h = s * [self frame].size.height;
94 if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer);
95 if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
96 if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer);
98 glGenFramebuffersOES (1, &gl_framebuffer);
99 glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer);
101 glGenRenderbuffersOES (1, &gl_renderbuffer);
102 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
105 // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, w, h);
106 [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
107 fromDrawable:(CAEAGLLayer*)self.layer];
109 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
110 GL_RENDERBUFFER_OES, gl_renderbuffer);
112 glGenRenderbuffersOES (1, &gl_depthbuffer);
113 // renderbufferStorage: must be called before this.
114 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_depthbuffer);
115 glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES,
117 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
118 GL_RENDERBUFFER_OES, gl_depthbuffer);
120 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
122 case GL_FRAMEBUFFER_COMPLETE_OES:
124 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
125 NSAssert (0, @"framebuffer incomplete attachment");
127 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
128 NSAssert (0, @"framebuffer incomplete missing attachment");
130 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
131 NSAssert (0, @"framebuffer incomplete dimensions");
133 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
134 NSAssert (0, @"framebuffer incomplete formats");
136 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
137 NSAssert (0, @"framebuffer unsupported");
140 case GL_FRAMEBUFFER_UNDEFINED:
141 NSAssert (0, @"framebuffer undefined");
143 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
144 NSAssert (0, @"framebuffer incomplete draw buffer");
146 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
147 NSAssert (0, @"framebuffer incomplete read buffer");
149 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
150 NSAssert (0, @"framebuffer incomplete multisample");
152 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
153 NSAssert (0, @"framebuffer incomplete layer targets");
157 NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
161 check_gl_error ("setOglContext");
163 # endif // USE_IPHONE
165 [self resizeContext];
171 return [CAEAGLLayer class];
175 /* On MacOS: drawRect does nothing, and animateOneFrame renders.
176 On iOS GL: drawRect does nothing, and animateOneFrame renders.
177 On iOS X11: drawRect renders, and animateOneFrame marks the view dirty.
179 - (void)drawRect:(NSRect)rect
184 - (void) animateOneFrame
186 UIGraphicsPushContext (backbuffer);
188 UIGraphicsPopContext();
192 /* The backbuffer isn't actually used for GL programs, but it needs to
193 be there for X11 calls to not error out. However, nothing done with
194 X11 calls will ever show up! It all gets written into the backbuffer
195 and discarded. That's ok, though, because mostly it's just calls to
196 XClearWindow and housekeeping stuff like that. So we make a tiny one.
198 - (void) createBackbuffer
200 // Don't resize the X11 window to match rotation.
201 // Rotation and scaling are handled in GL.
203 NSRect f = [self frame];
204 double s = self.contentScaleFactor;
205 backbuffer_size.width = (int) (s * f.size.width);
206 backbuffer_size.height = (int) (s * f.size.height);
209 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
212 backbuffer = CGBitmapContextCreate (NULL, w, h,
214 kCGImageAlphaPremultipliedLast);
215 CGColorSpaceRelease (cs);
222 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
223 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
225 # endif // USE_IPHONE
239 /* Utility functions...
243 // redefine these now since they don't work when not inside an ObjC method.
248 #define NSAssert(CC,S) do { if (!(CC)) { NSLog(S); abort();}} while(0)
249 #define NSAssert1(CC,S,A) do { if (!(CC)) { NSLog(S,A); abort();}} while(0)
250 #define NSAssert2(CC,S,A,B) do { if (!(CC)) { NSLog(S,A,B);abort();}} while(0)
253 /* Called by OpenGL savers using the XLockmore API.
256 init_GL (ModeInfo *mi)
258 Window win = mi->window;
259 XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
260 NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
261 @"wrong view class: %@", view);
262 NSOpenGLContext *ctx = [view oglContext];
268 NSOpenGLPixelFormatAttribute attrs[40];
270 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
271 attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
272 attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
274 if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
275 attrs[i++] = NSOpenGLPFADoubleBuffer;
277 Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample");
279 /* Sometimes, turning on multisampling kills performance. At one point,
280 I thought the answer was, "only run multisampling on one screen, and
281 leave it turned off on other screens". That's what this code does,
282 but it turns out, that solution is insufficient. I can't really tell
283 what causes poor performance with multisampling, but it's not
284 predictable. Without changing the code, some times a given saver will
285 perform fine with multisampling on, and other times it will perform
286 very badly. Without multisampling, they always perform fine.
288 // if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
292 attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
293 attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6;
294 // Don't really understand what this means:
295 // attrs[i++] = NSOpenGLPFANoRecovery;
300 NSOpenGLPixelFormat *pixfmt =
301 [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
303 if (ms_p && !pixfmt) { // Retry without multisampling.
306 pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
309 NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
311 ctx = [[NSOpenGLContext alloc]
312 initWithFormat:pixfmt
314 // [pixfmt release]; // #### ???
317 // Sync refreshes to the vertical blanking interval
319 [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
320 // check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
322 // #### "Build and Analyze" says that ctx leaks, because it doesn't
323 // seem to realize that makeCurrentContext retains it (right?)
324 // Not sure what to do to make this warning go away.
326 [ctx makeCurrentContext];
327 check_gl_error ("makeCurrentContext");
329 [view setOglContext:ctx];
331 // Clear frame buffer ASAP, else there are bits left over from other apps.
332 glClearColor (0, 0, 0, 1);
333 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
335 // glXSwapBuffers (mi->dpy, mi->window);
338 // Enable multi-threading, if possible. This runs most OpenGL commands
339 // and GPU management on a second CPU.
341 # ifndef kCGLCEMPEngine
342 # define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
344 CGLContextObj cctx = CGLGetCurrentContext();
345 CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
346 if (err != kCGLNoError) {
347 NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
351 check_gl_error ("init_GL");
355 EAGLContext *ogl_ctx = ctx;
359 get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer");
361 /* There seems to be no way to actually turn off double-buffering in
362 EAGLContext (e.g., no way to draw to the front buffer directly)
363 but if we turn on "retained backing" for non-buffering apps like
364 "pipes", at least the back buffer isn't auto-cleared on them.
366 CAEAGLLayer *eagl_layer = (CAEAGLLayer *) view.layer;
367 eagl_layer.opaque = TRUE;
368 eagl_layer.drawableProperties =
369 [NSDictionary dictionaryWithObjectsAndKeys:
370 kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
371 [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking,
374 ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
379 [view setOglContext:ogl_ctx];
381 check_gl_error ("OES_init");
383 # endif // USE_IPHONE
385 // Caller expects a pointer to an opaque struct... which it dereferences.
386 // Don't ask me, it's historical...
387 static int blort = -1;
388 return (void *) &blort;
392 /* Copy the back buffer to the front buffer.
395 glXSwapBuffers (Display *dpy, Window window)
397 XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
398 NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
399 @"wrong view class: %@", view);
401 NSOpenGLContext *ctx = [view oglContext];
402 if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
403 #else /* USE_IPHONE */
405 #endif /* USE_IPHONE */
408 /* Does nothing - prepareContext already did the work.
411 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
416 /* clear away any lingering error codes */
418 clear_gl_error (void)
420 while (glGetError() != GL_NO_ERROR)
425 /* report a GL error. */
427 check_gl_error (const char *type)
432 switch ((i = glGetError())) {
433 case GL_NO_ERROR: return;
434 case GL_INVALID_ENUM: e = "invalid enum"; break;
435 case GL_INVALID_VALUE: e = "invalid value"; break;
436 case GL_INVALID_OPERATION: e = "invalid operation"; break;
437 case GL_STACK_OVERFLOW: e = "stack overflow"; break;
438 case GL_STACK_UNDERFLOW: e = "stack underflow"; break;
439 case GL_OUT_OF_MEMORY: e = "out of memory"; break;
440 #ifdef GL_TABLE_TOO_LARGE_EXT
441 case GL_TABLE_TOO_LARGE_EXT: e = "table too large"; break;
443 #ifdef GL_TEXTURE_TOO_LARGE_EXT
444 case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
447 e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
449 NSAssert2 (0, @"%s GL error: %s", type, e);