blob: 6db4bdb9fda9e0dad3f04e0053426b295c295de3 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qcocoaglcontext.h"
#include "qcocoawindow.h"
#include "qcocoahelpers.h"
#include "qcocoascreen.h"
#include <qdebug.h>
#include <QtPlatformHeaders/qcocoanativecontext.h>
#include <dlfcn.h>
#import <AppKit/AppKit.h>
static inline QByteArray getGlString(GLenum param)
{
if (const GLubyte *s = glGetString(param))
return QByteArray(reinterpret_cast<const char*>(s));
return QByteArray();
}
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaOpenGLContext, "qt.qpa.openglcontext", QtWarningMsg);
QCocoaGLContext::QCocoaGLContext(QOpenGLContext *context)
: QPlatformOpenGLContext()
, m_format(context->format())
{
}
void QCocoaGLContext::initialize()
{
QVariant nativeHandle = context()->nativeHandle();
if (!nativeHandle.isNull()) {
if (!nativeHandle.canConvert<QCocoaNativeContext>()) {
qCWarning(lcQpaOpenGLContext, "QOpenGLContext native handle must be a QCocoaNativeContext");
return;
}
m_context = nativeHandle.value<QCocoaNativeContext>().context();
if (!m_context) {
qCWarning(lcQpaOpenGLContext, "QCocoaNativeContext's NSOpenGLContext cannot be null");
return;
}
[m_context retain];
// Note: We have no way of knowing whether the NSOpenGLContext was created with the
// share context as reported by the QOpenGLContext, but we just have to trust that
// it was. It's okey, as the only thing we're using it for is to report isShared().
if (QPlatformOpenGLContext *shareContext = context()->shareHandle())
m_shareContext = static_cast<QCocoaGLContext *>(shareContext)->nativeContext();
updateSurfaceFormat();
return;
}
// ----------- Default case, we own the NSOpenGLContext -----------
// We only support OpenGL contexts under Cocoa
if (m_format.renderableType() == QSurfaceFormat::DefaultRenderableType)
m_format.setRenderableType(QSurfaceFormat::OpenGL);
if (m_format.renderableType() != QSurfaceFormat::OpenGL)
return;
if (QPlatformOpenGLContext *shareContext = context()->shareHandle()) {
m_shareContext = static_cast<QCocoaGLContext *>(shareContext)->nativeContext();
// Allow sharing between 3.2 Core and 4.1 Core profile versions in
// cases where NSOpenGLContext creates a 4.1 context where a 3.2
// context was requested. Due to the semantics of QSurfaceFormat
// this 4.1 version can find its way onto the format for the new
// context, even though it was at no point requested by the user.
GLint shareContextRequestedProfile;
[m_shareContext.pixelFormat getValues:&shareContextRequestedProfile
forAttribute:NSOpenGLPFAOpenGLProfile forVirtualScreen:0];
auto shareContextActualProfile = shareContext->format().version();
if (shareContextRequestedProfile == NSOpenGLProfileVersion3_2Core
&& shareContextActualProfile >= qMakePair(4, 1)) {
// There is a mismatch. Downgrade requested format to make the
// NSOpenGLPFAOpenGLProfile attributes match. (NSOpenGLContext
// will fail to create a new context if there is a mismatch).
if (m_format.version() >= qMakePair(4, 1))
m_format.setVersion(3, 2);
}
}
// ------------------------- Create NSOpenGLContext -------------------------
NSOpenGLPixelFormat *pixelFormat = [pixelFormatForSurfaceFormat(m_format) autorelease];
m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:m_shareContext];
if (!m_context && m_shareContext) {
qCWarning(lcQpaOpenGLContext, "Could not create NSOpenGLContext with shared context, "
"falling back to unshared context.");
m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
m_shareContext = nil;
}
if (!m_context) {
qCWarning(lcQpaOpenGLContext, "Failed to create NSOpenGLContext");
return;
}
// The native handle should reflect the underlying context, even if we created it
context()->setNativeHandle(QVariant::fromValue<QCocoaNativeContext>(m_context));
// --------------------- Set NSOpenGLContext properties ---------------------
const GLint interval = m_format.swapInterval() >= 0 ? m_format.swapInterval() : 1;
[m_context setValues:&interval forParameter:NSOpenGLCPSwapInterval];
if (m_format.alphaBufferSize() > 0) {
int zeroOpacity = 0;
[m_context setValues:&zeroOpacity forParameter:NSOpenGLCPSurfaceOpacity];
}
// OpenGL surfaces can be ordered either above(default) or below the NSWindow
// FIXME: Promote to QSurfaceFormat option or property
const GLint order = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER");
[m_context setValues:&order forParameter:NSOpenGLCPSurfaceOrder];
updateSurfaceFormat();
qCDebug(lcQpaOpenGLContext).verbosity(3) << "Created" << this << "based on requested" << context()->format();
}
NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurfaceFormat &format)
{
QVector<NSOpenGLPixelFormatAttribute> attrs;
attrs << NSOpenGLPFAOpenGLProfile;
if (format.profile() == QSurfaceFormat::CoreProfile) {
if (format.version() >= qMakePair(4, 1))
attrs << NSOpenGLProfileVersion4_1Core;
else if (format.version() >= qMakePair(3, 2))
attrs << NSOpenGLProfileVersion3_2Core;
else
attrs << NSOpenGLProfileVersionLegacy;
} else {
attrs << NSOpenGLProfileVersionLegacy;
}
switch (format.swapBehavior()) {
case QSurfaceFormat::SingleBuffer:
break; // The NSOpenGLPixelFormat default, no attribute to set
case QSurfaceFormat::DefaultSwapBehavior:
// Technically this should be single-buffered, but we force double-buffered
// FIXME: Why do we force double-buffered?
Q_FALLTHROUGH();
case QSurfaceFormat::DoubleBuffer:
attrs.append(NSOpenGLPFADoubleBuffer);
break;
case QSurfaceFormat::TripleBuffer:
attrs.append(NSOpenGLPFATripleBuffer);
break;
}
if (format.depthBufferSize() > 0)
attrs << NSOpenGLPFADepthSize << format.depthBufferSize();
if (format.stencilBufferSize() > 0)
attrs << NSOpenGLPFAStencilSize << format.stencilBufferSize();
if (format.alphaBufferSize() > 0)
attrs << NSOpenGLPFAAlphaSize << format.alphaBufferSize();
auto rbz = format.redBufferSize();
auto gbz = format.greenBufferSize();
auto bbz = format.blueBufferSize();
if (rbz > 0 || gbz > 0 || bbz > 0) {
auto fallbackSize = qMax(rbz, qMax(gbz, bbz));
auto colorSize = (rbz > 0 ? rbz : fallbackSize)
+ (gbz > 0 ? gbz : fallbackSize)
+ (bbz > 0 ? bbz : fallbackSize);
attrs << NSOpenGLPFAColorSize << colorSize << NSOpenGLPFAMinimumPolicy;
}
if (format.samples() > 0) {
attrs << NSOpenGLPFAMultisample
<< NSOpenGLPFASampleBuffers << NSOpenGLPixelFormatAttribute(1)
<< NSOpenGLPFASamples << NSOpenGLPixelFormatAttribute(format.samples());
}
//Workaround for problems with Chromium and offline renderers on the lat 2013 MacPros.
//FIXME: Think if this could be solved via QSurfaceFormat in the future.
static bool offlineRenderersAllowed = qEnvironmentVariableIsEmpty("QT_MAC_PRO_WEBENGINE_WORKAROUND");
if (offlineRenderersAllowed) {
// Allow rendering on GPUs without a connected display
attrs << NSOpenGLPFAAllowOfflineRenderers;
}
if (qGuiApp->testAttribute(Qt::AA_UseSoftwareOpenGL)) {
// kCGLRendererGenericFloatID is the modern software renderer on macOS,
// as opposed to kCGLRendererGenericID, which is deprecated.
attrs << NSOpenGLPFARendererID << kCGLRendererGenericFloatID;
}
attrs << 0; // 0-terminate array
return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()];
}
/*!
Updates the surface format of this context based on properties of
the native context and GL state, so that the result of creating
the context is reflected back in QOpenGLContext.
*/
void QCocoaGLContext::updateSurfaceFormat()
{
NSOpenGLContext *oldContext = [NSOpenGLContext currentContext];
[m_context makeCurrentContext];
// --------------------- Query GL state ---------------------
int major = 0, minor = 0;
QByteArray versionString(getGlString(GL_VERSION));
if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) {
m_format.setMajorVersion(major);
m_format.setMinorVersion(minor);
}
m_format.setProfile(QSurfaceFormat::NoProfile);
if (m_format.version() >= qMakePair(3, 2)) {
GLint value = 0;
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value);
if (value & GL_CONTEXT_CORE_PROFILE_BIT)
m_format.setProfile(QSurfaceFormat::CoreProfile);
else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
m_format.setProfile(QSurfaceFormat::CompatibilityProfile);
}
m_format.setOption(QSurfaceFormat::DeprecatedFunctions, [&]() {
if (m_format.version() < qMakePair(3, 0)) {
return true;
} else {
GLint value = 0;
glGetIntegerv(GL_CONTEXT_FLAGS, &value);
return !(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT);
}
}());
// Debug contexts not supported on macOS
m_format.setOption(QSurfaceFormat::DebugContext, false);
// Nor are stereo buffers (deprecated in macOS 10.12)
m_format.setOption(QSurfaceFormat::StereoBuffers, false);
// ------------------ Query the pixel format ------------------
NSOpenGLPixelFormat *pixelFormat = m_context.pixelFormat;
GLint virtualScreen = [&, this]() {
auto *platformScreen = static_cast<QCocoaScreen*>(context()->screen()->handle());
auto displayId = platformScreen->nativeScreen().qt_displayId;
auto requestedDisplay = CGDisplayIDToOpenGLDisplayMask(displayId);
for (int i = 0; i < pixelFormat.numberOfVirtualScreens; ++i) {
GLint supportedDisplays;
[pixelFormat getValues:&supportedDisplays forAttribute:NSOpenGLPFAScreenMask forVirtualScreen:i];
// Note: The mask returned for NSOpenGLPFAScreenMask is a bit mask of
// physical displays that the renderer can drive, while the one returned
// from CGDisplayIDToOpenGLDisplayMask has a single bit set, representing
// that particular display.
if (requestedDisplay & supportedDisplays)
return i;
}
qCWarning(lcQpaOpenGLContext) << "Could not find virtual screen for"
<< platformScreen << "with displayId" << displayId;
return 0;
}();
auto pixelFormatAttribute = [&](NSOpenGLPixelFormatAttribute attribute) {
int value = 0;
[pixelFormat getValues:&value forAttribute:attribute forVirtualScreen:virtualScreen];
return value;
};
int colorSize = pixelFormatAttribute(NSOpenGLPFAColorSize);
colorSize /= 4; // The attribute includes the alpha component
m_format.setRedBufferSize(colorSize);
m_format.setGreenBufferSize(colorSize);
m_format.setBlueBufferSize(colorSize);
// Surfaces on macOS always have an alpha channel, but unless the user requested
// one via setAlphaBufferSize(), which triggered setting NSOpenGLCPSurfaceOpacity
// to make the surface non-opaque, we don't want to report back the actual alpha
// size, as that will make the user believe the alpha channel can be used for
// something useful, when in reality it can't, due to the surface being opaque.
if (m_format.alphaBufferSize() > 0)
m_format.setAlphaBufferSize(pixelFormatAttribute(NSOpenGLPFAAlphaSize));
m_format.setDepthBufferSize(pixelFormatAttribute(NSOpenGLPFADepthSize));
m_format.setStencilBufferSize(pixelFormatAttribute(NSOpenGLPFAStencilSize));
m_format.setSamples(pixelFormatAttribute(NSOpenGLPFASamples));
if (pixelFormatAttribute(NSOpenGLPFATripleBuffer))
m_format.setSwapBehavior(QSurfaceFormat::TripleBuffer);
else if (pixelFormatAttribute(NSOpenGLPFADoubleBuffer))
m_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
else
m_format.setSwapBehavior(QSurfaceFormat::SingleBuffer);
// ------------------- Query the context -------------------
auto glContextParameter = [&](NSOpenGLContextParameter parameter) {
int value = 0;
[m_context getValues:&value forParameter:parameter];
return value;
};
m_format.setSwapInterval(glContextParameter(NSOpenGLCPSwapInterval));
if (oldContext)
[oldContext makeCurrentContext];
else
[NSOpenGLContext clearCurrentContext];
}
QCocoaGLContext::~QCocoaGLContext()
{
[m_context release];
}
bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface)
{
qCDebug(lcQpaOpenGLContext) << "Making" << this << "current"
<< "in" << QThread::currentThread() << "for" << surface;
Q_ASSERT(surface->surface()->supportsOpenGL());
if (!setDrawable(surface))
return false;
[m_context makeCurrentContext];
if (surface->surface()->surfaceClass() == QSurface::Window) {
if (m_needsUpdate.fetchAndStoreRelaxed(false))
update();
}
return true;
}
/*!
Sets the drawable object of the NSOpenGLContext, which is the
frame buffer that is the target of OpenGL drawing operations.
*/
bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
{
// Make sure any surfaces released during this process are deallocated
// straight away, otherwise we may run out of surfaces when spinning a
// render-loop that doesn't return to one of the outer pools.
QMacAutoReleasePool pool;
if (!surface || surface->surface()->surfaceClass() == QSurface::Offscreen) {
// Clear the current drawable and reset the active window, so that GL
// commands that don't target a specific FBO will not end up stomping
// on the previously set drawable.
qCDebug(lcQpaOpenGLContext) << "Clearing current drawable" << m_context.view << "for" << m_context;
[m_context clearDrawable];
return true;
}
Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window);
auto *cocoaWindow = static_cast<QCocoaWindow *>(surface);
QNSView *view = qnsview_cast(cocoaWindow->view());
if (view == m_context.view)
return true;
prepareDrawable(cocoaWindow);
// Setting the drawable may happen on a separate thread as a result of
// a call to makeCurrent, so we need to set up the observers before we
// associate the view with the context. That way we will guarantee that
// as long as the view is the drawable of the context we will know about
// any updates to the view that require surface invalidation.
auto updateCallback = [this, view]() {
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (m_context.view != view)
return;
m_needsUpdate = true;
};
m_updateObservers.clear();
if (view.layer) {
m_updateObservers.append(QMacNotificationObserver(view, NSViewFrameDidChangeNotification, updateCallback));
m_updateObservers.append(QMacNotificationObserver(view.window, NSWindowDidChangeScreenNotification, updateCallback));
} else {
m_updateObservers.append(QMacNotificationObserver(view, NSViewGlobalFrameDidChangeNotification, updateCallback));
}
m_updateObservers.append(QMacNotificationObserver([NSApplication sharedApplication],
NSApplicationDidChangeScreenParametersNotification, updateCallback));
// If any of the observers fire at this point it's fine. We check the
// view association (atomically) in the update callback, and skip the
// update if we haven't associated yet. Setting the drawable below will
// have the same effect as an update.
// Now we are ready to associate the view with the context
m_context.view = view;
if (m_context.view != view) {
qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context;
m_updateObservers.clear();
return false;
}
qCInfo(lcQpaOpenGLContext) << "Set drawable for" << m_context << "to" << m_context.view;
return true;
}
void QCocoaGLContext::prepareDrawable(QCocoaWindow *platformWindow)
{
// We generally want high-DPI GL surfaces, unless the user has explicitly disabled them
bool prefersBestResolutionOpenGLSurface = qt_mac_resolveOption(YES,
platformWindow->window(), "_q_mac_wantsBestResolutionOpenGLSurface",
"QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE");
auto *view = platformWindow->view();
// The only case we have to opt out ourselves is when using the Apple software renderer
// in combination with surface-backed views, as these together do not support high-DPI.
if (prefersBestResolutionOpenGLSurface) {
int rendererID = 0;
[m_context getValues:&rendererID forParameter:NSOpenGLContextParameterCurrentRendererID];
bool isSoftwareRenderer = (rendererID & kCGLRendererIDMatchingMask) == kCGLRendererGenericFloatID;
if (isSoftwareRenderer && !view.layer) {
qCInfo(lcQpaOpenGLContext) << "Disabling high resolution GL surface due to software renderer";
prefersBestResolutionOpenGLSurface = false;
}
}
view.wantsBestResolutionOpenGLSurface = prefersBestResolutionOpenGLSurface;
}
// NSOpenGLContext is not re-entrant. Even when using separate contexts per thread,
// view, and window, calls into the API will still deadlock. For more information
// see https://openradar.appspot.com/37064579
static QMutex s_reentrancyMutex;
void QCocoaGLContext::update()
{
// Make sure any surfaces released during this process are deallocated
// straight away, otherwise we may run out of surfaces when spinning a
// render-loop that doesn't return to one of the outer pools.
QMacAutoReleasePool pool;
QMutexLocker locker(&s_reentrancyMutex);
qCInfo(lcQpaOpenGLContext) << "Updating" << m_context << "for" << m_context.view;
[m_context update];
}
void QCocoaGLContext::swapBuffers(QPlatformSurface *surface)
{
qCDebug(lcQpaOpenGLContext) << "Swapping" << m_context
<< "in" << QThread::currentThread() << "to" << surface;
if (surface->surface()->surfaceClass() == QSurface::Offscreen)
return; // Nothing to do
if (!setDrawable(surface)) {
qCWarning(lcQpaOpenGLContext) << "Can't flush" << m_context
<< "without" << surface << "as drawable";
return;
}
if (m_context.view.layer) {
// Flushing an NSOpenGLContext will hit the screen immediately, ignoring
// any Core Animation transactions in place. This may result in major
// visual artifacts if the flush happens out of sync with the size
// of the layer, view, and window reflected by other parts of the UI,
// e.g. if the application flushes in the resize event or a timer during
// window resizing, instead of in the expose event.
auto *cocoaWindow = static_cast<QCocoaWindow *>(surface);
if (cocoaWindow->geometry().size() != cocoaWindow->m_exposedRect.size()) {
qCInfo(lcQpaOpenGLContext) << "Window exposed size does not match geometry (yet)."
<< "Skipping flush to avoid visual artifacts.";
return;
}
}
QMutexLocker locker(&s_reentrancyMutex);
[m_context flushBuffer];
}
void QCocoaGLContext::doneCurrent()
{
qCDebug(lcQpaOpenGLContext) << "Clearing current context"
<< [NSOpenGLContext currentContext] << "in" << QThread::currentThread();
// Note: We do not need to clear the current drawable here.
// As long as there is no current context, GL calls will
// do nothing.
[NSOpenGLContext clearCurrentContext];
}
QSurfaceFormat QCocoaGLContext::format() const
{
return m_format;
}
bool QCocoaGLContext::isValid() const
{
return m_context != nil;
}
bool QCocoaGLContext::isSharing() const
{
return m_shareContext != nil;
}
NSOpenGLContext *QCocoaGLContext::nativeContext() const
{
return m_context;
}
QFunctionPointer QCocoaGLContext::getProcAddress(const char *procName)
{
return (QFunctionPointer)dlsym(RTLD_DEFAULT, procName);
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QCocoaGLContext *context)
{
QDebugStateSaver saver(debug);
debug.nospace();
debug << "QCocoaGLContext(" << (const void *)context;
if (context) {
if (debug.verbosity() > QDebug::DefaultVerbosity)
debug << ", " << context->format();
debug << ", " << context->nativeContext();
}
debug << ')';
return debug;
}
#endif // !QT_NO_DEBUG_STREAM
QT_END_NAMESPACE