/* * MACDRV Cocoa OpenGL code * * Copyright 2012, 2013 Ken Thomases for CodeWeavers Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #define GL_SILENCE_DEPRECATION #include #import "cocoa_opengl.h" #include "macdrv_cocoa.h" #include "cocoa_event.h" @interface WineOpenGLContext () @property (retain, nonatomic) NSView* latentView; + (NSView*) dummyView; - (void) wine_updateBackingSize:(const CGSize*)size; @end @implementation WineOpenGLContext @synthesize latentView, needsUpdate, needsReattach, shouldClearToBlack; - (void) dealloc { [[self view] release]; [latentView release]; [super dealloc]; } + (NSView*) dummyView { static NSWindow* dummyWindow; static dispatch_once_t once; dispatch_once(&once, ^{ OnMainThread(^{ dummyWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; }); }); return dummyWindow.contentView; } // Normally, we take care that disconnecting a context from a view doesn't // destroy that view's GL surface (see -clearDrawableLeavingSurfaceOnScreen). // However, if we're using a surface backing size and that size changes, we // need to destroy and recreate the surface or we get weird behavior. - (void) resetSurfaceIfBackingSizeChanged { if (!retina_enabled) return; int view_backing[2]; if (macdrv_get_view_backing_size((macdrv_view)self.view, view_backing) && (view_backing[0] != backing_size[0] || view_backing[1] != backing_size[1])) { view_backing[0] = backing_size[0]; view_backing[1] = backing_size[1]; macdrv_set_view_backing_size((macdrv_view)self.view, view_backing); NSView* save = self.view; OnMainThread(^{ [super clearDrawable]; [super setView:save]; }); shouldClearToBlack = TRUE; } } - (void) wine_updateBackingSize:(const CGSize*)size { GLint enabled; if (!retina_enabled) return; if (size) { if (CGLIsEnabled(self.CGLContextObj, kCGLCESurfaceBackingSize, &enabled) != kCGLNoError) enabled = 0; if (!enabled || backing_size[0] != size->width || backing_size[1] != size->height) { backing_size[0] = size->width; backing_size[1] = size->height; CGLSetParameter(self.CGLContextObj, kCGLCPSurfaceBackingSize, backing_size); } if (!enabled) CGLEnable(self.CGLContextObj, kCGLCESurfaceBackingSize); [self resetSurfaceIfBackingSizeChanged]; } else { backing_size[0] = 0; backing_size[1] = 0; if (CGLIsEnabled(self.CGLContextObj, kCGLCESurfaceBackingSize, &enabled) == kCGLNoError && enabled) CGLDisable(self.CGLContextObj, kCGLCESurfaceBackingSize); } } - (void) setView:(NSView*)newView { NSView* oldView = [self view]; if ([NSThread isMainThread]) [super setView:newView]; else OnMainThread(^{ [super setView:newView]; }); [newView retain]; [oldView release]; } - (void) clearDrawable { NSView* oldView = [self view]; if ([NSThread isMainThread]) [super clearDrawable]; else OnMainThread(^{ [super clearDrawable]; }); [oldView release]; [self wine_updateBackingSize:NULL]; } /* On at least some versions of Mac OS X, -[NSOpenGLContext clearDrawable] has the undesirable side effect of ordering the view's GL surface off-screen. This isn't done when just changing the context's view to a different view (which I would think would be analogous, since the old view and surface end up without a context attached). So, we finesse things by first setting the context's view to a different view (the content view of an off-screen window) and then letting the original implementation proceed. */ - (void) clearDrawableLeavingSurfaceOnScreen { [self setView:[[self class] dummyView]]; [self clearDrawable]; } - (void) clearToBlackIfNeeded { if (shouldClearToBlack) { NSOpenGLContext* origContext = [NSOpenGLContext currentContext]; const char *gl_version; unsigned int major; GLint draw_framebuffer_binding, draw_buffer; GLboolean scissor_test, color_mask[4]; GLfloat clear_color[4]; [self makeCurrentContext]; gl_version = (const char *)glGetString(GL_VERSION); major = gl_version[0] - '0'; /* FIXME: Should check for GL_ARB_framebuffer_object and GL_EXT_framebuffer_object * for older GL versions. */ if (major >= 3) { glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw_framebuffer_binding); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } glGetIntegerv(GL_DRAW_BUFFER, &draw_buffer); scissor_test = glIsEnabled(GL_SCISSOR_TEST); glGetBooleanv(GL_COLOR_WRITEMASK, color_mask); glGetFloatv(GL_COLOR_CLEAR_VALUE, clear_color); glDrawBuffer(GL_FRONT_AND_BACK); glDisable(GL_SCISSOR_TEST); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glClearColor(0, 0, 0, gl_surface_mode == GL_SURFACE_IN_FRONT_TRANSPARENT ? 0 : 1); glClear(GL_COLOR_BUFFER_BIT); glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); glColorMask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]); if (scissor_test) glEnable(GL_SCISSOR_TEST); glDrawBuffer(draw_buffer); if (major >= 3) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer_binding); glFlush(); if (origContext) [origContext makeCurrentContext]; else [NSOpenGLContext clearCurrentContext]; shouldClearToBlack = FALSE; } } - (void) removeFromViews:(BOOL)removeViews { if ([self view]) { macdrv_remove_view_opengl_context((macdrv_view)[self view], (macdrv_opengl_context)self); if (removeViews) [self clearDrawableLeavingSurfaceOnScreen]; } if ([self latentView]) { macdrv_remove_view_opengl_context((macdrv_view)[self latentView], (macdrv_opengl_context)self); if (removeViews) [self setLatentView:nil]; } needsUpdate = FALSE; needsReattach = FALSE; } @end /*********************************************************************** * macdrv_create_opengl_context * * Returns a Cocoa OpenGL context created from a CoreGL context. The * caller is responsible for calling macdrv_dispose_opengl_context() * when done with the context object. */ macdrv_opengl_context macdrv_create_opengl_context(void* cglctx) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineOpenGLContext *context; context = [[WineOpenGLContext alloc] initWithCGLContextObj:cglctx]; [pool release]; return (macdrv_opengl_context)context; } /*********************************************************************** * macdrv_dispose_opengl_context * * Destroys a Cocoa OpenGL context previously created by * macdrv_create_opengl_context(); */ void macdrv_dispose_opengl_context(macdrv_opengl_context c) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineOpenGLContext *context = (WineOpenGLContext*)c; [context removeFromViews:YES]; [context release]; [pool release]; } /*********************************************************************** * macdrv_make_context_current */ void macdrv_make_context_current(macdrv_opengl_context c, macdrv_view v, CGRect r) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineOpenGLContext *context = (WineOpenGLContext*)c; NSView* view = (NSView*)v; if (context && view) { if (view == [context view] || view == [context latentView]) { [context wine_updateBackingSize:&r.size]; macdrv_update_opengl_context(c); } else { [context removeFromViews:NO]; macdrv_add_view_opengl_context(v, c); if (context.needsUpdate) { context.needsUpdate = FALSE; context.needsReattach = FALSE; if (context.view) [context setView:[[context class] dummyView]]; [context wine_updateBackingSize:&r.size]; [context setView:view]; [context setLatentView:nil]; [context resetSurfaceIfBackingSizeChanged]; } else { if ([context view]) [context clearDrawableLeavingSurfaceOnScreen]; [context wine_updateBackingSize:&r.size]; [context setLatentView:view]; } } [context makeCurrentContext]; if ([context view]) [context clearToBlackIfNeeded]; } else { WineOpenGLContext* currentContext = (WineOpenGLContext*)[WineOpenGLContext currentContext]; if ([currentContext isKindOfClass:[WineOpenGLContext class]]) { [WineOpenGLContext clearCurrentContext]; if (currentContext != context) [currentContext removeFromViews:YES]; } if (context) [context removeFromViews:YES]; } [pool release]; } /*********************************************************************** * macdrv_update_opengl_context */ void macdrv_update_opengl_context(macdrv_opengl_context c) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineOpenGLContext *context = (WineOpenGLContext*)c; if (context.needsUpdate) { BOOL reattach = context.needsReattach; context.needsUpdate = FALSE; context.needsReattach = FALSE; if (context.latentView) { [context setView:context.latentView]; context.latentView = nil; [context resetSurfaceIfBackingSizeChanged]; [context clearToBlackIfNeeded]; } else { if (reattach) { NSView* view = [[context.view retain] autorelease]; [context clearDrawableLeavingSurfaceOnScreen]; context.view = view; } else OnMainThread(^{ [context update]; }); [context resetSurfaceIfBackingSizeChanged]; } } [pool release]; } /*********************************************************************** * macdrv_flush_opengl_context * * Performs an implicit glFlush() and then swaps the back buffer to the * front (if the context is double-buffered). */ void macdrv_flush_opengl_context(macdrv_opengl_context c) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineOpenGLContext *context = (WineOpenGLContext*)c; macdrv_update_opengl_context(c); [context flushBuffer]; [pool release]; }