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
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
85 - (void) setOglContext: (NSOpenGLContext *) ctx
90 [EAGLContext setCurrentContext: ogl_ctx];
92 double s = self.contentScaleFactor;
93 int w = s * [self frame].size.width;
94 int h = s * [self frame].size.height;
96 if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer);
97 if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
98 if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer);
100 glGenFramebuffersOES (1, &gl_framebuffer);
101 glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer);
103 glGenRenderbuffersOES (1, &gl_renderbuffer);
104 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
107 // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, w, h);
108 [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
109 fromDrawable:(CAEAGLLayer*)self.layer];
111 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
112 GL_RENDERBUFFER_OES, gl_renderbuffer);
114 glGenRenderbuffersOES (1, &gl_depthbuffer);
115 // renderbufferStorage: must be called before this.
116 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_depthbuffer);
117 glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES,
119 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
120 GL_RENDERBUFFER_OES, gl_depthbuffer);
122 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
124 case GL_FRAMEBUFFER_COMPLETE_OES:
126 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
127 NSAssert (0, @"framebuffer incomplete attachment");
129 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
130 NSAssert (0, @"framebuffer incomplete missing attachment");
132 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
133 NSAssert (0, @"framebuffer incomplete dimensions");
135 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
136 NSAssert (0, @"framebuffer incomplete formats");
138 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
139 NSAssert (0, @"framebuffer unsupported");
142 case GL_FRAMEBUFFER_UNDEFINED:
143 NSAssert (0, @"framebuffer undefined");
145 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
146 NSAssert (0, @"framebuffer incomplete draw buffer");
148 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
149 NSAssert (0, @"framebuffer incomplete read buffer");
151 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
152 NSAssert (0, @"framebuffer incomplete multisample");
154 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
155 NSAssert (0, @"framebuffer incomplete layer targets");
159 NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
163 check_gl_error ("setOglContext");
165 # endif // USE_IPHONE
167 [self resizeContext];
173 return [CAEAGLLayer class];
177 /* On MacOS: drawRect does nothing, and animateOneFrame renders.
178 On iOS GL: drawRect does nothing, and animateOneFrame renders.
179 On iOS X11: drawRect renders, and animateOneFrame marks the view dirty.
181 - (void)drawRect:(NSRect)rect
186 - (void) animateOneFrame
188 UIGraphicsPushContext (backbuffer);
190 UIGraphicsPopContext();
194 /* The backbuffer isn't actually used for GL programs, but it needs to
195 be there for X11 calls to not error out. However, nothing done with
196 X11 calls will ever show up! It all gets written into the backbuffer
197 and discarded. That's ok, though, because mostly it's just calls to
198 XClearWindow and housekeeping stuff like that. So we make a tiny one.
200 - (void) createBackbuffer
202 // Don't resize the X11 window to match rotation.
203 // Rotation and scaling are handled in GL.
205 NSRect f = [self frame];
206 double s = self.contentScaleFactor;
207 backbuffer_size.width = (int) (s * f.size.width);
208 backbuffer_size.height = (int) (s * f.size.height);
211 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
214 backbuffer = CGBitmapContextCreate (NULL, w, h,
216 kCGImageAlphaPremultipliedLast);
217 CGColorSpaceRelease (cs);
224 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
225 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
227 # endif // USE_IPHONE
241 /* Utility functions...
245 // redefine NSAssert, etc. here since they don't work when not inside
252 jwxyz_abort ("%s", [(S) cStringUsingEncoding:NSUTF8StringEncoding])
253 #define NSAssert(CC,S) do { if (!(CC)) { NSASS((S)); }} while(0)
254 #define NSAssert1(CC,S,A) do { if (!(CC)) { \
255 NSASS(([NSString stringWithFormat: S, A])); }} while(0)
256 #define NSAssert2(CC,S,A,B) do { if (!(CC)) { \
257 NSASS(([NSString stringWithFormat: S, A, B])); }} while(0)
260 /* Called by OpenGL savers using the XLockmore API.
263 init_GL (ModeInfo *mi)
265 Window win = mi->window;
266 XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
267 NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
268 @"wrong view class: %@", view);
269 NSOpenGLContext *ctx = [view oglContext];
275 NSOpenGLPixelFormatAttribute attrs[40];
277 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
278 attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
279 attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
281 if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
282 attrs[i++] = NSOpenGLPFADoubleBuffer;
284 Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample");
286 /* Sometimes, turning on multisampling kills performance. At one point,
287 I thought the answer was, "only run multisampling on one screen, and
288 leave it turned off on other screens". That's what this code does,
289 but it turns out, that solution is insufficient. I can't really tell
290 what causes poor performance with multisampling, but it's not
291 predictable. Without changing the code, some times a given saver will
292 perform fine with multisampling on, and other times it will perform
293 very badly. Without multisampling, they always perform fine.
295 // if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
299 attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
300 attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6;
301 // Don't really understand what this means:
302 // attrs[i++] = NSOpenGLPFANoRecovery;
307 NSOpenGLPixelFormat *pixfmt =
308 [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
310 if (ms_p && !pixfmt) { // Retry without multisampling.
313 pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
316 NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
318 ctx = [[NSOpenGLContext alloc]
319 initWithFormat:pixfmt
321 // [pixfmt release]; // #### ???
324 // Sync refreshes to the vertical blanking interval
326 [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
327 // check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
329 // #### "Build and Analyze" says that ctx leaks, because it doesn't
330 // seem to realize that makeCurrentContext retains it (right?)
331 // Not sure what to do to make this warning go away.
333 [ctx makeCurrentContext];
334 check_gl_error ("makeCurrentContext");
336 [view setOglContext:ctx];
338 // Clear frame buffer ASAP, else there are bits left over from other apps.
339 glClearColor (0, 0, 0, 1);
340 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
342 // glXSwapBuffers (mi->dpy, mi->window);
345 // Enable multi-threading, if possible. This runs most OpenGL commands
346 // and GPU management on a second CPU.
348 # ifndef kCGLCEMPEngine
349 # define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
351 CGLContextObj cctx = CGLGetCurrentContext();
352 CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
353 if (err != kCGLNoError) {
354 NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
358 check_gl_error ("init_GL");
362 EAGLContext *ogl_ctx = ctx;
366 get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer");
368 /* There seems to be no way to actually turn off double-buffering in
369 EAGLContext (e.g., no way to draw to the front buffer directly)
370 but if we turn on "retained backing" for non-buffering apps like
371 "pipes", at least the back buffer isn't auto-cleared on them.
373 CAEAGLLayer *eagl_layer = (CAEAGLLayer *) view.layer;
374 eagl_layer.opaque = TRUE;
375 eagl_layer.drawableProperties =
376 [NSDictionary dictionaryWithObjectsAndKeys:
377 kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
378 [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking,
381 // Without this, the GL frame buffer is half the screen resolution!
382 eagl_layer.contentsScale = [UIScreen mainScreen].scale;
384 ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
389 [view setOglContext:ogl_ctx];
391 check_gl_error ("OES_init");
393 # endif // USE_IPHONE
395 // Caller expects a pointer to an opaque struct... which it dereferences.
396 // Don't ask me, it's historical...
397 static int blort = -1;
398 return (void *) &blort;
402 /* Copy the back buffer to the front buffer.
405 glXSwapBuffers (Display *dpy, Window window)
407 XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
408 NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
409 @"wrong view class: %@", view);
411 NSOpenGLContext *ctx = [view oglContext];
412 if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
413 #else /* USE_IPHONE */
415 #endif /* USE_IPHONE */
418 /* Does nothing - prepareContext already did the work.
421 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
426 /* clear away any lingering error codes */
428 clear_gl_error (void)
430 while (glGetError() != GL_NO_ERROR)
435 /* report a GL error. */
437 check_gl_error (const char *type)
442 switch ((i = glGetError())) {
443 case GL_NO_ERROR: return;
444 case GL_INVALID_ENUM: e = "invalid enum"; break;
445 case GL_INVALID_VALUE: e = "invalid value"; break;
446 case GL_INVALID_OPERATION: e = "invalid operation"; break;
447 case GL_STACK_OVERFLOW: e = "stack overflow"; break;
448 case GL_STACK_UNDERFLOW: e = "stack underflow"; break;
449 case GL_OUT_OF_MEMORY: e = "out of memory"; break;
450 #ifdef GL_TABLE_TOO_LARGE_EXT
451 case GL_TABLE_TOO_LARGE_EXT: e = "table too large"; break;
453 #ifdef GL_TEXTURE_TOO_LARGE_EXT
454 case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
457 e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
459 NSAssert2 (0, @"%s GL error: %s", type, e);