From http://www.jwz.org/xscreensaver/xscreensaver-5.35.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)
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 #endif // !USE_IPHONE
266
267
268 - (void)dealloc {
269   // ogl_ctx
270   // gl_framebuffer
271   // gl_renderbuffer
272   // gl_depthbuffer
273   [super dealloc];
274 }
275
276 @end
277
278
279 /* Utility functions...
280  */
281
282
283 // redefine NSAssert, etc. here since they don't work when not inside
284 // an ObjC method.
285
286 #undef NSAssert
287 #undef NSAssert1
288 #undef NSAssert2
289 #define NSASS(S) \
290   jwxyz_abort ("%s", [(S) cStringUsingEncoding:NSUTF8StringEncoding])
291 #define NSAssert(CC,S)      do { if (!(CC)) { NSASS((S)); }} while(0)
292 #define NSAssert1(CC,S,A)   do { if (!(CC)) { \
293   NSASS(([NSString stringWithFormat: S, A])); }} while(0)
294 #define NSAssert2(CC,S,A,B) do { if (!(CC)) { \
295   NSASS(([NSString stringWithFormat: S, A, B])); }} while(0)
296
297
298 /* Called by OpenGL savers using the XLockmore API.
299  */
300 GLXContext *
301 init_GL (ModeInfo *mi)
302 {
303   Window win = mi->window;
304   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
305   NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
306              @"wrong view class: %@", view);
307
308   // OpenGL initialization is in [XScreenSaverView startAnimation].
309
310 # ifdef USE_IPHONE
311   jwzgles_reset ();
312 # endif // USE_IPHONE
313
314   // I don't know why this is necessary, but it beats randomly having some
315   // textures be upside down.
316   //
317   glMatrixMode(GL_TEXTURE);
318   glLoadIdentity();
319   glMatrixMode(GL_PROJECTION);
320   glLoadIdentity();
321   glMatrixMode(GL_MODELVIEW);
322   glLoadIdentity();
323
324   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
325
326   // Caller expects a pointer to an opaque struct...  which it dereferences.
327   // Don't ask me, it's historical...
328   static int blort = -1;
329   return (void *) &blort;
330 }
331
332
333 /* Copy the back buffer to the front buffer.
334  */
335 void
336 glXSwapBuffers (Display *dpy, Window window)
337 {
338   // This all is very much like what's in -[XScreenSaverView flushBackbuffer].
339 #ifdef JWXYZ_GL
340   jwxyz_bind_drawable (window, window);
341 #endif // JWXYZ_GL
342
343   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
344   NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]],
345              @"wrong view class: %@", view);
346 #ifndef USE_IPHONE
347   NSOpenGLContext *ctx = [view oglContext];
348   if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
349 #else /* USE_IPHONE */
350   [view swapBuffers];
351 #endif /* USE_IPHONE */
352 }
353
354 /* Does nothing - prepareContext already did the work.
355  */
356 void
357 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
358 {
359 }
360
361
362 /* clear away any lingering error codes */
363 void
364 clear_gl_error (void)
365 {
366   while (glGetError() != GL_NO_ERROR)
367     ;
368 }
369
370
371 #if defined GL_INVALID_FRAMEBUFFER_OPERATION_OES && \
372   !defined GL_INVALID_FRAMEBUFFER_OPERATION
373 # define GL_INVALID_FRAMEBUFFER_OPERATION GL_INVALID_FRAMEBUFFER_OPERATION_OES
374 #endif
375
376
377 /* report a GL error. */
378 void
379 check_gl_error (const char *type)
380 {
381   char buf[100];
382   GLenum i;
383   const char *e;
384   switch ((i = glGetError())) {
385     case GL_NO_ERROR: return;
386     case GL_INVALID_ENUM:          e = "invalid enum";      break;
387     case GL_INVALID_VALUE:         e = "invalid value";     break;
388     case GL_INVALID_OPERATION:     e = "invalid operation"; break;
389     case GL_STACK_OVERFLOW:        e = "stack overflow";    break;
390     case GL_STACK_UNDERFLOW:       e = "stack underflow";   break;
391     case GL_OUT_OF_MEMORY:         e = "out of memory";     break;
392 #ifdef GL_INVALID_FRAMEBUFFER_OPERATION
393     case GL_INVALID_FRAMEBUFFER_OPERATION:
394       e = "invalid framebuffer operation";
395       break;
396 #endif
397 #ifdef GL_TABLE_TOO_LARGE_EXT
398     case GL_TABLE_TOO_LARGE_EXT:   e = "table too large";   break;
399 #endif
400 #ifdef GL_TEXTURE_TOO_LARGE_EXT
401     case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
402 #endif
403     default:
404       e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
405   }
406   NSAssert2 (0, @"%s GL error: %s", type, e);
407 }