blob: 5a097a12d26b8ed8fc12fe697523d25c005a4153 [file] [log] [blame]
/*
* 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
*/
#include <OpenGL/gl.h>
#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;
[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];
[super setView:newView];
[newView retain];
[oldView release];
}
- (void) clearDrawable
{
NSView* oldView = [self view];
[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
[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];
}