blob: ccebf43d076ce5e288b67e46e945e363c678e8a9 [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 "qwaylandglcontext.h"
#include <QtWaylandClient/private/qwaylanddisplay_p.h>
#include <QtWaylandClient/private/qwaylandwindow_p.h>
#include <QtWaylandClient/private/qwaylandsubsurface_p.h>
#include <QtWaylandClient/private/qwaylandabstractdecoration_p.h>
#include <QtWaylandClient/private/qwaylandintegration_p.h>
#include "qwaylandeglwindow.h"
#include <QDebug>
#include <QtEglSupport/private/qeglconvenience_p.h>
#include <QtGui/private/qopenglcontext_p.h>
#include <QtGui/private/qopengltexturecache_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformopenglcontext.h>
#include <QtGui/QSurfaceFormat>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QtCore/qmutex.h>
#include <dlfcn.h>
// Constants from EGL_KHR_create_context
#ifndef EGL_CONTEXT_MINOR_VERSION_KHR
#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB
#endif
#ifndef EGL_CONTEXT_FLAGS_KHR
#define EGL_CONTEXT_FLAGS_KHR 0x30FC
#endif
#ifndef EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR
#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD
#endif
#ifndef EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR
#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001
#endif
#ifndef EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR
#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002
#endif
#ifndef EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR
#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001
#endif
#ifndef EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR
#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002
#endif
// Constants for OpenGL which are not available in the ES headers.
#ifndef GL_CONTEXT_FLAGS
#define GL_CONTEXT_FLAGS 0x821E
#endif
#ifndef GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT
#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001
#endif
#ifndef GL_CONTEXT_FLAG_DEBUG_BIT
#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002
#endif
#ifndef GL_CONTEXT_PROFILE_MASK
#define GL_CONTEXT_PROFILE_MASK 0x9126
#endif
#ifndef GL_CONTEXT_CORE_PROFILE_BIT
#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001
#endif
#ifndef GL_CONTEXT_COMPATIBILITY_PROFILE_BIT
#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
#endif
QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
class DecorationsBlitter : public QOpenGLFunctions
{
public:
DecorationsBlitter(QWaylandGLContext *context)
: m_context(context)
{
initializeOpenGLFunctions();
m_blitProgram = new QOpenGLShaderProgram();
m_blitProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, "attribute vec4 position;\n\
attribute vec4 texCoords;\n\
varying vec2 outTexCoords;\n\
void main()\n\
{\n\
gl_Position = position;\n\
outTexCoords = texCoords.xy;\n\
}");
m_blitProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, "varying highp vec2 outTexCoords;\n\
uniform sampler2D texture;\n\
void main()\n\
{\n\
gl_FragColor = texture2D(texture, outTexCoords);\n\
}");
m_blitProgram->bindAttributeLocation("position", 0);
m_blitProgram->bindAttributeLocation("texCoords", 1);
if (!m_blitProgram->link()) {
qDebug() << "Shader Program link failed.";
qDebug() << m_blitProgram->log();
}
m_blitProgram->bind();
m_blitProgram->enableAttributeArray(0);
m_blitProgram->enableAttributeArray(1);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
glDisable(GL_SCISSOR_TEST);
glDepthMask(GL_FALSE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
m_buffer.create();
m_buffer.bind();
static const GLfloat squareVertices[] = {
-1.f, -1.f,
1.0f, -1.f,
-1.f, 1.0f,
1.0f, 1.0f
};
static const GLfloat inverseSquareVertices[] = {
-1.f, 1.f,
1.f, 1.f,
-1.f, -1.f,
1.f, -1.f
};
static const GLfloat textureVertices[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
m_squareVerticesOffset = 0;
m_inverseSquareVerticesOffset = sizeof(squareVertices);
m_textureVerticesOffset = sizeof(squareVertices) + sizeof(textureVertices);
m_buffer.allocate(sizeof(squareVertices) + sizeof(inverseSquareVertices) + sizeof(textureVertices));
m_buffer.write(m_squareVerticesOffset, squareVertices, sizeof(squareVertices));
m_buffer.write(m_inverseSquareVerticesOffset, inverseSquareVertices, sizeof(inverseSquareVertices));
m_buffer.write(m_textureVerticesOffset, textureVertices, sizeof(textureVertices));
m_blitProgram->setAttributeBuffer(1, GL_FLOAT, m_textureVerticesOffset, 2);
m_textureWrap = m_context->context()->functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextureRepeat) ? GL_REPEAT : GL_CLAMP_TO_EDGE;
}
~DecorationsBlitter()
{
delete m_blitProgram;
}
void blit(QWaylandEglWindow *window)
{
Q_ASSERT(window->wlSurface());
QOpenGLTextureCache *cache = QOpenGLTextureCache::cacheForContext(m_context->context());
QSize surfaceSize = window->surfaceSize();
int scale = window->scale() ;
glViewport(0, 0, surfaceSize.width() * scale, surfaceSize.height() * scale);
//Draw Decoration
m_blitProgram->setAttributeBuffer(0, GL_FLOAT, m_inverseSquareVerticesOffset, 2);
QImage decorationImage = window->decoration()->contentImage();
cache->bindTexture(m_context->context(), decorationImage);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_textureWrap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_textureWrap);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//Draw Content
m_blitProgram->setAttributeBuffer(0, GL_FLOAT, m_squareVerticesOffset, 2);
glBindTexture(GL_TEXTURE_2D, window->contentTexture());
QRect r = window->contentsRect();
glViewport(r.x() * scale, r.y() * scale, r.width() * scale, r.height() * scale);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
QOpenGLShaderProgram *m_blitProgram = nullptr;
QWaylandGLContext *m_context = nullptr;
QOpenGLBuffer m_buffer;
int m_squareVerticesOffset;
int m_inverseSquareVerticesOffset;
int m_textureVerticesOffset;
int m_textureWrap;
};
QWaylandGLContext::QWaylandGLContext(EGLDisplay eglDisplay, QWaylandDisplay *display, const QSurfaceFormat &format, QPlatformOpenGLContext *share)
: QPlatformOpenGLContext()
, m_eglDisplay(eglDisplay)
, m_display(display)
{
QSurfaceFormat fmt = format;
if (static_cast<QWaylandIntegration *>(QGuiApplicationPrivate::platformIntegration())->display()->supportsWindowDecoration())
fmt.setAlphaBufferSize(8);
m_config = q_configFromGLFormat(m_eglDisplay, fmt);
m_format = q_glFormatFromConfig(m_eglDisplay, m_config, fmt);
m_shareEGLContext = share ? static_cast<QWaylandGLContext *>(share)->eglContext() : EGL_NO_CONTEXT;
QVector<EGLint> eglContextAttrs;
eglContextAttrs.append(EGL_CONTEXT_CLIENT_VERSION);
eglContextAttrs.append(format.majorVersion());
const bool hasKHRCreateContext = q_hasEglExtension(m_eglDisplay, "EGL_KHR_create_context");
if (hasKHRCreateContext) {
eglContextAttrs.append(EGL_CONTEXT_MINOR_VERSION_KHR);
eglContextAttrs.append(format.minorVersion());
int flags = 0;
// The debug bit is supported both for OpenGL and OpenGL ES.
if (format.testOption(QSurfaceFormat::DebugContext))
flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
// The fwdcompat bit is only for OpenGL 3.0+.
if (m_format.renderableType() == QSurfaceFormat::OpenGL
&& format.majorVersion() >= 3
&& !format.testOption(QSurfaceFormat::DeprecatedFunctions))
flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR;
if (flags) {
eglContextAttrs.append(EGL_CONTEXT_FLAGS_KHR);
eglContextAttrs.append(flags);
}
// Profiles are OpenGL only and mandatory in 3.2+. The value is silently ignored for < 3.2.
if (m_format.renderableType() == QSurfaceFormat::OpenGL) {
switch (format.profile()) {
case QSurfaceFormat::NoProfile:
break;
case QSurfaceFormat::CoreProfile:
eglContextAttrs.append(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
eglContextAttrs.append(EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR);
break;
case QSurfaceFormat::CompatibilityProfile:
eglContextAttrs.append(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
eglContextAttrs.append(EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR);
break;
}
}
}
eglContextAttrs.append(EGL_NONE);
switch (m_format.renderableType()) {
case QSurfaceFormat::OpenVG:
m_api = EGL_OPENVG_API;
break;
#ifdef EGL_VERSION_1_4
# if !defined(QT_OPENGL_ES_2)
case QSurfaceFormat::DefaultRenderableType:
# endif
case QSurfaceFormat::OpenGL:
m_api = EGL_OPENGL_API;
break;
#endif
case QSurfaceFormat::OpenGLES:
default:
m_api = EGL_OPENGL_ES_API;
break;
}
eglBindAPI(m_api);
m_context = eglCreateContext(m_eglDisplay, m_config, m_shareEGLContext, eglContextAttrs.constData());
if (m_context == EGL_NO_CONTEXT) {
m_context = eglCreateContext(m_eglDisplay, m_config, EGL_NO_CONTEXT, eglContextAttrs.constData());
m_shareEGLContext = EGL_NO_CONTEXT;
}
EGLint error = eglGetError();
if (error != EGL_SUCCESS) {
qWarning("QWaylandGLContext: failed to create EGLContext, error=%x", error);
return;
}
// Create an EGL context for the decorations blitter. By using a dedicated context we don't need to make sure to not
// change the context state and we also use OpenGL ES 2 API independently to what the app is using to draw.
QVector<EGLint> eglDecorationsContextAttrs = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
m_decorationsContext = eglCreateContext(m_eglDisplay, m_config, m_context, eglDecorationsContextAttrs.constData());
if (m_decorationsContext == EGL_NO_CONTEXT)
qWarning("QWaylandGLContext: Failed to create the decorations EGLContext. Decorations will not be drawn.");
EGLint a = EGL_MIN_SWAP_INTERVAL;
EGLint b = EGL_MAX_SWAP_INTERVAL;
if (!eglGetConfigAttrib(m_eglDisplay, m_config, a, &a) ||
!eglGetConfigAttrib(m_eglDisplay, m_config, b, &b) ||
a > 0) {
m_supportNonBlockingSwap = false;
}
{
bool ok;
int supportNonBlockingSwap = qEnvironmentVariableIntValue("QT_WAYLAND_FORCE_NONBLOCKING_SWAP_SUPPORT", &ok);
if (ok)
m_supportNonBlockingSwap = supportNonBlockingSwap != 0;
}
if (!m_supportNonBlockingSwap) {
qWarning(lcQpaWayland) << "Non-blocking swap buffers not supported."
<< "Subsurface rendering can be affected."
<< "It may also cause the event loop to freeze in some situations";
}
updateGLFormat();
}
void QWaylandGLContext::updateGLFormat()
{
// Have to save & restore to prevent QOpenGLContext::currentContext() from becoming
// inconsistent after QOpenGLContext::create().
EGLDisplay prevDisplay = eglGetCurrentDisplay();
if (prevDisplay == EGL_NO_DISPLAY) // when no context is current
prevDisplay = m_eglDisplay;
EGLContext prevContext = eglGetCurrentContext();
EGLSurface prevSurfaceDraw = eglGetCurrentSurface(EGL_DRAW);
EGLSurface prevSurfaceRead = eglGetCurrentSurface(EGL_READ);
wl_surface *wlSurface = m_display->createSurface(nullptr);
wl_egl_window *eglWindow = wl_egl_window_create(wlSurface, 1, 1);
#if QT_CONFIG(egl_extension_platform_wayland)
EGLSurface eglSurface = eglCreatePlatformWindowSurface(m_eglDisplay, m_config, eglWindow, nullptr);
#else
EGLSurface eglSurface = eglCreateWindowSurface(m_eglDisplay, m_config, eglWindow, nullptr);
#endif
if (eglMakeCurrent(m_eglDisplay, eglSurface, eglSurface, m_context)) {
if (m_format.renderableType() == QSurfaceFormat::OpenGL
|| m_format.renderableType() == QSurfaceFormat::OpenGLES) {
const GLubyte *s = glGetString(GL_VERSION);
if (s) {
QByteArray version = QByteArray(reinterpret_cast<const char *>(s));
int major, minor;
if (QPlatformOpenGLContext::parseOpenGLVersion(version, major, minor)) {
m_format.setMajorVersion(major);
m_format.setMinorVersion(minor);
}
}
m_format.setProfile(QSurfaceFormat::NoProfile);
m_format.setOptions(QSurfaceFormat::FormatOptions());
if (m_format.renderableType() == QSurfaceFormat::OpenGL) {
// Check profile and options.
if (m_format.majorVersion() < 3) {
m_format.setOption(QSurfaceFormat::DeprecatedFunctions);
} else {
GLint value = 0;
glGetIntegerv(GL_CONTEXT_FLAGS, &value);
if (!(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT))
m_format.setOption(QSurfaceFormat::DeprecatedFunctions);
if (value & GL_CONTEXT_FLAG_DEBUG_BIT)
m_format.setOption(QSurfaceFormat::DebugContext);
if (m_format.version() >= qMakePair(3, 2)) {
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);
}
}
}
}
eglMakeCurrent(prevDisplay, prevSurfaceDraw, prevSurfaceRead, prevContext);
}
eglDestroySurface(m_eglDisplay, eglSurface);
wl_egl_window_destroy(eglWindow);
wl_surface_destroy(wlSurface);
}
QWaylandGLContext::~QWaylandGLContext()
{
delete m_blitter;
eglDestroyContext(m_eglDisplay, m_context);
}
bool QWaylandGLContext::makeCurrent(QPlatformSurface *surface)
{
// in QWaylandGLContext() we called eglBindAPI with the correct value. However,
// eglBindAPI's documentation says:
// "eglBindAPI defines the current rendering API for EGL in the thread it is called from"
// Since makeCurrent() can be called from a different thread than the one we created the
// context in make sure to call eglBindAPI in the correct thread.
if (eglQueryAPI() != m_api) {
eglBindAPI(m_api);
}
QWaylandEglWindow *window = static_cast<QWaylandEglWindow *>(surface);
EGLSurface eglSurface = window->eglSurface();
if (!window->needToUpdateContentFBO() && (eglSurface != EGL_NO_SURFACE)) {
if (!eglMakeCurrent(m_eglDisplay, eglSurface, eglSurface, m_context)) {
qWarning("QWaylandGLContext::makeCurrent: eglError: %x, this: %p \n", eglGetError(), this);
return false;
}
return true;
}
if (window->isExposed())
window->setCanResize(false);
if (m_decorationsContext != EGL_NO_CONTEXT && !window->decoration())
window->createDecoration();
if (eglSurface == EGL_NO_SURFACE) {
window->updateSurface(true);
eglSurface = window->eglSurface();
}
if (!eglMakeCurrent(m_eglDisplay, eglSurface, eglSurface, m_context)) {
qWarning("QWaylandGLContext::makeCurrent: eglError: %x, this: %p \n", eglGetError(), this);
window->setCanResize(true);
return false;
}
//### setCurrentContext will be called in QOpenGLContext::makeCurrent after this function
// returns, but that's too late, as we need a current context in order to bind the content FBO.
QOpenGLContextPrivate::setCurrentContext(context());
window->bindContentFBO();
return true;
}
void QWaylandGLContext::doneCurrent()
{
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
void QWaylandGLContext::swapBuffers(QPlatformSurface *surface)
{
QWaylandEglWindow *window = static_cast<QWaylandEglWindow *>(surface);
EGLSurface eglSurface = window->eglSurface();
if (window->decoration()) {
if (m_api != EGL_OPENGL_ES_API)
eglBindAPI(EGL_OPENGL_ES_API);
// save the current EGL content and surface to set it again after the blitter is done
EGLDisplay currentDisplay = eglGetCurrentDisplay();
EGLContext currentContext = eglGetCurrentContext();
EGLSurface currentSurfaceDraw = eglGetCurrentSurface(EGL_DRAW);
EGLSurface currentSurfaceRead = eglGetCurrentSurface(EGL_READ);
eglMakeCurrent(m_eglDisplay, eglSurface, eglSurface, m_decorationsContext);
if (!m_blitter)
m_blitter = new DecorationsBlitter(this);
m_blitter->blit(window);
if (m_api != EGL_OPENGL_ES_API)
eglBindAPI(m_api);
eglMakeCurrent(currentDisplay, currentSurfaceDraw, currentSurfaceRead, currentContext);
}
int swapInterval = m_supportNonBlockingSwap ? 0 : m_format.swapInterval();
eglSwapInterval(m_eglDisplay, swapInterval);
if (swapInterval == 0 && m_format.swapInterval() > 0) {
// Emulating a blocking swap
glFlush(); // Flush before waiting so we can swap more quickly when the frame event arrives
window->waitForFrameSync(100);
}
window->handleUpdate();
eglSwapBuffers(m_eglDisplay, eglSurface);
window->setCanResize(true);
}
GLuint QWaylandGLContext::defaultFramebufferObject(QPlatformSurface *surface) const
{
return static_cast<QWaylandEglWindow *>(surface)->contentFBO();
}
bool QWaylandGLContext::isSharing() const
{
return m_shareEGLContext != EGL_NO_CONTEXT;
}
bool QWaylandGLContext::isValid() const
{
return m_context != EGL_NO_CONTEXT;
}
QFunctionPointer QWaylandGLContext::getProcAddress(const char *procName)
{
QFunctionPointer proc = (QFunctionPointer) eglGetProcAddress(procName);
if (!proc)
proc = (QFunctionPointer) dlsym(RTLD_DEFAULT, procName);
return proc;
}
EGLConfig QWaylandGLContext::eglConfig() const
{
return m_config;
}
}
QT_END_NAMESPACE