From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / jwxyz / jwxyz-cocoa.m
1 /* xscreensaver, Copyright (c) 1991-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 /* JWXYZ Is Not Xlib.
13
14    But it's a bunch of function definitions that bear some resemblance to
15    Xlib and that do Cocoa-ish or OpenGL-ish things that bear some resemblance
16    to the things that Xlib might have done.
17
18    This code is used by both the original jwxyz.m and the new jwxyz-gl.c.
19  */
20
21 #import "jwxyzI.h"
22 #import "jwxyz-cocoa.h"
23
24 #include <stdarg.h>
25
26 #ifdef USE_IPHONE
27 # import <OpenGLES/ES1/gl.h>
28 # import <OpenGLES/ES1/glext.h>
29 # define NSOpenGLContext EAGLContext
30 #endif
31
32 /* OS X/iOS-specific JWXYZ implementation. */
33
34 /* Instead of calling abort(), throw a real exception, so that
35    XScreenSaverView can catch it and display a dialog.
36  */
37 void
38 jwxyz_abort (const char *fmt, ...)
39 {
40   char s[10240];
41   if (!fmt || !*fmt)
42     strcpy (s, "abort");
43   else
44     {
45       va_list args;
46       va_start (args, fmt);
47       vsprintf (s, fmt, args);
48       va_end (args);
49     }
50   [[NSException exceptionWithName: NSInternalInconsistencyException
51                 reason: [NSString stringWithCString: s
52                                   encoding:NSUTF8StringEncoding]
53                 userInfo: nil]
54     raise];
55   abort();  // not reached
56 }
57
58
59 const XRectangle *
60 jwxyz_frame (Drawable d)
61 {
62   return &d->frame;
63 }
64
65
66 unsigned int
67 jwxyz_drawable_depth (Drawable d)
68 {
69   return (d->type == WINDOW
70           ? visual_depth (NULL, NULL)
71           : d->pixmap.depth);
72 }
73
74
75 void
76 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
77 {
78 # ifdef USE_IPHONE
79
80   xvpos->x = 0;
81   xvpos->y = 0;
82
83   if (xp) {
84     xp->x = w->window.last_mouse_x;
85     xp->y = w->window.last_mouse_y;
86   }
87
88 # else  // !USE_IPHONE
89
90   NSWindow *nsw = [w->window.view window];
91
92   // get bottom left of window on screen, from bottom left
93
94 # if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6)
95   NSRect rr1 = [w->window.view convertRect: NSMakeRect(0,0,0,0) toView:nil];
96   NSRect rr2 = [nsw convertRectToScreen: rr1];
97   NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
98                               rr2.origin.y - rr1.origin.y);
99 # else
100   // deprecated as of 10.7
101   NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
102 # endif
103
104
105   // get bottom left of view on window, from bottom left
106   NSPoint vpos;
107   vpos.x = vpos.y = 0;
108   vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]];
109
110   // get bottom left of view on screen, from bottom left
111   vpos.x += wpos.x;
112   vpos.y += wpos.y;
113
114   // get top left of view on screen, from bottom left
115   vpos.y += w->frame.height;
116
117   // get top left of view on screen, from top left
118   NSArray *screens = [NSScreen screens];
119   NSScreen *screen = (screens && [screens count] > 0
120                       ? [screens objectAtIndex:0]
121                       : [NSScreen mainScreen]);
122   NSRect srect = [screen frame];
123   vpos.y = srect.size.height - vpos.y;
124
125   xvpos->x = vpos.x;
126   xvpos->y = vpos.y;
127
128   if (xp) {
129     // get the mouse position on window, from bottom left
130     NSEvent *e = [NSApp currentEvent];
131     NSPoint p = [e locationInWindow];
132
133     // get mouse position on screen, from bottom left
134     p.x += wpos.x;
135     p.y += wpos.y;
136
137     // get mouse position on screen, from top left
138     p.y = srect.size.height - p.y;
139
140     xp->x = (int) p.x;
141     xp->y = (int) p.y;
142   }
143
144 # endif // !USE_IPHONE
145 }
146
147
148 #ifdef USE_IPHONE
149
150 void
151 check_framebuffer_status (void)
152 {
153   int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
154   switch (err) {
155   case GL_FRAMEBUFFER_COMPLETE_OES:
156     break;
157   case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
158     Assert (0, "framebuffer incomplete attachment");
159     break;
160   case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
161     Assert (0, "framebuffer incomplete missing attachment");
162     break;
163   case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
164     Assert (0, "framebuffer incomplete dimensions");
165     break;
166   case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
167     Assert (0, "framebuffer incomplete formats");
168     break;
169   case GL_FRAMEBUFFER_UNSUPPORTED_OES:
170     Assert (0, "framebuffer unsupported");
171     break;
172 /*
173   case GL_FRAMEBUFFER_UNDEFINED:
174     Assert (0, "framebuffer undefined");
175     break;
176   case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
177     Assert (0, "framebuffer incomplete draw buffer");
178     break;
179   case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
180     Assert (0, "framebuffer incomplete read buffer");
181     break;
182   case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
183     Assert (0, "framebuffer incomplete multisample");
184     break;
185   case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
186     Assert (0, "framebuffer incomplete layer targets");
187     break;
188  */
189   default:
190     NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
191     break;
192   }
193 }
194
195
196 void
197 create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
198 {
199   glGenFramebuffersOES  (1, gl_framebuffer);
200   glBindFramebufferOES  (GL_FRAMEBUFFER_OES,  *gl_framebuffer);
201
202   glGenRenderbuffersOES (1, gl_renderbuffer);
203   glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
204 }
205
206 #endif // USE_IPHONE
207
208
209 #if defined JWXYZ_QUARTZ
210
211 /* Pushes a GC context; sets Fill and Stroke colors to the background color.
212  */
213 static void
214 push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
215 {
216   XGCValues *gcv = jwxyz_gc_gcv (gc);
217   push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
218 }
219
220
221 void
222 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
223                  int src_x, int src_y,
224                  unsigned int width, unsigned int height,
225                  int dst_x, int dst_y)
226 {
227   XRectangle src_frame = src->frame, dst_frame = dst->frame;
228   XGCValues *gcv = jwxyz_gc_gcv (gc);
229
230   BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
231
232
233   /* If we're copying from a bitmap to a bitmap, and there's nothing funny
234      going on with clipping masks or depths or anything, optimize it by
235      just doing a memcpy instead of going through a CGI.
236    */
237   if (gcv->function == GXcopy &&
238       !gcv->clip_mask &&
239       jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
240
241     Assert(!src_frame.x &&
242            !src_frame.y &&
243            !dst_frame.x &&
244            !dst_frame.y,
245            "unexpected non-zero origin");
246
247     ptrdiff_t src_pitch = CGBitmapContextGetBytesPerRow(src->cgc);
248     ptrdiff_t dst_pitch = CGBitmapContextGetBytesPerRow(dst->cgc);
249     char *src_data = seek_xy (CGBitmapContextGetData (src->cgc), src_pitch,
250                               src_x, src_y);
251     char *dst_data = seek_xy (CGBitmapContextGetData (dst->cgc), dst_pitch,
252                               dst_x, dst_y);
253
254     size_t bytes = width * 4;
255
256     if (src == dst && dst_y > src_y) {
257       // Copy upwards if the areas might overlap.
258       src_data += src_pitch * (height - 1);
259       dst_data += dst_pitch * (height - 1);
260       src_pitch = -src_pitch;
261       dst_pitch = -dst_pitch;
262     }
263
264     while (height) {
265       // memcpy is an alias for memmove on OS X.
266       memmove(dst_data, src_data, bytes);
267       src_data += src_pitch;
268       dst_data += dst_pitch;
269       --height;
270     }
271   } else {
272     CGRect
273     src_rect = CGRectMake(src_x, src_y, width, height),
274     dst_rect = CGRectMake(dst_x, dst_y, width, height);
275
276     src_rect.origin = map_point (src, src_rect.origin.x,
277                                  src_rect.origin.y + src_rect.size.height);
278     dst_rect.origin = map_point (dst, dst_rect.origin.x,
279                                  dst_rect.origin.y + src_rect.size.height);
280
281     NSObject *releaseme = 0;
282     CGImageRef cgi;
283     BOOL free_cgi_p = NO;
284
285     // We must first copy the bits to an intermediary CGImage object, then
286     // copy that to the destination drawable's CGContext.
287     //
288     // First we get a CGImage out of the pixmap CGContext -- it's the whole
289     // pixmap, but it presumably shares the data pointer instead of copying
290     // it.  We then cache that CGImage it inside the Pixmap object.  Note:
291     // invalidate_drawable_cache() must be called to discard this any time a
292     // modification is made to the pixmap, or we'll end up re-using old bits.
293     //
294     if (!src->cgi)
295       src->cgi = CGBitmapContextCreateImage (src->cgc);
296     cgi = src->cgi;
297
298     // if doing a sub-rect, trim it down.
299     if (src_rect.origin.x    != src_frame.x   ||
300         src_rect.origin.y    != src_frame.y   ||
301         src_rect.size.width  != src_frame.width ||
302         src_rect.size.height != src_frame.height) {
303       // #### I don't understand why this is needed...
304       src_rect.origin.y = (src_frame.height -
305                            src_rect.size.height - src_rect.origin.y);
306       // This does not copy image data, so it should be fast.
307       cgi = CGImageCreateWithImageInRect (cgi, src_rect);
308       free_cgi_p = YES;
309     }
310
311     CGContextRef cgc = dst->cgc;
312
313     if (mask_p) {               // src depth == 1
314
315       push_bg_gc (dpy, dst, gc, YES);
316
317       // fill the destination rectangle with solid background...
318       CGContextFillRect (cgc, dst_rect);
319
320       Assert (cgc, "no CGC with 1-bit XCopyArea");
321
322       // then fill in a solid rectangle of the fg color, using the image as an
323       // alpha mask.  (the image has only values of BlackPixel or WhitePixel.)
324       set_color (dpy, cgc, gcv->foreground, jwxyz_gc_depth (gc),
325                  gcv->alpha_allowed_p, YES);
326       CGContextClipToMask (cgc, dst_rect, cgi);
327       CGContextFillRect (cgc, dst_rect);
328
329       pop_gc (dst, gc);
330
331     } else {            // src depth > 1
332
333       push_gc (dst, gc);
334
335       // copy the CGImage onto the destination CGContext
336       //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
337       CGContextDrawImage (cgc, dst_rect, cgi);
338
339       pop_gc (dst, gc);
340     }
341
342     if (free_cgi_p) CGImageRelease (cgi);
343     
344     if (releaseme) [releaseme release];
345   }
346   
347   invalidate_drawable_cache (dst);
348 }
349
350 #elif defined JWXYZ_GL
351
352 /* Warning! The JWXYZ_GL code here is experimental and provisional and not at
353    all ready for prime time. Please be careful.
354  */
355
356 void
357 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
358                  int src_x, int src_y,
359                  unsigned int width, unsigned int height,
360                  int dst_x, int dst_y)
361 {
362   /* TODO:
363    - glCopyPixels if src == dst
364    - Pixel buffer copying
365    - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
366      which is like a blit.
367    */
368
369   /* Strange and ugly flickering when going the glCopyTexImage2D route on
370      OS X. (Early 2009 Mac mini, OS X 10.10)
371    */
372 # ifdef USE_IPHONE
373   jwxyz_gl_copy_area_copy_tex_image (dpy, src, dst, gc, src_x, src_y,
374                                      width, height, dst_x, dst_y);
375 # else // !USE_IPHONE
376   jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc,
377                                   src_x, src_y, width, height, dst_x, dst_y);
378 # endif // !USE_IPHONE
379   jwxyz_assert_gl ();
380 }
381
382
383 void
384 jwxyz_assert_gl ()
385 {
386   // This is like check_gl_error, except this happens for debug builds only.
387 #ifndef __OPTIMIZE__
388   if([NSOpenGLContext currentContext])
389   {
390     // glFinish here drops FPS into the toilet. It might need to be on if
391     // something goes wrong.
392     // glFinish();
393     GLenum error = glGetError();
394     Assert (!error, "jwxyz_assert_gl: OpenGL error");
395   }
396 #endif // !__OPTIMIZE__
397 }
398
399 void
400 jwxyz_assert_drawable (Window main_window, Drawable d)
401 {
402 #if !defined USE_IPHONE && !defined __OPTIMIZE__
403   XScreenSaverView *view = main_window->window.view;
404   NSOpenGLContext *ogl_ctx = [view oglContext];
405
406   if (d->type == WINDOW) {
407     Assert([ogl_ctx view] == view,
408            "jwxyz_assert_display: ogl_ctx view not set!");
409   }
410
411   @try {
412     /* Assert([d->ctx isKindOfClass:[NSOpenGLContext class]], "Not a context."); */
413     Class c = [ogl_ctx class];
414     Assert([c isSubclassOfClass:[NSOpenGLContext class]], "Not a context.");
415     // [d->ctx makeCurrentContext];
416   }
417   @catch (NSException *exception) {
418     perror([[exception reason] UTF8String]);
419     abort();
420   }
421 #endif // !USE_IPHONE && !__OPTIMIZE__
422 }
423
424
425 void
426 jwxyz_bind_drawable (Window main_window, Drawable d)
427 {
428   /* Windows and Pixmaps need to use different contexts with OpenGL
429      screenhacks, because an OpenGL screenhack sets state in an arbitrary
430      fashion, but jwxyz-gl.c has its own ideas w.r.t. OpenGL state.
431
432      On iOS, all pixmaps can use the same context with different FBOs. Which
433      is nice.
434    */
435
436   /* OpenGL screenhacks in general won't be drawing on the Window, but they
437      can and will draw on a Pixmap -- but an OpenGL call following an Xlib
438      call won't be able to fix the fact that it's drawing offscreen.
439    */
440
441   /* EXT_direct_state_access might be appropriate, but it's apparently not
442      available on Mac OS X.
443    */
444
445   // jwxyz_assert_display (dpy);
446   jwxyz_assert_drawable (main_window, main_window);
447   jwxyz_assert_gl ();
448   jwxyz_assert_drawable (main_window, d);
449
450 #if defined USE_IPHONE && !defined __OPTIMIZE__
451   Drawable current_drawable = main_window->window.current_drawable;
452   Assert (!current_drawable
453             || current_drawable->ogl_ctx == [EAGLContext currentContext],
454           "bind_drawable: Wrong context.");
455   if (current_drawable) {
456     GLint framebuffer;
457     glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
458     Assert (framebuffer == current_drawable->gl_framebuffer,
459             "bind_drawable: Wrong framebuffer.");
460   }
461 #endif
462
463   if (main_window->window.current_drawable != d) {
464     main_window->window.current_drawable = d;
465
466     /* Doing this repeatedly is probably not OK performance-wise. Probably. */
467 #ifndef USE_IPHONE
468     [d->ogl_ctx makeCurrentContext];
469 #else
470     [EAGLContext setCurrentContext:d->ogl_ctx];
471     glBindFramebufferOES(GL_FRAMEBUFFER_OES, d->gl_framebuffer);
472     if (d->type == PIXMAP) {
473       glViewport (0, 0, d->frame.width, d->frame.height);
474       jwxyz_set_matrices (d->frame.width, d->frame.height);
475     }
476 #endif
477   }
478
479   jwxyz_assert_gl ();
480 }
481
482
483 Pixmap
484 XCreatePixmap (Display *dpy, Drawable d,
485                unsigned int width, unsigned int height, unsigned int depth)
486 {
487   Pixmap p = (Pixmap) calloc (1, sizeof(*p));
488   p->type = PIXMAP;
489   p->frame.width  = width;
490   p->frame.height = height;
491   p->pixmap.depth = depth;
492
493   Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
494
495   /* TODO: If Pixel buffers are not supported, do something about it. */
496   Window w = XRootWindow (dpy, 0);
497
498 # ifndef USE_IPHONE
499
500   p->ogl_ctx = [[NSOpenGLContext alloc]
501                 initWithFormat:w->window.pixfmt
502                 shareContext:w->ogl_ctx];
503   CFRetain (p->ogl_ctx);
504
505   [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
506
507   p->pixmap.gl_pbuffer = [[NSOpenGLPixelBuffer alloc]
508                           /* TODO: Only if there are rectangluar textures. */
509                           initWithTextureTarget:GL_TEXTURE_RECTANGLE_EXT
510                           /* TODO: Make sure GL_RGBA isn't better. */
511                           textureInternalFormat:GL_RGB
512                           textureMaxMipMapLevel:0
513                           pixelsWide:width
514                           pixelsHigh:height];
515   CFRetain (p->pixmap.gl_pbuffer);
516
517   [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
518                  cubeMapFace:0
519                  mipMapLevel:0
520         currentVirtualScreen:w->window.virtual_screen];
521
522 # else // USE_IPHONE
523
524   p->ogl_ctx = w->window.ogl_ctx_pixmap;
525
526   [EAGLContext setCurrentContext:p->ogl_ctx];
527   create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
528
529   glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height);
530   glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
531                                 GL_RENDERBUFFER_OES, p->gl_renderbuffer);
532
533   check_framebuffer_status ();
534
535   glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
536
537 # endif // USE_IPHONE
538
539   w->window.current_drawable = p;
540   glViewport (0, 0, width, height);
541   jwxyz_set_matrices (width, height);
542
543 # ifndef __OPTIMIZE__
544   glClearColor (frand(1), frand(1), frand(1), 0);
545   glClear (GL_COLOR_BUFFER_BIT);
546 # endif
547
548   return p;
549 }
550
551 int
552 XFreePixmap (Display *d, Pixmap p)
553 {
554   Assert (p && p->type == PIXMAP, "not a pixmap");
555
556   Window w = RootWindow (d, 0);
557
558 # ifndef USE_IPHONE
559   CFRelease (p->ogl_ctx);
560   [p->ogl_ctx release];
561
562   CFRelease (p->pixmap.gl_pbuffer);
563   [p->pixmap.gl_pbuffer release];
564 # else  // USE_IPHONE
565   glDeleteRenderbuffersOES (1, &p->gl_renderbuffer);
566   glDeleteFramebuffersOES (1, &p->gl_framebuffer);
567 # endif // USE_IPHONE
568
569   if (w->window.current_drawable == p) {
570     w->window.current_drawable = NULL;
571   }
572
573   free (p);
574   return 0;
575 }
576
577 #endif // JWXYZ_GL