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