3d03f4e989
Static builds can not rely on a constructor function in the QtOpenGL library, as that will be linked out unless something in the application pulls it in. Instead we export a helper function that clients that depend on OpenGL support in QPlatformBackingStore can use to bring it it. Change-Id: Ic54058bf413a476287884c78df5624b862f97695 Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
465 lines
18 KiB
C++
465 lines
18 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2020 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the QtGui module 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$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#ifndef QT_NO_OPENGL
|
|
|
|
#include "qplatformbackingstoreopenglsupport.h"
|
|
|
|
#include <QtGui/private/qwindow_p.h>
|
|
|
|
#include <qpa/qplatformgraphicsbuffer.h>
|
|
#include <qpa/qplatformgraphicsbufferhelper.h>
|
|
|
|
#include <QtOpenGL/QOpenGLTextureBlitter>
|
|
#include <QtGui/qopengl.h>
|
|
#include <QtGui/QOpenGLFunctions>
|
|
#include <QtGui/QOpenGLContext>
|
|
#include <QtGui/QOffscreenSurface>
|
|
|
|
#ifndef GL_TEXTURE_BASE_LEVEL
|
|
#define GL_TEXTURE_BASE_LEVEL 0x813C
|
|
#endif
|
|
#ifndef GL_TEXTURE_MAX_LEVEL
|
|
#define GL_TEXTURE_MAX_LEVEL 0x813D
|
|
#endif
|
|
#ifndef GL_UNPACK_ROW_LENGTH
|
|
#define GL_UNPACK_ROW_LENGTH 0x0CF2
|
|
#endif
|
|
#ifndef GL_RGB10_A2
|
|
#define GL_RGB10_A2 0x8059
|
|
#endif
|
|
#ifndef GL_UNSIGNED_INT_2_10_10_10_REV
|
|
#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
|
|
#endif
|
|
|
|
#ifndef GL_FRAMEBUFFER_SRGB
|
|
#define GL_FRAMEBUFFER_SRGB 0x8DB9
|
|
#endif
|
|
#ifndef GL_FRAMEBUFFER_SRGB_CAPABLE
|
|
#define GL_FRAMEBUFFER_SRGB_CAPABLE 0x8DBA
|
|
#endif
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
static inline QRect deviceRect(const QRect &rect, QWindow *window)
|
|
{
|
|
QRect deviceRect(rect.topLeft() * window->devicePixelRatio(),
|
|
rect.size() * window->devicePixelRatio());
|
|
return deviceRect;
|
|
}
|
|
|
|
static inline QPoint deviceOffset(const QPoint &pt, QWindow *window)
|
|
{
|
|
return pt * window->devicePixelRatio();
|
|
}
|
|
|
|
static QRegion deviceRegion(const QRegion ®ion, QWindow *window, const QPoint &offset)
|
|
{
|
|
if (offset.isNull() && window->devicePixelRatio() <= 1)
|
|
return region;
|
|
|
|
QVarLengthArray<QRect, 4> rects;
|
|
rects.reserve(region.rectCount());
|
|
for (const QRect &rect : region)
|
|
rects.append(deviceRect(rect.translated(offset), window));
|
|
|
|
QRegion deviceRegion;
|
|
deviceRegion.setRects(rects.constData(), rects.count());
|
|
return deviceRegion;
|
|
}
|
|
|
|
static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
|
|
{
|
|
return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
|
|
topLeftRect.width(), topLeftRect.height());
|
|
}
|
|
|
|
static void blitTextureForWidget(const QPlatformTextureList *textures, int idx, QWindow *window, const QRect &deviceWindowRect,
|
|
QOpenGLTextureBlitter *blitter, const QPoint &offset, bool canUseSrgb)
|
|
{
|
|
const QRect clipRect = textures->clipRect(idx);
|
|
if (clipRect.isEmpty())
|
|
return;
|
|
|
|
QRect rectInWindow = textures->geometry(idx);
|
|
// relative to the TLW, not necessarily our window (if the flush is for a native child widget), have to adjust
|
|
rectInWindow.translate(-offset);
|
|
|
|
const QRect clippedRectInWindow = rectInWindow & clipRect.translated(rectInWindow.topLeft());
|
|
const QRect srcRect = toBottomLeftRect(clipRect, rectInWindow.height());
|
|
|
|
const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(deviceRect(clippedRectInWindow, window),
|
|
deviceWindowRect);
|
|
|
|
const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(deviceRect(srcRect, window),
|
|
deviceRect(rectInWindow, window).size(),
|
|
QOpenGLTextureBlitter::OriginBottomLeft);
|
|
|
|
QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
|
|
const bool srgb = textures->flags(idx).testFlag(QPlatformTextureList::TextureIsSrgb);
|
|
if (srgb && canUseSrgb)
|
|
funcs->glEnable(GL_FRAMEBUFFER_SRGB);
|
|
|
|
blitter->blit(textures->textureId(idx), target, source);
|
|
|
|
if (srgb && canUseSrgb)
|
|
funcs->glDisable(GL_FRAMEBUFFER_SRGB);
|
|
}
|
|
|
|
QPlatformBackingStoreOpenGLSupport::~QPlatformBackingStoreOpenGLSupport() {
|
|
if (context) {
|
|
QOffscreenSurface offscreenSurface;
|
|
offscreenSurface.setFormat(context->format());
|
|
offscreenSurface.create();
|
|
context->makeCurrent(&offscreenSurface);
|
|
if (textureId)
|
|
context->functions()->glDeleteTextures(1, &textureId);
|
|
if (blitter)
|
|
blitter->destroy();
|
|
}
|
|
delete blitter;
|
|
}
|
|
|
|
void QPlatformBackingStoreOpenGLSupport::composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, bool translucentBackground)
|
|
{
|
|
if (!qt_window_private(window)->receivedExpose)
|
|
return;
|
|
|
|
if (!context) {
|
|
context.reset(new QOpenGLContext);
|
|
context->setFormat(window->requestedFormat());
|
|
context->setScreen(window->screen());
|
|
context->setShareContext(qt_window_private(window)->shareContext());
|
|
if (!context->create()) {
|
|
qCWarning(lcQpaBackingStore, "composeAndFlush: QOpenGLContext creation failed");
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool current = context->makeCurrent(window);
|
|
|
|
if (!current && context->isValid()) {
|
|
delete blitter;
|
|
blitter = nullptr;
|
|
textureId = 0;
|
|
current = context->create() && context->makeCurrent(window);
|
|
}
|
|
|
|
if (!current) {
|
|
qCWarning(lcQpaBackingStore, "composeAndFlush: makeCurrent() failed");
|
|
return;
|
|
}
|
|
|
|
qCDebug(lcQpaBackingStore) << "Composing and flushing" << region << "of" << window
|
|
<< "at offset" << offset << "with" << textures->count() << "texture(s) in" << textures;
|
|
|
|
QWindowPrivate::get(window)->lastComposeTime.start();
|
|
|
|
QOpenGLFunctions *funcs = context->functions();
|
|
funcs->glViewport(0, 0, qRound(window->width() * window->devicePixelRatio()), qRound(window->height() * window->devicePixelRatio()));
|
|
funcs->glClearColor(0, 0, 0, translucentBackground ? 0 : 1);
|
|
funcs->glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
if (!blitter) {
|
|
blitter = new QOpenGLTextureBlitter;
|
|
blitter->create();
|
|
}
|
|
|
|
blitter->bind();
|
|
|
|
const QRect deviceWindowRect = deviceRect(QRect(QPoint(), window->size()), window);
|
|
const QPoint deviceWindowOffset = deviceOffset(offset, window);
|
|
|
|
bool canUseSrgb = false;
|
|
// If there are any sRGB textures in the list, check if the destination
|
|
// framebuffer is sRGB capable.
|
|
for (int i = 0; i < textures->count(); ++i) {
|
|
if (textures->flags(i).testFlag(QPlatformTextureList::TextureIsSrgb)) {
|
|
GLint cap = 0;
|
|
funcs->glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE, &cap);
|
|
if (cap)
|
|
canUseSrgb = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Textures for renderToTexture widgets.
|
|
for (int i = 0; i < textures->count(); ++i) {
|
|
if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop))
|
|
blitTextureForWidget(textures, i, window, deviceWindowRect, blitter, offset, canUseSrgb);
|
|
}
|
|
|
|
// Backingstore texture with the normal widgets.
|
|
GLuint textureId = 0;
|
|
QOpenGLTextureBlitter::Origin origin = QOpenGLTextureBlitter::OriginTopLeft;
|
|
if (QPlatformGraphicsBuffer *graphicsBuffer = backingStore->graphicsBuffer()) {
|
|
if (graphicsBuffer->size() != textureSize) {
|
|
if (this->textureId)
|
|
funcs->glDeleteTextures(1, &this->textureId);
|
|
funcs->glGenTextures(1, &this->textureId);
|
|
funcs->glBindTexture(GL_TEXTURE_2D, this->textureId);
|
|
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
|
if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
|
|
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
|
|
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
|
}
|
|
funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
if (QPlatformGraphicsBufferHelper::lockAndBindToTexture(graphicsBuffer, &needsSwizzle, &premultiplied)) {
|
|
textureSize = graphicsBuffer->size();
|
|
} else {
|
|
textureSize = QSize(0,0);
|
|
}
|
|
|
|
graphicsBuffer->unlock();
|
|
} else if (!region.isEmpty()){
|
|
funcs->glBindTexture(GL_TEXTURE_2D, this->textureId);
|
|
QPlatformGraphicsBufferHelper::lockAndBindToTexture(graphicsBuffer, &needsSwizzle, &premultiplied);
|
|
graphicsBuffer->unlock();
|
|
}
|
|
|
|
if (graphicsBuffer->origin() == QPlatformGraphicsBuffer::OriginBottomLeft)
|
|
origin = QOpenGLTextureBlitter::OriginBottomLeft;
|
|
textureId = this->textureId;
|
|
} else {
|
|
QPlatformBackingStore::TextureFlags flags;
|
|
textureId = backingStore->toTexture(deviceRegion(region, window, offset), &textureSize, &flags);
|
|
needsSwizzle = (flags & QPlatformBackingStore::TextureSwizzle) != 0;
|
|
premultiplied = (flags & QPlatformBackingStore::TexturePremultiplied) != 0;
|
|
if (flags & QPlatformBackingStore::TextureFlip)
|
|
origin = QOpenGLTextureBlitter::OriginBottomLeft;
|
|
}
|
|
|
|
funcs->glEnable(GL_BLEND);
|
|
if (premultiplied)
|
|
funcs->glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
|
|
else
|
|
funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
|
|
|
|
if (textureId) {
|
|
if (needsSwizzle)
|
|
blitter->setRedBlueSwizzle(true);
|
|
// The backingstore is for the entire tlw.
|
|
// In case of native children offset tells the position relative to the tlw.
|
|
const QRect srcRect = toBottomLeftRect(deviceWindowRect.translated(deviceWindowOffset), textureSize.height());
|
|
const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(srcRect,
|
|
textureSize,
|
|
origin);
|
|
blitter->blit(textureId, QMatrix4x4(), source);
|
|
if (needsSwizzle)
|
|
blitter->setRedBlueSwizzle(false);
|
|
}
|
|
|
|
// Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set.
|
|
bool blendIsPremultiplied = premultiplied;
|
|
for (int i = 0; i < textures->count(); ++i) {
|
|
const QPlatformTextureList::Flags flags = textures->flags(i);
|
|
if (flags.testFlag(QPlatformTextureList::NeedsPremultipliedAlphaBlending)) {
|
|
if (!blendIsPremultiplied) {
|
|
funcs->glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
|
|
blendIsPremultiplied = true;
|
|
}
|
|
} else {
|
|
if (blendIsPremultiplied) {
|
|
funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
|
|
blendIsPremultiplied = false;
|
|
}
|
|
}
|
|
if (flags.testFlag(QPlatformTextureList::StacksOnTop))
|
|
blitTextureForWidget(textures, i, window, deviceWindowRect, blitter, offset, canUseSrgb);
|
|
}
|
|
|
|
funcs->glDisable(GL_BLEND);
|
|
blitter->release();
|
|
|
|
context->swapBuffers(window);
|
|
}
|
|
|
|
GLuint QPlatformBackingStoreOpenGLSupport::toTexture(const QRegion &dirtyRegion, QSize *textureSize, QPlatformBackingStore::TextureFlags *flags) const
|
|
{
|
|
Q_ASSERT(textureSize);
|
|
Q_ASSERT(flags);
|
|
|
|
QImage image = backingStore->toImage();
|
|
QSize imageSize = image.size();
|
|
|
|
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
|
GLenum internalFormat = GL_RGBA;
|
|
GLuint pixelType = GL_UNSIGNED_BYTE;
|
|
|
|
bool needsConversion = false;
|
|
*flags = { };
|
|
switch (image.format()) {
|
|
case QImage::Format_ARGB32_Premultiplied:
|
|
*flags |= QPlatformBackingStore::TexturePremultiplied;
|
|
Q_FALLTHROUGH();
|
|
case QImage::Format_RGB32:
|
|
case QImage::Format_ARGB32:
|
|
*flags |= QPlatformBackingStore::TextureSwizzle;
|
|
break;
|
|
case QImage::Format_RGBA8888_Premultiplied:
|
|
*flags |= QPlatformBackingStore::TexturePremultiplied;
|
|
Q_FALLTHROUGH();
|
|
case QImage::Format_RGBX8888:
|
|
case QImage::Format_RGBA8888:
|
|
break;
|
|
case QImage::Format_BGR30:
|
|
case QImage::Format_A2BGR30_Premultiplied:
|
|
if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
|
|
pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
|
internalFormat = GL_RGB10_A2;
|
|
*flags |= QPlatformBackingStore::TexturePremultiplied;
|
|
} else {
|
|
needsConversion = true;
|
|
}
|
|
break;
|
|
case QImage::Format_RGB30:
|
|
case QImage::Format_A2RGB30_Premultiplied:
|
|
if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
|
|
pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
|
internalFormat = GL_RGB10_A2;
|
|
*flags |= QPlatformBackingStore::TextureSwizzle | QPlatformBackingStore::TexturePremultiplied;
|
|
} else {
|
|
needsConversion = true;
|
|
}
|
|
break;
|
|
default:
|
|
needsConversion = true;
|
|
break;
|
|
}
|
|
if (imageSize.isEmpty()) {
|
|
*textureSize = imageSize;
|
|
return 0;
|
|
}
|
|
|
|
// Must rely on the input only, not d_ptr.
|
|
// With the default composeAndFlush() textureSize is &d_ptr->textureSize.
|
|
bool resized = *textureSize != imageSize;
|
|
if (dirtyRegion.isEmpty() && !resized)
|
|
return textureId;
|
|
|
|
*textureSize = imageSize;
|
|
|
|
if (needsConversion)
|
|
image = image.convertToFormat(QImage::Format_RGBA8888);
|
|
|
|
// The image provided by the backingstore may have a stride larger than width * 4, for
|
|
// instance on platforms that manually implement client-side decorations.
|
|
static const int bytesPerPixel = 4;
|
|
const qsizetype strideInPixels = image.bytesPerLine() / bytesPerPixel;
|
|
const bool hasUnpackRowLength = !ctx->isOpenGLES() || ctx->format().majorVersion() >= 3;
|
|
|
|
QOpenGLFunctions *funcs = ctx->functions();
|
|
|
|
if (hasUnpackRowLength) {
|
|
funcs->glPixelStorei(GL_UNPACK_ROW_LENGTH, strideInPixels);
|
|
} else if (strideInPixels != image.width()) {
|
|
// No UNPACK_ROW_LENGTH on ES 2.0 and yet we would need it. This case is typically
|
|
// hit with QtWayland which is rarely used in combination with a ES2.0-only GL
|
|
// implementation. Therefore, accept the performance hit and do a copy.
|
|
image = image.copy();
|
|
}
|
|
|
|
if (resized) {
|
|
if (textureId)
|
|
funcs->glDeleteTextures(1, &textureId);
|
|
funcs->glGenTextures(1, &textureId);
|
|
funcs->glBindTexture(GL_TEXTURE_2D, textureId);
|
|
if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
|
|
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
|
|
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
|
}
|
|
funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
funcs->glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, imageSize.width(), imageSize.height(), 0, GL_RGBA, pixelType,
|
|
const_cast<uchar*>(image.constBits()));
|
|
} else {
|
|
funcs->glBindTexture(GL_TEXTURE_2D, textureId);
|
|
QRect imageRect = image.rect();
|
|
QRect rect = dirtyRegion.boundingRect() & imageRect;
|
|
|
|
if (hasUnpackRowLength) {
|
|
funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
|
|
image.constScanLine(rect.y()) + rect.x() * bytesPerPixel);
|
|
} else {
|
|
// if the rect is wide enough it's cheaper to just
|
|
// extend it instead of doing an image copy
|
|
if (rect.width() >= imageRect.width() / 2) {
|
|
rect.setX(0);
|
|
rect.setWidth(imageRect.width());
|
|
}
|
|
|
|
// if the sub-rect is full-width we can pass the image data directly to
|
|
// OpenGL instead of copying, since there's no gap between scanlines
|
|
|
|
if (rect.width() == imageRect.width()) {
|
|
funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
|
|
image.constScanLine(rect.y()));
|
|
} else {
|
|
funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
|
|
image.copy(rect).constBits());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasUnpackRowLength)
|
|
funcs->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
|
|
return textureId;
|
|
}
|
|
|
|
void qt_registerDefaultPlatformBackingStoreOpenGLSupport()
|
|
{
|
|
if (!QPlatformBackingStoreOpenGLSupportBase::factoryFunction()) {
|
|
QPlatformBackingStoreOpenGLSupportBase::setFactoryFunction([]() -> QPlatformBackingStoreOpenGLSupportBase* {
|
|
return new QPlatformBackingStoreOpenGLSupport;
|
|
});
|
|
}
|
|
}
|
|
|
|
#endif // QT_NO_OPENGL
|
|
|
|
QT_END_NAMESPACE
|