1/* xscreensaver, Copyright (c) 2006-2019 Jamie Zawinski <jwz@jwz.org> 2 * 3 * Permission to use, copy, modify, distribute, and sell this software and its 4 * documentation for any purpose is hereby granted without fee, provided that 5 * the above copyright notice appear in all copies and that both that 6 * copyright notice and this permission notice appear in supporting 7 * documentation. No representations are made about the suitability of this 8 * software for any purpose. It is provided "as is" without express or 9 * implied warranty. 10 */ 11 12/* This is a subclass of Apple's ScreenSaverView that knows how to run 13 xscreensaver programs without X11 via the dark magic of the "jwxyz" 14 library. In xscreensaver terminology, this is the replacement for 15 the "screenhack.c" module. 16 */ 17 18#import "XScreenSaverGLView.h" 19#import "XScreenSaverConfigSheet.h" 20#import "jwxyz-cocoa.h" 21#import "jwxyzI.h" 22#import "screenhackI.h" 23#import "xlockmoreI.h" 24 25#ifdef USE_IPHONE 26# include "jwzgles.h" 27# import <OpenGLES/ES1/glext.h> 28#else 29# import <OpenGL/OpenGL.h> 30#endif 31 32/* used by the OpenGL screen savers 33 */ 34extern GLXContext *init_GL (ModeInfo *); 35extern void glXSwapBuffers (Display *, Window); 36extern void glXMakeCurrent (Display *, Window, GLXContext); 37extern void clear_gl_error (void); 38extern void check_gl_error (const char *type); 39 40 41@implementation XScreenSaverGLView 42 43 44/* With GL programs, drawing at full resolution isn't a problem. 45 */ 46- (CGFloat) hackedContentScaleFactor:(BOOL)fonts_p 47{ 48# ifdef USE_IPHONE 49 return [self contentScaleFactor]; 50# else 51 return self.window.backingScaleFactor; 52# endif 53} 54 55# ifdef USE_IPHONE 56 57- (BOOL)ignoreRotation 58{ 59 return FALSE; // Allow xwindow and the glViewport to change shape 60} 61 62- (BOOL) suppressRotationAnimation 63{ 64 return _suppressRotationAnimation; // per-hack setting, default FALSE 65} 66 67- (BOOL) rotateTouches 68{ 69 return TRUE; // We need the XY axes swapped in our events 70} 71 72 73- (void) swapBuffers 74{ 75# ifdef JWXYZ_GL 76 GLint gl_renderbuffer = xwindow->gl_renderbuffer; 77# endif // JWXYZ_GL 78 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer); 79 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES]; 80} 81#endif // USE_IPHONE 82 83 84- (void) animateOneFrame 85{ 86# if defined USE_IPHONE && defined JWXYZ_QUARTZ 87 UIGraphicsPushContext (backbuffer); 88# endif 89 90 [self render_x11]; 91 92# if defined USE_IPHONE && defined JWXYZ_QUARTZ 93 UIGraphicsPopContext(); 94# endif 95} 96 97 98/* GL screenhacks don't display a backbuffer, so this is a stub. */ 99- (void) enableBackbuffer:(CGSize)new_backbuffer_size 100{ 101} 102 103 104/* GL screenhacks set their own viewport and matrices. */ 105- (void) setViewport 106{ 107} 108 109 110#ifdef USE_IPHONE 111 112/* Keep the GL scene oriented into a portrait-mode View, regardless of 113 what the physical device orientation is. 114 */ 115- (void) reshape_x11 116{ 117 [super reshape_x11]; 118 119 glMatrixMode(GL_PROJECTION); 120 glRotatef (-current_device_rotation(), 0, 0, 1); 121 glMatrixMode(GL_MODELVIEW); 122} 123 124- (void) render_x11 125{ 126 BOOL was_initted_p = initted_p; 127 [super render_x11]; 128 129 if (! was_initted_p && xdpy) 130 _suppressRotationAnimation = 131 get_boolean_resource (xdpy, 132 "suppressRotationAnimation", 133 "SuppressRotationAnimation"); 134} 135 136#endif // USE_IPHONE 137 138 139 140/* The backbuffer isn't actually used for GL programs, but it needs to 141 be there for X11 calls to not error out. However, nothing done with 142 X11 calls will ever show up! It all gets written into the backbuffer 143 and discarded. That's ok, though, because mostly it's just calls to 144 XClearWindow and housekeeping stuff like that. So we make a tiny one. 145 */ 146- (void) createBackbuffer:(CGSize)new_size 147{ 148#ifdef JWXYZ_QUARTZ 149 NSAssert (! backbuffer_texture, 150 @"backbuffer_texture shouldn't be used for GL hacks"); 151 152 if (! backbuffer) { 153 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); 154 int w = 8; 155 int h = 8; 156 backbuffer = CGBitmapContextCreate (NULL, w, h, // yup, only 8px x 8px. 157 8, w*4, cs, 158 (kCGBitmapByteOrder32Little | 159 kCGImageAlphaNoneSkipLast)); 160 CGColorSpaceRelease (cs); 161 } 162#endif // JWXYZ_QUARTZ 163} 164 165 166/* Another stub for GL screenhacks. */ 167- (void) drawBackbuffer 168{ 169} 170 171 172/* Likewise. GL screenhacks control display with glXSwapBuffers(). */ 173- (void) flushBackbuffer 174{ 175} 176 177 178#ifndef USE_IPHONE 179 180- (NSOpenGLPixelFormat *) getGLPixelFormat 181{ 182 NSOpenGLPixelFormatAttribute attrs[40]; 183 int i = 0; 184 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; 185 attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; 186 attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; 187 188 if ([prefsReader getBooleanResource:"doubleBuffer"]) 189 attrs[i++] = NSOpenGLPFADoubleBuffer; 190 191 Bool ms_p = [prefsReader getBooleanResource:"multiSample"]; 192 193 /* Sometimes, turning on multisampling kills performance. At one point, 194 I thought the answer was, "only run multisampling on one screen, and 195 leave it turned off on other screens". That's what this code does, 196 but it turns out, that solution is insufficient. I can't really tell 197 what causes poor performance with multisampling, but it's not 198 predictable. Without changing the code, some times a given saver will 199 perform fine with multisampling on, and other times it will perform 200 very badly. Without multisampling, they always perform fine. 201 */ 202 // if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0]) 203 // ms_p = 0; 204 205 if (ms_p) { 206 attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; 207 attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6; 208 // Don't really understand what this means: 209 // attrs[i++] = NSOpenGLPFANoRecovery; 210 } 211 212 attrs[i++] = NSOpenGLPFAWindow; 213# ifdef JWXYZ_GL 214 attrs[i++] = NSOpenGLPFAPixelBuffer; 215# endif 216 217 attrs[i] = 0; 218 219 NSOpenGLPixelFormat *result = [[NSOpenGLPixelFormat alloc] 220 initWithAttributes:attrs]; 221 222 if (ms_p && !result) { // Retry without multisampling. 223 i -= 2; 224 attrs[i] = 0; 225 result = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; 226 } 227 228 return [result autorelease]; 229} 230 231#else // !USE_IPHONE 232 233- (NSDictionary *)getGLProperties 234{ 235 Bool dbuf_p = [prefsReader getBooleanResource:"doubleBuffer"]; 236 237 /* There seems to be no way to actually turn off double-buffering in 238 EAGLContext (e.g., no way to draw to the front buffer directly) 239 but if we turn on "retained backing" for non-buffering apps like 240 "pipes", at least the back buffer isn't auto-cleared on them. 241 */ 242 243 return [NSDictionary dictionaryWithObjectsAndKeys: 244 kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, 245 [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking, 246 nil]; 247} 248 249- (void)addExtraRenderbuffers:(CGSize)size 250{ 251 int w = size.width; 252 int h = size.height; 253 254 if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer); 255 256 glGenRenderbuffersOES (1, &gl_depthbuffer); 257 // [EAGLContext renderbufferStorage:fromDrawable:] must be called before this. 258 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_depthbuffer); 259 glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, 260 w, h); 261 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, 262 GL_RENDERBUFFER_OES, gl_depthbuffer); 263} 264 265- (NSString *)getCAGravity 266{ 267 return kCAGravityCenter; 268} 269 270- (void) startAnimation 271{ 272 [super startAnimation]; 273 if (ogl_ctx) /* Almost always true. */ 274 _glesState = jwzgles_make_state (); 275} 276 277- (void) stopAnimation 278{ 279 [super stopAnimation]; 280#ifdef USE_IPHONE 281 if (_glesState) { 282 [EAGLContext setCurrentContext:ogl_ctx]; 283 jwzgles_make_current (_glesState); 284 jwzgles_free_state (); 285 } 286#endif 287} 288 289- (void) prepareContext 290{ 291 [super prepareContext]; 292 jwzgles_make_current (_glesState); 293} 294 295#endif // !USE_IPHONE 296 297 298- (void)dealloc { 299 // ogl_ctx 300 // gl_framebuffer 301 // gl_renderbuffer 302 // gl_depthbuffer 303 [super dealloc]; 304} 305 306@end 307 308 309/* Utility functions... 310 */ 311 312 313// redefine NSAssert, etc. here since they don't work when not inside 314// an ObjC method. 315 316#undef NSAssert 317#undef NSAssert1 318#undef NSAssert2 319#define NSASS(S) \ 320 jwxyz_abort ("%s", [(S) cStringUsingEncoding:NSUTF8StringEncoding]) 321#define NSAssert(CC,S) do { if (!(CC)) { NSASS((S)); }} while(0) 322#define NSAssert1(CC,S,A) do { if (!(CC)) { \ 323 NSASS(([NSString stringWithFormat: S, A])); }} while(0) 324#define NSAssert2(CC,S,A,B) do { if (!(CC)) { \ 325 NSASS(([NSString stringWithFormat: S, A, B])); }} while(0) 326 327 328/* Called by OpenGL savers using the XLockmore API. 329 */ 330GLXContext * 331init_GL (ModeInfo *mi) 332{ 333 Window win = mi->window; 334 XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win); 335 NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]], 336 @"wrong view class: %@", view); 337 338 // OpenGL initialization is in [XScreenSaverView startAnimation]. 339 340 // I don't know why this is necessary, but it beats randomly having some 341 // textures be upside down. 342 // 343 glMatrixMode(GL_TEXTURE); 344 glLoadIdentity(); 345 glMatrixMode(GL_PROJECTION); 346 glLoadIdentity(); 347 glMatrixMode(GL_MODELVIEW); 348 glLoadIdentity(); 349 350 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 351 352 // Caller expects a pointer to an opaque struct... which it dereferences. 353 // Don't ask me, it's historical... 354 static GLXContext blort = (GLXContext) -1; 355 return &blort; 356} 357 358 359/* Copy the back buffer to the front buffer. 360 */ 361void 362glXSwapBuffers (Display *dpy, Window window) 363{ 364 // This all is very much like what's in -[XScreenSaverView flushBackbuffer]. 365#ifdef JWXYZ_GL 366 jwxyz_bind_drawable (window, window); 367#endif // JWXYZ_GL 368 369 XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window); 370 NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]], 371 @"wrong view class: %@", view); 372#ifndef USE_IPHONE 373 NSOpenGLContext *ctx = [view oglContext]; 374 if (ctx) [ctx flushBuffer]; // despite name, this actually swaps 375#else /* USE_IPHONE */ 376 [view swapBuffers]; 377#endif /* USE_IPHONE */ 378} 379 380/* Does nothing - prepareContext already did the work. 381 */ 382void 383glXMakeCurrent (Display *dpy, Window window, GLXContext context) 384{ 385} 386 387 388/* clear away any lingering error codes */ 389void 390clear_gl_error (void) 391{ 392 while (glGetError() != GL_NO_ERROR) 393 ; 394} 395 396 397#if defined GL_INVALID_FRAMEBUFFER_OPERATION_OES && \ 398 !defined GL_INVALID_FRAMEBUFFER_OPERATION 399# define GL_INVALID_FRAMEBUFFER_OPERATION GL_INVALID_FRAMEBUFFER_OPERATION_OES 400#endif 401 402 403/* report a GL error. */ 404void 405check_gl_error (const char *type) 406{ 407 char buf[100]; 408 GLenum i; 409 const char *e; 410 switch ((i = glGetError())) { 411 case GL_NO_ERROR: return; 412 case GL_INVALID_ENUM: e = "invalid enum"; break; 413 case GL_INVALID_VALUE: e = "invalid value"; break; 414 case GL_INVALID_OPERATION: e = "invalid operation"; break; 415 case GL_STACK_OVERFLOW: e = "stack overflow"; break; 416 case GL_STACK_UNDERFLOW: e = "stack underflow"; break; 417 case GL_OUT_OF_MEMORY: e = "out of memory"; break; 418#ifdef GL_INVALID_FRAMEBUFFER_OPERATION 419 case GL_INVALID_FRAMEBUFFER_OPERATION: 420 e = "invalid framebuffer operation"; 421 break; 422#endif 423#ifdef GL_TABLE_TOO_LARGE_EXT 424 case GL_TABLE_TOO_LARGE_EXT: e = "table too large"; break; 425#endif 426#ifdef GL_TEXTURE_TOO_LARGE_EXT 427 case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break; 428#endif 429 default: 430 e = buf; sprintf (buf, "unknown GL error %d", (int) i); break; 431 } 432 NSAssert2 (0, @"%s GL error: %s", type, e); 433} 434