From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / OSX / XScreenSaverGLView.m
index da77021c89f63f489970a054f7b1d8e30d03edcb..d61964f8d3b3fd0dad5fd909ecc3938c5b6be79b 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 2006-2014 Jamie Zawinski <jwz@jwz.org>
+/* 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
 
 #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
@@ -37,190 +40,96 @@ extern void check_gl_error (const char *type);
 
 @implementation XScreenSaverGLView
 
-- (void)stopAnimation
-{
-  [super stopAnimation];
-  
-  // Without this, the GL frame stays on screen when switching tabs
-  // in System Preferences.
-  //
-# ifndef USE_IPHONE
-  [NSOpenGLContext clearCurrentContext];
-# endif // !USE_IPHONE
 
-  clear_gl_error();    // This hack is defunct, don't let this linger.
+#ifdef USE_IPHONE
+/* With GL programs, drawing at full resolution isn't a problem.
+ */
+- (CGFloat) hackedContentScaleFactor
+{
+  return [self contentScaleFactor];
 }
 
-
-// #### maybe this could/should just be on 'lockFocus' instead?
-- (void) prepareContext
+- (BOOL)ignoreRotation
 {
-  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 FALSE;                // Allow xwindow and the glViewport to change shape
 }
 
-
-- (void) resizeContext
+- (BOOL) suppressRotationAnimation
 {
-# ifndef USE_IPHONE
-  if (ogl_ctx) 
-    [ogl_ctx setView:self];
-# endif // !USE_IPHONE
+  return _suppressRotationAnimation;  // per-hack setting, default FALSE
 }
 
-
-- (NSOpenGLContext *) oglContext
+- (BOOL) rotateTouches
 {
-  return ogl_ctx;
+  return TRUE;         // We need the XY axes swapped in our events
 }
 
 
-#ifdef USE_IPHONE
-/* With GL programs, drawing at full resolution isn't a problem.
- */
-- (CGFloat) hackedContentScaleFactor
+- (void) swapBuffers
 {
-  NSSize ssize = [[[UIScreen mainScreen] currentMode] size];
-  NSSize bsize = [self bounds].size;
-
-  // Ratio of screen size in pixels to view size in points.
-  GLfloat s = ((ssize.width > ssize.height ? ssize.width : ssize.height) /
-               (bsize.width > bsize.height ? bsize.width : bsize.height));
-  return s;
+#  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) setOglContext: (NSOpenGLContext *) ctx
+- (void) animateOneFrame
 {
-  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;
-
-  if (gl_framebuffer)  glDeleteFramebuffersOES  (1, &gl_framebuffer);
-  if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
-  if (gl_depthbuffer)  glDeleteRenderbuffersOES (1, &gl_depthbuffer);
-
-  glGenFramebuffersOES  (1, &gl_framebuffer);
-  glBindFramebufferOES  (GL_FRAMEBUFFER_OES,  gl_framebuffer);
-
-  glGenRenderbuffersOES (1, &gl_renderbuffer);
-  glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
-
-// redundant?
-//   glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, w, h);
-  [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
-           fromDrawable:(CAEAGLLayer*)self.layer];
-
-  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;
- */
-  default:
-    NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
-    break;
-  }
-
-  check_gl_error ("setOglContext");
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+  UIGraphicsPushContext (backbuffer);
+# endif
 
-# endif // USE_IPHONE
+  [self render_x11];
 
-  [self resizeContext];
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+  UIGraphicsPopContext();
+# endif
 }
 
-#ifdef USE_IPHONE
-+ (Class) layerClass
+
+/* GL screenhacks don't display a backbuffer, so this is a stub. */
+- (void) enableBackbuffer:(CGSize)new_backbuffer_size
 {
-    return [CAEAGLLayer class];
 }
 
-- (void) swapBuffers
+
+/* GL screenhacks set their own viewport and matrices. */
+- (void) setViewport
 {
-  glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
-  [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
 }
-#endif // USE_IPHONE
 
 
-#ifdef USE_BACKBUFFER
+#ifdef USE_IPHONE
 
-- (void) initLayer
+/* Keep the GL scene oriented into a portrait-mode View, regardless of
+   what the physical device orientation is.
+ */
+- (void) reshape_x11
 {
-  // Do nothing.
-}
+  [super reshape_x11];
 
-- (void)drawRect:(NSRect)rect
-{
+  glMatrixMode(GL_PROJECTION);
+  glRotatef (-current_device_rotation(), 0, 0, 1);
+  glMatrixMode(GL_MODELVIEW);
 }
 
-
-- (void) animateOneFrame
+- (void) render_x11
 {
-# ifdef USE_IPHONE
-  UIGraphicsPushContext (backbuffer);
-# endif
+  BOOL was_initted_p = initted_p;
+  [super render_x11];
+
+  if (! was_initted_p && xdpy)
+    _suppressRotationAnimation =
+      get_boolean_resource (xdpy,
+                            "suppressRotationAnimation",
+                            "SuppressRotationAnimation");
+}
 
-  [self render_x11];
+#endif // USE_IPHONE
 
-# ifdef USE_IPHONE
-  UIGraphicsPopContext();
-# endif
-}
 
 
 /* The backbuffer isn't actually used for GL programs, but it needs to
@@ -231,7 +140,9 @@ extern void check_gl_error (const char *type);
  */
 - (void) createBackbuffer:(CGSize)new_size
 {
-  backbuffer_size = new_size;
+#ifdef JWXYZ_QUARTZ
+  NSAssert (! backbuffer_texture,
+                       @"backbuffer_texture shouldn't be used for GL hacks");
 
   if (! backbuffer) {
     CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
@@ -239,23 +150,146 @@ extern void check_gl_error (const char *type);
     int h = 8;
     backbuffer = CGBitmapContextCreate (NULL, w, h,   // yup, only 8px x 8px.
                                         8, w*4, cs,
-                                        kCGImageAlphaPremultipliedLast);
+                                        (kCGBitmapByteOrder32Little |
+                                         kCGImageAlphaNoneSkipLast));
     CGColorSpaceRelease (cs);
   }
+#endif // JWXYZ_QUARTZ
 }
-# endif // USE_BACKBUFFER
 
 
-/* When changing the device orientation, leave the X11 Window and glViewport
-   in portrait configuration.  OpenGL hacks examine current_device_rotation()
-   within the scene as needed.
- */
-- (BOOL)reshapeRotatedWindow
+/* Another stub for GL screenhacks. */
+- (void) drawBackbuffer
 {
-  return NO;
 }
 
 
+/* 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
+
+  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.
+   */
+
+  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
@@ -295,134 +329,8 @@ init_GL (ModeInfo *mi)
   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?
-
-  // #### Analyze says: "Potential leak of an object stored into "ctx"
-  // But makeCurrentContext retains it (right?)
-
-  [ctx makeCurrentContext];
-  check_gl_error ("makeCurrentContext");
 
-  [view setOglContext:ctx];
-
-  // 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");
-
-  jwzgles_reset ();
-  
-# endif // USE_IPHONE
+  // OpenGL initialization is in [XScreenSaverView startAnimation].
 
   // I don't know why this is necessary, but it beats randomly having some
   // textures be upside down.
@@ -434,6 +342,7 @@ init_GL (ModeInfo *mi)
   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...
@@ -447,6 +356,11 @@ 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);
   NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
              @"wrong view class: %@", view);
@@ -475,6 +389,12 @@ clear_gl_error (void)
 }
 
 
+#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)
@@ -490,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