9476d2a0dcefe1c9e16bef123766e176cb17c8a2
[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
572   NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
573                               rr2.origin.y - rr1.origin.y);
574 # else
575   // deprecated as of 10.7
576   NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
577 # endif
578
579
580   // get bottom left of view on window, from bottom left
581   NSPoint vpos;
582   vpos.x = vpos.y = 0;
583   vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]];
584
585   // get bottom left of view on screen, from bottom left
586   vpos.x += wpos.x;
587   vpos.y += wpos.y;
588
589   // get top left of view on screen, from bottom left
590   double s = [w->window.view hackedContentScaleFactor];
591   vpos.y += w->frame.height / s;
592
593   // get top left of view on screen, from top left
594   NSArray *screens = [NSScreen screens];
595   NSScreen *screen = (screens && [screens count] > 0
596                       ? [screens objectAtIndex:0]
597                       : [NSScreen mainScreen]);
598   NSRect srect = [screen frame];
599   vpos.y = srect.size.height - vpos.y;
600
601   xvpos->x = vpos.x;
602   xvpos->y = vpos.y;
603
604   if (xp) {
605     // get the mouse position on window, from bottom left
606     NSEvent *e = [NSApp currentEvent];
607     NSPoint p = [e locationInWindow];
608
609     // get mouse position on screen, from bottom left
610     p.x += wpos.x;
611     p.y += wpos.y;
612
613     // get mouse position on screen, from top left
614     p.y = srect.size.height - p.y;
615
616     xp->x = (int) p.x;
617     xp->y = (int) p.y;
618   }
619
620 # endif // !USE_IPHONE
621 }
622
623
624 #ifdef USE_IPHONE
625
626 void
627 check_framebuffer_status (void)
628 {
629   int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
630   switch (err) {
631   case GL_FRAMEBUFFER_COMPLETE_OES:
632     break;
633   case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
634     Assert (0, "framebuffer incomplete attachment");
635     break;
636   case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
637     Assert (0, "framebuffer incomplete missing attachment");
638     break;
639   case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
640     Assert (0, "framebuffer incomplete dimensions");
641     break;
642   case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
643     Assert (0, "framebuffer incomplete formats");
644     break;
645   case GL_FRAMEBUFFER_UNSUPPORTED_OES:
646     Assert (0, "framebuffer unsupported");
647     break;
648 /*
649   case GL_FRAMEBUFFER_UNDEFINED:
650     Assert (0, "framebuffer undefined");
651     break;
652   case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
653     Assert (0, "framebuffer incomplete draw buffer");
654     break;
655   case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
656     Assert (0, "framebuffer incomplete read buffer");
657     break;
658   case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
659     Assert (0, "framebuffer incomplete multisample");
660     break;
661   case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
662     Assert (0, "framebuffer incomplete layer targets");
663     break;
664  */
665   default:
666     NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
667     break;
668   }
669 }
670
671
672 void
673 create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
674 {
675   glGenFramebuffersOES  (1, gl_framebuffer);
676   glBindFramebufferOES  (GL_FRAMEBUFFER_OES,  *gl_framebuffer);
677
678   glGenRenderbuffersOES (1, gl_renderbuffer);
679   glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
680 }
681
682 #endif // USE_IPHONE
683
684
685 #if defined JWXYZ_QUARTZ
686
687 /* Pushes a GC context; sets Fill and Stroke colors to the background color.
688  */
689 static void
690 push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
691 {
692   XGCValues *gcv = VTBL->gc_gcv (gc);
693   push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
694 }
695
696
697 void
698 jwxyz_quartz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
699                         int src_x, int src_y,
700                         unsigned int width, unsigned int height,
701                         int dst_x, int dst_y)
702 {
703   XRectangle src_frame = src->frame, dst_frame = dst->frame;
704   XGCValues *gcv = VTBL->gc_gcv (gc);
705
706   BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
707
708
709   /* If we're copying from a bitmap to a bitmap, and there's nothing funny
710      going on with clipping masks or depths or anything, optimize it by
711      just doing a memcpy instead of going through a CGI.
712    */
713   if (gcv->function == GXcopy &&
714       !gcv->clip_mask &&
715       jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
716
717     Assert(!src_frame.x &&
718            !src_frame.y &&
719            !dst_frame.x &&
720            !dst_frame.y,
721            "unexpected non-zero origin");
722
723     jwxyz_blit (CGBitmapContextGetData (src->cgc),
724                 CGBitmapContextGetBytesPerRow (src->cgc), src_x, src_y,
725                 CGBitmapContextGetData (dst->cgc),
726                 CGBitmapContextGetBytesPerRow (dst->cgc), dst_x, dst_y,
727                 width, height);
728
729   } else {
730     CGRect
731     src_rect = CGRectMake(src_x, src_y, width, height),
732     dst_rect = CGRectMake(dst_x, dst_y, width, height);
733
734     src_rect.origin = map_point (src, src_rect.origin.x,
735                                  src_rect.origin.y + src_rect.size.height);
736     dst_rect.origin = map_point (dst, dst_rect.origin.x,
737                                  dst_rect.origin.y + src_rect.size.height);
738
739     NSObject *releaseme = 0;
740     CGImageRef cgi;
741     BOOL free_cgi_p = NO;
742
743     // We must first copy the bits to an intermediary CGImage object, then
744     // copy that to the destination drawable's CGContext.
745     //
746     // First we get a CGImage out of the pixmap CGContext -- it's the whole
747     // pixmap, but it presumably shares the data pointer instead of copying
748     // it.  We then cache that CGImage it inside the Pixmap object.  Note:
749     // invalidate_drawable_cache() must be called to discard this any time a
750     // modification is made to the pixmap, or we'll end up re-using old bits.
751     //
752     if (!src->cgi)
753       src->cgi = CGBitmapContextCreateImage (src->cgc);
754     cgi = src->cgi;
755
756     // if doing a sub-rect, trim it down.
757     if (src_rect.origin.x    != src_frame.x   ||
758         src_rect.origin.y    != src_frame.y   ||
759         src_rect.size.width  != src_frame.width ||
760         src_rect.size.height != src_frame.height) {
761       // #### I don't understand why this is needed...
762       src_rect.origin.y = (src_frame.height -
763                            src_rect.size.height - src_rect.origin.y);
764       // This does not copy image data, so it should be fast.
765       cgi = CGImageCreateWithImageInRect (cgi, src_rect);
766       free_cgi_p = YES;
767     }
768
769     CGContextRef cgc = dst->cgc;
770
771     if (mask_p) {               // src depth == 1
772
773       push_bg_gc (dpy, dst, gc, YES);
774
775       // fill the destination rectangle with solid background...
776       CGContextFillRect (cgc, dst_rect);
777
778       Assert (cgc, "no CGC with 1-bit XCopyArea");
779
780       // then fill in a solid rectangle of the fg color, using the image as an
781       // alpha mask.  (the image has only values of BlackPixel or WhitePixel.)
782       set_color (dpy, cgc, gcv->foreground, VTBL->gc_depth (gc),
783                  gcv->alpha_allowed_p, YES);
784       CGContextClipToMask (cgc, dst_rect, cgi);
785       CGContextFillRect (cgc, dst_rect);
786
787       pop_gc (dst, gc);
788
789     } else {            // src depth > 1
790
791       push_gc (dst, gc);
792
793       // copy the CGImage onto the destination CGContext
794       //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
795       CGContextDrawImage (cgc, dst_rect, cgi);
796
797       pop_gc (dst, gc);
798     }
799
800     if (free_cgi_p) CGImageRelease (cgi);
801     
802     if (releaseme) [releaseme release];
803   }
804   
805   invalidate_drawable_cache (dst);
806 }
807
808 #elif defined JWXYZ_GL
809
810 /* Warning! The JWXYZ_GL code here is experimental and provisional and not at
811    all ready for prime time. Please be careful.
812  */
813
814 void
815 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
816                  int src_x, int src_y,
817                  unsigned int width, unsigned int height,
818                  int dst_x, int dst_y)
819 {
820   /* TODO:
821    - glCopyPixels if src == dst
822    - Pixel buffer copying
823    - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
824      which is like a blit.
825    */
826
827   /* Strange and ugly flickering when going the glCopyTexImage2D route on
828      OS X. (Early 2009 Mac mini, OS X 10.10)
829    */
830 # ifdef USE_IPHONE
831
832   /* TODO: This might not still work. */
833   jwxyz_bind_drawable (dpy, dpy->main_window, src);
834   jwxyz_gl_copy_area_read_tex_image (dpy, jwxyz_frame (src)->height,
835                                      src_x, src_y, width, height, dst_x, dst_y);
836   jwxyz_bind_drawable (dpy, dpy->main_window, dst);
837   jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
838                                       width, height, dst_x, dst_y);
839 # else // !USE_IPHONE
840   jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc,
841                                   src_x, src_y, width, height, dst_x, dst_y);
842 # endif // !USE_IPHONE
843   jwxyz_assert_gl ();
844 }
845
846
847 void
848 jwxyz_assert_gl ()
849 {
850   // This is like check_gl_error, except this happens for debug builds only.
851 #ifndef __OPTIMIZE__
852   if([NSOpenGLContext currentContext])
853   {
854     // glFinish here drops FPS into the toilet. It might need to be on if
855     // something goes wrong.
856     // glFinish();
857     GLenum error = glGetError();
858     Assert (!error, "jwxyz_assert_gl: OpenGL error");
859   }
860 #endif // !__OPTIMIZE__
861 }
862
863 void
864 jwxyz_assert_drawable (Window main_window, Drawable d)
865 {
866 #if !defined USE_IPHONE && !defined __OPTIMIZE__
867   XScreenSaverView *view = main_window->window.view;
868   NSOpenGLContext *ogl_ctx = [view oglContext];
869
870   if (d->type == WINDOW) {
871     Assert([ogl_ctx view] == view,
872            "jwxyz_assert_display: ogl_ctx view not set!");
873   }
874
875   @try {
876     /* Assert([d->ctx isKindOfClass:[NSOpenGLContext class]], "Not a context."); */
877     Class c = [ogl_ctx class];
878     Assert([c isSubclassOfClass:[NSOpenGLContext class]], "Not a context.");
879     // [d->ctx makeCurrentContext];
880   }
881   @catch (NSException *exception) {
882     perror([[exception reason] UTF8String]);
883     jwxyz_abort();
884   }
885 #endif // !USE_IPHONE && !__OPTIMIZE__
886 }
887
888
889 void
890 jwxyz_bind_drawable (Window main_window, Drawable d)
891 {
892   /* Windows and Pixmaps need to use different contexts with OpenGL
893      screenhacks, because an OpenGL screenhack sets state in an arbitrary
894      fashion, but jwxyz-gl.c has its own ideas w.r.t. OpenGL state.
895
896      On iOS, all pixmaps can use the same context with different FBOs. Which
897      is nice.
898    */
899
900   /* OpenGL screenhacks in general won't be drawing on the Window, but they
901      can and will draw on a Pixmap -- but an OpenGL call following an Xlib
902      call won't be able to fix the fact that it's drawing offscreen.
903    */
904
905   /* EXT_direct_state_access might be appropriate, but it's apparently not
906      available on Mac OS X.
907    */
908
909   // jwxyz_assert_display (dpy);
910   jwxyz_assert_drawable (main_window, main_window);
911   jwxyz_assert_gl ();
912   jwxyz_assert_drawable (main_window, d);
913
914 #if defined USE_IPHONE && !defined __OPTIMIZE__
915   Drawable current_drawable = main_window->window.current_drawable;
916   Assert (!current_drawable
917             || current_drawable->ogl_ctx == [EAGLContext currentContext],
918           "bind_drawable: Wrong context.");
919   if (current_drawable) {
920     GLint framebuffer;
921     glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
922     Assert (framebuffer == current_drawable->gl_framebuffer,
923             "bind_drawable: Wrong framebuffer.");
924   }
925 #endif
926
927   if (main_window->window.current_drawable != d) {
928     main_window->window.current_drawable = d;
929
930     /* Doing this repeatedly is probably not OK performance-wise. Probably. */
931 #ifndef USE_IPHONE
932     [d->ogl_ctx makeCurrentContext];
933 #else
934     [EAGLContext setCurrentContext:d->ogl_ctx];
935     glBindFramebufferOES(GL_FRAMEBUFFER_OES, d->gl_framebuffer);
936     if (d->type == PIXMAP) {
937       glViewport (0, 0, d->frame.width, d->frame.height);
938       jwxyz_set_matrices (d->frame.width, d->frame.height);
939     }
940 #endif
941   }
942
943   jwxyz_assert_gl ();
944 }
945
946
947 Pixmap
948 XCreatePixmap (Display *dpy, Drawable d,
949                unsigned int width, unsigned int height, unsigned int depth)
950 {
951   Pixmap p = (Pixmap) calloc (1, sizeof(*p));
952   p->type = PIXMAP;
953   p->frame.width  = width;
954   p->frame.height = height;
955   p->pixmap.depth = depth;
956
957   Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
958
959   /* TODO: If Pixel buffers are not supported, do something about it. */
960   Window w = XRootWindow (dpy, 0);
961
962 # ifndef USE_IPHONE
963
964   p->ogl_ctx = [[NSOpenGLContext alloc]
965                 initWithFormat:w->window.pixfmt
966                 shareContext:w->ogl_ctx];
967   CFRetain (p->ogl_ctx);
968
969   [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
970
971   p->pixmap.gl_pbuffer = [[NSOpenGLPixelBuffer alloc]
972                           /* TODO: Only if there are rectangluar textures. */
973                           initWithTextureTarget:GL_TEXTURE_RECTANGLE_EXT
974                           /* TODO: Make sure GL_RGBA isn't better. */
975                           textureInternalFormat:GL_RGB
976                           textureMaxMipMapLevel:0
977                           pixelsWide:width
978                           pixelsHigh:height];
979   CFRetain (p->pixmap.gl_pbuffer);
980
981   [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
982                  cubeMapFace:0
983                  mipMapLevel:0
984         currentVirtualScreen:w->window.virtual_screen];
985
986 # else // USE_IPHONE
987
988   p->ogl_ctx = w->window.ogl_ctx_pixmap;
989
990   [EAGLContext setCurrentContext:p->ogl_ctx];
991   create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
992
993   glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height);
994   glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
995                                 GL_RENDERBUFFER_OES, p->gl_renderbuffer);
996
997   check_framebuffer_status ();
998
999   glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
1000
1001 # endif // USE_IPHONE
1002
1003   w->window.current_drawable = p;
1004   glViewport (0, 0, width, height);
1005   jwxyz_set_matrices (width, height);
1006
1007 # ifndef __OPTIMIZE__
1008   glClearColor (frand(1), frand(1), frand(1), 0);
1009   glClear (GL_COLOR_BUFFER_BIT);
1010 # endif
1011
1012   return p;
1013 }
1014
1015 int
1016 XFreePixmap (Display *d, Pixmap p)
1017 {
1018   Assert (p && p->type == PIXMAP, "not a pixmap");
1019
1020   Window w = RootWindow (d, 0);
1021
1022 # ifndef USE_IPHONE
1023   CFRelease (p->ogl_ctx);
1024   [p->ogl_ctx release];
1025
1026   CFRelease (p->pixmap.gl_pbuffer);
1027   [p->pixmap.gl_pbuffer release];
1028 # else  // USE_IPHONE
1029   glDeleteRenderbuffersOES (1, &p->gl_renderbuffer);
1030   glDeleteFramebuffersOES (1, &p->gl_framebuffer);
1031 # endif // USE_IPHONE
1032
1033   if (w->window.current_drawable == p) {
1034     w->window.current_drawable = NULL;
1035   }
1036
1037   free (p);
1038   return 0;
1039 }
1040
1041 #endif // JWXYZ_GL