http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / OSX / XScreenSaverGLView.m
1 /* xscreensaver, Copyright (c) 2006-2011 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 #import <OpenGL/OpenGL.h>
24
25 /* used by the OpenGL screen savers
26  */
27 extern GLXContext *init_GL (ModeInfo *);
28 extern void glXSwapBuffers (Display *, Window);
29 extern void glXMakeCurrent (Display *, Window, GLXContext);
30 extern void clear_gl_error (void);
31 extern void check_gl_error (const char *type);
32
33
34 @implementation XScreenSaverGLView
35
36 #if 0
37 - (void) dealloc
38 {
39   /* #### Do we have to destroy ogl_ctx? */
40   [super dealloc];
41 }
42 #endif
43
44
45 - (void)stopAnimation
46 {
47   [super stopAnimation];
48   
49   // Without this, the GL frame stays on screen when switching tabs
50   // in System Preferences.
51   //
52   [NSOpenGLContext clearCurrentContext];
53 }
54
55
56 // #### maybe this could/should just be on 'lockFocus' instead?
57 - (void) prepareContext
58 {
59   if (ogl_ctx) {
60     [ogl_ctx makeCurrentContext];
61 //    check_gl_error ("makeCurrentContext");
62     [ogl_ctx update];
63   }
64 }
65
66
67 - (void) resizeContext
68 {
69   if (ogl_ctx) 
70     [ogl_ctx setView:self];
71 }
72
73
74 - (void)drawRect:(NSRect)rect
75 {
76   if (! ogl_ctx)
77     [super drawRect:rect];
78 }
79
80
81 - (NSOpenGLContext *) oglContext
82 {
83   return ogl_ctx;
84 }
85
86
87 - (void) setOglContext: (NSOpenGLContext *) ctx
88 {
89   ogl_ctx = ctx;
90   [self resizeContext];
91 }
92
93 @end
94
95
96 /* Utility functions...
97  */
98
99
100 /* Called by OpenGL savers using the XLockmore API.
101  */
102 GLXContext *
103 init_GL (ModeInfo *mi)
104 {
105   Window win = mi->window;
106   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
107   NSOpenGLContext *ctx = [view oglContext];
108
109   if (!ctx) {
110
111     NSOpenGLPixelFormatAttribute attrs[40];
112     int i = 0;
113     attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
114     attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8;
115     attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16;
116
117     if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"))
118       attrs[i++] = NSOpenGLPFADoubleBuffer;
119
120     Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample");
121
122     /* Sometimes, turning on multisampling kills performance.  At one point,
123        I thought the answer was, "only run multisampling on one screen, and
124        leave it turned off on other screens".  That's what this code does,
125        but it turns out, that solution is insufficient.  I can't really tell
126        what causes poor performance with multisampling, but it's not
127        predictable.  Without changing the code, some times a given saver will
128        perform fine with multisampling on, and other times it will perform
129        very badly.  Without multisampling, they always perform fine.
130      */
131 //  if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0])
132 //    ms_p = 0;
133
134     if (ms_p) {
135       attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
136       attrs[i++] = NSOpenGLPFASamples;       attrs[i++] = 6;
137       // Don't really understand what this means:
138       // attrs[i++] = NSOpenGLPFANoRecovery;
139     }
140
141     attrs[i] = 0;
142
143     NSOpenGLPixelFormat *pixfmt = 
144       [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
145
146     if (ms_p && !pixfmt) {   // Retry without multisampling.
147       i -= 2;
148       attrs[i] = 0;
149       pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
150     }
151
152     if (! pixfmt) {
153       NSLog (@"unable to create NSOpenGLPixelFormat");
154       exit (1);
155     }
156
157     ctx = [[NSOpenGLContext alloc] 
158             initWithFormat:pixfmt
159               shareContext:nil];
160 //    [pixfmt release]; // #### ???
161   }
162
163   // Sync refreshes to the vertical blanking interval
164   GLint r = 1;
165   [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
166 //  check_gl_error ("NSOpenGLCPSwapInterval");  // SEGV sometimes. Too early?
167
168   // #### "Build and Analyze" says that ctx leaks, because it doesn't
169   //      seem to realize that makeCurrentContext retains it (right?)
170   //      Not sure what to do to make this warning go away.
171
172   [ctx makeCurrentContext];
173   check_gl_error ("makeCurrentContext");
174
175   [view setOglContext:ctx];
176
177   // Clear frame buffer ASAP, else there are bits left over from other apps.
178   glClearColor (0, 0, 0, 1);
179   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
180 //  glFinish ();
181 //  glXSwapBuffers (mi->dpy, mi->window);
182
183
184   // Enable multi-threading, if possible.  This runs most OpenGL commands
185   // and GPU management on a second CPU.
186   {
187 #   ifndef  kCGLCEMPEngine
188 #    define kCGLCEMPEngine 313  // Added in MacOS 10.4.8 + XCode 2.4.
189 #   endif
190     CGLContextObj cctx = CGLGetCurrentContext();
191     CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
192     if (err != kCGLNoError) {
193       NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
194     }
195   }
196
197   check_gl_error ("init_GL");
198
199   // Caller expects a pointer to an opaque struct...  which it dereferences.
200   // Don't ask me, it's historical...
201   static int blort = -1;
202   return (void *) &blort;
203 }
204
205
206 /* Copy the back buffer to the front buffer.
207  */
208 void
209 glXSwapBuffers (Display *dpy, Window window)
210 {
211   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
212   NSOpenGLContext *ctx = [view oglContext];
213   if (ctx) [ctx flushBuffer]; // despite name, this actually swaps
214 }
215
216 /* Does nothing - prepareContext already did the work.
217  */
218 void
219 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
220 {
221 }
222
223
224 /* clear away any lingering error codes */
225 void
226 clear_gl_error (void)
227 {
228   while (glGetError() != GL_NO_ERROR)
229     ;
230 }
231
232
233 /* report a GL error. */
234 void
235 check_gl_error (const char *type)
236 {
237   char buf[100];
238   GLenum i;
239   const char *e;
240   switch ((i = glGetError())) {
241     case GL_NO_ERROR: return;
242     case GL_INVALID_ENUM:          e = "invalid enum";      break;
243     case GL_INVALID_VALUE:         e = "invalid value";     break;
244     case GL_INVALID_OPERATION:     e = "invalid operation"; break;
245     case GL_STACK_OVERFLOW:        e = "stack overflow";    break;
246     case GL_STACK_UNDERFLOW:       e = "stack underflow";   break;
247     case GL_OUT_OF_MEMORY:         e = "out of memory";     break;
248 #ifdef GL_TABLE_TOO_LARGE_EXT
249     case GL_TABLE_TOO_LARGE_EXT:   e = "table too large";   break;
250 #endif
251 #ifdef GL_TEXTURE_TOO_LARGE_EXT
252     case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
253 #endif
254     default:
255       e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
256   }
257   NSLog (@"%s GL error: %s", type, e);
258   exit (1);
259 }