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