1 /* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org>
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
14 Cocoa glue for macOS and iOS: fonts and such.
16 See the comment at the top of jwxyz-common.c for an explanation of
17 the division of labor between these various modules.
21 #import "jwxyz-cocoa.h"
27 # import <OpenGLES/ES1/gl.h>
28 # import <OpenGLES/ES1/glext.h>
29 # define NSOpenGLContext EAGLContext
30 # define NSFont UIFont
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
40 #import <CoreText/CTLine.h>
41 #import <CoreText/CTRun.h>
43 #define VTBL JWXYZ_VTBL(dpy)
45 /* OS X/iOS-specific JWXYZ implementation. */
48 jwxyz_logv (Bool error, const char *fmt, va_list args)
50 vfprintf (stderr, fmt, args);
54 /* Instead of calling abort(), throw a real exception, so that
55 XScreenSaverView can catch it and display a dialog.
58 jwxyz_abort (const char *fmt, ...)
67 vsprintf (s, fmt, args);
70 [[NSException exceptionWithName: NSInternalInconsistencyException
71 reason: [NSString stringWithCString: s
72 encoding:NSUTF8StringEncoding]
76 abort(); // not reached
81 jwxyz_frame (Drawable d)
88 jwxyz_drawable_depth (Drawable d)
90 return (d->type == WINDOW
91 ? visual_depth (NULL, NULL)
97 jwxyz_scale_1 (Window main_window, BOOL fonts_p)
102 /* Since iOS screens are physically smaller than desktop screens, scale up
103 the fonts to make them more readable.
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.
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.
114 # else // !HAVE_IPHONE
116 /* Desktop retina displays also need fonts doubled. */
117 scale = [main_window->window.view hackedContentScaleFactor:fonts_p];
119 # endif // !HAVE_IPHONE
125 jwxyz_scale (Window main_window)
127 return jwxyz_scale_1 (main_window, FALSE);
131 jwxyz_font_scale (Window main_window)
133 return jwxyz_scale_1 (main_window, TRUE);
137 /* Font metric terminology, as used by X11:
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.
142 "rbearing" is the distance from the logical origin to the rightmost pixel.
144 "descent" is the distance from the logical origin to the bottommost pixel.
145 For characters with descenders, it is positive. For superscripts, it
148 "ascent" is the distance from the logical origin to the topmost pixel.
149 It is the number of pixels above the baseline.
151 "width" is the distance from the logical origin to the position where
152 the logical origin of the next character should be placed.
154 If "rbearing" is greater than "width", then this character overlaps the
155 following character. If smaller, then there is trailing blank space.
158 utf8_metrics (Display *dpy, NSFont *nsfont, NSString *nsstr, XCharStruct *cs)
160 // Returns the metrics of the multi-character, single-line UTF8 string.
162 Drawable d = XRootWindow (dpy, 0);
164 CGContextRef cgc = d->cgc;
166 [NSDictionary dictionaryWithObjectsAndKeys:
167 nsfont, NSFontAttributeName,
169 NSAttributedString *astr = [[NSAttributedString alloc]
172 CTLineRef ctline = CTLineCreateWithAttributedString (
173 (__bridge CFAttributedStringRef) astr);
174 CGContextSetTextPosition (cgc, 0, 0);
175 CGContextSetShouldAntialias (cgc, True); // #### Guess?
177 memset (cs, 0, sizeof(*cs));
179 // "CTRun represents set of consecutive glyphs sharing the same
180 // attributes and direction".
182 // We also get multiple runs any time font subsitution happens:
183 // E.g., if the current font is Verdana-Bold, a ← character
184 // in the NSString will actually be rendered in LucidaGrande-Bold.
187 for (id runid in (NSArray *)CTLineGetGlyphRuns(ctline)) {
188 CTRunRef run = (CTRunRef) runid;
190 CGRect bbox = CTRunGetImageBounds (run, cgc, r);
191 CGFloat ascent, descent, leading;
192 CGFloat advancement =
193 CTRunGetTypographicBounds (run, r, &ascent, &descent, &leading);
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;
202 // Create the metrics for this run:
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);
210 // Add those metrics into the cumulative metrics:
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);
222 // Why no y? What about vertical text?
223 // XCharStruct doesn't encapsulate that but XGlyphInfo does.
234 font_family_members (NSString *family_name)
237 return [[NSFontManager sharedFontManager]
238 availableMembersOfFontFamily:family_name];
240 return [UIFont fontNamesForFamilyName:family_name];
246 jwxyz_default_font_family (int require)
248 return require & JWXYZ_STYLE_MONOSPACE ? "Courier" : "Verdana";
252 static NSFontTraitMask
253 nsfonttraitmask_for (int font_style)
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;
267 try_font (NSFontTraitMask traits, NSFontTraitMask mask,
268 NSString *family_name, float size)
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)]);
278 for (unsigned k = 0; k != family_members.count; ++k) {
280 NSArray *member = [family_members objectAtIndex:k];
281 NSFontTraitMask font_mask =
282 [(NSNumber *)[member objectAtIndex:3] unsignedIntValue];
284 if ((font_mask & mask) == traits) {
286 NSString *name = [member objectAtIndex:0];
287 NSFont *f = [NSFont fontWithName:name size:size];
291 /* Don't use this font if it (probably) doesn't include ASCII characters.
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);
305 // NSLog(@"using \"%@\": %d", name, enc);
310 # else // HAVE_IPHONE
312 // This trick needs iOS 3.1, see "Using SDK-Based Development".
313 Class has_font_descriptor = [UIFontDescriptor class];
315 for (NSString *fn in family_members) {
317 ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \
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}]
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;
337 if ((font_mask & mask) == traits) {
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.
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
356 return [UIFont fontWithName:fn size:size];
366 /* Returns a random font in the given size and face.
369 random_font (NSFontTraitMask traits, NSFontTraitMask mask, float size)
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;
379 NSArray *families = [UIFont familyNames];
381 // There are many dups in the families array -- uniquify it.
383 NSArray *sorted_families =
384 [families sortedArrayUsingSelector:@selector(compare:)];
385 NSMutableArray *new_families =
386 [NSMutableArray arrayWithCapacity:sorted_families.count];
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;
395 families = new_families;
397 # endif // HAVE_IPHONE
399 long n = [families count];
400 if (n <= 0) return 0;
403 for (j = 0; j < n; j++) {
404 int i = random() % n;
405 NSString *family_name = [families objectAtIndex:i];
407 NSFont *result = try_font (traits, mask, family_name, size);
412 // None of the fonts support ASCII?
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)
423 NSFont *nsfont = NULL;
426 traits = nsfonttraitmask_for (traits_jwxyz),
427 mask = nsfonttraitmask_for (mask_jwxyz);
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] :
435 size *= jwxyz_font_scale (main_window);
437 if (font_name_type == JWXYZ_FONT_RANDOM) {
439 nsfont = random_font (traits, mask, size);
442 } else if (font_name_type == JWXYZ_FONT_FACE) {
444 nsfont = [NSFont fontWithName:font_name size:size];
446 } else if (font_name_type == JWXYZ_FONT_FAMILY) {
448 Assert (size > 0, "zero font size");
451 nsfont = try_font (traits, mask, font_name, size);
453 // if that didn't work, turn off attibutes until it does
454 // (e.g., there is no "Monaco-Bold".)
456 if (!nsfont && (mask & NSItalicFontMask)) {
457 traits &= ~NSItalicFontMask;
458 mask &= ~NSItalicFontMask;
459 nsfont = try_font (traits, mask, font_name, size);
461 if (!nsfont && (mask & NSBoldFontMask)) {
462 traits &= ~NSBoldFontMask;
463 mask &= ~NSBoldFontMask;
464 nsfont = try_font (traits, mask, font_name, size);
466 if (!nsfont && (mask & NSFixedPitchFontMask)) {
467 traits &= ~NSFixedPitchFontMask;
468 mask &= ~NSFixedPitchFontMask;
469 nsfont = try_font (traits, mask, font_name, size);
478 *family_name_ret = strdup (nsfont.familyName.UTF8String);
480 CFRetain (nsfont); // needed for garbage collection?
482 *ascent_ret = ceil ([nsfont ascender]);
483 *descent_ret = -floor ([nsfont descender]);
485 Assert([nsfont fontName], "broken NSFont in fid");
493 jwxyz_release_native_font (Display *dpy, void *native_font)
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...
500 // [fid->nsfont release];
501 // CFRelease (fid->nsfont);
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.
510 sanitize_utf8 (const char *in, size_t in_len, Bool *latin1_pP)
512 size_t out_len = in_len * 4; // length of string might increase
513 char *s2 = (char *) malloc (out_len);
515 const char *in_end = in + in_len;
516 const char *out_end = out + out_len;
517 Bool latin1_p = True;
522 long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc);
523 long L2 = utf8_encode (uc, out, out_end - out);
526 if (uc > 255) latin1_p = False;
530 [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding];
532 if (latin1_pP) *latin1_pP = latin1_p;
533 return (nsstr ? nsstr : @"");
538 nsstring_from(const char *str, size_t len, int utf8_p)
541 NSString *nsstr = utf8_p ?
542 sanitize_utf8 (str, len, &latin1_p) :
543 [[[NSString alloc] initWithBytes:str
545 encoding:NSISOLatin1StringEncoding]
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)
555 utf8_metrics (dpy, (NSFont *)native_font, nsstring_from (str, len, utf8_p),
558 Assert (!pixmap_ret, "TODO");
563 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
571 xp->x = w->window.last_mouse_x;
572 xp->y = w->window.last_mouse_y;
575 # else // !HAVE_IPHONE
577 NSWindow *nsw = [w->window.view window];
579 // get bottom left of window on screen, from bottom left
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];
585 NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
586 rr2.origin.y - rr1.origin.y);
588 // deprecated as of 10.7
589 NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
593 // get bottom left of view on window, from bottom left
596 vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]];
598 // get bottom left of view on screen, from bottom left
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;
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;
618 // get the mouse position on window, from bottom left
619 NSEvent *e = [NSApp currentEvent];
620 NSPoint p = [e locationInWindow];
622 // get mouse position on screen, from bottom left
626 // get mouse position on screen, from top left
627 p.y = srect.size.height - p.y;
633 # endif // !HAVE_IPHONE
640 check_framebuffer_status (void)
642 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
644 case GL_FRAMEBUFFER_COMPLETE_OES:
646 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
647 Assert (0, "framebuffer incomplete attachment");
649 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
650 Assert (0, "framebuffer incomplete missing attachment");
652 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
653 Assert (0, "framebuffer incomplete dimensions");
655 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
656 Assert (0, "framebuffer incomplete formats");
658 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
659 Assert (0, "framebuffer unsupported");
662 case GL_FRAMEBUFFER_UNDEFINED:
663 Assert (0, "framebuffer undefined");
665 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
666 Assert (0, "framebuffer incomplete draw buffer");
668 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
669 Assert (0, "framebuffer incomplete read buffer");
671 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
672 Assert (0, "framebuffer incomplete multisample");
674 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
675 Assert (0, "framebuffer incomplete layer targets");
679 NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
686 create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
688 glGenFramebuffersOES (1, gl_framebuffer);
689 glBindFramebufferOES (GL_FRAMEBUFFER_OES, *gl_framebuffer);
691 glGenRenderbuffersOES (1, gl_renderbuffer);
692 glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
695 #endif // HAVE_IPHONE
698 #if defined JWXYZ_QUARTZ
700 /* Pushes a GC context; sets Fill and Stroke colors to the background color.
703 push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
705 XGCValues *gcv = VTBL->gc_gcv (gc);
706 push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
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)
716 XRectangle src_frame = src->frame, dst_frame = dst->frame;
717 XGCValues *gcv = VTBL->gc_gcv (gc);
719 BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
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.
726 if (gcv->function == GXcopy &&
728 jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
730 Assert(!src_frame.x &&
734 "unexpected non-zero origin");
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,
744 src_rect = CGRectMake(src_x, src_y, width, height),
745 dst_rect = CGRectMake(dst_x, dst_y, width, height);
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);
752 NSObject *releaseme = 0;
754 BOOL free_cgi_p = NO;
756 // We must first copy the bits to an intermediary CGImage object, then
757 // copy that to the destination drawable's CGContext.
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.
766 src->cgi = CGBitmapContextCreateImage (src->cgc);
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);
782 CGContextRef cgc = dst->cgc;
784 if (mask_p) { // src depth == 1
786 push_bg_gc (dpy, dst, gc, YES);
788 // fill the destination rectangle with solid background...
789 CGContextFillRect (cgc, dst_rect);
791 Assert (cgc, "no CGC with 1-bit XCopyArea");
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);
802 } else { // src depth > 1
806 // copy the CGImage onto the destination CGContext
807 //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
808 CGContextDrawImage (cgc, dst_rect, cgi);
813 if (free_cgi_p) CGImageRelease (cgi);
815 if (releaseme) [releaseme release];
818 invalidate_drawable_cache (dst);
821 #elif defined JWXYZ_GL
823 /* Warning! The JWXYZ_GL code here is experimental and provisional and not at
824 all ready for prime time. Please be careful.
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)
834 - glCopyPixels if src == dst
835 - Pixel buffer copying
836 - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
837 which is like a blit.
840 /* Strange and ugly flickering when going the glCopyTexImage2D route on
841 OS X. (Early 2009 Mac mini, OS X 10.10)
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
861 jwxyz_assert_gl (void)
863 // This is like check_gl_error, except this happens for debug builds only.
865 if([NSOpenGLContext currentContext])
867 // glFinish here drops FPS into the toilet. It might need to be on if
868 // something goes wrong.
870 GLenum error = glGetError();
871 Assert (!error, "jwxyz_assert_gl: OpenGL error");
873 #endif // !__OPTIMIZE__
877 jwxyz_assert_drawable (Window main_window, Drawable d)
879 #if !defined HAVE_IPHONE && !defined __OPTIMIZE__
880 XScreenSaverView *view = main_window->window.view;
881 NSOpenGLContext *ogl_ctx = [view oglContext];
883 if (d->type == WINDOW) {
884 Assert([ogl_ctx view] == view,
885 "jwxyz_assert_display: ogl_ctx view not set!");
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];
894 @catch (NSException *exception) {
895 perror([[exception reason] UTF8String]);
898 #endif // !HAVE_IPHONE && !__OPTIMIZE__
903 jwxyz_bind_drawable (Window main_window, Drawable d)
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.
909 On iOS, all pixmaps can use the same context with different FBOs. Which
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.
918 /* EXT_direct_state_access might be appropriate, but it's apparently not
919 available on Mac OS X.
922 // jwxyz_assert_display (dpy);
923 jwxyz_assert_drawable (main_window, main_window);
925 jwxyz_assert_drawable (main_window, d);
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) {
934 glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
935 Assert (framebuffer == current_drawable->gl_framebuffer,
936 "bind_drawable: Wrong framebuffer.");
940 if (main_window->window.current_drawable != d) {
941 main_window->window.current_drawable = d;
943 /* Doing this repeatedly is probably not OK performance-wise. Probably. */
945 [d->ogl_ctx makeCurrentContext];
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);
961 XCreatePixmap (Display *dpy, Drawable d,
962 unsigned int width, unsigned int height, unsigned int depth)
964 Pixmap p = (Pixmap) calloc (1, sizeof(*p));
966 p->frame.width = width;
967 p->frame.height = height;
968 p->pixmap.depth = depth;
970 Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
972 /* TODO: If Pixel buffers are not supported, do something about it. */
973 Window w = XRootWindow (dpy, 0);
977 p->ogl_ctx = [[NSOpenGLContext alloc]
978 initWithFormat:w->window.pixfmt
979 shareContext:w->ogl_ctx];
980 CFRetain (p->ogl_ctx);
982 [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
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
992 CFRetain (p->pixmap.gl_pbuffer);
994 [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
997 currentVirtualScreen:w->window.virtual_screen];
999 # else // HAVE_IPHONE
1001 p->ogl_ctx = w->window.ogl_ctx_pixmap;
1003 [EAGLContext setCurrentContext:p->ogl_ctx];
1004 create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
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);
1010 check_framebuffer_status ();
1012 glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
1014 # endif // HAVE_IPHONE
1016 w->window.current_drawable = p;
1017 glViewport (0, 0, width, height);
1018 jwxyz_set_matrices (width, height);
1020 # ifndef __OPTIMIZE__
1021 glClearColor (frand(1), frand(1), frand(1), 0);
1022 glClear (GL_COLOR_BUFFER_BIT);
1029 XFreePixmap (Display *d, Pixmap p)
1031 Assert (p && p->type == PIXMAP, "not a pixmap");
1033 Window w = RootWindow (d, 0);
1035 # ifndef HAVE_IPHONE
1036 CFRelease (p->ogl_ctx);
1037 [p->ogl_ctx release];
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
1046 if (w->window.current_drawable == p) {
1047 w->window.current_drawable = NULL;