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