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