1 /* xscreensaver, Copyright (c) 1991-2016 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"
27 # import <OpenGLES/ES1/gl.h>
28 # import <OpenGLES/ES1/glext.h>
29 # define NSOpenGLContext EAGLContext
32 /* OS X/iOS-specific JWXYZ implementation. */
35 jwxyz_logv (Bool error, const char *fmt, va_list args)
37 vfprintf (stderr, fmt, args);
41 /* Instead of calling abort(), throw a real exception, so that
42 XScreenSaverView can catch it and display a dialog.
45 jwxyz_abort (const char *fmt, ...)
54 vsprintf (s, fmt, args);
57 [[NSException exceptionWithName: NSInternalInconsistencyException
58 reason: [NSString stringWithCString: s
59 encoding:NSUTF8StringEncoding]
62 abort(); // not reached
67 jwxyz_frame (Drawable d)
74 jwxyz_drawable_depth (Drawable d)
76 return (d->type == WINDOW
77 ? visual_depth (NULL, NULL)
83 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
91 xp->x = w->window.last_mouse_x;
92 xp->y = w->window.last_mouse_y;
97 NSWindow *nsw = [w->window.view window];
99 // get bottom left of window on screen, from bottom left
101 # if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6)
102 NSRect rr1 = [w->window.view convertRect: NSMakeRect(0,0,0,0) toView:nil];
103 NSRect rr2 = [nsw convertRectToScreen: rr1];
104 NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
105 rr2.origin.y - rr1.origin.y);
107 // deprecated as of 10.7
108 NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
112 // get bottom left of view on window, from bottom left
115 vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]];
117 // get bottom left of view on screen, from bottom left
121 // get top left of view on screen, from bottom left
122 vpos.y += w->frame.height;
124 // get top left of view on screen, from top left
125 NSArray *screens = [NSScreen screens];
126 NSScreen *screen = (screens && [screens count] > 0
127 ? [screens objectAtIndex:0]
128 : [NSScreen mainScreen]);
129 NSRect srect = [screen frame];
130 vpos.y = srect.size.height - vpos.y;
136 // get the mouse position on window, from bottom left
137 NSEvent *e = [NSApp currentEvent];
138 NSPoint p = [e locationInWindow];
140 // get mouse position on screen, from bottom left
144 // get mouse position on screen, from top left
145 p.y = srect.size.height - p.y;
151 # endif // !USE_IPHONE
158 check_framebuffer_status (void)
160 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
162 case GL_FRAMEBUFFER_COMPLETE_OES:
164 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
165 Assert (0, "framebuffer incomplete attachment");
167 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
168 Assert (0, "framebuffer incomplete missing attachment");
170 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
171 Assert (0, "framebuffer incomplete dimensions");
173 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
174 Assert (0, "framebuffer incomplete formats");
176 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
177 Assert (0, "framebuffer unsupported");
180 case GL_FRAMEBUFFER_UNDEFINED:
181 Assert (0, "framebuffer undefined");
183 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
184 Assert (0, "framebuffer incomplete draw buffer");
186 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
187 Assert (0, "framebuffer incomplete read buffer");
189 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
190 Assert (0, "framebuffer incomplete multisample");
192 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
193 Assert (0, "framebuffer incomplete layer targets");
197 NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
204 create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
206 glGenFramebuffersOES (1, gl_framebuffer);
207 glBindFramebufferOES (GL_FRAMEBUFFER_OES, *gl_framebuffer);
209 glGenRenderbuffersOES (1, gl_renderbuffer);
210 glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
216 #if defined JWXYZ_QUARTZ
218 /* Pushes a GC context; sets Fill and Stroke colors to the background color.
221 push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
223 XGCValues *gcv = jwxyz_gc_gcv (gc);
224 push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
229 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
230 int src_x, int src_y,
231 unsigned int width, unsigned int height,
232 int dst_x, int dst_y)
234 XRectangle src_frame = src->frame, dst_frame = dst->frame;
235 XGCValues *gcv = jwxyz_gc_gcv (gc);
237 BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
240 /* If we're copying from a bitmap to a bitmap, and there's nothing funny
241 going on with clipping masks or depths or anything, optimize it by
242 just doing a memcpy instead of going through a CGI.
244 if (gcv->function == GXcopy &&
246 jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
248 Assert(!src_frame.x &&
252 "unexpected non-zero origin");
254 ptrdiff_t src_pitch = CGBitmapContextGetBytesPerRow(src->cgc);
255 ptrdiff_t dst_pitch = CGBitmapContextGetBytesPerRow(dst->cgc);
256 char *src_data = seek_xy (CGBitmapContextGetData (src->cgc), src_pitch,
258 char *dst_data = seek_xy (CGBitmapContextGetData (dst->cgc), dst_pitch,
261 size_t bytes = width * 4;
263 if (src == dst && dst_y > src_y) {
264 // Copy upwards if the areas might overlap.
265 src_data += src_pitch * (height - 1);
266 dst_data += dst_pitch * (height - 1);
267 src_pitch = -src_pitch;
268 dst_pitch = -dst_pitch;
272 // memcpy is an alias for memmove on OS X.
273 memmove(dst_data, src_data, bytes);
274 src_data += src_pitch;
275 dst_data += dst_pitch;
280 src_rect = CGRectMake(src_x, src_y, width, height),
281 dst_rect = CGRectMake(dst_x, dst_y, width, height);
283 src_rect.origin = map_point (src, src_rect.origin.x,
284 src_rect.origin.y + src_rect.size.height);
285 dst_rect.origin = map_point (dst, dst_rect.origin.x,
286 dst_rect.origin.y + src_rect.size.height);
288 NSObject *releaseme = 0;
290 BOOL free_cgi_p = NO;
292 // We must first copy the bits to an intermediary CGImage object, then
293 // copy that to the destination drawable's CGContext.
295 // First we get a CGImage out of the pixmap CGContext -- it's the whole
296 // pixmap, but it presumably shares the data pointer instead of copying
297 // it. We then cache that CGImage it inside the Pixmap object. Note:
298 // invalidate_drawable_cache() must be called to discard this any time a
299 // modification is made to the pixmap, or we'll end up re-using old bits.
302 src->cgi = CGBitmapContextCreateImage (src->cgc);
305 // if doing a sub-rect, trim it down.
306 if (src_rect.origin.x != src_frame.x ||
307 src_rect.origin.y != src_frame.y ||
308 src_rect.size.width != src_frame.width ||
309 src_rect.size.height != src_frame.height) {
310 // #### I don't understand why this is needed...
311 src_rect.origin.y = (src_frame.height -
312 src_rect.size.height - src_rect.origin.y);
313 // This does not copy image data, so it should be fast.
314 cgi = CGImageCreateWithImageInRect (cgi, src_rect);
318 CGContextRef cgc = dst->cgc;
320 if (mask_p) { // src depth == 1
322 push_bg_gc (dpy, dst, gc, YES);
324 // fill the destination rectangle with solid background...
325 CGContextFillRect (cgc, dst_rect);
327 Assert (cgc, "no CGC with 1-bit XCopyArea");
329 // then fill in a solid rectangle of the fg color, using the image as an
330 // alpha mask. (the image has only values of BlackPixel or WhitePixel.)
331 set_color (dpy, cgc, gcv->foreground, jwxyz_gc_depth (gc),
332 gcv->alpha_allowed_p, YES);
333 CGContextClipToMask (cgc, dst_rect, cgi);
334 CGContextFillRect (cgc, dst_rect);
338 } else { // src depth > 1
342 // copy the CGImage onto the destination CGContext
343 //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
344 CGContextDrawImage (cgc, dst_rect, cgi);
349 if (free_cgi_p) CGImageRelease (cgi);
351 if (releaseme) [releaseme release];
354 invalidate_drawable_cache (dst);
357 #elif defined JWXYZ_GL
359 /* Warning! The JWXYZ_GL code here is experimental and provisional and not at
360 all ready for prime time. Please be careful.
364 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
365 int src_x, int src_y,
366 unsigned int width, unsigned int height,
367 int dst_x, int dst_y)
370 - glCopyPixels if src == dst
371 - Pixel buffer copying
372 - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
373 which is like a blit.
376 /* Strange and ugly flickering when going the glCopyTexImage2D route on
377 OS X. (Early 2009 Mac mini, OS X 10.10)
380 jwxyz_gl_copy_area_copy_tex_image (dpy, src, dst, gc, src_x, src_y,
381 width, height, dst_x, dst_y);
382 # else // !USE_IPHONE
383 jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc,
384 src_x, src_y, width, height, dst_x, dst_y);
385 # endif // !USE_IPHONE
393 // This is like check_gl_error, except this happens for debug builds only.
395 if([NSOpenGLContext currentContext])
397 // glFinish here drops FPS into the toilet. It might need to be on if
398 // something goes wrong.
400 GLenum error = glGetError();
401 Assert (!error, "jwxyz_assert_gl: OpenGL error");
403 #endif // !__OPTIMIZE__
407 jwxyz_assert_drawable (Window main_window, Drawable d)
409 #if !defined USE_IPHONE && !defined __OPTIMIZE__
410 XScreenSaverView *view = main_window->window.view;
411 NSOpenGLContext *ogl_ctx = [view oglContext];
413 if (d->type == WINDOW) {
414 Assert([ogl_ctx view] == view,
415 "jwxyz_assert_display: ogl_ctx view not set!");
419 /* Assert([d->ctx isKindOfClass:[NSOpenGLContext class]], "Not a context."); */
420 Class c = [ogl_ctx class];
421 Assert([c isSubclassOfClass:[NSOpenGLContext class]], "Not a context.");
422 // [d->ctx makeCurrentContext];
424 @catch (NSException *exception) {
425 perror([[exception reason] UTF8String]);
428 #endif // !USE_IPHONE && !__OPTIMIZE__
433 jwxyz_bind_drawable (Window main_window, Drawable d)
435 /* Windows and Pixmaps need to use different contexts with OpenGL
436 screenhacks, because an OpenGL screenhack sets state in an arbitrary
437 fashion, but jwxyz-gl.c has its own ideas w.r.t. OpenGL state.
439 On iOS, all pixmaps can use the same context with different FBOs. Which
443 /* OpenGL screenhacks in general won't be drawing on the Window, but they
444 can and will draw on a Pixmap -- but an OpenGL call following an Xlib
445 call won't be able to fix the fact that it's drawing offscreen.
448 /* EXT_direct_state_access might be appropriate, but it's apparently not
449 available on Mac OS X.
452 // jwxyz_assert_display (dpy);
453 jwxyz_assert_drawable (main_window, main_window);
455 jwxyz_assert_drawable (main_window, d);
457 #if defined USE_IPHONE && !defined __OPTIMIZE__
458 Drawable current_drawable = main_window->window.current_drawable;
459 Assert (!current_drawable
460 || current_drawable->ogl_ctx == [EAGLContext currentContext],
461 "bind_drawable: Wrong context.");
462 if (current_drawable) {
464 glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
465 Assert (framebuffer == current_drawable->gl_framebuffer,
466 "bind_drawable: Wrong framebuffer.");
470 if (main_window->window.current_drawable != d) {
471 main_window->window.current_drawable = d;
473 /* Doing this repeatedly is probably not OK performance-wise. Probably. */
475 [d->ogl_ctx makeCurrentContext];
477 [EAGLContext setCurrentContext:d->ogl_ctx];
478 glBindFramebufferOES(GL_FRAMEBUFFER_OES, d->gl_framebuffer);
479 if (d->type == PIXMAP) {
480 glViewport (0, 0, d->frame.width, d->frame.height);
481 jwxyz_set_matrices (d->frame.width, d->frame.height);
491 XCreatePixmap (Display *dpy, Drawable d,
492 unsigned int width, unsigned int height, unsigned int depth)
494 Pixmap p = (Pixmap) calloc (1, sizeof(*p));
496 p->frame.width = width;
497 p->frame.height = height;
498 p->pixmap.depth = depth;
500 Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
502 /* TODO: If Pixel buffers are not supported, do something about it. */
503 Window w = XRootWindow (dpy, 0);
507 p->ogl_ctx = [[NSOpenGLContext alloc]
508 initWithFormat:w->window.pixfmt
509 shareContext:w->ogl_ctx];
510 CFRetain (p->ogl_ctx);
512 [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
514 p->pixmap.gl_pbuffer = [[NSOpenGLPixelBuffer alloc]
515 /* TODO: Only if there are rectangluar textures. */
516 initWithTextureTarget:GL_TEXTURE_RECTANGLE_EXT
517 /* TODO: Make sure GL_RGBA isn't better. */
518 textureInternalFormat:GL_RGB
519 textureMaxMipMapLevel:0
522 CFRetain (p->pixmap.gl_pbuffer);
524 [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
527 currentVirtualScreen:w->window.virtual_screen];
531 p->ogl_ctx = w->window.ogl_ctx_pixmap;
533 [EAGLContext setCurrentContext:p->ogl_ctx];
534 create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
536 glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height);
537 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
538 GL_RENDERBUFFER_OES, p->gl_renderbuffer);
540 check_framebuffer_status ();
542 glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
544 # endif // USE_IPHONE
546 w->window.current_drawable = p;
547 glViewport (0, 0, width, height);
548 jwxyz_set_matrices (width, height);
550 # ifndef __OPTIMIZE__
551 glClearColor (frand(1), frand(1), frand(1), 0);
552 glClear (GL_COLOR_BUFFER_BIT);
559 XFreePixmap (Display *d, Pixmap p)
561 Assert (p && p->type == PIXMAP, "not a pixmap");
563 Window w = RootWindow (d, 0);
566 CFRelease (p->ogl_ctx);
567 [p->ogl_ctx release];
569 CFRelease (p->pixmap.gl_pbuffer);
570 [p->pixmap.gl_pbuffer release];
572 glDeleteRenderbuffersOES (1, &p->gl_renderbuffer);
573 glDeleteFramebuffersOES (1, &p->gl_framebuffer);
574 # endif // USE_IPHONE
576 if (w->window.current_drawable == p) {
577 w->window.current_drawable = NULL;