From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / jwxyz / jwxyz-cocoa.m
1 /* xscreensaver, Copyright (c) 1991-2017 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 #import "utf8wc.h"
24
25 #include <stdarg.h>
26
27 #ifdef USE_IPHONE
28 # import <OpenGLES/ES1/gl.h>
29 # import <OpenGLES/ES1/glext.h>
30 # define NSOpenGLContext EAGLContext
31 # define NSFont          UIFont
32
33 # define NSFontTraitMask      UIFontDescriptorSymbolicTraits
34 // The values for the flags for NSFontTraitMask and
35 // UIFontDescriptorSymbolicTraits match up, not that it really matters here.
36 # define NSBoldFontMask       UIFontDescriptorTraitBold
37 # define NSFixedPitchFontMask UIFontDescriptorTraitMonoSpace
38 # define NSItalicFontMask     UIFontDescriptorTraitItalic
39 #endif
40
41 #import <CoreText/CTLine.h>
42 #import <CoreText/CTRun.h>
43
44 #define VTBL JWXYZ_VTBL(dpy)
45
46 /* OS X/iOS-specific JWXYZ implementation. */
47
48 void
49 jwxyz_logv (Bool error, const char *fmt, va_list args)
50 {
51   vfprintf (stderr, fmt, args);
52   fputc ('\n', stderr);
53 }
54
55 /* Instead of calling abort(), throw a real exception, so that
56    XScreenSaverView can catch it and display a dialog.
57  */
58 void
59 jwxyz_abort (const char *fmt, ...)
60 {
61   char s[10240];
62   if (!fmt || !*fmt)
63     strcpy (s, "abort");
64   else
65     {
66       va_list args;
67       va_start (args, fmt);
68       vsprintf (s, fmt, args);
69       va_end (args);
70     }
71   [[NSException exceptionWithName: NSInternalInconsistencyException
72                 reason: [NSString stringWithCString: s
73                                   encoding:NSUTF8StringEncoding]
74                 userInfo: nil]
75     raise];
76 # undef abort
77   abort();  // not reached
78 }
79
80
81 const XRectangle *
82 jwxyz_frame (Drawable d)
83 {
84   return &d->frame;
85 }
86
87
88 unsigned int
89 jwxyz_drawable_depth (Drawable d)
90 {
91   return (d->type == WINDOW
92           ? visual_depth (NULL, NULL)
93           : d->pixmap.depth);
94 }
95
96
97 float
98 jwxyz_scale (Window main_window)
99 {
100   float scale = 1;
101
102 # ifdef USE_IPHONE
103   /* Since iOS screens are physically smaller than desktop screens, scale up
104      the fonts to make them more readable.
105
106      Note that X11 apps on iOS also have the backbuffer sized in points
107      instead of pixels, resulting in an effective X11 screen size of 768x1024
108      or so, even if the display has significantly higher resolution.  That is
109      unrelated to this hack, which is really about DPI.
110    */
111   scale = main_window->window.view.hackedContentScaleFactor;
112   if (scale < 1) // iPad Pro magnifies the backbuffer by 3x, which makes text
113     scale = 1;   // excessively blurry in BSOD.
114
115 # else  // !USE_IPHONE
116
117   /* Desktop retina displays also need fonts doubled. */
118   scale = main_window->window.view.hackedContentScaleFactor;
119
120 # endif // !USE_IPHONE
121
122   return scale;
123 }
124
125
126 /* Font metric terminology, as used by X11:
127
128    "lbearing" is the distance from the logical origin to the leftmost pixel.
129    If a character's ink extends to the left of the origin, it is negative.
130
131    "rbearing" is the distance from the logical origin to the rightmost pixel.
132
133    "descent" is the distance from the logical origin to the bottommost pixel.
134    For characters with descenders, it is positive.  For superscripts, it
135    is negative.
136
137    "ascent" is the distance from the logical origin to the topmost pixel.
138    It is the number of pixels above the baseline.
139
140    "width" is the distance from the logical origin to the position where
141    the logical origin of the next character should be placed.
142
143    If "rbearing" is greater than "width", then this character overlaps the
144    following character.  If smaller, then there is trailing blank space.
145  */
146 static void
147 utf8_metrics (Display *dpy, NSFont *nsfont, NSString *nsstr, XCharStruct *cs)
148 {
149   // Returns the metrics of the multi-character, single-line UTF8 string.
150
151   Drawable d = XRootWindow (dpy, 0);
152
153   CGContextRef cgc = d->cgc;
154   NSDictionary *attr =
155     [NSDictionary dictionaryWithObjectsAndKeys:
156                     nsfont, NSFontAttributeName,
157                   nil];
158   NSAttributedString *astr = [[NSAttributedString alloc]
159                                initWithString:nsstr
160                                    attributes:attr];
161   CTLineRef ctline = CTLineCreateWithAttributedString (
162                        (__bridge CFAttributedStringRef) astr);
163   CGContextSetTextPosition (cgc, 0, 0);
164   CGContextSetShouldAntialias (cgc, True);  // #### Guess?
165
166   memset (cs, 0, sizeof(*cs));
167
168   // "CTRun represents set of consecutive glyphs sharing the same
169   // attributes and direction".
170   //
171   // We also get multiple runs any time font subsitution happens:
172   // E.g., if the current font is Verdana-Bold, a &larr; character
173   // in the NSString will actually be rendered in LucidaGrande-Bold.
174   //
175   int count = 0;
176   for (id runid in (NSArray *)CTLineGetGlyphRuns(ctline)) {
177     CTRunRef run = (CTRunRef) runid;
178     CFRange r = { 0, };
179     CGRect bbox = CTRunGetImageBounds (run, cgc, r);
180     CGFloat ascent, descent, leading;
181     CGFloat advancement =
182       CTRunGetTypographicBounds (run, r, &ascent, &descent, &leading);
183
184 # ifndef USE_IPHONE
185     // Only necessary for when LCD smoothing is enabled, which iOS doesn't do.
186     bbox.origin.x    -= 2.0/3.0;
187     bbox.size.width  += 4.0/3.0;
188     bbox.size.height += 1.0/2.0;
189 # endif
190
191     // Create the metrics for this run:
192     XCharStruct cc;
193     cc.ascent   = ceil  (bbox.origin.y + bbox.size.height);
194     cc.descent  = ceil (-bbox.origin.y);
195     cc.lbearing = floor (bbox.origin.x);
196     cc.rbearing = ceil  (bbox.origin.x + bbox.size.width);
197     cc.width    = floor (advancement + 0.5);
198
199     // Add those metrics into the cumulative metrics:
200     if (count == 0)
201       *cs = cc;
202     else
203       {
204         cs->ascent   = MAX (cs->ascent,     cc.ascent);
205         cs->descent  = MAX (cs->descent,    cc.descent);
206         cs->lbearing = MIN (cs->lbearing,   cs->width + cc.lbearing);
207         cs->rbearing = MAX (cs->rbearing,   cs->width + cc.rbearing);
208         cs->width    = MAX (cs->width,      cs->width + cc.width);
209       }
210
211     // Why no y? What about vertical text?
212     // XCharStruct doesn't encapsulate that but XGlyphInfo does.
213
214     count++;
215   }
216
217   [astr release];
218   CFRelease (ctline);
219 }
220
221
222 static NSArray *
223 font_family_members (NSString *family_name)
224 {
225 # ifndef USE_IPHONE
226   return [[NSFontManager sharedFontManager]
227           availableMembersOfFontFamily:family_name];
228 # else
229   return [UIFont fontNamesForFamilyName:family_name];
230 # endif
231 }
232
233
234 const char *
235 jwxyz_default_font_family (int require)
236 {
237   return require & JWXYZ_STYLE_MONOSPACE ? "Courier" : "Verdana";
238 }
239
240
241 static NSFontTraitMask
242 nsfonttraitmask_for (int font_style)
243 {
244   NSFontTraitMask result = 0;
245   if (font_style & JWXYZ_STYLE_BOLD)
246     result |= NSBoldFontMask;
247   if (font_style & JWXYZ_STYLE_ITALIC)
248     result |= NSItalicFontMask;
249   if (font_style & JWXYZ_STYLE_MONOSPACE)
250     result |= NSFixedPitchFontMask;
251   return result;
252 }
253
254
255 static NSFont *
256 try_font (NSFontTraitMask traits, NSFontTraitMask mask,
257           NSString *family_name, float size)
258 {
259   NSArray *family_members = font_family_members (family_name);
260   if (!family_members.count) {
261     family_members = font_family_members (
262       [NSString stringWithUTF8String:jwxyz_default_font_family (
263         traits & NSFixedPitchFontMask ? JWXYZ_STYLE_MONOSPACE : 0)]);
264   }
265
266 # ifndef USE_IPHONE
267   for (unsigned k = 0; k != family_members.count; ++k) {
268
269     NSArray *member = [family_members objectAtIndex:k];
270     NSFontTraitMask font_mask =
271     [(NSNumber *)[member objectAtIndex:3] unsignedIntValue];
272
273     if ((font_mask & mask) == traits) {
274
275       NSString *name = [member objectAtIndex:0];
276       NSFont *f = [NSFont fontWithName:name size:size];
277       if (!f)
278         break;
279
280       /* Don't use this font if it (probably) doesn't include ASCII characters.
281        */
282       NSStringEncoding enc = [f mostCompatibleStringEncoding];
283       if (! (enc == NSUTF8StringEncoding ||
284              enc == NSISOLatin1StringEncoding ||
285              enc == NSNonLossyASCIIStringEncoding ||
286              enc == NSISOLatin2StringEncoding ||
287              enc == NSUnicodeStringEncoding ||
288              enc == NSWindowsCP1250StringEncoding ||
289              enc == NSWindowsCP1252StringEncoding ||
290              enc == NSMacOSRomanStringEncoding)) {
291         // NSLog(@"skipping \"%@\": encoding = %d", name, enc);
292         break;
293       }
294       // NSLog(@"using \"%@\": %d", name, enc);
295
296       return f;
297     }
298   }
299 # else // USE_IPHONE
300
301   // This trick needs iOS 3.1, see "Using SDK-Based Development".
302   Class has_font_descriptor = [UIFontDescriptor class];
303
304   for (NSString *fn in family_members) {
305 # define MATCH(X) \
306        ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \
307        != NSNotFound)
308
309     NSFontTraitMask font_mask;
310     if (has_font_descriptor) {
311       // This only works on iOS 7 and later.
312       font_mask = [[UIFontDescriptor
313                     fontDescriptorWithFontAttributes:
314                     @{UIFontDescriptorNameAttribute:fn}]
315                    symbolicTraits];
316     } else {
317       font_mask = 0;
318       if (MATCH(@"Bold"))
319         font_mask |= NSBoldFontMask;
320       if (MATCH(@"Italic") || MATCH(@"Oblique"))
321         font_mask |= NSItalicFontMask;
322       if (MATCH(@"Courier"))
323         font_mask |= NSFixedPitchFontMask;
324     }
325
326     if ((font_mask & mask) == traits) {
327
328       /* Check if it can do ASCII.  No good way to accomplish this!
329          These are fonts present in iPhone Simulator as of June 2012
330          that don't include ASCII.
331        */
332       if (MATCH(@"AppleGothic") ||      // Korean
333           MATCH(@"Dingbats") ||         // Dingbats
334           MATCH(@"Emoji") ||            // Emoticons
335           MATCH(@"Geeza") ||            // Arabic
336           MATCH(@"Hebrew") ||           // Hebrew
337           MATCH(@"HiraKaku") ||         // Japanese
338           MATCH(@"HiraMin") ||          // Japanese
339           MATCH(@"Kailasa") ||          // Tibetan
340           MATCH(@"Ornaments") ||        // Dingbats
341           MATCH(@"STHeiti")             // Chinese
342        )
343          break;
344
345       return [UIFont fontWithName:fn size:size];
346     }
347 # undef MATCH
348   }
349 # endif
350
351   return NULL;
352 }
353
354
355 /* Returns a random font in the given size and face.
356  */
357 static NSFont *
358 random_font (NSFontTraitMask traits, NSFontTraitMask mask, float size)
359 {
360
361 # ifndef USE_IPHONE
362   // Providing Unbold or Unitalic in the mask for availableFontNamesWithTraits
363   // returns an empty list, at least on a system with default fonts only.
364   NSArray *families = [[NSFontManager sharedFontManager]
365                        availableFontFamilies];
366   if (!families) return 0;
367 # else
368   NSArray *families = [UIFont familyNames];
369
370   // There are many dups in the families array -- uniquify it.
371   {
372     NSArray *sorted_families =
373     [families sortedArrayUsingSelector:@selector(compare:)];
374     NSMutableArray *new_families =
375     [NSMutableArray arrayWithCapacity:sorted_families.count];
376
377     NSString *prev_family = @"";
378     for (NSString *family in sorted_families) {
379       if ([family compare:prev_family])
380         [new_families addObject:family];
381       prev_family = family;
382     }
383
384     families = new_families;
385   }
386 # endif // USE_IPHONE
387
388   long n = [families count];
389   if (n <= 0) return 0;
390
391   int j;
392   for (j = 0; j < n; j++) {
393     int i = random() % n;
394     NSString *family_name = [families objectAtIndex:i];
395
396     NSFont *result = try_font (traits, mask, family_name, size);
397     if (result)
398       return result;
399   }
400
401   // None of the fonts support ASCII?
402   return 0;
403 }
404
405 void *
406 jwxyz_load_native_font (Window main_window, int traits_jwxyz, int mask_jwxyz,
407                         const char *font_name_ptr, size_t font_name_length,
408                         int font_name_type, float size,
409                         char **family_name_ret,
410                         int *ascent_ret, int *descent_ret)
411 {
412   NSFont *nsfont = NULL;
413
414   NSFontTraitMask
415     traits = nsfonttraitmask_for (traits_jwxyz),
416     mask = nsfonttraitmask_for (mask_jwxyz);
417
418   NSString *font_name = font_name_type != JWXYZ_FONT_RANDOM ?
419     [[NSString alloc] initWithBytes:font_name_ptr
420                              length:font_name_length
421                            encoding:NSUTF8StringEncoding] :
422     nil;
423
424   size *= jwxyz_scale (main_window);
425
426   if (font_name_type == JWXYZ_FONT_RANDOM) {
427
428     nsfont = random_font (traits, mask, size);
429     [font_name release];
430
431   } else if (font_name_type == JWXYZ_FONT_FACE) {
432
433     nsfont = [NSFont fontWithName:font_name size:size];
434
435   } else if (font_name_type == JWXYZ_FONT_FAMILY) {
436   
437     Assert (size > 0, "zero font size");
438
439     if (!nsfont)
440       nsfont   = try_font (traits, mask, font_name, size);
441
442     // if that didn't work, turn off attibutes until it does
443     // (e.g., there is no "Monaco-Bold".)
444     //
445     if (!nsfont && (mask & NSItalicFontMask)) {
446       traits &= ~NSItalicFontMask;
447       mask &= ~NSItalicFontMask;
448       nsfont = try_font (traits, mask, font_name, size);
449     }
450     if (!nsfont && (mask & NSBoldFontMask)) {
451       traits &= ~NSBoldFontMask;
452       mask &= ~NSBoldFontMask;
453       nsfont = try_font (traits, mask, font_name, size);
454     }
455     if (!nsfont && (mask & NSFixedPitchFontMask)) {
456       traits &= ~NSFixedPitchFontMask;
457       mask &= ~NSFixedPitchFontMask;
458       nsfont = try_font (traits, mask, font_name, size);
459     }
460   }
461
462   if (nsfont)
463   {
464     if (family_name_ret)
465       *family_name_ret = strdup (nsfont.familyName.UTF8String);
466
467     CFRetain (nsfont);   // needed for garbage collection?
468
469     *ascent_ret  =  ceil ([nsfont ascender]);
470     *descent_ret = -floor ([nsfont descender]);
471
472     Assert([nsfont fontName], "broken NSFont in fid");
473   }
474
475   return nsfont;
476 }
477
478
479 void
480 jwxyz_release_native_font (Display *dpy, void *native_font)
481 {
482   // #### DAMMIT!  I can't tell what's going wrong here, but I keep getting
483   //      crashes in [NSFont ascender] <- query_font, and it seems to go away
484   //      if I never release the nsfont.  So, fuck it, we'll just leak fonts.
485   //      They're probably not very big...
486   //
487   //  [fid->nsfont release];
488   //  CFRelease (fid->nsfont);
489 }
490
491
492 // Given a UTF8 string, return an NSString.  Bogus UTF8 characters are ignored.
493 // We have to do this because stringWithCString returns NULL if there are
494 // any invalid characters at all.
495 //
496 static NSString *
497 sanitize_utf8 (const char *in, size_t in_len, Bool *latin1_pP)
498 {
499   size_t out_len = in_len * 4;   // length of string might increase
500   char *s2 = (char *) malloc (out_len);
501   char *out = s2;
502   const char *in_end  = in  + in_len;
503   const char *out_end = out + out_len;
504   Bool latin1_p = True;
505
506   while (in < in_end)
507     {
508       unsigned long uc;
509       long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc);
510       long L2 = utf8_encode (uc, out, out_end - out);
511       in  += L1;
512       out += L2;
513       if (uc > 255) latin1_p = False;
514     }
515   *out = 0;
516   NSString *nsstr =
517     [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding];
518   free (s2);
519   if (latin1_pP) *latin1_pP = latin1_p;
520   return (nsstr ? nsstr : @"");
521 }
522
523
524 NSString *
525 nsstring_from(const char *str, size_t len, int utf8_p)
526 {
527   Bool latin1_p;
528   NSString *nsstr = utf8_p ?
529     sanitize_utf8 (str, len, &latin1_p) :
530     [[[NSString alloc] initWithBytes:str
531                               length:len
532                             encoding:NSISOLatin1StringEncoding]
533      autorelease];
534   return nsstr;
535 }
536
537 void
538 jwxyz_render_text (Display *dpy, void *native_font,
539                    const char *str, size_t len, Bool utf8_p, Bool antialias_p,
540                    XCharStruct *cs_ret, char **pixmap_ret)
541 {
542   utf8_metrics (dpy, (NSFont *)native_font, nsstring_from (str, len, utf8_p),
543                 cs_ret);
544
545   Assert (!pixmap_ret, "TODO");
546 }
547
548
549 void
550 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
551 {
552 # ifdef USE_IPHONE
553
554   xvpos->x = 0;
555   xvpos->y = 0;
556
557   if (xp) {
558     xp->x = w->window.last_mouse_x;
559     xp->y = w->window.last_mouse_y;
560   }
561
562 # else  // !USE_IPHONE
563
564   NSWindow *nsw = [w->window.view window];
565
566   // get bottom left of window on screen, from bottom left
567
568 # if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6)
569   NSRect rr1 = [w->window.view convertRect: NSMakeRect(0,0,0,0) toView:nil];
570   NSRect rr2 = [nsw convertRectToScreen: rr1];
571   NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
572                               rr2.origin.y - rr1.origin.y);
573 # else
574   // deprecated as of 10.7
575   NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
576 # endif
577
578
579   // get bottom left of view on window, from bottom left
580   NSPoint vpos;
581   vpos.x = vpos.y = 0;
582   vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]];
583
584   // get bottom left of view on screen, from bottom left
585   vpos.x += wpos.x;
586   vpos.y += wpos.y;
587
588   // get top left of view on screen, from bottom left
589   vpos.y += w->frame.height;
590
591   // get top left of view on screen, from top left
592   NSArray *screens = [NSScreen screens];
593   NSScreen *screen = (screens && [screens count] > 0
594                       ? [screens objectAtIndex:0]
595                       : [NSScreen mainScreen]);
596   NSRect srect = [screen frame];
597   vpos.y = srect.size.height - vpos.y;
598
599   xvpos->x = vpos.x;
600   xvpos->y = vpos.y;
601
602   if (xp) {
603     // get the mouse position on window, from bottom left
604     NSEvent *e = [NSApp currentEvent];
605     NSPoint p = [e locationInWindow];
606
607     // get mouse position on screen, from bottom left
608     p.x += wpos.x;
609     p.y += wpos.y;
610
611     // get mouse position on screen, from top left
612     p.y = srect.size.height - p.y;
613
614     xp->x = (int) p.x;
615     xp->y = (int) p.y;
616   }
617
618 # endif // !USE_IPHONE
619 }
620
621
622 #ifdef USE_IPHONE
623
624 void
625 check_framebuffer_status (void)
626 {
627   int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
628   switch (err) {
629   case GL_FRAMEBUFFER_COMPLETE_OES:
630     break;
631   case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
632     Assert (0, "framebuffer incomplete attachment");
633     break;
634   case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
635     Assert (0, "framebuffer incomplete missing attachment");
636     break;
637   case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
638     Assert (0, "framebuffer incomplete dimensions");
639     break;
640   case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
641     Assert (0, "framebuffer incomplete formats");
642     break;
643   case GL_FRAMEBUFFER_UNSUPPORTED_OES:
644     Assert (0, "framebuffer unsupported");
645     break;
646 /*
647   case GL_FRAMEBUFFER_UNDEFINED:
648     Assert (0, "framebuffer undefined");
649     break;
650   case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
651     Assert (0, "framebuffer incomplete draw buffer");
652     break;
653   case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
654     Assert (0, "framebuffer incomplete read buffer");
655     break;
656   case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
657     Assert (0, "framebuffer incomplete multisample");
658     break;
659   case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
660     Assert (0, "framebuffer incomplete layer targets");
661     break;
662  */
663   default:
664     NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
665     break;
666   }
667 }
668
669
670 void
671 create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
672 {
673   glGenFramebuffersOES  (1, gl_framebuffer);
674   glBindFramebufferOES  (GL_FRAMEBUFFER_OES,  *gl_framebuffer);
675
676   glGenRenderbuffersOES (1, gl_renderbuffer);
677   glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
678 }
679
680 #endif // USE_IPHONE
681
682
683 #if defined JWXYZ_QUARTZ
684
685 /* Pushes a GC context; sets Fill and Stroke colors to the background color.
686  */
687 static void
688 push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
689 {
690   XGCValues *gcv = VTBL->gc_gcv (gc);
691   push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
692 }
693
694
695 void
696 jwxyz_quartz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
697                         int src_x, int src_y,
698                         unsigned int width, unsigned int height,
699                         int dst_x, int dst_y)
700 {
701   XRectangle src_frame = src->frame, dst_frame = dst->frame;
702   XGCValues *gcv = VTBL->gc_gcv (gc);
703
704   BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
705
706
707   /* If we're copying from a bitmap to a bitmap, and there's nothing funny
708      going on with clipping masks or depths or anything, optimize it by
709      just doing a memcpy instead of going through a CGI.
710    */
711   if (gcv->function == GXcopy &&
712       !gcv->clip_mask &&
713       jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
714
715     Assert(!src_frame.x &&
716            !src_frame.y &&
717            !dst_frame.x &&
718            !dst_frame.y,
719            "unexpected non-zero origin");
720
721     jwxyz_blit (CGBitmapContextGetData (src->cgc),
722                 CGBitmapContextGetBytesPerRow (src->cgc), src_x, src_y,
723                 CGBitmapContextGetData (dst->cgc),
724                 CGBitmapContextGetBytesPerRow (dst->cgc), dst_x, dst_y,
725                 width, height);
726
727   } else {
728     CGRect
729     src_rect = CGRectMake(src_x, src_y, width, height),
730     dst_rect = CGRectMake(dst_x, dst_y, width, height);
731
732     src_rect.origin = map_point (src, src_rect.origin.x,
733                                  src_rect.origin.y + src_rect.size.height);
734     dst_rect.origin = map_point (dst, dst_rect.origin.x,
735                                  dst_rect.origin.y + src_rect.size.height);
736
737     NSObject *releaseme = 0;
738     CGImageRef cgi;
739     BOOL free_cgi_p = NO;
740
741     // We must first copy the bits to an intermediary CGImage object, then
742     // copy that to the destination drawable's CGContext.
743     //
744     // First we get a CGImage out of the pixmap CGContext -- it's the whole
745     // pixmap, but it presumably shares the data pointer instead of copying
746     // it.  We then cache that CGImage it inside the Pixmap object.  Note:
747     // invalidate_drawable_cache() must be called to discard this any time a
748     // modification is made to the pixmap, or we'll end up re-using old bits.
749     //
750     if (!src->cgi)
751       src->cgi = CGBitmapContextCreateImage (src->cgc);
752     cgi = src->cgi;
753
754     // if doing a sub-rect, trim it down.
755     if (src_rect.origin.x    != src_frame.x   ||
756         src_rect.origin.y    != src_frame.y   ||
757         src_rect.size.width  != src_frame.width ||
758         src_rect.size.height != src_frame.height) {
759       // #### I don't understand why this is needed...
760       src_rect.origin.y = (src_frame.height -
761                            src_rect.size.height - src_rect.origin.y);
762       // This does not copy image data, so it should be fast.
763       cgi = CGImageCreateWithImageInRect (cgi, src_rect);
764       free_cgi_p = YES;
765     }
766
767     CGContextRef cgc = dst->cgc;
768
769     if (mask_p) {               // src depth == 1
770
771       push_bg_gc (dpy, dst, gc, YES);
772
773       // fill the destination rectangle with solid background...
774       CGContextFillRect (cgc, dst_rect);
775
776       Assert (cgc, "no CGC with 1-bit XCopyArea");
777
778       // then fill in a solid rectangle of the fg color, using the image as an
779       // alpha mask.  (the image has only values of BlackPixel or WhitePixel.)
780       set_color (dpy, cgc, gcv->foreground, VTBL->gc_depth (gc),
781                  gcv->alpha_allowed_p, YES);
782       CGContextClipToMask (cgc, dst_rect, cgi);
783       CGContextFillRect (cgc, dst_rect);
784
785       pop_gc (dst, gc);
786
787     } else {            // src depth > 1
788
789       push_gc (dst, gc);
790
791       // copy the CGImage onto the destination CGContext
792       //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
793       CGContextDrawImage (cgc, dst_rect, cgi);
794
795       pop_gc (dst, gc);
796     }
797
798     if (free_cgi_p) CGImageRelease (cgi);
799     
800     if (releaseme) [releaseme release];
801   }
802   
803   invalidate_drawable_cache (dst);
804 }
805
806 #elif defined JWXYZ_GL
807
808 /* Warning! The JWXYZ_GL code here is experimental and provisional and not at
809    all ready for prime time. Please be careful.
810  */
811
812 void
813 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
814                  int src_x, int src_y,
815                  unsigned int width, unsigned int height,
816                  int dst_x, int dst_y)
817 {
818   /* TODO:
819    - glCopyPixels if src == dst
820    - Pixel buffer copying
821    - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
822      which is like a blit.
823    */
824
825   /* Strange and ugly flickering when going the glCopyTexImage2D route on
826      OS X. (Early 2009 Mac mini, OS X 10.10)
827    */
828 # ifdef USE_IPHONE
829
830   /* TODO: This might not still work. */
831   jwxyz_bind_drawable (dpy, dpy->main_window, src);
832   jwxyz_gl_copy_area_read_tex_image (dpy, jwxyz_frame (src)->height,
833                                      src_x, src_y, width, height, dst_x, dst_y);
834   jwxyz_bind_drawable (dpy, dpy->main_window, dst);
835   jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
836                                       width, height, dst_x, dst_y);
837 # else // !USE_IPHONE
838   jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc,
839                                   src_x, src_y, width, height, dst_x, dst_y);
840 # endif // !USE_IPHONE
841   jwxyz_assert_gl ();
842 }
843
844
845 void
846 jwxyz_assert_gl ()
847 {
848   // This is like check_gl_error, except this happens for debug builds only.
849 #ifndef __OPTIMIZE__
850   if([NSOpenGLContext currentContext])
851   {
852     // glFinish here drops FPS into the toilet. It might need to be on if
853     // something goes wrong.
854     // glFinish();
855     GLenum error = glGetError();
856     Assert (!error, "jwxyz_assert_gl: OpenGL error");
857   }
858 #endif // !__OPTIMIZE__
859 }
860
861 void
862 jwxyz_assert_drawable (Window main_window, Drawable d)
863 {
864 #if !defined USE_IPHONE && !defined __OPTIMIZE__
865   XScreenSaverView *view = main_window->window.view;
866   NSOpenGLContext *ogl_ctx = [view oglContext];
867
868   if (d->type == WINDOW) {
869     Assert([ogl_ctx view] == view,
870            "jwxyz_assert_display: ogl_ctx view not set!");
871   }
872
873   @try {
874     /* Assert([d->ctx isKindOfClass:[NSOpenGLContext class]], "Not a context."); */
875     Class c = [ogl_ctx class];
876     Assert([c isSubclassOfClass:[NSOpenGLContext class]], "Not a context.");
877     // [d->ctx makeCurrentContext];
878   }
879   @catch (NSException *exception) {
880     perror([[exception reason] UTF8String]);
881     jwxyz_abort();
882   }
883 #endif // !USE_IPHONE && !__OPTIMIZE__
884 }
885
886
887 void
888 jwxyz_bind_drawable (Window main_window, Drawable d)
889 {
890   /* Windows and Pixmaps need to use different contexts with OpenGL
891      screenhacks, because an OpenGL screenhack sets state in an arbitrary
892      fashion, but jwxyz-gl.c has its own ideas w.r.t. OpenGL state.
893
894      On iOS, all pixmaps can use the same context with different FBOs. Which
895      is nice.
896    */
897
898   /* OpenGL screenhacks in general won't be drawing on the Window, but they
899      can and will draw on a Pixmap -- but an OpenGL call following an Xlib
900      call won't be able to fix the fact that it's drawing offscreen.
901    */
902
903   /* EXT_direct_state_access might be appropriate, but it's apparently not
904      available on Mac OS X.
905    */
906
907   // jwxyz_assert_display (dpy);
908   jwxyz_assert_drawable (main_window, main_window);
909   jwxyz_assert_gl ();
910   jwxyz_assert_drawable (main_window, d);
911
912 #if defined USE_IPHONE && !defined __OPTIMIZE__
913   Drawable current_drawable = main_window->window.current_drawable;
914   Assert (!current_drawable
915             || current_drawable->ogl_ctx == [EAGLContext currentContext],
916           "bind_drawable: Wrong context.");
917   if (current_drawable) {
918     GLint framebuffer;
919     glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
920     Assert (framebuffer == current_drawable->gl_framebuffer,
921             "bind_drawable: Wrong framebuffer.");
922   }
923 #endif
924
925   if (main_window->window.current_drawable != d) {
926     main_window->window.current_drawable = d;
927
928     /* Doing this repeatedly is probably not OK performance-wise. Probably. */
929 #ifndef USE_IPHONE
930     [d->ogl_ctx makeCurrentContext];
931 #else
932     [EAGLContext setCurrentContext:d->ogl_ctx];
933     glBindFramebufferOES(GL_FRAMEBUFFER_OES, d->gl_framebuffer);
934     if (d->type == PIXMAP) {
935       glViewport (0, 0, d->frame.width, d->frame.height);
936       jwxyz_set_matrices (d->frame.width, d->frame.height);
937     }
938 #endif
939   }
940
941   jwxyz_assert_gl ();
942 }
943
944
945 Pixmap
946 XCreatePixmap (Display *dpy, Drawable d,
947                unsigned int width, unsigned int height, unsigned int depth)
948 {
949   Pixmap p = (Pixmap) calloc (1, sizeof(*p));
950   p->type = PIXMAP;
951   p->frame.width  = width;
952   p->frame.height = height;
953   p->pixmap.depth = depth;
954
955   Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
956
957   /* TODO: If Pixel buffers are not supported, do something about it. */
958   Window w = XRootWindow (dpy, 0);
959
960 # ifndef USE_IPHONE
961
962   p->ogl_ctx = [[NSOpenGLContext alloc]
963                 initWithFormat:w->window.pixfmt
964                 shareContext:w->ogl_ctx];
965   CFRetain (p->ogl_ctx);
966
967   [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
968
969   p->pixmap.gl_pbuffer = [[NSOpenGLPixelBuffer alloc]
970                           /* TODO: Only if there are rectangluar textures. */
971                           initWithTextureTarget:GL_TEXTURE_RECTANGLE_EXT
972                           /* TODO: Make sure GL_RGBA isn't better. */
973                           textureInternalFormat:GL_RGB
974                           textureMaxMipMapLevel:0
975                           pixelsWide:width
976                           pixelsHigh:height];
977   CFRetain (p->pixmap.gl_pbuffer);
978
979   [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
980                  cubeMapFace:0
981                  mipMapLevel:0
982         currentVirtualScreen:w->window.virtual_screen];
983
984 # else // USE_IPHONE
985
986   p->ogl_ctx = w->window.ogl_ctx_pixmap;
987
988   [EAGLContext setCurrentContext:p->ogl_ctx];
989   create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
990
991   glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height);
992   glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
993                                 GL_RENDERBUFFER_OES, p->gl_renderbuffer);
994
995   check_framebuffer_status ();
996
997   glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
998
999 # endif // USE_IPHONE
1000
1001   w->window.current_drawable = p;
1002   glViewport (0, 0, width, height);
1003   jwxyz_set_matrices (width, height);
1004
1005 # ifndef __OPTIMIZE__
1006   glClearColor (frand(1), frand(1), frand(1), 0);
1007   glClear (GL_COLOR_BUFFER_BIT);
1008 # endif
1009
1010   return p;
1011 }
1012
1013 int
1014 XFreePixmap (Display *d, Pixmap p)
1015 {
1016   Assert (p && p->type == PIXMAP, "not a pixmap");
1017
1018   Window w = RootWindow (d, 0);
1019
1020 # ifndef USE_IPHONE
1021   CFRelease (p->ogl_ctx);
1022   [p->ogl_ctx release];
1023
1024   CFRelease (p->pixmap.gl_pbuffer);
1025   [p->pixmap.gl_pbuffer release];
1026 # else  // USE_IPHONE
1027   glDeleteRenderbuffersOES (1, &p->gl_renderbuffer);
1028   glDeleteFramebuffersOES (1, &p->gl_framebuffer);
1029 # endif // USE_IPHONE
1030
1031   if (w->window.current_drawable == p) {
1032     w->window.current_drawable = NULL;
1033   }
1034
1035   free (p);
1036   return 0;
1037 }
1038
1039 #endif // JWXYZ_GL