From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / OSX / XScreenSaverGLView.m
index da155211a3c058f32d5f7a5424179cf519be41c8..d61964f8d3b3fd0dad5fd909ecc3938c5b6be79b 100644 (file)
@@ -1,13 +1,13 @@
-/* xscreensaver, Copyright (c) 2006-2012 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
@@ -37,157 +40,97 @@ extern void check_gl_error (const char *type);
 
 @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];
 }
 
-
-// #### 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
 }
 
 
-- (void) setOglContext: (NSOpenGLContext *) ctx
+- (void) swapBuffers
 {
-  ogl_ctx = ctx;
-
-# ifdef USE_IPHONE
-  [EAGLContext setCurrentContext: ogl_ctx];
-
-  double s = self.contentScaleFactor;
-  int w = s * [self frame].size.width;
-  int h = s * [self frame].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);
+#  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
 
-// 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);
+- (void) animateOneFrame
+{
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+  UIGraphicsPushContext (backbuffer);
+# endif
 
-  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;
-  }
+  [self render_x11];
 
-  check_gl_error ("setOglContext");
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+  UIGraphicsPopContext();
+# endif
+}
 
-# endif // USE_IPHONE
 
-  [self resizeContext];
+/* GL screenhacks don't display a backbuffer, so this is a stub. */
+- (void) enableBackbuffer:(CGSize)new_backbuffer_size
+{
 }
 
-#ifdef USE_IPHONE
-+ (Class) layerClass
+
+/* GL screenhacks set their own viewport and matrices. */
+- (void) setViewport
 {
-    return [CAEAGLLayer class];
 }
 
 
-/* On MacOS:   drawRect does nothing, and animateOneFrame renders.
-   On iOS GL:  drawRect does nothing, and animateOneFrame renders.
-   On iOS X11: drawRect renders, and animateOneFrame marks the view dirty.
+#ifdef USE_IPHONE
+
+/* Keep the GL scene oriented into a portrait-mode View, regardless of
+   what the physical device orientation is.
  */
-- (void)drawRect:(NSRect)rect
+- (void) reshape_x11
 {
-}
+  [super reshape_x11];
 
+  glMatrixMode(GL_PROJECTION);
+  glRotatef (-current_device_rotation(), 0, 0, 1);
+  glMatrixMode(GL_MODELVIEW);
+}
 
-- (void) animateOneFrame
+- (void) render_x11
 {
-  UIGraphicsPushContext (backbuffer);
-  [self render_x11];
-  UIGraphicsPopContext();
+  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
@@ -195,34 +138,156 @@ extern void check_gl_error (const char *type);
    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) createBackbuffer:(CGSize)new_size
 {
-  // Don't resize the X11 window to match rotation. 
-  // Rotation and scaling are handled in GL.
-  //
-  NSRect f = [self frame];
-  double s = self.contentScaleFactor;
-  backbuffer_size.width  = (int) (s * f.size.width);
-  backbuffer_size.height = (int) (s * f.size.height);
+#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,
+    backbuffer = CGBitmapContextCreate (NULL, w, h,   // yup, only 8px x 8px.
                                         8, w*4, cs,
-                                        kCGImageAlphaPremultipliedLast);
+                                        (kCGBitmapByteOrder32Little |
+                                         kCGImageAlphaNoneSkipLast));
     CGColorSpaceRelease (cs);
   }
+#endif // JWXYZ_QUARTZ
 }
 
 
-- (void) swapBuffers
+/* Another stub for GL screenhacks. */
+- (void) drawBackbuffer
 {
-  glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
-  [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
 }
-# endif // USE_IPHONE
+
+
+/* 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 {
@@ -240,14 +305,19 @@ extern void check_gl_error (const char *type);
  */
 
 
-// redefine these now since they don't work when not inside an ObjC method.
+// redefine NSAssert, etc. here since they don't work when not inside
+// an ObjC method.
 
 #undef NSAssert
 #undef NSAssert1
 #undef NSAssert2
-#define NSAssert(CC,S)      do { if (!(CC)) { NSLog(S);    abort();}} while(0)
-#define NSAssert1(CC,S,A)   do { if (!(CC)) { NSLog(S,A);  abort();}} while(0)
-#define NSAssert2(CC,S,A,B) do { if (!(CC)) { NSLog(S,A,B);abort();}} while(0)
+#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.
@@ -259,131 +329,20 @@ 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");
-
-    ctx = [[NSOpenGLContext alloc] 
-            initWithFormat:pixfmt
-              shareContext:nil];
-//    [pixfmt release]; // #### ???
-  }
+  // OpenGL initialization is in [XScreenSaverView startAnimation].
 
-  // Sync refreshes to the vertical blanking interval
-  GLint r = 1;
-  [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
-//  check_gl_error ("NSOpenGLCPSwapInterval");  // SEGV sometimes. Too early?
-
-  // #### "Build and Analyze" says that ctx leaks, because it doesn't
-  //      seem to realize that makeCurrentContext retains it (right?)
-  //      Not sure what to do to make this warning go away.
-
-  [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];
-
-  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...
@@ -397,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);
@@ -425,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)
@@ -440,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