wine-wine/dlls/winemac.drv/cocoa_opengl.m

389 lines
12 KiB
Matlab
Raw Normal View History

2013-03-06 10:59:07 +00:00
/*
* 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 <OpenGL/gl.h>
2013-03-06 10:59:07 +00:00
#import "cocoa_opengl.h"
#include "macdrv_cocoa.h"
#include "cocoa_event.h"
2013-03-06 10:59:07 +00:00
@interface WineOpenGLContext ()
@property (retain, nonatomic) NSView* latentView;
+ (NSView*) dummyView;
- (void) wine_updateBackingSize:(const CGSize*)size;
2013-03-06 10:59:07 +00:00
@end
@implementation WineOpenGLContext
@synthesize latentView, needsUpdate, needsReattach, shouldClearToBlack;
2013-03-06 10:59:07 +00:00
- (void) dealloc
{
[[self view] release];
2013-03-06 10:59:07 +00:00
[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);
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 01:35:25 +00:00
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;
}
2013-03-06 10:59:07 +00:00
@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];
2013-03-06 10:59:07 +00:00
[context release];
[pool release];
}
/***********************************************************************
* macdrv_make_context_current
*/
void macdrv_make_context_current(macdrv_opengl_context c, macdrv_view v, CGRect r)
2013-03-06 10:59:07 +00:00
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineOpenGLContext *context = (WineOpenGLContext*)c;
NSView* view = (NSView*)v;
if (context && view)
2013-03-06 10:59:07 +00:00
{
if (view == [context view] || view == [context latentView])
{
[context wine_updateBackingSize:&r.size];
macdrv_update_opengl_context(c);
}
2013-03-06 10:59:07 +00:00
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];
}
2013-03-06 10:59:07 +00:00
}
[context makeCurrentContext];
if ([context view])
[context clearToBlackIfNeeded];
2013-03-06 10:59:07 +00:00
}
else
{
WineOpenGLContext* currentContext = (WineOpenGLContext*)[WineOpenGLContext currentContext];
if ([currentContext isKindOfClass:[WineOpenGLContext class]])
{
[WineOpenGLContext clearCurrentContext];
if (currentContext != context)
[currentContext removeFromViews:YES];
}
if (context)
[context removeFromViews:YES];
}
2013-03-06 10:59:07 +00:00
[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;
2013-03-06 10:59:07 +00:00
context.needsUpdate = FALSE;
context.needsReattach = FALSE;
2013-03-06 10:59:07 +00:00
if (context.latentView)
{
[context setView:context.latentView];
context.latentView = nil;
[context resetSurfaceIfBackingSizeChanged];
[context clearToBlackIfNeeded];
2013-03-06 10:59:07 +00:00
}
else
{
if (reattach)
{
NSView* view = [[context.view retain] autorelease];
[context clearDrawableLeavingSurfaceOnScreen];
context.view = view;
}
else OnMainThread(^{
[context update];
});
[context resetSurfaceIfBackingSizeChanged];
}
2013-03-06 10:59:07 +00:00
}
[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];
}