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();
}
void GLWidget::resizeGL(int w, int h)
void GLWidget::resizeGL(int, int)
{
if (m_hasButton) {
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);
}
m_btn->move(w / 2, h / 2);
m_btn->move(20, 80);
}
}

View File

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

View File

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

View File

@ -86,6 +86,7 @@ struct QBackingstoreTextureInfo
QWidget *widget; // may be null
GLuint textureId;
QRect rect;
QRect clipRect;
QPlatformTextureList::Flags flags;
};
@ -142,6 +143,12 @@ QRect QPlatformTextureList::geometry(int index) const
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)
{
Q_D(QPlatformTextureList);
@ -157,13 +164,15 @@ bool QPlatformTextureList::isLocked() const
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);
QBackingstoreTextureInfo bi;
bi.widget = widget;
bi.textureId = textureId;
bi.rect = geometry;
bi.clipRect = clipRect;
bi.flags = flags;
d->textures.append(bi);
}
@ -198,7 +207,7 @@ void QPlatformTextureList::clear()
#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(),
rect.size() * window->devicePixelRatio());
@ -219,6 +228,32 @@ static QRegion deviceRegion(const QRegion &region, QWindow *window)
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
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();
QRect windowRect(QPoint(), window->size() * window->devicePixelRatio());
const QRect deviceWindowRect = deviceRect(QRect(QPoint(), window->size()), window);
// Textures for renderToTexture widgets.
for (int i = 0; i < textures->count(); ++i) {
if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
QRect targetRect = deviceRect(textures->geometry(i), window);
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, windowRect);
d_ptr->blitter->blit(textures->textureId(i), target, QOpenGLTextureBlitter::OriginBottomLeft);
}
if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop))
blit(textures, i, window, deviceWindowRect, d_ptr->blitter);
}
funcs->glEnable(GL_BLEND);
@ -272,6 +304,7 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
// semi-transparency even when it is not wanted.
funcs->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
// Backingstore texture with the normal widgets.
GLuint textureId = 0;
QOpenGLTextureBlitter::Origin origin = QOpenGLTextureBlitter::OriginTopLeft;
if (QPlatformGraphicsBuffer *graphicsBuffer = this->graphicsBuffer()) {
@ -307,7 +340,6 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
origin = QOpenGLTextureBlitter::OriginBottomLeft;
textureId = d_ptr->textureId;
} else {
// Backingstore texture with the normal widgets.
TextureFlags flags = 0;
textureId = toTexture(deviceRegion(region, window), &d_ptr->textureSize, &flags);
d_ptr->needsSwizzle = (flags & TextureSwizzle) != 0;
@ -316,7 +348,7 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
}
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)
d_ptr->blitter->setSwizzleRB(true);
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.
for (int i = 0; i < textures->count(); ++i) {
if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
QRect targetRect = deviceRect(textures->geometry(i), window);
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, windowRect);
d_ptr->blitter->blit(textures->textureId(i), target, QOpenGLTextureBlitter::OriginBottomLeft);
}
if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop))
blit(textures, i, window, deviceWindowRect, d_ptr->blitter);
}
funcs->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

View File

@ -82,12 +82,14 @@ public:
bool isEmpty() const { return count() == 0; }
GLuint textureId(int index) const;
QRect geometry(int index) const;
QRect clipRect(int index) const;
QWidget *widget(int index);
Flags flags(int index) const;
void lock(bool on);
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();
Q_SIGNALS:

View File

@ -169,6 +169,29 @@ struct BlendStateBinder
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)
{
const QPlatformTextureList *textures = window->textures();
@ -181,7 +204,6 @@ void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
for (int i = 0; i < textures->count(); ++i) {
uint textureId = textures->textureId(i);
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
const float opacity = window->sourceWindow()->opacity();
if (opacity != currentOpacity) {
currentOpacity = opacity;
@ -191,24 +213,25 @@ void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
if (textures->count() > 1 && i == textures->count() - 1) {
// Backingstore for a widget with QOpenGLWidget subwidgets
blend.set(true);
const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft);
} else if (textures->count() == 1) {
// A regular QWidget window
const bool translucent = window->sourceWindow()->requestedFormat().alphaBufferSize() > 0;
blend.set(translucent);
const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft);
} else if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
// Texture from an FBO belonging to a QOpenGLWidget
blend.set(false);
m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginBottomLeft);
clippedBlit(textures, i, targetWindowRect, &m_blitter);
}
}
for (int i = 0; i < textures->count(); ++i) {
if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
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();
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();
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;
if (widget->testAttribute(Qt::WA_AlwaysStackOnTop))
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) {