X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=jwxyz%2Fjwxyz-cocoa.m;fp=jwxyz%2Fjwxyz-cocoa.m;h=3b6bf4119f03ebfe297c7353d1e6e2bb360839a8;hp=0000000000000000000000000000000000000000;hb=aa75c7476aeaa84cf3abc192b376a8b03c325213;hpb=88cfe534a698a0562e81345957a50714af1453bc diff --git a/jwxyz/jwxyz-cocoa.m b/jwxyz/jwxyz-cocoa.m new file mode 100644 index 00000000..3b6bf411 --- /dev/null +++ b/jwxyz/jwxyz-cocoa.m @@ -0,0 +1,577 @@ +/* xscreensaver, Copyright (c) 1991-2016 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* JWXYZ Is Not Xlib. + + But it's a bunch of function definitions that bear some resemblance to + Xlib and that do Cocoa-ish or OpenGL-ish things that bear some resemblance + to the things that Xlib might have done. + + This code is used by both the original jwxyz.m and the new jwxyz-gl.c. + */ + +#import "jwxyzI.h" +#import "jwxyz-cocoa.h" + +#include + +#ifdef USE_IPHONE +# import +# import +# define NSOpenGLContext EAGLContext +#endif + +/* OS X/iOS-specific JWXYZ implementation. */ + +/* Instead of calling abort(), throw a real exception, so that + XScreenSaverView can catch it and display a dialog. + */ +void +jwxyz_abort (const char *fmt, ...) +{ + char s[10240]; + if (!fmt || !*fmt) + strcpy (s, "abort"); + else + { + va_list args; + va_start (args, fmt); + vsprintf (s, fmt, args); + va_end (args); + } + [[NSException exceptionWithName: NSInternalInconsistencyException + reason: [NSString stringWithCString: s + encoding:NSUTF8StringEncoding] + userInfo: nil] + raise]; + abort(); // not reached +} + + +const XRectangle * +jwxyz_frame (Drawable d) +{ + return &d->frame; +} + + +unsigned int +jwxyz_drawable_depth (Drawable d) +{ + return (d->type == WINDOW + ? visual_depth (NULL, NULL) + : d->pixmap.depth); +} + + +void +jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp) +{ +# ifdef USE_IPHONE + + xvpos->x = 0; + xvpos->y = 0; + + if (xp) { + xp->x = w->window.last_mouse_x; + xp->y = w->window.last_mouse_y; + } + +# else // !USE_IPHONE + + NSWindow *nsw = [w->window.view window]; + + // get bottom left of window on screen, from bottom left + +# if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6) + NSRect rr1 = [w->window.view convertRect: NSMakeRect(0,0,0,0) toView:nil]; + NSRect rr2 = [nsw convertRectToScreen: rr1]; + NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x, + rr2.origin.y - rr1.origin.y); +# else + // deprecated as of 10.7 + NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)]; +# endif + + + // get bottom left of view on window, from bottom left + NSPoint vpos; + vpos.x = vpos.y = 0; + vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]]; + + // get bottom left of view on screen, from bottom left + vpos.x += wpos.x; + vpos.y += wpos.y; + + // get top left of view on screen, from bottom left + vpos.y += w->frame.height; + + // get top left of view on screen, from top left + NSArray *screens = [NSScreen screens]; + NSScreen *screen = (screens && [screens count] > 0 + ? [screens objectAtIndex:0] + : [NSScreen mainScreen]); + NSRect srect = [screen frame]; + vpos.y = srect.size.height - vpos.y; + + xvpos->x = vpos.x; + xvpos->y = vpos.y; + + if (xp) { + // get the mouse position on window, from bottom left + NSEvent *e = [NSApp currentEvent]; + NSPoint p = [e locationInWindow]; + + // get mouse position on screen, from bottom left + p.x += wpos.x; + p.y += wpos.y; + + // get mouse position on screen, from top left + p.y = srect.size.height - p.y; + + xp->x = (int) p.x; + xp->y = (int) p.y; + } + +# endif // !USE_IPHONE +} + + +#ifdef USE_IPHONE + +void +check_framebuffer_status (void) +{ + int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES); + switch (err) { + case GL_FRAMEBUFFER_COMPLETE_OES: + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES: + Assert (0, "framebuffer incomplete attachment"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES: + Assert (0, "framebuffer incomplete missing attachment"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES: + Assert (0, "framebuffer incomplete dimensions"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES: + Assert (0, "framebuffer incomplete formats"); + break; + case GL_FRAMEBUFFER_UNSUPPORTED_OES: + Assert (0, "framebuffer unsupported"); + break; +/* + case GL_FRAMEBUFFER_UNDEFINED: + Assert (0, "framebuffer undefined"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + Assert (0, "framebuffer incomplete draw buffer"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + Assert (0, "framebuffer incomplete read buffer"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + Assert (0, "framebuffer incomplete multisample"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: + Assert (0, "framebuffer incomplete layer targets"); + break; + */ + default: + NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err); + break; + } +} + + +void +create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer) +{ + glGenFramebuffersOES (1, gl_framebuffer); + glBindFramebufferOES (GL_FRAMEBUFFER_OES, *gl_framebuffer); + + glGenRenderbuffersOES (1, gl_renderbuffer); + glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer); +} + +#endif // USE_IPHONE + + +#if defined JWXYZ_QUARTZ + +/* Pushes a GC context; sets Fill and Stroke colors to the background color. + */ +static void +push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p) +{ + XGCValues *gcv = jwxyz_gc_gcv (gc); + push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p); +} + + +void +jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc, + int src_x, int src_y, + unsigned int width, unsigned int height, + int dst_x, int dst_y) +{ + XRectangle src_frame = src->frame, dst_frame = dst->frame; + XGCValues *gcv = jwxyz_gc_gcv (gc); + + BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1; + + + /* If we're copying from a bitmap to a bitmap, and there's nothing funny + going on with clipping masks or depths or anything, optimize it by + just doing a memcpy instead of going through a CGI. + */ + if (gcv->function == GXcopy && + !gcv->clip_mask && + jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) { + + Assert(!src_frame.x && + !src_frame.y && + !dst_frame.x && + !dst_frame.y, + "unexpected non-zero origin"); + + ptrdiff_t src_pitch = CGBitmapContextGetBytesPerRow(src->cgc); + ptrdiff_t dst_pitch = CGBitmapContextGetBytesPerRow(dst->cgc); + char *src_data = seek_xy (CGBitmapContextGetData (src->cgc), src_pitch, + src_x, src_y); + char *dst_data = seek_xy (CGBitmapContextGetData (dst->cgc), dst_pitch, + dst_x, dst_y); + + size_t bytes = width * 4; + + if (src == dst && dst_y > src_y) { + // Copy upwards if the areas might overlap. + src_data += src_pitch * (height - 1); + dst_data += dst_pitch * (height - 1); + src_pitch = -src_pitch; + dst_pitch = -dst_pitch; + } + + while (height) { + // memcpy is an alias for memmove on OS X. + memmove(dst_data, src_data, bytes); + src_data += src_pitch; + dst_data += dst_pitch; + --height; + } + } else { + CGRect + src_rect = CGRectMake(src_x, src_y, width, height), + dst_rect = CGRectMake(dst_x, dst_y, width, height); + + src_rect.origin = map_point (src, src_rect.origin.x, + src_rect.origin.y + src_rect.size.height); + dst_rect.origin = map_point (dst, dst_rect.origin.x, + dst_rect.origin.y + src_rect.size.height); + + NSObject *releaseme = 0; + CGImageRef cgi; + BOOL free_cgi_p = NO; + + // We must first copy the bits to an intermediary CGImage object, then + // copy that to the destination drawable's CGContext. + // + // First we get a CGImage out of the pixmap CGContext -- it's the whole + // pixmap, but it presumably shares the data pointer instead of copying + // it. We then cache that CGImage it inside the Pixmap object. Note: + // invalidate_drawable_cache() must be called to discard this any time a + // modification is made to the pixmap, or we'll end up re-using old bits. + // + if (!src->cgi) + src->cgi = CGBitmapContextCreateImage (src->cgc); + cgi = src->cgi; + + // if doing a sub-rect, trim it down. + if (src_rect.origin.x != src_frame.x || + src_rect.origin.y != src_frame.y || + src_rect.size.width != src_frame.width || + src_rect.size.height != src_frame.height) { + // #### I don't understand why this is needed... + src_rect.origin.y = (src_frame.height - + src_rect.size.height - src_rect.origin.y); + // This does not copy image data, so it should be fast. + cgi = CGImageCreateWithImageInRect (cgi, src_rect); + free_cgi_p = YES; + } + + CGContextRef cgc = dst->cgc; + + if (mask_p) { // src depth == 1 + + push_bg_gc (dpy, dst, gc, YES); + + // fill the destination rectangle with solid background... + CGContextFillRect (cgc, dst_rect); + + Assert (cgc, "no CGC with 1-bit XCopyArea"); + + // then fill in a solid rectangle of the fg color, using the image as an + // alpha mask. (the image has only values of BlackPixel or WhitePixel.) + set_color (dpy, cgc, gcv->foreground, jwxyz_gc_depth (gc), + gcv->alpha_allowed_p, YES); + CGContextClipToMask (cgc, dst_rect, cgi); + CGContextFillRect (cgc, dst_rect); + + pop_gc (dst, gc); + + } else { // src depth > 1 + + push_gc (dst, gc); + + // copy the CGImage onto the destination CGContext + //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace"); + CGContextDrawImage (cgc, dst_rect, cgi); + + pop_gc (dst, gc); + } + + if (free_cgi_p) CGImageRelease (cgi); + + if (releaseme) [releaseme release]; + } + + invalidate_drawable_cache (dst); +} + +#elif defined JWXYZ_GL + +/* Warning! The JWXYZ_GL code here is experimental and provisional and not at + all ready for prime time. Please be careful. + */ + +void +jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc, + int src_x, int src_y, + unsigned int width, unsigned int height, + int dst_x, int dst_y) +{ + /* TODO: + - glCopyPixels if src == dst + - Pixel buffer copying + - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE, + which is like a blit. + */ + + /* Strange and ugly flickering when going the glCopyTexImage2D route on + OS X. (Early 2009 Mac mini, OS X 10.10) + */ +# ifdef USE_IPHONE + jwxyz_gl_copy_area_copy_tex_image (dpy, src, dst, gc, src_x, src_y, + width, height, dst_x, dst_y); +# else // !USE_IPHONE + jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, + src_x, src_y, width, height, dst_x, dst_y); +# endif // !USE_IPHONE + jwxyz_assert_gl (); +} + + +void +jwxyz_assert_gl () +{ + // This is like check_gl_error, except this happens for debug builds only. +#ifndef __OPTIMIZE__ + if([NSOpenGLContext currentContext]) + { + // glFinish here drops FPS into the toilet. It might need to be on if + // something goes wrong. + // glFinish(); + GLenum error = glGetError(); + Assert (!error, "jwxyz_assert_gl: OpenGL error"); + } +#endif // !__OPTIMIZE__ +} + +void +jwxyz_assert_drawable (Window main_window, Drawable d) +{ +#if !defined USE_IPHONE && !defined __OPTIMIZE__ + XScreenSaverView *view = main_window->window.view; + NSOpenGLContext *ogl_ctx = [view oglContext]; + + if (d->type == WINDOW) { + Assert([ogl_ctx view] == view, + "jwxyz_assert_display: ogl_ctx view not set!"); + } + + @try { + /* Assert([d->ctx isKindOfClass:[NSOpenGLContext class]], "Not a context."); */ + Class c = [ogl_ctx class]; + Assert([c isSubclassOfClass:[NSOpenGLContext class]], "Not a context."); + // [d->ctx makeCurrentContext]; + } + @catch (NSException *exception) { + perror([[exception reason] UTF8String]); + abort(); + } +#endif // !USE_IPHONE && !__OPTIMIZE__ +} + + +void +jwxyz_bind_drawable (Window main_window, Drawable d) +{ + /* Windows and Pixmaps need to use different contexts with OpenGL + screenhacks, because an OpenGL screenhack sets state in an arbitrary + fashion, but jwxyz-gl.c has its own ideas w.r.t. OpenGL state. + + On iOS, all pixmaps can use the same context with different FBOs. Which + is nice. + */ + + /* OpenGL screenhacks in general won't be drawing on the Window, but they + can and will draw on a Pixmap -- but an OpenGL call following an Xlib + call won't be able to fix the fact that it's drawing offscreen. + */ + + /* EXT_direct_state_access might be appropriate, but it's apparently not + available on Mac OS X. + */ + + // jwxyz_assert_display (dpy); + jwxyz_assert_drawable (main_window, main_window); + jwxyz_assert_gl (); + jwxyz_assert_drawable (main_window, d); + +#if defined USE_IPHONE && !defined __OPTIMIZE__ + Drawable current_drawable = main_window->window.current_drawable; + Assert (!current_drawable + || current_drawable->ogl_ctx == [EAGLContext currentContext], + "bind_drawable: Wrong context."); + if (current_drawable) { + GLint framebuffer; + glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer); + Assert (framebuffer == current_drawable->gl_framebuffer, + "bind_drawable: Wrong framebuffer."); + } +#endif + + if (main_window->window.current_drawable != d) { + main_window->window.current_drawable = d; + + /* Doing this repeatedly is probably not OK performance-wise. Probably. */ +#ifndef USE_IPHONE + [d->ogl_ctx makeCurrentContext]; +#else + [EAGLContext setCurrentContext:d->ogl_ctx]; + glBindFramebufferOES(GL_FRAMEBUFFER_OES, d->gl_framebuffer); + if (d->type == PIXMAP) { + glViewport (0, 0, d->frame.width, d->frame.height); + jwxyz_set_matrices (d->frame.width, d->frame.height); + } +#endif + } + + jwxyz_assert_gl (); +} + + +Pixmap +XCreatePixmap (Display *dpy, Drawable d, + unsigned int width, unsigned int height, unsigned int depth) +{ + Pixmap p = (Pixmap) calloc (1, sizeof(*p)); + p->type = PIXMAP; + p->frame.width = width; + p->frame.height = height; + p->pixmap.depth = depth; + + Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32"); + + /* TODO: If Pixel buffers are not supported, do something about it. */ + Window w = XRootWindow (dpy, 0); + +# ifndef USE_IPHONE + + p->ogl_ctx = [[NSOpenGLContext alloc] + initWithFormat:w->window.pixfmt + shareContext:w->ogl_ctx]; + CFRetain (p->ogl_ctx); + + [p->ogl_ctx makeCurrentContext]; // This is indeed necessary. + + p->pixmap.gl_pbuffer = [[NSOpenGLPixelBuffer alloc] + /* TODO: Only if there are rectangluar textures. */ + initWithTextureTarget:GL_TEXTURE_RECTANGLE_EXT + /* TODO: Make sure GL_RGBA isn't better. */ + textureInternalFormat:GL_RGB + textureMaxMipMapLevel:0 + pixelsWide:width + pixelsHigh:height]; + CFRetain (p->pixmap.gl_pbuffer); + + [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer + cubeMapFace:0 + mipMapLevel:0 + currentVirtualScreen:w->window.virtual_screen]; + +# else // USE_IPHONE + + p->ogl_ctx = w->window.ogl_ctx_pixmap; + + [EAGLContext setCurrentContext:p->ogl_ctx]; + create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer); + + glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height); + glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, + GL_RENDERBUFFER_OES, p->gl_renderbuffer); + + check_framebuffer_status (); + + glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer); + +# endif // USE_IPHONE + + w->window.current_drawable = p; + glViewport (0, 0, width, height); + jwxyz_set_matrices (width, height); + +# ifndef __OPTIMIZE__ + glClearColor (frand(1), frand(1), frand(1), 0); + glClear (GL_COLOR_BUFFER_BIT); +# endif + + return p; +} + +int +XFreePixmap (Display *d, Pixmap p) +{ + Assert (p && p->type == PIXMAP, "not a pixmap"); + + Window w = RootWindow (d, 0); + +# ifndef USE_IPHONE + CFRelease (p->ogl_ctx); + [p->ogl_ctx release]; + + CFRelease (p->pixmap.gl_pbuffer); + [p->pixmap.gl_pbuffer release]; +# else // USE_IPHONE + glDeleteRenderbuffersOES (1, &p->gl_renderbuffer); + glDeleteFramebuffersOES (1, &p->gl_framebuffer); +# endif // USE_IPHONE + + if (w->window.current_drawable == p) { + w->window.current_drawable = NULL; + } + + free (p); + return 0; +} + +#endif // JWXYZ_GL