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