1 /* xscreensaver, Copyright (c) 1991-2017 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 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.
18 This code is used by both the original jwxyz.m and the new jwxyz-gl.c.
22 #import "jwxyz-cocoa.h"
28 # import <OpenGLES/ES1/gl.h>
29 # import <OpenGLES/ES1/glext.h>
30 # define NSOpenGLContext EAGLContext
31 # define NSFont UIFont
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
41 #import <CoreText/CTLine.h>
42 #import <CoreText/CTRun.h>
44 #define VTBL JWXYZ_VTBL(dpy)
46 /* OS X/iOS-specific JWXYZ implementation. */
49 jwxyz_logv (Bool error, const char *fmt, va_list args)
51 vfprintf (stderr, fmt, args);
55 /* Instead of calling abort(), throw a real exception, so that
56 XScreenSaverView can catch it and display a dialog.
59 jwxyz_abort (const char *fmt, ...)
68 vsprintf (s, fmt, args);
71 [[NSException exceptionWithName: NSInternalInconsistencyException
72 reason: [NSString stringWithCString: s
73 encoding:NSUTF8StringEncoding]
77 abort(); // not reached
82 jwxyz_frame (Drawable d)
89 jwxyz_drawable_depth (Drawable d)
91 return (d->type == WINDOW
92 ? visual_depth (NULL, NULL)
98 jwxyz_scale (Window main_window)
103 /* Since iOS screens are physically smaller than desktop screens, scale up
104 the fonts to make them more readable.
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.
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.
115 # else // !USE_IPHONE
117 /* Desktop retina displays also need fonts doubled. */
118 scale = main_window->window.view.hackedContentScaleFactor;
120 # endif // !USE_IPHONE
126 /* Font metric terminology, as used by X11:
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.
131 "rbearing" is the distance from the logical origin to the rightmost pixel.
133 "descent" is the distance from the logical origin to the bottommost pixel.
134 For characters with descenders, it is positive. For superscripts, it
137 "ascent" is the distance from the logical origin to the topmost pixel.
138 It is the number of pixels above the baseline.
140 "width" is the distance from the logical origin to the position where
141 the logical origin of the next character should be placed.
143 If "rbearing" is greater than "width", then this character overlaps the
144 following character. If smaller, then there is trailing blank space.
147 utf8_metrics (Display *dpy, NSFont *nsfont, NSString *nsstr, XCharStruct *cs)
149 // Returns the metrics of the multi-character, single-line UTF8 string.
151 Drawable d = XRootWindow (dpy, 0);
153 CGContextRef cgc = d->cgc;
155 [NSDictionary dictionaryWithObjectsAndKeys:
156 nsfont, NSFontAttributeName,
158 NSAttributedString *astr = [[NSAttributedString alloc]
161 CTLineRef ctline = CTLineCreateWithAttributedString (
162 (__bridge CFAttributedStringRef) astr);
163 CGContextSetTextPosition (cgc, 0, 0);
164 CGContextSetShouldAntialias (cgc, True); // #### Guess?
166 memset (cs, 0, sizeof(*cs));
168 // "CTRun represents set of consecutive glyphs sharing the same
169 // attributes and direction".
171 // We also get multiple runs any time font subsitution happens:
172 // E.g., if the current font is Verdana-Bold, a ← character
173 // in the NSString will actually be rendered in LucidaGrande-Bold.
176 for (id runid in (NSArray *)CTLineGetGlyphRuns(ctline)) {
177 CTRunRef run = (CTRunRef) runid;
179 CGRect bbox = CTRunGetImageBounds (run, cgc, r);
180 CGFloat ascent, descent, leading;
181 CGFloat advancement =
182 CTRunGetTypographicBounds (run, r, &ascent, &descent, &leading);
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;
191 // Create the metrics for this run:
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);
199 // Add those metrics into the cumulative metrics:
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);
211 // Why no y? What about vertical text?
212 // XCharStruct doesn't encapsulate that but XGlyphInfo does.
223 font_family_members (NSString *family_name)
226 return [[NSFontManager sharedFontManager]
227 availableMembersOfFontFamily:family_name];
229 return [UIFont fontNamesForFamilyName:family_name];
235 jwxyz_default_font_family (int require)
237 return require & JWXYZ_STYLE_MONOSPACE ? "Courier" : "Verdana";
241 static NSFontTraitMask
242 nsfonttraitmask_for (int font_style)
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;
256 try_font (NSFontTraitMask traits, NSFontTraitMask mask,
257 NSString *family_name, float size)
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)]);
267 for (unsigned k = 0; k != family_members.count; ++k) {
269 NSArray *member = [family_members objectAtIndex:k];
270 NSFontTraitMask font_mask =
271 [(NSNumber *)[member objectAtIndex:3] unsignedIntValue];
273 if ((font_mask & mask) == traits) {
275 NSString *name = [member objectAtIndex:0];
276 NSFont *f = [NSFont fontWithName:name size:size];
280 /* Don't use this font if it (probably) doesn't include ASCII characters.
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);
294 // NSLog(@"using \"%@\": %d", name, enc);
301 // This trick needs iOS 3.1, see "Using SDK-Based Development".
302 Class has_font_descriptor = [UIFontDescriptor class];
304 for (NSString *fn in family_members) {
306 ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \
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}]
319 font_mask |= NSBoldFontMask;
320 if (MATCH(@"Italic") || MATCH(@"Oblique"))
321 font_mask |= NSItalicFontMask;
322 if (MATCH(@"Courier"))
323 font_mask |= NSFixedPitchFontMask;
326 if ((font_mask & mask) == traits) {
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.
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
345 return [UIFont fontWithName:fn size:size];
355 /* Returns a random font in the given size and face.
358 random_font (NSFontTraitMask traits, NSFontTraitMask mask, float size)
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;
368 NSArray *families = [UIFont familyNames];
370 // There are many dups in the families array -- uniquify it.
372 NSArray *sorted_families =
373 [families sortedArrayUsingSelector:@selector(compare:)];
374 NSMutableArray *new_families =
375 [NSMutableArray arrayWithCapacity:sorted_families.count];
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;
384 families = new_families;
386 # endif // USE_IPHONE
388 long n = [families count];
389 if (n <= 0) return 0;
392 for (j = 0; j < n; j++) {
393 int i = random() % n;
394 NSString *family_name = [families objectAtIndex:i];
396 NSFont *result = try_font (traits, mask, family_name, size);
401 // None of the fonts support ASCII?
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)
412 NSFont *nsfont = NULL;
415 traits = nsfonttraitmask_for (traits_jwxyz),
416 mask = nsfonttraitmask_for (mask_jwxyz);
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] :
424 size *= jwxyz_scale (main_window);
426 if (font_name_type == JWXYZ_FONT_RANDOM) {
428 nsfont = random_font (traits, mask, size);
431 } else if (font_name_type == JWXYZ_FONT_FACE) {
433 nsfont = [NSFont fontWithName:font_name size:size];
435 } else if (font_name_type == JWXYZ_FONT_FAMILY) {
437 Assert (size > 0, "zero font size");
440 nsfont = try_font (traits, mask, font_name, size);
442 // if that didn't work, turn off attibutes until it does
443 // (e.g., there is no "Monaco-Bold".)
445 if (!nsfont && (mask & NSItalicFontMask)) {
446 traits &= ~NSItalicFontMask;
447 mask &= ~NSItalicFontMask;
448 nsfont = try_font (traits, mask, font_name, size);
450 if (!nsfont && (mask & NSBoldFontMask)) {
451 traits &= ~NSBoldFontMask;
452 mask &= ~NSBoldFontMask;
453 nsfont = try_font (traits, mask, font_name, size);
455 if (!nsfont && (mask & NSFixedPitchFontMask)) {
456 traits &= ~NSFixedPitchFontMask;
457 mask &= ~NSFixedPitchFontMask;
458 nsfont = try_font (traits, mask, font_name, size);
465 *family_name_ret = strdup (nsfont.familyName.UTF8String);
467 CFRetain (nsfont); // needed for garbage collection?
469 *ascent_ret = ceil ([nsfont ascender]);
470 *descent_ret = -floor ([nsfont descender]);
472 Assert([nsfont fontName], "broken NSFont in fid");
480 jwxyz_release_native_font (Display *dpy, void *native_font)
482 // #### DAMMIT! I can't tell what's going wrong here, but I keep getting
483 // crashes in [NSFont ascender] <- query_font, and it seems to go away
484 // if I never release the nsfont. So, fuck it, we'll just leak fonts.
485 // They're probably not very big...
487 // [fid->nsfont release];
488 // CFRelease (fid->nsfont);
492 // Given a UTF8 string, return an NSString. Bogus UTF8 characters are ignored.
493 // We have to do this because stringWithCString returns NULL if there are
494 // any invalid characters at all.
497 sanitize_utf8 (const char *in, size_t in_len, Bool *latin1_pP)
499 size_t out_len = in_len * 4; // length of string might increase
500 char *s2 = (char *) malloc (out_len);
502 const char *in_end = in + in_len;
503 const char *out_end = out + out_len;
504 Bool latin1_p = True;
509 long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc);
510 long L2 = utf8_encode (uc, out, out_end - out);
513 if (uc > 255) latin1_p = False;
517 [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding];
519 if (latin1_pP) *latin1_pP = latin1_p;
520 return (nsstr ? nsstr : @"");
525 nsstring_from(const char *str, size_t len, int utf8_p)
528 NSString *nsstr = utf8_p ?
529 sanitize_utf8 (str, len, &latin1_p) :
530 [[[NSString alloc] initWithBytes:str
532 encoding:NSISOLatin1StringEncoding]
538 jwxyz_render_text (Display *dpy, void *native_font,
539 const char *str, size_t len, Bool utf8_p, Bool antialias_p,
540 XCharStruct *cs_ret, char **pixmap_ret)
542 utf8_metrics (dpy, (NSFont *)native_font, nsstring_from (str, len, utf8_p),
545 Assert (!pixmap_ret, "TODO");
550 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
558 xp->x = w->window.last_mouse_x;
559 xp->y = w->window.last_mouse_y;
562 # else // !USE_IPHONE
564 NSWindow *nsw = [w->window.view window];
566 // get bottom left of window on screen, from bottom left
568 # if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6)
569 NSRect rr1 = [w->window.view convertRect: NSMakeRect(0,0,0,0) toView:nil];
570 NSRect rr2 = [nsw convertRectToScreen: rr1];
572 NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
573 rr2.origin.y - rr1.origin.y);
575 // deprecated as of 10.7
576 NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
580 // get bottom left of view on window, from bottom left
583 vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]];
585 // get bottom left of view on screen, from bottom left
589 // get top left of view on screen, from bottom left
590 double s = [w->window.view hackedContentScaleFactor];
591 vpos.y += w->frame.height / s;
593 // get top left of view on screen, from top left
594 NSArray *screens = [NSScreen screens];
595 NSScreen *screen = (screens && [screens count] > 0
596 ? [screens objectAtIndex:0]
597 : [NSScreen mainScreen]);
598 NSRect srect = [screen frame];
599 vpos.y = srect.size.height - vpos.y;
605 // get the mouse position on window, from bottom left
606 NSEvent *e = [NSApp currentEvent];
607 NSPoint p = [e locationInWindow];
609 // get mouse position on screen, from bottom left
613 // get mouse position on screen, from top left
614 p.y = srect.size.height - p.y;
620 # endif // !USE_IPHONE
627 check_framebuffer_status (void)
629 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
631 case GL_FRAMEBUFFER_COMPLETE_OES:
633 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
634 Assert (0, "framebuffer incomplete attachment");
636 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
637 Assert (0, "framebuffer incomplete missing attachment");
639 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
640 Assert (0, "framebuffer incomplete dimensions");
642 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
643 Assert (0, "framebuffer incomplete formats");
645 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
646 Assert (0, "framebuffer unsupported");
649 case GL_FRAMEBUFFER_UNDEFINED:
650 Assert (0, "framebuffer undefined");
652 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
653 Assert (0, "framebuffer incomplete draw buffer");
655 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
656 Assert (0, "framebuffer incomplete read buffer");
658 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
659 Assert (0, "framebuffer incomplete multisample");
661 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
662 Assert (0, "framebuffer incomplete layer targets");
666 NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
673 create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
675 glGenFramebuffersOES (1, gl_framebuffer);
676 glBindFramebufferOES (GL_FRAMEBUFFER_OES, *gl_framebuffer);
678 glGenRenderbuffersOES (1, gl_renderbuffer);
679 glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
685 #if defined JWXYZ_QUARTZ
687 /* Pushes a GC context; sets Fill and Stroke colors to the background color.
690 push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
692 XGCValues *gcv = VTBL->gc_gcv (gc);
693 push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
698 jwxyz_quartz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
699 int src_x, int src_y,
700 unsigned int width, unsigned int height,
701 int dst_x, int dst_y)
703 XRectangle src_frame = src->frame, dst_frame = dst->frame;
704 XGCValues *gcv = VTBL->gc_gcv (gc);
706 BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
709 /* If we're copying from a bitmap to a bitmap, and there's nothing funny
710 going on with clipping masks or depths or anything, optimize it by
711 just doing a memcpy instead of going through a CGI.
713 if (gcv->function == GXcopy &&
715 jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
717 Assert(!src_frame.x &&
721 "unexpected non-zero origin");
723 jwxyz_blit (CGBitmapContextGetData (src->cgc),
724 CGBitmapContextGetBytesPerRow (src->cgc), src_x, src_y,
725 CGBitmapContextGetData (dst->cgc),
726 CGBitmapContextGetBytesPerRow (dst->cgc), dst_x, dst_y,
731 src_rect = CGRectMake(src_x, src_y, width, height),
732 dst_rect = CGRectMake(dst_x, dst_y, width, height);
734 src_rect.origin = map_point (src, src_rect.origin.x,
735 src_rect.origin.y + src_rect.size.height);
736 dst_rect.origin = map_point (dst, dst_rect.origin.x,
737 dst_rect.origin.y + src_rect.size.height);
739 NSObject *releaseme = 0;
741 BOOL free_cgi_p = NO;
743 // We must first copy the bits to an intermediary CGImage object, then
744 // copy that to the destination drawable's CGContext.
746 // First we get a CGImage out of the pixmap CGContext -- it's the whole
747 // pixmap, but it presumably shares the data pointer instead of copying
748 // it. We then cache that CGImage it inside the Pixmap object. Note:
749 // invalidate_drawable_cache() must be called to discard this any time a
750 // modification is made to the pixmap, or we'll end up re-using old bits.
753 src->cgi = CGBitmapContextCreateImage (src->cgc);
756 // if doing a sub-rect, trim it down.
757 if (src_rect.origin.x != src_frame.x ||
758 src_rect.origin.y != src_frame.y ||
759 src_rect.size.width != src_frame.width ||
760 src_rect.size.height != src_frame.height) {
761 // #### I don't understand why this is needed...
762 src_rect.origin.y = (src_frame.height -
763 src_rect.size.height - src_rect.origin.y);
764 // This does not copy image data, so it should be fast.
765 cgi = CGImageCreateWithImageInRect (cgi, src_rect);
769 CGContextRef cgc = dst->cgc;
771 if (mask_p) { // src depth == 1
773 push_bg_gc (dpy, dst, gc, YES);
775 // fill the destination rectangle with solid background...
776 CGContextFillRect (cgc, dst_rect);
778 Assert (cgc, "no CGC with 1-bit XCopyArea");
780 // then fill in a solid rectangle of the fg color, using the image as an
781 // alpha mask. (the image has only values of BlackPixel or WhitePixel.)
782 set_color (dpy, cgc, gcv->foreground, VTBL->gc_depth (gc),
783 gcv->alpha_allowed_p, YES);
784 CGContextClipToMask (cgc, dst_rect, cgi);
785 CGContextFillRect (cgc, dst_rect);
789 } else { // src depth > 1
793 // copy the CGImage onto the destination CGContext
794 //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
795 CGContextDrawImage (cgc, dst_rect, cgi);
800 if (free_cgi_p) CGImageRelease (cgi);
802 if (releaseme) [releaseme release];
805 invalidate_drawable_cache (dst);
808 #elif defined JWXYZ_GL
810 /* Warning! The JWXYZ_GL code here is experimental and provisional and not at
811 all ready for prime time. Please be careful.
815 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
816 int src_x, int src_y,
817 unsigned int width, unsigned int height,
818 int dst_x, int dst_y)
821 - glCopyPixels if src == dst
822 - Pixel buffer copying
823 - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
824 which is like a blit.
827 /* Strange and ugly flickering when going the glCopyTexImage2D route on
828 OS X. (Early 2009 Mac mini, OS X 10.10)
832 /* TODO: This might not still work. */
833 jwxyz_bind_drawable (dpy, dpy->main_window, src);
834 jwxyz_gl_copy_area_read_tex_image (dpy, jwxyz_frame (src)->height,
835 src_x, src_y, width, height, dst_x, dst_y);
836 jwxyz_bind_drawable (dpy, dpy->main_window, dst);
837 jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
838 width, height, dst_x, dst_y);
839 # else // !USE_IPHONE
840 jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc,
841 src_x, src_y, width, height, dst_x, dst_y);
842 # endif // !USE_IPHONE
850 // This is like check_gl_error, except this happens for debug builds only.
852 if([NSOpenGLContext currentContext])
854 // glFinish here drops FPS into the toilet. It might need to be on if
855 // something goes wrong.
857 GLenum error = glGetError();
858 Assert (!error, "jwxyz_assert_gl: OpenGL error");
860 #endif // !__OPTIMIZE__
864 jwxyz_assert_drawable (Window main_window, Drawable d)
866 #if !defined USE_IPHONE && !defined __OPTIMIZE__
867 XScreenSaverView *view = main_window->window.view;
868 NSOpenGLContext *ogl_ctx = [view oglContext];
870 if (d->type == WINDOW) {
871 Assert([ogl_ctx view] == view,
872 "jwxyz_assert_display: ogl_ctx view not set!");
876 /* Assert([d->ctx isKindOfClass:[NSOpenGLContext class]], "Not a context."); */
877 Class c = [ogl_ctx class];
878 Assert([c isSubclassOfClass:[NSOpenGLContext class]], "Not a context.");
879 // [d->ctx makeCurrentContext];
881 @catch (NSException *exception) {
882 perror([[exception reason] UTF8String]);
885 #endif // !USE_IPHONE && !__OPTIMIZE__
890 jwxyz_bind_drawable (Window main_window, Drawable d)
892 /* Windows and Pixmaps need to use different contexts with OpenGL
893 screenhacks, because an OpenGL screenhack sets state in an arbitrary
894 fashion, but jwxyz-gl.c has its own ideas w.r.t. OpenGL state.
896 On iOS, all pixmaps can use the same context with different FBOs. Which
900 /* OpenGL screenhacks in general won't be drawing on the Window, but they
901 can and will draw on a Pixmap -- but an OpenGL call following an Xlib
902 call won't be able to fix the fact that it's drawing offscreen.
905 /* EXT_direct_state_access might be appropriate, but it's apparently not
906 available on Mac OS X.
909 // jwxyz_assert_display (dpy);
910 jwxyz_assert_drawable (main_window, main_window);
912 jwxyz_assert_drawable (main_window, d);
914 #if defined USE_IPHONE && !defined __OPTIMIZE__
915 Drawable current_drawable = main_window->window.current_drawable;
916 Assert (!current_drawable
917 || current_drawable->ogl_ctx == [EAGLContext currentContext],
918 "bind_drawable: Wrong context.");
919 if (current_drawable) {
921 glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
922 Assert (framebuffer == current_drawable->gl_framebuffer,
923 "bind_drawable: Wrong framebuffer.");
927 if (main_window->window.current_drawable != d) {
928 main_window->window.current_drawable = d;
930 /* Doing this repeatedly is probably not OK performance-wise. Probably. */
932 [d->ogl_ctx makeCurrentContext];
934 [EAGLContext setCurrentContext:d->ogl_ctx];
935 glBindFramebufferOES(GL_FRAMEBUFFER_OES, d->gl_framebuffer);
936 if (d->type == PIXMAP) {
937 glViewport (0, 0, d->frame.width, d->frame.height);
938 jwxyz_set_matrices (d->frame.width, d->frame.height);
948 XCreatePixmap (Display *dpy, Drawable d,
949 unsigned int width, unsigned int height, unsigned int depth)
951 Pixmap p = (Pixmap) calloc (1, sizeof(*p));
953 p->frame.width = width;
954 p->frame.height = height;
955 p->pixmap.depth = depth;
957 Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
959 /* TODO: If Pixel buffers are not supported, do something about it. */
960 Window w = XRootWindow (dpy, 0);
964 p->ogl_ctx = [[NSOpenGLContext alloc]
965 initWithFormat:w->window.pixfmt
966 shareContext:w->ogl_ctx];
967 CFRetain (p->ogl_ctx);
969 [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
971 p->pixmap.gl_pbuffer = [[NSOpenGLPixelBuffer alloc]
972 /* TODO: Only if there are rectangluar textures. */
973 initWithTextureTarget:GL_TEXTURE_RECTANGLE_EXT
974 /* TODO: Make sure GL_RGBA isn't better. */
975 textureInternalFormat:GL_RGB
976 textureMaxMipMapLevel:0
979 CFRetain (p->pixmap.gl_pbuffer);
981 [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
984 currentVirtualScreen:w->window.virtual_screen];
988 p->ogl_ctx = w->window.ogl_ctx_pixmap;
990 [EAGLContext setCurrentContext:p->ogl_ctx];
991 create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
993 glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height);
994 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
995 GL_RENDERBUFFER_OES, p->gl_renderbuffer);
997 check_framebuffer_status ();
999 glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
1001 # endif // USE_IPHONE
1003 w->window.current_drawable = p;
1004 glViewport (0, 0, width, height);
1005 jwxyz_set_matrices (width, height);
1007 # ifndef __OPTIMIZE__
1008 glClearColor (frand(1), frand(1), frand(1), 0);
1009 glClear (GL_COLOR_BUFFER_BIT);
1016 XFreePixmap (Display *d, Pixmap p)
1018 Assert (p && p->type == PIXMAP, "not a pixmap");
1020 Window w = RootWindow (d, 0);
1023 CFRelease (p->ogl_ctx);
1024 [p->ogl_ctx release];
1026 CFRelease (p->pixmap.gl_pbuffer);
1027 [p->pixmap.gl_pbuffer release];
1028 # else // USE_IPHONE
1029 glDeleteRenderbuffersOES (1, &p->gl_renderbuffer);
1030 glDeleteFramebuffersOES (1, &p->gl_framebuffer);
1031 # endif // USE_IPHONE
1033 if (w->window.current_drawable == p) {
1034 w->window.current_drawable = NULL;