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