From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / OSX / XScreenSaverGLView.m
index 265654a1e9b95c54e62b2aa83a91f38f9241f6a3..d61964f8d3b3fd0dad5fd909ecc3938c5b6be79b 100644 (file)
@@ -1,13 +1,13 @@
-/* xscreensaver, Copyright (c) 2006, 2007 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"
 
-#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
  */
@@ -33,89 +40,286 @@ extern void check_gl_error (const char *type);
 
 @implementation XScreenSaverGLView
 
-- (void) dealloc
+
+#ifdef USE_IPHONE
+/* With GL programs, drawing at full resolution isn't a problem.
+ */
+- (CGFloat) hackedContentScaleFactor
 {
-  if (agl_ctx)
-    aglDestroyContext (agl_ctx);
-  [super dealloc];
+  return [self contentScaleFactor];
 }
 
+- (BOOL)ignoreRotation
+{
+  return FALSE;                // Allow xwindow and the glViewport to change shape
+}
 
-- (void)stopAnimation
+- (BOOL) suppressRotationAnimation
 {
-  [super stopAnimation];
-  
-  // Without this, the GL frame stays on screen when switching tabs
-  // in System Preferences.
-  //
-  if (agl_ctx) {
-    aglSetCurrentContext (NULL);
-    aglDestroyContext (agl_ctx);
-    agl_ctx = 0;
+  return _suppressRotationAnimation;  // per-hack setting, default FALSE
+}
+
+- (BOOL) rotateTouches
+{
+  return TRUE;         // We need the XY axes swapped in our events
+}
+
+
+- (void) swapBuffers
+{
+#  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) animateOneFrame
+{
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+  UIGraphicsPushContext (backbuffer);
+# endif
+
+  [self render_x11];
+
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+  UIGraphicsPopContext();
+# endif
+}
+
+
+/* GL screenhacks don't display a backbuffer, so this is a stub. */
+- (void) enableBackbuffer:(CGSize)new_backbuffer_size
+{
+}
+
+
+/* GL screenhacks set their own viewport and matrices. */
+- (void) setViewport
+{
+}
+
+
+#ifdef USE_IPHONE
+
+/* 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);
+}
+
+- (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.
+ */
+- (void) createBackbuffer:(CGSize)new_size
+{
+#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
 }
 
 
-// #### maybe this could/should just be on 'lockFocus' instead?
-- (void) prepareContext
+/* Another stub for GL screenhacks. */
+- (void) drawBackbuffer
 {
-  if (agl_ctx)
-    if (!aglSetCurrentContext (agl_ctx)) {
-      check_gl_error ("aglSetCurrentContext");
-      abort();
-    }
 }
-      
-- (void) resizeContext
+
+
+/* Likewise. GL screenhacks control display with glXSwapBuffers(). */
+- (void) flushBackbuffer
 {
-  if (! agl_ctx) 
-    return;
+}
+
 
-  /* Constrain the AGL context to the rectangle of this view
-     (not of our parent window).
+#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
+
+  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];
+}
+
+#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.
    */
-  GLint aglrect[4];
-  NSRect rect = [[[self window] contentView] convertRect:[self frame]
-                                                fromView:self];
-  aglrect[0] = rect.origin.x;
-  aglrect[1] = rect.origin.y;
-  aglrect[2] = rect.size.width;
-  aglrect[3] = rect.size.height;
-  aglEnable (agl_ctx, AGL_BUFFER_RECT);
-  aglSetInteger (agl_ctx, AGL_BUFFER_RECT, aglrect);
-  aglUpdateContext (agl_ctx);
+
+  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);
+}
 
-- (void)drawRect:(NSRect)rect
+- (NSString *)getCAGravity
 {
-  if (! agl_ctx)
-    [super drawRect:rect];
+  return kCAGravityCenter;
 }
 
+- (void) startAnimation
+{
+  [super startAnimation];
+  if (ogl_ctx) /* Almost always true. */
+    _glesState = jwzgles_make_state ();
+}
 
-- (AGLContext) aglContext
+- (void) stopAnimation
 {
-  return agl_ctx;
+  [super stopAnimation];
+#ifdef USE_IPHONE
+  if (_glesState) {
+    [EAGLContext setCurrentContext:ogl_ctx];
+    jwzgles_make_current (_glesState);
+    jwzgles_free_state ();
+  }
+#endif
 }
 
-- (void) setAglContext: (AGLContext) ctx
+- (void) prepareContext
 {
-  if (agl_ctx)
-    if (! aglDestroyContext (agl_ctx)) {
-      check_gl_error("aglDestroyContext");
-      abort();
-    }
-  agl_ctx = ctx;
-  [self resizeContext];
+  [super prepareContext];
+  jwzgles_make_current (_glesState);
+}
+
+#endif // !USE_IPHONE
+
+
+- (void)dealloc {
+  // ogl_ctx
+  // gl_framebuffer
+  // gl_renderbuffer
+  // gl_depthbuffer
+  [super dealloc];
 }
 
 @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 *
@@ -123,48 +327,22 @@ init_GL (ModeInfo *mi)
 {
   Window win = mi->window;
   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
-  
-  GLint agl_attrs[] = {
-    AGL_RGBA,
-    AGL_DOUBLEBUFFER,
-    AGL_DEPTH_SIZE, 16,
-    0 };
-  AGLPixelFormat aglpixf = aglChoosePixelFormat (NULL, 0, agl_attrs);
-  AGLContext ctx = aglCreateContext (aglpixf, NULL);
-  aglDestroyPixelFormat (aglpixf);
-  if (! ctx) {
-    check_gl_error("aglCreateContext");
-    abort();
-  }
-  
-  if (! aglSetDrawable (ctx, GetWindowPort ([[view window] windowRef]))) {
-    check_gl_error("aglSetDrawable");
-    abort();
-  }
+  NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
+             @"wrong view class: %@", view);
 
-  if (! aglSetCurrentContext (ctx)) {
-    check_gl_error("aglSetCurrentContext");
-    abort();
-  }
+  // OpenGL initialization is in [XScreenSaverView startAnimation].
 
-  [view setAglContext:ctx];
-
-  // Sync refreshes to the vertical blanking interval
-  GLint r = 1;
-  aglSetInteger (ctx, AGL_SWAP_INTERVAL, &r);
-
-  // 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);
-    }
-  }
+  // 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();
+
+  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
   // Caller expects a pointer to an opaque struct...  which it dereferences.
   // Don't ask me, it's historical...
@@ -178,12 +356,23 @@ init_GL (ModeInfo *mi)
 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);
-  AGLContext ctx = [view aglContext];
-  if (ctx) aglSwapBuffers (ctx);
+  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
+/* Does nothing - prepareContext already did the work.
  */
 void
 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
@@ -197,50 +386,19 @@ clear_gl_error (void)
 {
   while (glGetError() != GL_NO_ERROR)
     ;
-  while (aglGetError() != AGL_NO_ERROR)
-    ;
 }
 
-static void
-check_agl_error (const char *type)
-{
-  char buf[100];
-  GLenum i;
-  const char *e;
-  switch ((i = aglGetError())) {
-    case AGL_NO_ERROR: return;
-    case AGL_BAD_ATTRIBUTE:        e = "bad attribute";        break;
-    case AGL_BAD_PROPERTY:         e = "bad propery";  break;
-    case AGL_BAD_PIXELFMT:         e = "bad pixelfmt"; break;
-    case AGL_BAD_RENDINFO:         e = "bad rendinfo"; break;
-    case AGL_BAD_CONTEXT:          e = "bad context";  break;
-    case AGL_BAD_DRAWABLE:         e = "bad drawable"; break;
-    case AGL_BAD_GDEV:             e = "bad gdev";     break;
-    case AGL_BAD_STATE:            e = "bad state";    break;
-    case AGL_BAD_VALUE:            e = "bad value";    break;
-    case AGL_BAD_MATCH:            e = "bad match";    break;
-    case AGL_BAD_ENUM:             e = "bad enum";     break;
-    case AGL_BAD_OFFSCREEN:        e = "bad offscreen";        break;
-    case AGL_BAD_FULLSCREEN:       e = "bad fullscreen";break;
-    case AGL_BAD_WINDOW:           e = "bad window";   break;
-    case AGL_BAD_POINTER:          e = "bad pointer";  break;
-    case AGL_BAD_MODULE:           e = "bad module";   break;
-    case AGL_BAD_ALLOC:            e = "bad alloc";    break;
-    case AGL_BAD_CONNECTION:       e = "bad connection";break;
-    default:
-      e = buf; sprintf (buf, "unknown AGL error %d", (int) i); break;
-  }
-  NSLog (@"%s AGL error: %s", type, e);
-  exit (1);
-}
+
+#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)
 {
-  check_agl_error (type);
-  
   char buf[100];
   GLenum i;
   const char *e;
@@ -252,6 +410,11 @@ 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
@@ -261,6 +424,5 @@ check_gl_error (const char *type)
     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);
 }