-/* xscreensaver, Copyright (c) 2006-2009 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-2017 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"
-#import <OpenGL/OpenGL.h>
+#ifdef USE_IPHONE
+# include "jwzgles.h"
+# import <OpenGLES/ES1/glext.h>
+#else
+# import <OpenGL/OpenGL.h>
+#endif
/* used by the OpenGL screen savers
*/
@implementation XScreenSaverGLView
-#if 0
-- (void) dealloc
+
+/* With GL programs, drawing at full resolution isn't a problem.
+ */
+- (CGFloat) hackedContentScaleFactor
{
- /* #### Do we have to destroy ogl_ctx? */
- [super dealloc];
+# ifdef USE_IPHONE
+ return [self contentScaleFactor];
+# else
+ return self.window.backingScaleFactor;
+# endif
}
-#endif
+# ifdef USE_IPHONE
-- (void)stopAnimation
+- (BOOL)ignoreRotation
{
- [super stopAnimation];
-
- // Without this, the GL frame stays on screen when switching tabs
- // in System Preferences.
- //
- [NSOpenGLContext clearCurrentContext];
+ 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) {
- [ogl_ctx makeCurrentContext];
-// check_gl_error ("makeCurrentContext");
- [ogl_ctx update];
- }
+ return TRUE; // We need the XY axes swapped in our events
}
-- (void) resizeContext
+- (void) swapBuffers
{
- if (ogl_ctx)
- [ogl_ctx setView:self];
+# 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
-- (void)drawRect:(NSRect)rect
+- (void) animateOneFrame
{
- if (! ogl_ctx)
- [super drawRect:rect];
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+ UIGraphicsPushContext (backbuffer);
+# endif
+
+ [self render_x11];
+
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+ UIGraphicsPopContext();
+# endif
}
-- (NSOpenGLContext *) oglContext
+/* GL screenhacks don't display a backbuffer, so this is a stub. */
+- (void) enableBackbuffer:(CGSize)new_backbuffer_size
{
- return ogl_ctx;
}
-- (void) setOglContext: (NSOpenGLContext *) ctx
+/* GL screenhacks set their own viewport and matrices. */
+- (void) setViewport
{
- ogl_ctx = ctx;
- [self resizeContext];
}
-@end
+#ifdef USE_IPHONE
-/* Utility functions...
+/* Keep the GL scene oriented into a portrait-mode View, regardless of
+ what the physical device orientation is.
*/
+- (void) reshape_x11
+{
+ [super reshape_x11];
+ glMatrixMode(GL_PROJECTION);
+ glRotatef (-current_device_rotation(), 0, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+}
-/* Called by OpenGL savers using the XLockmore API.
+- (void) render_x11
+{
+ BOOL was_initted_p = initted_p;
+ [super render_x11];
+
+ if (! was_initted_p && xdpy)
+ _suppressRotationAnimation =
+ get_boolean_resource (xdpy,
+ "suppressRotationAnimation",
+ "SuppressRotationAnimation");
+}
+
+#endif // USE_IPHONE
+
+
+
+/* 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.
*/
-GLXContext *
-init_GL (ModeInfo *mi)
+- (void) createBackbuffer:(CGSize)new_size
{
- Window win = mi->window;
- XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
- NSOpenGLContext *ctx = [view oglContext];
+#ifdef JWXYZ_QUARTZ
+ NSAssert (! backbuffer_texture,
+ @"backbuffer_texture shouldn't be used for GL hacks");
+
+ 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
+}
+
+
+/* Another stub for GL screenhacks. */
+- (void) drawBackbuffer
+{
+}
+
- if (!ctx) {
+/* Likewise. GL screenhacks control display with glXSwapBuffers(). */
+- (void) flushBackbuffer
+{
+}
+
+
+#ifndef USE_IPHONE
+
+- (NSOpenGLPixelFormat *) getGLPixelFormat
+{
+ 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
- NSOpenGLPixelFormatAttribute attrs[20];
- int i = 0;
- attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
- attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
- attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
+ attrs[i] = 0;
- if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
- attrs[i++] = NSOpenGLPFADoubleBuffer;
+ NSOpenGLPixelFormat *result = [[NSOpenGLPixelFormat alloc]
+ initWithAttributes:attrs];
+ if (ms_p && !result) { // Retry without multisampling.
+ i -= 2;
attrs[i] = 0;
+ result = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
+ }
- NSOpenGLPixelFormat *pixfmt = [[NSOpenGLPixelFormat alloc]
- initWithAttributes:attrs];
+ return [result autorelease];
+}
+
+#else // !USE_IPHONE
- ctx = [[NSOpenGLContext alloc]
- initWithFormat:pixfmt
- shareContext:nil];
+- (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)addExtraRenderbuffers:(CGSize)size
+{
+ int w = size.width;
+ int h = size.height;
+
+ if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer);
+
+ 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;
+}
+
+- (void) startAnimation
+{
+ [super startAnimation];
+ if (ogl_ctx) /* Almost always true. */
+ _glesState = jwzgles_make_state ();
+}
+
+- (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_IPHONE
+
+
+- (void)dealloc {
+ // ogl_ctx
+ // gl_framebuffer
+ // gl_renderbuffer
+ // gl_depthbuffer
+ [super dealloc];
+}
- // Sync refreshes to the vertical blanking interval
- GLint r = 1;
- [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
- check_gl_error ("NSOpenGLCPSwapInterval");
+@end
+
+
+/* Utility functions...
+ */
+
+
+// redefine NSAssert, etc. here since they don't work when not inside
+// an ObjC method.
+
+#undef NSAssert
+#undef NSAssert1
+#undef NSAssert2
+#define NSASS(S) \
+ jwxyz_abort ("%s", [(S) cStringUsingEncoding:NSUTF8StringEncoding])
+#define NSAssert(CC,S) do { if (!(CC)) { NSASS((S)); }} while(0)
+#define NSAssert1(CC,S,A) do { if (!(CC)) { \
+ NSASS(([NSString stringWithFormat: S, A])); }} while(0)
+#define NSAssert2(CC,S,A,B) do { if (!(CC)) { \
+ NSASS(([NSString stringWithFormat: S, A, B])); }} while(0)
+
+
+/* Called by OpenGL savers using the XLockmore API.
+ */
+GLXContext *
+init_GL (ModeInfo *mi)
+{
+ Window win = mi->window;
+ XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
+ NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
+ @"wrong view class: %@", view);
- [ctx makeCurrentContext];
- check_gl_error ("makeCurrentContext");
+ // OpenGL initialization is in [XScreenSaverView startAnimation].
- [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.
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);
- }
- }
// 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);
+#ifndef USE_IPHONE
NSOpenGLContext *ctx = [view oglContext];
if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
+#else /* USE_IPHONE */
+ [view swapBuffers];
+#endif /* USE_IPHONE */
}
/* Does nothing - prepareContext already did the work.
}
+#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
default:
e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
}
- NSLog (@"%s GL error: %s", type, e);
- exit (1);
+ NSAssert2 (0, @"%s GL error: %s", type, e);
}