405 lines
14 KiB
Plaintext
405 lines
14 KiB
Plaintext
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file cocoaGraphicsStateGuardian.mm
|
|
* @author rdb
|
|
* @date 2012-05-14
|
|
*/
|
|
|
|
#include "cocoaGraphicsStateGuardian.h"
|
|
#include "config_cocoadisplay.h"
|
|
#include "lightReMutexHolder.h"
|
|
|
|
#include <mach-o/dyld.h>
|
|
#import <AppKit/AppKit.h>
|
|
#import <OpenGL/CGLRenderers.h>
|
|
|
|
#ifndef kCGLRendererIDMatchingMask
|
|
// For older versions of Mac OS X
|
|
#define kCGLRendererIDMatchingMask 0x00FE7F00
|
|
#endif
|
|
|
|
#ifndef NSAppKitVersionNumber10_7
|
|
#define NSAppKitVersionNumber10_7 1138
|
|
#endif
|
|
|
|
/**
|
|
* Called whenever a display wants a frame. The context argument contains the
|
|
* applicable CocoaGraphicsStateGuardian.
|
|
*/
|
|
static CVReturn
|
|
display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
|
|
const CVTimeStamp* output_time, CVOptionFlags flags_in,
|
|
CVOptionFlags *flags_out, void *context) {
|
|
CocoaGraphicsStateGuardian *gsg = (CocoaGraphicsStateGuardian *)context;
|
|
gsg->_swap_lock.lock();
|
|
gsg->_swap_condition.notify();
|
|
gsg->_swap_lock.unlock();
|
|
return kCVReturnSuccess;
|
|
}
|
|
|
|
TypeHandle CocoaGraphicsStateGuardian::_type_handle;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
CocoaGraphicsStateGuardian::
|
|
CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
|
|
CocoaGraphicsStateGuardian *share_with) :
|
|
GLGraphicsStateGuardian(engine, pipe),
|
|
_swap_condition(_swap_lock)
|
|
{
|
|
_share_context = nil;
|
|
_context = nil;
|
|
|
|
if (share_with != (CocoaGraphicsStateGuardian *)NULL) {
|
|
_prepared_objects = share_with->get_prepared_objects();
|
|
_share_context = share_with->_context;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
CocoaGraphicsStateGuardian::
|
|
~CocoaGraphicsStateGuardian() {
|
|
if (_format != nil) {
|
|
[_format release];
|
|
}
|
|
if (_display_link != nil) {
|
|
CVDisplayLinkRelease(_display_link);
|
|
_display_link = nil;
|
|
_swap_lock.lock();
|
|
_swap_condition.notify();
|
|
_swap_lock.unlock();
|
|
}
|
|
if (_context != nil) {
|
|
[_context clearDrawable];
|
|
[_context release];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a CVDisplayLink, which tells us when the display the window is on
|
|
* will want a frame.
|
|
*/
|
|
bool CocoaGraphicsStateGuardian::
|
|
setup_vsync() {
|
|
if (_display_link != nil) {
|
|
// Already set up.
|
|
return true;
|
|
}
|
|
|
|
CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&_display_link);
|
|
if (result != kCVReturnSuccess) {
|
|
cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n";
|
|
return false;
|
|
}
|
|
|
|
result = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_display_link, (CGLContextObj)[_context CGLContextObj], (CGLPixelFormatObj)[_format CGLPixelFormatObj]);
|
|
if (result != kCVReturnSuccess) {
|
|
cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n";
|
|
return false;
|
|
}
|
|
|
|
result = CVDisplayLinkSetOutputCallback(_display_link, &display_link_cb, this);
|
|
if (result != kCVReturnSuccess) {
|
|
cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n";
|
|
return false;
|
|
}
|
|
|
|
result = CVDisplayLinkStart(_display_link);
|
|
if (result != kCVReturnSuccess) {
|
|
cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets the FrameBufferProperties to match the indicated config.
|
|
*/
|
|
void CocoaGraphicsStateGuardian::
|
|
get_properties(FrameBufferProperties &properties, NSOpenGLPixelFormat* pixel_format, int screen) {
|
|
|
|
properties.clear();
|
|
|
|
// Now update our framebuffer_mode and bit depth appropriately.
|
|
GLint double_buffer, stereo, aux_buffers, color_float, color_size,
|
|
alpha_size, depth_size, stencil_size, accum_size, sample_buffers,
|
|
samples, renderer_id, accelerated, window, pbuffer;
|
|
|
|
[pixel_format getValues:&double_buffer forAttribute:NSOpenGLPFADoubleBuffer forVirtualScreen:screen];
|
|
[pixel_format getValues:&stereo forAttribute:NSOpenGLPFAStereo forVirtualScreen:screen];
|
|
[pixel_format getValues:&aux_buffers forAttribute:NSOpenGLPFAAuxBuffers forVirtualScreen:screen];
|
|
[pixel_format getValues:&color_float forAttribute:NSOpenGLPFAColorFloat forVirtualScreen:screen];
|
|
[pixel_format getValues:&color_size forAttribute:NSOpenGLPFAColorSize forVirtualScreen:screen];
|
|
[pixel_format getValues:&alpha_size forAttribute:NSOpenGLPFAAlphaSize forVirtualScreen:screen];
|
|
[pixel_format getValues:&depth_size forAttribute:NSOpenGLPFADepthSize forVirtualScreen:screen];
|
|
[pixel_format getValues:&stencil_size forAttribute:NSOpenGLPFAStencilSize forVirtualScreen:screen];
|
|
[pixel_format getValues:&accum_size forAttribute:NSOpenGLPFAAccumSize forVirtualScreen:screen];
|
|
[pixel_format getValues:&sample_buffers forAttribute:NSOpenGLPFASampleBuffers forVirtualScreen:screen];
|
|
[pixel_format getValues:&samples forAttribute:NSOpenGLPFASamples forVirtualScreen:screen];
|
|
[pixel_format getValues:&renderer_id forAttribute:NSOpenGLPFARendererID forVirtualScreen:screen];
|
|
[pixel_format getValues:&accelerated forAttribute:NSOpenGLPFAAccelerated forVirtualScreen:screen];
|
|
[pixel_format getValues:&window forAttribute:NSOpenGLPFAWindow forVirtualScreen:screen];
|
|
[pixel_format getValues:&pbuffer forAttribute:NSOpenGLPFAPixelBuffer forVirtualScreen:screen];
|
|
|
|
properties.set_back_buffers(double_buffer);
|
|
properties.set_stereo(stereo);
|
|
properties.set_rgb_color(1);
|
|
properties.set_float_color(color_float);
|
|
properties.set_color_bits(color_size);
|
|
properties.set_stencil_bits(stencil_size);
|
|
properties.set_depth_bits(depth_size);
|
|
properties.set_alpha_bits(alpha_size);
|
|
properties.set_accum_bits(accum_size);
|
|
if (sample_buffers > 0) {
|
|
properties.set_multisamples(samples);
|
|
}
|
|
// TODO: add aux buffers
|
|
|
|
// Cocoa doesn't provide individual RGB bits. Make assumptions.
|
|
// Note that color_size seems to be returning values like 32, which
|
|
// suggests that it contains the alpha size as well.
|
|
|
|
if (color_size == 24 || color_size == 32) {
|
|
properties.set_rgba_bits(8, 8, 8, alpha_size);
|
|
|
|
} else if (color_size == 64) {
|
|
properties.set_rgba_bits(16, 16, 16, alpha_size);
|
|
|
|
} else if (color_size == 128) {
|
|
properties.set_rgba_bits(32, 32, 32, alpha_size);
|
|
|
|
} else if (color_size >= 3) {
|
|
// Assume it's giving us at least one of each.
|
|
properties.set_red_bits(1);
|
|
properties.set_green_bits(1);
|
|
properties.set_blue_bits(1);
|
|
}
|
|
|
|
// Extract the renderer ID bits and check if our renderer matches the known
|
|
// software renderers.
|
|
renderer_id &= kCGLRendererIDMatchingMask;
|
|
if (renderer_id == kCGLRendererGenericID ||
|
|
renderer_id == kCGLRendererGenericFloatID ||
|
|
renderer_id == kCGLRendererAppleSWID) {
|
|
|
|
properties.set_force_software(1);
|
|
}
|
|
|
|
if (accelerated) {
|
|
properties.set_force_hardware(1);
|
|
}
|
|
|
|
// Cautiously setting this to true. It appears that macOS framebuffers are
|
|
// sRGB-capable, but I don't really know how to verify this.
|
|
if (color_size == 32 && !color_float) {
|
|
properties.set_srgb_color(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects a visual or fbconfig for all the windows and buffers that use this
|
|
* gsg. Also creates the GL context and obtains the visual.
|
|
*/
|
|
void CocoaGraphicsStateGuardian::
|
|
choose_pixel_format(const FrameBufferProperties &properties,
|
|
CGDirectDisplayID display,
|
|
bool need_pbuffer) {
|
|
|
|
_context = nil;
|
|
_fbprops.clear();
|
|
|
|
// Neither Cocoa nor CGL seem to have a mechanism to query the available
|
|
// pixel formats, unfortunately, so the only thing we can do is ask for one
|
|
// with the properties we have requested.
|
|
pvector<NSOpenGLPixelFormatAttribute> attribs;
|
|
attribs.reserve(15);
|
|
|
|
// Picked this up from the pyglet source - seems to be necessary to support
|
|
// RAGE-II, which is not compliant.
|
|
attribs.push_back(NSOpenGLPFAAllRenderers);
|
|
|
|
// Don't let it fall back to a different renderer.
|
|
attribs.push_back(NSOpenGLPFANoRecovery);
|
|
|
|
// Consider pixel formats with properties equal to or better than we
|
|
// requested.
|
|
attribs.push_back(NSOpenGLPFAMinimumPolicy);
|
|
|
|
if (!properties.is_single_buffered()) {
|
|
attribs.push_back(NSOpenGLPFADoubleBuffer);
|
|
}
|
|
|
|
if (properties.is_stereo()) {
|
|
attribs.push_back(NSOpenGLPFAStereo);
|
|
}
|
|
|
|
int aux_buffers = properties.get_aux_rgba() + properties.get_aux_hrgba() + properties.get_aux_float();
|
|
attribs.push_back(NSOpenGLPFAAuxBuffers);
|
|
attribs.push_back(aux_buffers);
|
|
attribs.push_back(NSOpenGLPFAColorSize);
|
|
attribs.push_back(properties.get_color_bits());
|
|
|
|
// Set the depth buffer bits to 24 manually when 1 is requested.
|
|
// This prevents getting a depth buffer of only 16 bits when requesting 1.
|
|
attribs.push_back(NSOpenGLPFADepthSize);
|
|
if (properties.get_depth_bits() == 1) {
|
|
attribs.push_back(24);
|
|
}
|
|
else {
|
|
attribs.push_back(properties.get_depth_bits());
|
|
}
|
|
|
|
attribs.push_back(NSOpenGLPFAStencilSize);
|
|
attribs.push_back(properties.get_stencil_bits());
|
|
|
|
// Curious case - if we request anything less than 8 alpha bits, then on
|
|
// some ATI cards, it will grab a pixel format with just 2 alpha bits, which
|
|
// just shows a white window and nothing else. Might have something to do
|
|
// with the compositing window manager. Omitting it altogether seems to
|
|
// make it grab one with 8 bits, though. Dirty hack. Needs more research.
|
|
if (properties.get_alpha_bits() > 0) {
|
|
attribs.push_back(NSOpenGLPFAAlphaSize);
|
|
attribs.push_back(std::max(8, properties.get_alpha_bits()));
|
|
}
|
|
|
|
if (properties.get_multisamples() > 0) {
|
|
attribs.push_back(NSOpenGLPFASampleBuffers);
|
|
attribs.push_back(1);
|
|
attribs.push_back(NSOpenGLPFASamples);
|
|
attribs.push_back(properties.get_multisamples());
|
|
attribs.push_back(NSOpenGLPFAMultisample);
|
|
}
|
|
|
|
if (properties.get_force_software()) {
|
|
// Request the generic software renderer.
|
|
attribs.push_back(NSOpenGLPFARendererID);
|
|
attribs.push_back(kCGLRendererGenericFloatID);
|
|
}
|
|
|
|
if (properties.get_force_hardware()) {
|
|
attribs.push_back(NSOpenGLPFAAccelerated);
|
|
}
|
|
|
|
// This seems to cause getting a 3.2+ context to fail.
|
|
//attribs.push_back(NSOpenGLPFAWindow);
|
|
|
|
if (need_pbuffer) {
|
|
attribs.push_back(NSOpenGLPFAPixelBuffer);
|
|
}
|
|
|
|
// Required when going fullscreen, optional when windowed
|
|
attribs.push_back(NSOpenGLPFAScreenMask);
|
|
attribs.push_back(CGDisplayIDToOpenGLDisplayMask(display));
|
|
|
|
// Set OpenGL version if a minimum was requested.
|
|
if (gl_version.size() >= 1 && NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) {
|
|
//NB. There is also NSOpenGLProfileVersion4_1Core, but this seems to cause
|
|
// a software implementation to be selected on my mac mini running 10.11.
|
|
if (gl_version[0] >= 4 || (gl_version.size() >= 2 && gl_version[0] == 3 && gl_version[1] >= 2)) {
|
|
attribs.push_back((NSOpenGLPixelFormatAttribute)99); // NSOpenGLPFAOpenGLProfile
|
|
attribs.push_back((NSOpenGLPixelFormatAttribute)0x3200); // NSOpenGLProfileVersion3_2Core
|
|
}
|
|
}
|
|
|
|
// End of the array
|
|
attribs.push_back((NSOpenGLPixelFormatAttribute)0);
|
|
|
|
// Create the format.
|
|
NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
|
|
if (format == nil) {
|
|
cocoadisplay_cat.error() <<
|
|
"Could not find a usable pixel format.\n";
|
|
return;
|
|
}
|
|
|
|
// For now, I'm just using the first virtual screen.
|
|
if (cocoadisplay_cat.is_debug()) {
|
|
cocoadisplay_cat.debug() <<
|
|
"Pixel format has " << [format numberOfVirtualScreens] << " virtual screens.\n";
|
|
}
|
|
get_properties(_fbprops, format, 0);
|
|
|
|
// Don't enable sRGB unless it was explicitly requested.
|
|
if (!properties.get_srgb_color()) {
|
|
_fbprops.set_srgb_color(false);
|
|
}
|
|
|
|
// TODO: print out renderer
|
|
|
|
_context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:_share_context];
|
|
_format = format;
|
|
if (_context == nil) {
|
|
cocoadisplay_cat.error() <<
|
|
"Failed to create OpenGL context!\n";
|
|
return;
|
|
}
|
|
|
|
// Disable vsync via the built-in mechanism, which doesn't work on Mojave
|
|
GLint swap = 0;
|
|
[_context setValues:&swap forParameter:NSOpenGLCPSwapInterval];
|
|
|
|
if (cocoadisplay_cat.is_debug()) {
|
|
cocoadisplay_cat.debug()
|
|
<< "Created context " << _context << ": " << _fbprops << "\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queries the runtime version of OpenGL in use.
|
|
*/
|
|
void CocoaGraphicsStateGuardian::
|
|
query_gl_version() {
|
|
GLGraphicsStateGuardian::query_gl_version();
|
|
|
|
// We output to glgsg_cat instead of glxdisplay_cat, since this is where the
|
|
// GL version has been output, and it's nice to see the two of these
|
|
// together.
|
|
if (glgsg_cat.is_debug()) {
|
|
// XXX this is supposed to work, but the NSOpenGLGetVersion symbol cannot
|
|
// be found when I do this
|
|
|
|
// GLint major, minor; NSOpenGLGetVersion(&major, &minor);
|
|
|
|
// glgsg_cat.debug() << "NSOpenGLVersion = " << major << "." << minor <<
|
|
// "\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the pointer to the GL extension function with the indicated name.
|
|
* It is the responsibility of the caller to ensure that the required
|
|
* extension is defined in the OpenGL runtime prior to calling this; it is an
|
|
* error to call this for a function that is not defined.
|
|
*/
|
|
void *CocoaGraphicsStateGuardian::
|
|
do_get_extension_func(const char *name) {
|
|
char* fullname = (char*) malloc(strlen(name) + 2);
|
|
strcpy(fullname + 1, name);
|
|
fullname[0] = '_';
|
|
|
|
// Believe it or not, but this is actually the Apple-recommended way to do
|
|
// it. I know, right?
|
|
|
|
if (NSIsSymbolNameDefined(fullname)) {
|
|
NSSymbol symbol = NSLookupAndBindSymbol(fullname);
|
|
free(fullname);
|
|
return (void *) NSAddressOfSymbol(symbol);
|
|
}
|
|
|
|
cocoadisplay_cat.warning() <<
|
|
"do_get_extension_func failed for " << fullname << "!\n";
|
|
|
|
free(fullname);
|
|
return NULL;
|
|
}
|