1 /* xscreensaver, Copyright (c) 1991-2018 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);
467 *family_name_ret = strdup (nsfont.familyName.UTF8String);
469 CFRetain (nsfont); // needed for garbage collection?
471 *ascent_ret = ceil ([nsfont ascender]);
472 *descent_ret = -floor ([nsfont descender]);
474 Assert([nsfont fontName], "broken NSFont in fid");
482 jwxyz_release_native_font (Display *dpy, void *native_font)
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...
489 // [fid->nsfont release];
490 // CFRelease (fid->nsfont);
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.
499 sanitize_utf8 (const char *in, size_t in_len, Bool *latin1_pP)
501 size_t out_len = in_len * 4; // length of string might increase
502 char *s2 = (char *) malloc (out_len);
504 const char *in_end = in + in_len;
505 const char *out_end = out + out_len;
506 Bool latin1_p = True;
511 long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc);
512 long L2 = utf8_encode (uc, out, out_end - out);
515 if (uc > 255) latin1_p = False;
519 [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding];
521 if (latin1_pP) *latin1_pP = latin1_p;
522 return (nsstr ? nsstr : @"");
527 nsstring_from(const char *str, size_t len, int utf8_p)
530 NSString *nsstr = utf8_p ?
531 sanitize_utf8 (str, len, &latin1_p) :
532 [[[NSString alloc] initWithBytes:str
534 encoding:NSISOLatin1StringEncoding]
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)
544 utf8_metrics (dpy, (NSFont *)native_font, nsstring_from (str, len, utf8_p),
547 Assert (!pixmap_ret, "TODO");
552 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
560 xp->x = w->window.last_mouse_x;
561 xp->y = w->window.last_mouse_y;
564 # else // !USE_IPHONE
566 NSWindow *nsw = [w->window.view window];
568 // get bottom left of window on screen, from bottom left
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];
574 NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
575 rr2.origin.y - rr1.origin.y);
577 // deprecated as of 10.7
578 NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
582 // get bottom left of view on window, from bottom left
585 vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]];
587 // get bottom left of view on screen, from bottom left
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;
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;
607 // get the mouse position on window, from bottom left
608 NSEvent *e = [NSApp currentEvent];
609 NSPoint p = [e locationInWindow];
611 // get mouse position on screen, from bottom left
615 // get mouse position on screen, from top left
616 p.y = srect.size.height - p.y;
622 # endif // !USE_IPHONE
629 check_framebuffer_status (void)
631 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
633 case GL_FRAMEBUFFER_COMPLETE_OES:
635 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
636 Assert (0, "framebuffer incomplete attachment");
638 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
639 Assert (0, "framebuffer incomplete missing attachment");
641 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
642 Assert (0, "framebuffer incomplete dimensions");
644 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
645 Assert (0, "framebuffer incomplete formats");
647 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
648 Assert (0, "framebuffer unsupported");
651 case GL_FRAMEBUFFER_UNDEFINED:
652 Assert (0, "framebuffer undefined");
654 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
655 Assert (0, "framebuffer incomplete draw buffer");
657 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
658 Assert (0, "framebuffer incomplete read buffer");
660 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
661 Assert (0, "framebuffer incomplete multisample");
663 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
664 Assert (0, "framebuffer incomplete layer targets");
668 NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
675 create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
677 glGenFramebuffersOES (1, gl_framebuffer);
678 glBindFramebufferOES (GL_FRAMEBUFFER_OES, *gl_framebuffer);
680 glGenRenderbuffersOES (1, gl_renderbuffer);
681 glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
687 #if defined JWXYZ_QUARTZ
689 /* Pushes a GC context; sets Fill and Stroke colors to the background color.
692 push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
694 XGCValues *gcv = VTBL->gc_gcv (gc);
695 push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
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)
705 XRectangle src_frame = src->frame, dst_frame = dst->frame;
706 XGCValues *gcv = VTBL->gc_gcv (gc);
708 BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
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.
715 if (gcv->function == GXcopy &&
717 jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
719 Assert(!src_frame.x &&
723 "unexpected non-zero origin");
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,
733 src_rect = CGRectMake(src_x, src_y, width, height),
734 dst_rect = CGRectMake(dst_x, dst_y, width, height);
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);
741 NSObject *releaseme = 0;
743 BOOL free_cgi_p = NO;
745 // We must first copy the bits to an intermediary CGImage object, then
746 // copy that to the destination drawable's CGContext.
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.
755 src->cgi = CGBitmapContextCreateImage (src->cgc);
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);
771 CGContextRef cgc = dst->cgc;
773 if (mask_p) { // src depth == 1
775 push_bg_gc (dpy, dst, gc, YES);
777 // fill the destination rectangle with solid background...
778 CGContextFillRect (cgc, dst_rect);
780 Assert (cgc, "no CGC with 1-bit XCopyArea");
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);
791 } else { // src depth > 1
795 // copy the CGImage onto the destination CGContext
796 //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
797 CGContextDrawImage (cgc, dst_rect, cgi);
802 if (free_cgi_p) CGImageRelease (cgi);
804 if (releaseme) [releaseme release];
807 invalidate_drawable_cache (dst);
810 #elif defined JWXYZ_GL
812 /* Warning! The JWXYZ_GL code here is experimental and provisional and not at
813 all ready for prime time. Please be careful.
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)
823 - glCopyPixels if src == dst
824 - Pixel buffer copying
825 - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
826 which is like a blit.
829 /* Strange and ugly flickering when going the glCopyTexImage2D route on
830 OS X. (Early 2009 Mac mini, OS X 10.10)
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
852 // This is like check_gl_error, except this happens for debug builds only.
854 if([NSOpenGLContext currentContext])
856 // glFinish here drops FPS into the toilet. It might need to be on if
857 // something goes wrong.
859 GLenum error = glGetError();
860 Assert (!error, "jwxyz_assert_gl: OpenGL error");
862 #endif // !__OPTIMIZE__
866 jwxyz_assert_drawable (Window main_window, Drawable d)
868 #if !defined USE_IPHONE && !defined __OPTIMIZE__
869 XScreenSaverView *view = main_window->window.view;
870 NSOpenGLContext *ogl_ctx = [view oglContext];
872 if (d->type == WINDOW) {
873 Assert([ogl_ctx view] == view,
874 "jwxyz_assert_display: ogl_ctx view not set!");
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];
883 @catch (NSException *exception) {
884 perror([[exception reason] UTF8String]);
887 #endif // !USE_IPHONE && !__OPTIMIZE__
892 jwxyz_bind_drawable (Window main_window, Drawable d)
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.
898 On iOS, all pixmaps can use the same context with different FBOs. Which
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.
907 /* EXT_direct_state_access might be appropriate, but it's apparently not
908 available on Mac OS X.
911 // jwxyz_assert_display (dpy);
912 jwxyz_assert_drawable (main_window, main_window);
914 jwxyz_assert_drawable (main_window, d);
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) {
923 glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
924 Assert (framebuffer == current_drawable->gl_framebuffer,
925 "bind_drawable: Wrong framebuffer.");
929 if (main_window->window.current_drawable != d) {
930 main_window->window.current_drawable = d;
932 /* Doing this repeatedly is probably not OK performance-wise. Probably. */
934 [d->ogl_ctx makeCurrentContext];
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);
950 XCreatePixmap (Display *dpy, Drawable d,
951 unsigned int width, unsigned int height, unsigned int depth)
953 Pixmap p = (Pixmap) calloc (1, sizeof(*p));
955 p->frame.width = width;
956 p->frame.height = height;
957 p->pixmap.depth = depth;
959 Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
961 /* TODO: If Pixel buffers are not supported, do something about it. */
962 Window w = XRootWindow (dpy, 0);
966 p->ogl_ctx = [[NSOpenGLContext alloc]
967 initWithFormat:w->window.pixfmt
968 shareContext:w->ogl_ctx];
969 CFRetain (p->ogl_ctx);
971 [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
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
981 CFRetain (p->pixmap.gl_pbuffer);
983 [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
986 currentVirtualScreen:w->window.virtual_screen];
990 p->ogl_ctx = w->window.ogl_ctx_pixmap;
992 [EAGLContext setCurrentContext:p->ogl_ctx];
993 create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
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);
999 check_framebuffer_status ();
1001 glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
1003 # endif // USE_IPHONE
1005 w->window.current_drawable = p;
1006 glViewport (0, 0, width, height);
1007 jwxyz_set_matrices (width, height);
1009 # ifndef __OPTIMIZE__
1010 glClearColor (frand(1), frand(1), frand(1), 0);
1011 glClear (GL_COLOR_BUFFER_BIT);
1018 XFreePixmap (Display *d, Pixmap p)
1020 Assert (p && p->type == PIXMAP, "not a pixmap");
1022 Window w = RootWindow (d, 0);
1025 CFRelease (p->ogl_ctx);
1026 [p->ogl_ctx release];
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
1035 if (w->window.current_drawable == p) {
1036 w->window.current_drawable = NULL;