b7ac9c1e49dad4b1e02251f4d27db94d84ab3c42
[xscreensaver] / OSX / XScreenSaverGLView.m
1 /* xscreensaver, Copyright (c) 2006-2008 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 - (void) dealloc
37 {
38   if (agl_ctx)
39     aglDestroyContext (agl_ctx);
40   [super dealloc];
41 }
42
43
44 - (void)stopAnimation
45 {
46   [super stopAnimation];
47   
48   // Without this, the GL frame stays on screen when switching tabs
49   // in System Preferences.
50   //
51   if (agl_ctx) {
52     aglSetCurrentContext (NULL);
53     aglDestroyContext (agl_ctx);
54     agl_ctx = 0;
55   }
56 }
57
58
59 // #### maybe this could/should just be on 'lockFocus' instead?
60 - (void) prepareContext
61 {
62   if (agl_ctx)
63     if (!aglSetCurrentContext (agl_ctx)) {
64       check_gl_error ("aglSetCurrentContext");
65       abort();
66     }
67 }
68       
69 - (void) resizeContext
70 {
71   if (! agl_ctx) 
72     return;
73
74   /* Constrain the AGL context to the rectangle of this view
75      (not of our parent window).
76    */
77   GLint aglrect[4];
78   NSRect rect = [[[self window] contentView] convertRect:[self frame]
79                                                 fromView:self];
80   aglrect[0] = rect.origin.x;
81   aglrect[1] = rect.origin.y;
82   aglrect[2] = rect.size.width;
83   aglrect[3] = rect.size.height;
84   aglEnable (agl_ctx, AGL_BUFFER_RECT);
85   aglSetInteger (agl_ctx, AGL_BUFFER_RECT, aglrect);
86   aglUpdateContext (agl_ctx);
87 }
88
89
90 - (void)drawRect:(NSRect)rect
91 {
92   if (! agl_ctx)
93     [super drawRect:rect];
94 }
95
96
97 - (AGLContext) aglContext
98 {
99   return agl_ctx;
100 }
101
102 - (void) setAglContext: (AGLContext) ctx
103 {
104   if (agl_ctx)
105     if (! aglDestroyContext (agl_ctx)) {
106       check_gl_error("aglDestroyContext");
107       abort();
108     }
109   agl_ctx = ctx;
110   [self resizeContext];
111 }
112
113 @end
114
115 /* Utility functions...
116  */
117
118
119 /* Called by OpenGL savers using the XLockmore API.
120  */
121 GLXContext *
122 init_GL (ModeInfo *mi)
123 {
124   Window win = mi->window;
125   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win);
126   
127   GLint agl_attrs[] = {
128     AGL_RGBA,
129     AGL_DOUBLEBUFFER,
130     AGL_DEPTH_SIZE, 16,
131     0 };
132   AGLPixelFormat aglpixf = aglChoosePixelFormat (NULL, 0, agl_attrs);
133   AGLContext ctx = aglCreateContext (aglpixf, NULL);
134   aglDestroyPixelFormat (aglpixf);
135   if (! ctx) {
136     check_gl_error("aglCreateContext");
137     abort();
138   }
139   
140   if (! aglSetDrawable (ctx, GetWindowPort ([[view window] windowRef]))) {
141     check_gl_error("aglSetDrawable");
142     abort();
143   }
144
145   if (! aglSetCurrentContext (ctx)) {
146     check_gl_error("aglSetCurrentContext");
147     abort();
148   }
149
150   [view setAglContext:ctx];
151
152   // Sync refreshes to the vertical blanking interval
153   GLint r = 1;
154   aglSetInteger (ctx, AGL_SWAP_INTERVAL, &r);
155
156   // Enable multi-threading, if possible.  This runs most OpenGL commands
157   // and GPU management on a second CPU.
158   {
159 #   ifndef  kCGLCEMPEngine
160 #    define kCGLCEMPEngine 313  // Added in MacOS 10.4.8 + XCode 2.4.
161 #   endif
162     CGLContextObj cctx = CGLGetCurrentContext();
163     CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
164     if (err != kCGLNoError) {
165       NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
166     }
167   }
168
169   // Caller expects a pointer to an opaque struct...  which it dereferences.
170   // Don't ask me, it's historical...
171   static int blort = -1;
172   return (void *) &blort;
173 }
174
175
176 /* Copy the back buffer to the front buffer.
177  */
178 void
179 glXSwapBuffers (Display *dpy, Window window)
180 {
181   XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window);
182   AGLContext ctx = [view aglContext];
183   if (ctx) aglSwapBuffers (ctx);
184 }
185
186 /* Does nothing. 
187  */
188 void
189 glXMakeCurrent (Display *dpy, Window window, GLXContext context)
190 {
191 }
192
193
194 /* clear away any lingering error codes */
195 void
196 clear_gl_error (void)
197 {
198   while (glGetError() != GL_NO_ERROR)
199     ;
200   while (aglGetError() != AGL_NO_ERROR)
201     ;
202 }
203
204 static void
205 check_agl_error (const char *type)
206 {
207   char buf[100];
208   GLenum i;
209   const char *e;
210   switch ((i = aglGetError())) {
211     case AGL_NO_ERROR: return;
212     case AGL_BAD_ATTRIBUTE:        e = "bad attribute"; break;
213     case AGL_BAD_PROPERTY:         e = "bad propery";   break;
214     case AGL_BAD_PIXELFMT:         e = "bad pixelfmt";  break;
215     case AGL_BAD_RENDINFO:         e = "bad rendinfo";  break;
216     case AGL_BAD_CONTEXT:          e = "bad context";   break;
217     case AGL_BAD_DRAWABLE:         e = "bad drawable";  break;
218     case AGL_BAD_GDEV:             e = "bad gdev";      break;
219     case AGL_BAD_STATE:            e = "bad state";     break;
220     case AGL_BAD_VALUE:            e = "bad value";     break;
221     case AGL_BAD_MATCH:            e = "bad match";     break;
222     case AGL_BAD_ENUM:             e = "bad enum";      break;
223     case AGL_BAD_OFFSCREEN:        e = "bad offscreen"; break;
224     case AGL_BAD_FULLSCREEN:       e = "bad fullscreen";break;
225     case AGL_BAD_WINDOW:           e = "bad window";    break;
226     case AGL_BAD_POINTER:          e = "bad pointer";   break;
227     case AGL_BAD_MODULE:           e = "bad module";    break;
228     case AGL_BAD_ALLOC:            e = "bad alloc";     break;
229     case AGL_BAD_CONNECTION:       e = "bad connection";break;
230     default:
231       e = buf; sprintf (buf, "unknown AGL error %d", (int) i); break;
232   }
233   NSLog (@"%s AGL error: %s", type, e);
234   exit (1);
235 }
236
237
238 /* report a GL error. */
239 void
240 check_gl_error (const char *type)
241 {
242   check_agl_error (type);
243   
244   char buf[100];
245   GLenum i;
246   const char *e;
247   switch ((i = glGetError())) {
248     case GL_NO_ERROR: return;
249     case GL_INVALID_ENUM:          e = "invalid enum";      break;
250     case GL_INVALID_VALUE:         e = "invalid value";     break;
251     case GL_INVALID_OPERATION:     e = "invalid operation"; break;
252     case GL_STACK_OVERFLOW:        e = "stack overflow";    break;
253     case GL_STACK_UNDERFLOW:       e = "stack underflow";   break;
254     case GL_OUT_OF_MEMORY:         e = "out of memory";     break;
255 #ifdef GL_TABLE_TOO_LARGE_EXT
256     case GL_TABLE_TOO_LARGE_EXT:   e = "table too large";   break;
257 #endif
258 #ifdef GL_TEXTURE_TOO_LARGE_EXT
259     case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
260 #endif
261     default:
262       e = buf; sprintf (buf, "unknown GL error %d", (int) i); break;
263   }
264   NSLog (@"%s GL error: %s", type, e);
265   exit (1);
266 }