-/* xscreensaver, Copyright (c) 2006-2013 Jamie Zawinski <jwz@jwz.org>
-*
-* Permission to use, copy, modify, distribute, and sell this software and its
-* documentation for any purpose is hereby granted without fee, provided that
-* the above copyright notice appear in all copies and that both that
-* copyright notice and this permission notice appear in supporting
-* documentation. No representations are made about the suitability of this
-* software for any purpose. It is provided "as is" without express or
-* implied warranty.
-*/
+/* xscreensaver, Copyright (c) 2006-2016 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
/* This is a subclass of Apple's ScreenSaverView that knows how to run
xscreensaver programs without X11 via the dark magic of the "jwxyz"
#import "XScreenSaverGLView.h"
#import "XScreenSaverConfigSheet.h"
+#import "jwxyz-cocoa.h"
+#import "jwxyzI.h"
#import "screenhackI.h"
#import "xlockmoreI.h"
#ifdef USE_IPHONE
# include "jwzgles.h"
+# import <OpenGLES/ES1/glext.h>
#else
# import <OpenGL/OpenGL.h>
#endif
@implementation XScreenSaverGLView
-- (void)stopAnimation
+
+#ifdef USE_IPHONE
+/* With GL programs, drawing at full resolution isn't a problem.
+ */
+- (CGFloat) hackedContentScaleFactor
{
- [super stopAnimation];
-
- // Without this, the GL frame stays on screen when switching tabs
- // in System Preferences.
- //
-# ifndef USE_IPHONE
- [NSOpenGLContext clearCurrentContext];
-# endif // !USE_IPHONE
+ return [self contentScaleFactor];
+}
- clear_gl_error(); // This hack is defunct, don't let this linger.
+- (BOOL)ignoreRotation
+{
+ return FALSE; // Allow xwindow and the glViewport to change shape
}
+- (BOOL) suppressRotationAnimation
+{
+ return _suppressRotationAnimation; // per-hack setting, default FALSE
+}
-// #### maybe this could/should just be on 'lockFocus' instead?
-- (void) prepareContext
+- (BOOL) rotateTouches
{
- if (ogl_ctx) {
-#ifdef USE_IPHONE
- [EAGLContext setCurrentContext:ogl_ctx];
-#else // !USE_IPHONE
- [ogl_ctx makeCurrentContext];
-// check_gl_error ("makeCurrentContext");
- [ogl_ctx update];
-#endif // !USE_IPHONE
- }
+ return TRUE; // We need the XY axes swapped in our events
}
-- (void) resizeContext
+- (void) swapBuffers
{
-# ifndef USE_IPHONE
- if (ogl_ctx)
- [ogl_ctx setView:self];
-# endif // !USE_IPHONE
+# ifdef JWXYZ_GL
+ GLint gl_renderbuffer = xwindow->gl_renderbuffer;
+# endif // JWXYZ_GL
+ glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
+ [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
}
+#endif // USE_IPHONE
-- (NSOpenGLContext *) oglContext
+- (void) animateOneFrame
{
- return ogl_ctx;
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+ UIGraphicsPushContext (backbuffer);
+# endif
+
+ [self render_x11];
+
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+ UIGraphicsPopContext();
+# endif
}
-#ifdef USE_IPHONE
-/* With GL programs, drawing at full resolution isn't a problem.
- */
-- (CGFloat) hackedContentScaleFactor
+/* GL screenhacks don't display a backbuffer, so this is a stub. */
+- (void) enableBackbuffer:(CGSize)new_backbuffer_size
{
- return [self contentScaleFactor];
}
-#endif // USE_IPHONE
-- (void) setOglContext: (NSOpenGLContext *) ctx
+/* GL screenhacks set their own viewport and matrices. */
+- (void) setViewport
{
- ogl_ctx = ctx;
+}
-# ifdef USE_IPHONE
- [EAGLContext setCurrentContext: ogl_ctx];
- double s = [self hackedContentScaleFactor];
- int w = s * [self bounds].size.width;
- int h = s * [self bounds].size.height;
+#ifdef USE_IPHONE
- if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer);
- if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
- if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer);
+/* Keep the GL scene oriented into a portrait-mode View, regardless of
+ what the physical device orientation is.
+ */
+- (void) reshape_x11
+{
+ [super reshape_x11];
- glGenFramebuffersOES (1, &gl_framebuffer);
- glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer);
+ glMatrixMode(GL_PROJECTION);
+ glRotatef (-current_device_rotation(), 0, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+}
- glGenRenderbuffersOES (1, &gl_renderbuffer);
- glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
+- (void) render_x11
+{
+ BOOL was_initted_p = initted_p;
+ [super render_x11];
+
+ if (! was_initted_p && xdpy)
+ _suppressRotationAnimation =
+ get_boolean_resource (xdpy,
+ "suppressRotationAnimation",
+ "SuppressRotationAnimation");
+}
-// redundant?
-// glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, w, h);
- [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
- fromDrawable:(CAEAGLLayer*)self.layer];
+#endif // USE_IPHONE
- glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
- GL_RENDERBUFFER_OES, gl_renderbuffer);
- glGenRenderbuffersOES (1, &gl_depthbuffer);
- // renderbufferStorage: must be called before this.
- glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_depthbuffer);
- glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES,
- w, h);
- glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
- GL_RENDERBUFFER_OES, gl_depthbuffer);
- int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
- switch (err) {
- case GL_FRAMEBUFFER_COMPLETE_OES:
- break;
- case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
- NSAssert (0, @"framebuffer incomplete attachment");
- break;
- case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
- NSAssert (0, @"framebuffer incomplete missing attachment");
- break;
- case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
- NSAssert (0, @"framebuffer incomplete dimensions");
- break;
- case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
- NSAssert (0, @"framebuffer incomplete formats");
- break;
- case GL_FRAMEBUFFER_UNSUPPORTED_OES:
- NSAssert (0, @"framebuffer unsupported");
- break;
-/*
- case GL_FRAMEBUFFER_UNDEFINED:
- NSAssert (0, @"framebuffer undefined");
- break;
- case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
- NSAssert (0, @"framebuffer incomplete draw buffer");
- break;
- case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
- NSAssert (0, @"framebuffer incomplete read buffer");
- break;
- case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
- NSAssert (0, @"framebuffer incomplete multisample");
- break;
- case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
- NSAssert (0, @"framebuffer incomplete layer targets");
- break;
+/* The backbuffer isn't actually used for GL programs, but it needs to
+ be there for X11 calls to not error out. However, nothing done with
+ X11 calls will ever show up! It all gets written into the backbuffer
+ and discarded. That's ok, though, because mostly it's just calls to
+ XClearWindow and housekeeping stuff like that. So we make a tiny one.
*/
- default:
- NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
- break;
- }
-
- check_gl_error ("setOglContext");
-
-# endif // USE_IPHONE
+- (void) createBackbuffer:(CGSize)new_size
+{
+#ifdef JWXYZ_QUARTZ
+ NSAssert (! backbuffer_texture,
+ @"backbuffer_texture shouldn't be used for GL hacks");
- [self resizeContext];
+ if (! backbuffer) {
+ CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+ int w = 8;
+ int h = 8;
+ backbuffer = CGBitmapContextCreate (NULL, w, h, // yup, only 8px x 8px.
+ 8, w*4, cs,
+ (kCGBitmapByteOrder32Little |
+ kCGImageAlphaNoneSkipLast));
+ CGColorSpaceRelease (cs);
+ }
+#endif // JWXYZ_QUARTZ
}
-#ifdef USE_IPHONE
-+ (Class) layerClass
+
+/* Another stub for GL screenhacks. */
+- (void) drawBackbuffer
{
- return [CAEAGLLayer class];
}
-- (void) swapBuffers
+
+/* Likewise. GL screenhacks control display with glXSwapBuffers(). */
+- (void) flushBackbuffer
{
- glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
- [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
}
-#endif // USE_IPHONE
-#ifdef USE_BACKBUFFER
+#ifndef USE_IPHONE
-- (void) initLayer
+- (NSOpenGLPixelFormat *) getGLPixelFormat
{
- // Do nothing.
+ NSOpenGLPixelFormatAttribute attrs[40];
+ int i = 0;
+ attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
+ attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
+ attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24;
+
+ if ([prefsReader getBooleanResource:"doubleBuffer"])
+ attrs[i++] = NSOpenGLPFADoubleBuffer;
+
+ Bool ms_p = [prefsReader getBooleanResource:"multiSample"];
+
+ /* Sometimes, turning on multisampling kills performance. At one point,
+ I thought the answer was, "only run multisampling on one screen, and
+ leave it turned off on other screens". That's what this code does,
+ but it turns out, that solution is insufficient. I can't really tell
+ what causes poor performance with multisampling, but it's not
+ predictable. Without changing the code, some times a given saver will
+ perform fine with multisampling on, and other times it will perform
+ very badly. Without multisampling, they always perform fine.
+ */
+ // if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
+ // ms_p = 0;
+
+ if (ms_p) {
+ attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
+ attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6;
+ // Don't really understand what this means:
+ // attrs[i++] = NSOpenGLPFANoRecovery;
+ }
+
+ attrs[i++] = NSOpenGLPFAWindow;
+# ifdef JWXYZ_GL
+ attrs[i++] = NSOpenGLPFAPixelBuffer;
+# endif
+
+ attrs[i] = 0;
+
+ NSOpenGLPixelFormat *result = [[NSOpenGLPixelFormat alloc]
+ initWithAttributes:attrs];
+
+ if (ms_p && !result) { // Retry without multisampling.
+ i -= 2;
+ attrs[i] = 0;
+ result = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
+ }
+
+ return [result autorelease];
}
-- (void)drawRect:(NSRect)rect
+#else // !USE_IPHONE
+
+- (NSDictionary *)getGLProperties
{
+ Bool dbuf_p = [prefsReader getBooleanResource:"doubleBuffer"];
+
+ /* There seems to be no way to actually turn off double-buffering in
+ EAGLContext (e.g., no way to draw to the front buffer directly)
+ but if we turn on "retained backing" for non-buffering apps like
+ "pipes", at least the back buffer isn't auto-cleared on them.
+ */
+
+ return [NSDictionary dictionaryWithObjectsAndKeys:
+ kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
+ [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking,
+ nil];
}
-
-- (void) animateOneFrame
+- (void)addExtraRenderbuffers:(CGSize)size
{
-# ifdef USE_IPHONE
- UIGraphicsPushContext (backbuffer);
-# endif
+ int w = size.width;
+ int h = size.height;
- [self render_x11];
+ if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer);
-# ifdef USE_IPHONE
- UIGraphicsPopContext();
-# endif
+ glGenRenderbuffersOES (1, &gl_depthbuffer);
+ // [EAGLContext renderbufferStorage:fromDrawable:] must be called before this.
+ glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_depthbuffer);
+ glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES,
+ w, h);
+ glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
+ GL_RENDERBUFFER_OES, gl_depthbuffer);
}
+- (NSString *)getCAGravity
+{
+ return kCAGravityCenter;
+}
-/* The backbuffer isn't actually used for GL programs, but it needs to
- be there for X11 calls to not error out. However, nothing done with
- X11 calls will ever show up! It all gets written into the backbuffer
- and discarded. That's ok, though, because mostly it's just calls to
- XClearWindow and housekeeping stuff like that. So we make a tiny one.
- */
-- (void) createBackbuffer
+- (void) startAnimation
{
- // Don't resize the X11 window to match rotation.
- // Rotation and scaling are handled in GL.
- //
-# ifdef USE_IPHONE
- double s = [self hackedContentScaleFactor];
-# else
- double s = 1;
-# endif
- // Store a realistic size in backbuffer_size, though the buffer is minimal.
- NSRect f = [self bounds];
- backbuffer_size.width = (int) (s * f.size.width);
- backbuffer_size.height = (int) (s * f.size.height);
+ [super startAnimation];
+ if (ogl_ctx) /* Almost always true. */
+ _glesState = jwzgles_make_state ();
+}
- if (! backbuffer) {
- CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
- int w = 8;
- int h = 8;
- backbuffer = CGBitmapContextCreate (NULL, w, h,
- 8, w*4, cs,
- kCGImageAlphaPremultipliedLast);
- CGColorSpaceRelease (cs);
+- (void) stopAnimation
+{
+ [super stopAnimation];
+#ifdef USE_IPHONE
+ if (_glesState) {
+ [EAGLContext setCurrentContext:ogl_ctx];
+ jwzgles_make_current (_glesState);
+ jwzgles_free_state ();
}
+#endif
+}
+
+- (void) prepareContext
+{
+ [super prepareContext];
+ jwzgles_make_current (_glesState);
}
-# endif // USE_BACKBUFFER
+
+#endif // !USE_IPHONE
- (void)dealloc {
XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
@"wrong view class: %@", view);
- NSOpenGLContext *ctx = [view oglContext];
-
-# ifndef USE_IPHONE
-
- if (!ctx) {
-
- NSOpenGLPixelFormatAttribute attrs[40];
- int i = 0;
- attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
- attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
- attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
-
- if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
- attrs[i++] = NSOpenGLPFADoubleBuffer;
-
- Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample");
-
- /* Sometimes, turning on multisampling kills performance. At one point,
- I thought the answer was, "only run multisampling on one screen, and
- leave it turned off on other screens". That's what this code does,
- but it turns out, that solution is insufficient. I can't really tell
- what causes poor performance with multisampling, but it's not
- predictable. Without changing the code, some times a given saver will
- perform fine with multisampling on, and other times it will perform
- very badly. Without multisampling, they always perform fine.
- */
-// if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
-// ms_p = 0;
-
- if (ms_p) {
- attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
- attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6;
- // Don't really understand what this means:
- // attrs[i++] = NSOpenGLPFANoRecovery;
- }
-
- attrs[i] = 0;
-
- NSOpenGLPixelFormat *pixfmt =
- [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
-
- if (ms_p && !pixfmt) { // Retry without multisampling.
- i -= 2;
- attrs[i] = 0;
- pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
- }
-
- NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
-
- // #### Analyze says: "Potential leak of an object stored into pixfmt"
- ctx = [[NSOpenGLContext alloc]
- initWithFormat:pixfmt
- shareContext:nil];
-// [pixfmt release]; // #### ???
- }
- // Sync refreshes to the vertical blanking interval
- GLint r = 1;
- [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
-// check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
+ // OpenGL initialization is in [XScreenSaverView startAnimation].
- // #### Analyze says: "Potential leak of an object stored into "ctx"
- // But makeCurrentContext retains it (right?)
-
- [ctx makeCurrentContext];
- check_gl_error ("makeCurrentContext");
-
- [view setOglContext:ctx];
+ // I don't know why this is necessary, but it beats randomly having some
+ // textures be upside down.
+ //
+ glMatrixMode(GL_TEXTURE);
+ glLoadIdentity();
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
- // Clear frame buffer ASAP, else there are bits left over from other apps.
- glClearColor (0, 0, 0, 1);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-// glFinish ();
-// glXSwapBuffers (mi->dpy, mi->window);
-
-
- // Enable multi-threading, if possible. This runs most OpenGL commands
- // and GPU management on a second CPU.
- {
-# ifndef kCGLCEMPEngine
-# define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
-# endif
- CGLContextObj cctx = CGLGetCurrentContext();
- CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
- if (err != kCGLNoError) {
- NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
- }
- }
-
- check_gl_error ("init_GL");
-
-# else // USE_IPHONE
-
- EAGLContext *ogl_ctx = ctx;
- if (!ogl_ctx) {
-
- Bool dbuf_p =
- get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer");
-
- /* There seems to be no way to actually turn off double-buffering in
- EAGLContext (e.g., no way to draw to the front buffer directly)
- but if we turn on "retained backing" for non-buffering apps like
- "pipes", at least the back buffer isn't auto-cleared on them.
- */
- CAEAGLLayer *eagl_layer = (CAEAGLLayer *) view.layer;
- eagl_layer.opaque = TRUE;
- eagl_layer.drawableProperties =
- [NSDictionary dictionaryWithObjectsAndKeys:
- kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
- [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking,
- nil];
-
- // Without this, the GL frame buffer is half the screen resolution!
- eagl_layer.contentsScale = [UIScreen mainScreen].scale;
-
- ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
- }
-
- if (!ogl_ctx)
- return 0;
- [view setOglContext:ogl_ctx];
- // #### Analyze says "Potential leak of an object stored into ogl_ctx"
-
- check_gl_error ("OES_init");
-
-# endif // USE_IPHONE
// Caller expects a pointer to an opaque struct... which it dereferences.
// Don't ask me, it's historical...
void
glXSwapBuffers (Display *dpy, Window window)
{
+ // This all is very much like what's in -[XScreenSaverView flushBackbuffer].
+#ifdef JWXYZ_GL
+ jwxyz_bind_drawable (window, window);
+#endif // JWXYZ_GL
+
XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
@"wrong view class: %@", view);
}
+#if defined GL_INVALID_FRAMEBUFFER_OPERATION_OES && \
+ !defined GL_INVALID_FRAMEBUFFER_OPERATION
+# define GL_INVALID_FRAMEBUFFER_OPERATION GL_INVALID_FRAMEBUFFER_OPERATION_OES
+#endif
+
+
/* report a GL error. */
void
check_gl_error (const char *type)
case GL_STACK_OVERFLOW: e = "stack overflow"; break;
case GL_STACK_UNDERFLOW: e = "stack underflow"; break;
case GL_OUT_OF_MEMORY: e = "out of memory"; break;
+#ifdef GL_INVALID_FRAMEBUFFER_OPERATION
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ e = "invalid framebuffer operation";
+ break;
+#endif
#ifdef GL_TABLE_TOO_LARGE_EXT
case GL_TABLE_TOO_LARGE_EXT: e = "table too large"; break;
#endif