From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / OSX / XScreenSaverGLView.m
1 /* xscreensaver, Copyright (c) 2006-2017 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  */
34 extern GLXContext *init_GL (ModeInfo *);
35 extern void glXSwapBuffers (Display *, Window);
36 extern void glXMakeCurrent (Display *, Window, GLXContext);
37 extern void clear_gl_error (void);
38 extern 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
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  */
330 GLXContext *
331 init_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 int blort = -1;
355   return (void *) &blort;
356 }
357
358
359 /* Copy the back buffer to the front buffer.
360  */
361 void
362 glXSwapBuffers (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  */
382 void
383 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
384 {
385 }
386
387
388 /* clear away any lingering error codes */
389 void
390 clear_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. */
404 void
405 check_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 }