X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=OSX%2FXScreenSaverGLView.m;h=72fe702ac32c0793680b76424db7bba699300766;hp=b7ac9c1e49dad4b1e02251f4d27db94d84ab3c42;hb=019de959b265701cd0c3fccbb61f2b69f06bf9ee;hpb=c1b9b55ad8d59dc05ef55e316aebf5863e7dfa56 diff --git a/OSX/XScreenSaverGLView.m b/OSX/XScreenSaverGLView.m index b7ac9c1e..72fe702a 100644 --- a/OSX/XScreenSaverGLView.m +++ b/OSX/XScreenSaverGLView.m @@ -1,13 +1,13 @@ -/* xscreensaver, Copyright (c) 2006-2008 Jamie Zawinski -* -* 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-2013 Jamie Zawinski + * + * 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" @@ -20,7 +20,11 @@ #import "screenhackI.h" #import "xlockmoreI.h" -#import +#ifdef USE_IPHONE +# include "jwzgles.h" +#else +# import +#endif /* used by the OpenGL screen savers */ @@ -33,14 +37,6 @@ extern void check_gl_error (const char *type); @implementation XScreenSaverGLView -- (void) dealloc -{ - if (agl_ctx) - aglDestroyContext (agl_ctx); - [super dealloc]; -} - - - (void)stopAnimation { [super stopAnimation]; @@ -48,74 +44,243 @@ extern void check_gl_error (const char *type); // 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; - } +# ifndef USE_IPHONE + [NSOpenGLContext clearCurrentContext]; +# endif // !USE_IPHONE + + clear_gl_error(); // This hack is defunct, don't let this linger. } // #### maybe this could/should just be on 'lockFocus' instead? - (void) prepareContext { - if (agl_ctx) - if (!aglSetCurrentContext (agl_ctx)) { - check_gl_error ("aglSetCurrentContext"); - abort(); - } + 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 + } } - + + - (void) resizeContext { - if (! agl_ctx) - return; - - /* Constrain the AGL context to the rectangle of this view - (not of our parent window). - */ - 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); +# ifndef USE_IPHONE + if (ogl_ctx) + [ogl_ctx setView:self]; +# endif // !USE_IPHONE } -- (void)drawRect:(NSRect)rect +- (NSOpenGLContext *) oglContext { - if (! agl_ctx) - [super drawRect:rect]; + return ogl_ctx; } -- (AGLContext) aglContext +#ifdef USE_IPHONE +/* With GL programs, drawing at full resolution isn't a problem. + */ +- (CGFloat) hackedContentScaleFactor { - return agl_ctx; + return [self contentScaleFactor]; } +#endif // USE_IPHONE -- (void) setAglContext: (AGLContext) ctx + +- (void) setOglContext: (NSOpenGLContext *) ctx { - if (agl_ctx) - if (! aglDestroyContext (agl_ctx)) { - check_gl_error("aglDestroyContext"); - abort(); - } - agl_ctx = ctx; + 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"); + +# endif // USE_IPHONE + [self resizeContext]; } +#ifdef USE_IPHONE ++ (Class) layerClass +{ + return [CAEAGLLayer class]; +} + +- (void) swapBuffers +{ + glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer); + [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES]; +} +#endif // USE_IPHONE + + +#ifdef USE_BACKBUFFER + +- (void) initLayer +{ + // Do nothing. +} + +- (void)drawRect:(NSRect)rect +{ +} + + +- (void) animateOneFrame +{ +# ifdef USE_IPHONE + UIGraphicsPushContext (backbuffer); +# endif + + [self render_x11]; + +# ifdef USE_IPHONE + UIGraphicsPopContext(); +# endif +} + + +/* 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 +{ + // 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); + + if (! backbuffer) { + CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); + int w = 8; + int h = 8; + backbuffer = CGBitmapContextCreate (NULL, w, h, + 8, w*4, cs, + kCGImageAlphaPremultipliedLast); + CGColorSpaceRelease (cs); + } +} +# endif // USE_BACKBUFFER + + +- (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,35 +288,83 @@ 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); + 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; + } - if (! aglSetCurrentContext (ctx)) { - check_gl_error("aglSetCurrentContext"); - abort(); - } + 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"); - [view setAglContext:ctx]; + // #### 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; - aglSetInteger (ctx, AGL_SWAP_INTERVAL, &r); + [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. @@ -166,6 +379,44 @@ init_GL (ModeInfo *mi) } } + 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... static int blort = -1; @@ -179,11 +430,17 @@ void glXSwapBuffers (Display *dpy, Window window) { 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,41 +454,6 @@ 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); } @@ -239,8 +461,6 @@ check_agl_error (const char *type) void check_gl_error (const char *type) { - check_agl_error (type); - char buf[100]; GLenum i; const char *e; @@ -261,6 +481,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); }