Clip QOpenGLWidget and QQuickWidget correctly

Introduce support for the widgets' clipRect(). Right now render-to-texture widgets
in scroll areas placed close to each other result in broken (non-existent) clipping.
Similarly, stack-on-top widgets fail to clip when placed inside a scroll area.

This is now corrected and the qopenglwidget example is enhanced to utilize a scroll
area.

Task-number: QTBUG-45860
Change-Id: I859a63d61a50d64ba9e87244f83c5969dce12337
Reviewed-by: Jørgen Lind <jorgen.lind@theqtcompany.com>
This commit is contained in:
Laszlo Agocs 2015-06-01 11:44:37 +02:00
parent 069be16543
commit 78e3354083
8 changed files with 99 additions and 29 deletions

View File

@ -537,14 +537,14 @@ void GLWidget::setTransparent(bool transparent)
window()->update(); window()->update();
} }
void GLWidget::resizeGL(int w, int h) void GLWidget::resizeGL(int, int)
{ {
if (m_hasButton) { if (m_hasButton) {
if (!m_btn) { if (!m_btn) {
m_btn = new QPushButton("A widget on top.\nPress me!", this); m_btn = new QPushButton("A widget on top.\nPress for more widgets.", this);
connect(m_btn, &QPushButton::clicked, this, &GLWidget::handleButtonPress); connect(m_btn, &QPushButton::clicked, this, &GLWidget::handleButtonPress);
} }
m_btn->move(w / 2, h / 2); m_btn->move(20, 80);
} }
} }

View File

@ -47,6 +47,7 @@
#include <QLabel> #include <QLabel>
#include <QCheckBox> #include <QCheckBox>
#include <QSpinBox> #include <QSpinBox>
#include <QScrollArea>
#include "glwidget.h" #include "glwidget.h"
@ -69,7 +70,7 @@ MainWindow::MainWindow()
"and therefore an interval < 16 ms will likely lead to a 60 FPS update rate."); "and therefore an interval < 16 ms will likely lead to a 60 FPS update rate.");
QGroupBox *updateGroupBox = new QGroupBox(this); QGroupBox *updateGroupBox = new QGroupBox(this);
QCheckBox *timerBased = new QCheckBox("Use timer", this); QCheckBox *timerBased = new QCheckBox("Use timer", this);
timerBased->setChecked(true); timerBased->setChecked(false);
timerBased->setToolTip("Toggles using a timer to trigger update().\n" timerBased->setToolTip("Toggles using a timer to trigger update().\n"
"When not set, each paintGL() schedules the next update immediately,\n" "When not set, each paintGL() schedules the next update immediately,\n"
"expecting the blocking swap to throttle the thread.\n" "expecting the blocking swap to throttle the thread.\n"
@ -87,7 +88,7 @@ MainWindow::MainWindow()
slider->setRange(0, 50); slider->setRange(0, 50);
slider->setSliderPosition(30); slider->setSliderPosition(30);
m_timer->setInterval(10); m_timer->setInterval(10);
label->setText("A QOpenGLWidget"); label->setText("A scrollable QOpenGLWidget");
label->setAlignment(Qt::AlignHCenter); label->setAlignment(Qt::AlignHCenter);
QGroupBox * groupBox = new QGroupBox(this); QGroupBox * groupBox = new QGroupBox(this);
@ -96,7 +97,10 @@ MainWindow::MainWindow()
m_layout = new QGridLayout(groupBox); m_layout = new QGridLayout(groupBox);
m_layout->addWidget(glwidget,1,0,8,1); QScrollArea *scrollArea = new QScrollArea;
scrollArea->setWidget(glwidget);
m_layout->addWidget(scrollArea,1,0,8,1);
m_layout->addWidget(label,9,0,1,1); m_layout->addWidget(label,9,0,1,1);
m_layout->addWidget(updateGroupBox, 10, 0, 1, 1); m_layout->addWidget(updateGroupBox, 10, 0, 1, 1);
m_layout->addWidget(slider, 11,0,1,1); m_layout->addWidget(slider, 11,0,1,1);
@ -134,7 +138,10 @@ MainWindow::MainWindow()
connect(timerBased, &QCheckBox::toggled, this, &MainWindow::timerUsageChanged); connect(timerBased, &QCheckBox::toggled, this, &MainWindow::timerUsageChanged);
connect(timerBased, &QCheckBox::toggled, updateInterval, &QWidget::setEnabled); connect(timerBased, &QCheckBox::toggled, updateInterval, &QWidget::setEnabled);
m_timer->start(); if (timerBased->isChecked())
m_timer->start();
else
updateInterval->setEnabled(false);
} }
void MainWindow::updateIntervalChanged(int value) void MainWindow::updateIntervalChanged(int value)
@ -170,3 +177,8 @@ void MainWindow::timerUsageChanged(bool enabled)
w->update(); w->update();
} }
} }
void MainWindow::resizeEvent(QResizeEvent *)
{
m_glWidgets[0]->setMinimumSize(size() + QSize(128, 128));
}

View File

@ -56,6 +56,8 @@ public:
void addNew(); void addNew();
bool timerEnabled() const { return m_timer->isActive(); } bool timerEnabled() const { return m_timer->isActive(); }
void resizeEvent(QResizeEvent *);
private slots: private slots:
void updateIntervalChanged(int value); void updateIntervalChanged(int value);
void timerUsageChanged(bool enabled); void timerUsageChanged(bool enabled);

View File

@ -86,6 +86,7 @@ struct QBackingstoreTextureInfo
QWidget *widget; // may be null QWidget *widget; // may be null
GLuint textureId; GLuint textureId;
QRect rect; QRect rect;
QRect clipRect;
QPlatformTextureList::Flags flags; QPlatformTextureList::Flags flags;
}; };
@ -142,6 +143,12 @@ QRect QPlatformTextureList::geometry(int index) const
return d->textures.at(index).rect; return d->textures.at(index).rect;
} }
QRect QPlatformTextureList::clipRect(int index) const
{
Q_D(const QPlatformTextureList);
return d->textures.at(index).clipRect;
}
void QPlatformTextureList::lock(bool on) void QPlatformTextureList::lock(bool on)
{ {
Q_D(QPlatformTextureList); Q_D(QPlatformTextureList);
@ -157,13 +164,15 @@ bool QPlatformTextureList::isLocked() const
return d->locked; return d->locked;
} }
void QPlatformTextureList::appendTexture(QWidget *widget, GLuint textureId, const QRect &geometry, Flags flags) void QPlatformTextureList::appendTexture(QWidget *widget, GLuint textureId, const QRect &geometry,
const QRect &clipRect, Flags flags)
{ {
Q_D(QPlatformTextureList); Q_D(QPlatformTextureList);
QBackingstoreTextureInfo bi; QBackingstoreTextureInfo bi;
bi.widget = widget; bi.widget = widget;
bi.textureId = textureId; bi.textureId = textureId;
bi.rect = geometry; bi.rect = geometry;
bi.clipRect = clipRect;
bi.flags = flags; bi.flags = flags;
d->textures.append(bi); d->textures.append(bi);
} }
@ -198,7 +207,7 @@ void QPlatformTextureList::clear()
#ifndef QT_NO_OPENGL #ifndef QT_NO_OPENGL
static QRect deviceRect(const QRect &rect, QWindow *window) static inline QRect deviceRect(const QRect &rect, QWindow *window)
{ {
QRect deviceRect(rect.topLeft() * window->devicePixelRatio(), QRect deviceRect(rect.topLeft() * window->devicePixelRatio(),
rect.size() * window->devicePixelRatio()); rect.size() * window->devicePixelRatio());
@ -219,6 +228,32 @@ static QRegion deviceRegion(const QRegion &region, QWindow *window)
return deviceRegion; 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 blit(const QPlatformTextureList *textures, int idx, QWindow *window, const QRect &deviceWindowRect,
QOpenGLTextureBlitter *blitter)
{
const QRect rectInWindow = textures->geometry(idx);
QRect clipRect = textures->clipRect(idx);
if (clipRect.isEmpty())
clipRect = QRect(QPoint(0, 0), rectInWindow.size());
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);
blitter->blit(textures->textureId(idx), target, source);
}
/*! /*!
Flushes the given \a region from the specified \a window onto the Flushes the given \a region from the specified \a window onto the
screen, and composes it with the specified \a textures. screen, and composes it with the specified \a textures.
@ -254,15 +289,12 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
d_ptr->blitter->bind(); d_ptr->blitter->bind();
QRect windowRect(QPoint(), window->size() * window->devicePixelRatio()); const QRect deviceWindowRect = deviceRect(QRect(QPoint(), window->size()), window);
// Textures for renderToTexture widgets. // Textures for renderToTexture widgets.
for (int i = 0; i < textures->count(); ++i) { for (int i = 0; i < textures->count(); ++i) {
if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) { if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop))
QRect targetRect = deviceRect(textures->geometry(i), window); blit(textures, i, window, deviceWindowRect, d_ptr->blitter);
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, windowRect);
d_ptr->blitter->blit(textures->textureId(i), target, QOpenGLTextureBlitter::OriginBottomLeft);
}
} }
funcs->glEnable(GL_BLEND); funcs->glEnable(GL_BLEND);
@ -272,6 +304,7 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
// semi-transparency even when it is not wanted. // semi-transparency even when it is not wanted.
funcs->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); funcs->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
// Backingstore texture with the normal widgets.
GLuint textureId = 0; GLuint textureId = 0;
QOpenGLTextureBlitter::Origin origin = QOpenGLTextureBlitter::OriginTopLeft; QOpenGLTextureBlitter::Origin origin = QOpenGLTextureBlitter::OriginTopLeft;
if (QPlatformGraphicsBuffer *graphicsBuffer = this->graphicsBuffer()) { if (QPlatformGraphicsBuffer *graphicsBuffer = this->graphicsBuffer()) {
@ -307,7 +340,6 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
origin = QOpenGLTextureBlitter::OriginBottomLeft; origin = QOpenGLTextureBlitter::OriginBottomLeft;
textureId = d_ptr->textureId; textureId = d_ptr->textureId;
} else { } else {
// Backingstore texture with the normal widgets.
TextureFlags flags = 0; TextureFlags flags = 0;
textureId = toTexture(deviceRegion(region, window), &d_ptr->textureSize, &flags); textureId = toTexture(deviceRegion(region, window), &d_ptr->textureSize, &flags);
d_ptr->needsSwizzle = (flags & TextureSwizzle) != 0; d_ptr->needsSwizzle = (flags & TextureSwizzle) != 0;
@ -316,7 +348,7 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
} }
if (textureId) { if (textureId) {
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(QRect(QPoint(), d_ptr->textureSize), windowRect); QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(QRect(QPoint(), d_ptr->textureSize), deviceWindowRect);
if (d_ptr->needsSwizzle) if (d_ptr->needsSwizzle)
d_ptr->blitter->setSwizzleRB(true); d_ptr->blitter->setSwizzleRB(true);
d_ptr->blitter->blit(textureId, target, origin); d_ptr->blitter->blit(textureId, target, origin);
@ -326,11 +358,8 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
// Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set. // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set.
for (int i = 0; i < textures->count(); ++i) { for (int i = 0; i < textures->count(); ++i) {
if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) { if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop))
QRect targetRect = deviceRect(textures->geometry(i), window); blit(textures, i, window, deviceWindowRect, d_ptr->blitter);
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, windowRect);
d_ptr->blitter->blit(textures->textureId(i), target, QOpenGLTextureBlitter::OriginBottomLeft);
}
} }
funcs->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); funcs->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

View File

@ -82,12 +82,14 @@ public:
bool isEmpty() const { return count() == 0; } bool isEmpty() const { return count() == 0; }
GLuint textureId(int index) const; GLuint textureId(int index) const;
QRect geometry(int index) const; QRect geometry(int index) const;
QRect clipRect(int index) const;
QWidget *widget(int index); QWidget *widget(int index);
Flags flags(int index) const; Flags flags(int index) const;
void lock(bool on); void lock(bool on);
bool isLocked() const; bool isLocked() const;
void appendTexture(QWidget *widget, GLuint textureId, const QRect &geometry, Flags flags = 0); void appendTexture(QWidget *widget, GLuint textureId, const QRect &geometry,
const QRect &clipRect = QRect(), Flags flags = 0);
void clear(); void clear();
Q_SIGNALS: Q_SIGNALS:

View File

@ -169,6 +169,29 @@ struct BlendStateBinder
bool m_blend; bool m_blend;
}; };
static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
{
return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
topLeftRect.width(), topLeftRect.height());
}
static void clippedBlit(const QPlatformTextureList *textures, int idx, const QRect &targetWindowRect, QOpenGLTextureBlitter *blitter)
{
const QRect rectInWindow = textures->geometry(idx);
QRect clipRect = textures->clipRect(idx);
if (clipRect.isEmpty())
clipRect = QRect(QPoint(0, 0), rectInWindow.size());
const QRect clippedRectInWindow = rectInWindow & clipRect.translated(rectInWindow.topLeft());
const QRect srcRect = toBottomLeftRect(clipRect, rectInWindow.height());
const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(clippedRectInWindow, targetWindowRect);
const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(srcRect, rectInWindow.size(),
QOpenGLTextureBlitter::OriginBottomLeft);
blitter->blit(textures->textureId(idx), target, source);
}
void QOpenGLCompositor::render(QOpenGLCompositorWindow *window) void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
{ {
const QPlatformTextureList *textures = window->textures(); const QPlatformTextureList *textures = window->textures();
@ -181,7 +204,6 @@ void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
for (int i = 0; i < textures->count(); ++i) { for (int i = 0; i < textures->count(); ++i) {
uint textureId = textures->textureId(i); uint textureId = textures->textureId(i);
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
const float opacity = window->sourceWindow()->opacity(); const float opacity = window->sourceWindow()->opacity();
if (opacity != currentOpacity) { if (opacity != currentOpacity) {
currentOpacity = opacity; currentOpacity = opacity;
@ -191,24 +213,25 @@ void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
if (textures->count() > 1 && i == textures->count() - 1) { if (textures->count() > 1 && i == textures->count() - 1) {
// Backingstore for a widget with QOpenGLWidget subwidgets // Backingstore for a widget with QOpenGLWidget subwidgets
blend.set(true); blend.set(true);
const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft); m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft);
} else if (textures->count() == 1) { } else if (textures->count() == 1) {
// A regular QWidget window // A regular QWidget window
const bool translucent = window->sourceWindow()->requestedFormat().alphaBufferSize() > 0; const bool translucent = window->sourceWindow()->requestedFormat().alphaBufferSize() > 0;
blend.set(translucent); blend.set(translucent);
const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft); m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft);
} else if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) { } else if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
// Texture from an FBO belonging to a QOpenGLWidget // Texture from an FBO belonging to a QOpenGLWidget
blend.set(false); blend.set(false);
m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginBottomLeft); clippedBlit(textures, i, targetWindowRect, &m_blitter);
} }
} }
for (int i = 0; i < textures->count(); ++i) { for (int i = 0; i < textures->count(); ++i) {
if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) { if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
blend.set(true); blend.set(true);
m_blitter.blit(textures->textureId(i), target, QOpenGLTextureBlitter::OriginBottomLeft); clippedBlit(textures, i, targetWindowRect, &m_blitter);
} }
} }

View File

@ -175,7 +175,8 @@ void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegi
m_textures->clear(); m_textures->clear();
for (int i = 0; i < textures->count(); ++i) for (int i = 0; i < textures->count(); ++i)
m_textures->appendTexture(textures->widget(i), textures->textureId(i), textures->geometry(i), textures->flags(i)); m_textures->appendTexture(textures->widget(i), textures->textureId(i), textures->geometry(i),
textures->clipRect(i), textures->flags(i));
updateTexture(); updateTexture();
m_textures->appendTexture(Q_NULLPTR, m_bsTexture, window->geometry()); m_textures->appendTexture(Q_NULLPTR, m_bsTexture, window->geometry());

View File

@ -962,7 +962,8 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatfo
QPlatformTextureList::Flags flags = 0; QPlatformTextureList::Flags flags = 0;
if (widget->testAttribute(Qt::WA_AlwaysStackOnTop)) if (widget->testAttribute(Qt::WA_AlwaysStackOnTop))
flags |= QPlatformTextureList::StacksOnTop; flags |= QPlatformTextureList::StacksOnTop;
widgetTextures->appendTexture(widget, wd->textureId(), QRect(widget->mapTo(tlw, QPoint()), widget->size()), flags); const QRect rect(widget->mapTo(tlw, QPoint()), widget->size());
widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags);
} }
for (int i = 0; i < wd->children.size(); ++i) { for (int i = 0; i < wd->children.size(); ++i) {