Prevent continuous painting with viewport QOpenGLWidget

Add the source widget to the texture list (may be null for custom
compositor implementations that add textures not belonging to actual
widgets). This allows us to do proper checks with the
dirtyRenderToTextureWidgets list.

As a result paint events are only sent to a QOpenGLWidget if (1) there
was an update() for it or (2) it was actually marked dirty. (2) was
previously behaving differently: the widget got a paint event when
anything in the window has changed. This is fine for naive animating
OpenGL code but less ideal for QGraphicsView.

Bool properties like stacksOnTop are now stored in a flags value to
prevent future explosion of texture list fields and parameters.

Task-number: QTBUG-43178
Change-Id: I48cbcf93df72ac682c9b5d64982a8b648fe21ef3
Reviewed-by: Jørgen Lind <jorgen.lind@theqtcompany.com>
Reviewed-by: Paul Olav Tvete <paul.tvete@theqtcompany.com>
bb10
Laszlo Agocs 2014-12-12 12:59:43 +01:00 committed by Kai Koehne
parent b8e71aa847
commit 6c2da36c22
6 changed files with 94 additions and 32 deletions

View File

@ -82,9 +82,10 @@ public:
struct QBackingstoreTextureInfo
{
QWidget *widget; // may be null
GLuint textureId;
QRect rect;
bool stacksOnTop;
QPlatformTextureList::Flags flags;
};
Q_DECLARE_TYPEINFO(QBackingstoreTextureInfo, Q_MOVABLE_TYPE);
@ -122,10 +123,16 @@ GLuint QPlatformTextureList::textureId(int index) const
return d->textures.at(index).textureId;
}
bool QPlatformTextureList::stacksOnTop(int index) const
QWidget *QPlatformTextureList::widget(int index)
{
Q_D(const QPlatformTextureList);
return d->textures.at(index).stacksOnTop;
return d->textures.at(index).widget;
}
QPlatformTextureList::Flags QPlatformTextureList::flags(int index) const
{
Q_D(const QPlatformTextureList);
return d->textures.at(index).flags;
}
QRect QPlatformTextureList::geometry(int index) const
@ -149,13 +156,14 @@ bool QPlatformTextureList::isLocked() const
return d->locked;
}
void QPlatformTextureList::appendTexture(GLuint textureId, const QRect &geometry, bool stacksOnTop)
void QPlatformTextureList::appendTexture(QWidget *widget, GLuint textureId, const QRect &geometry, Flags flags)
{
Q_D(QPlatformTextureList);
QBackingstoreTextureInfo bi;
bi.widget = widget;
bi.textureId = textureId;
bi.rect = geometry;
bi.stacksOnTop = stacksOnTop;
bi.flags = flags;
d->textures.append(bi);
}
@ -245,7 +253,7 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
// Textures for renderToTexture widgets.
for (int i = 0; i < textures->count(); ++i) {
if (!textures->stacksOnTop(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);
@ -272,7 +280,7 @@ 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->stacksOnTop(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);

View File

@ -69,6 +69,11 @@ class Q_GUI_EXPORT QPlatformTextureList : public QObject
Q_OBJECT
Q_DECLARE_PRIVATE(QPlatformTextureList)
public:
enum Flag {
StacksOnTop = 0x01
};
Q_DECLARE_FLAGS(Flags, Flag)
explicit QPlatformTextureList(QObject *parent = 0);
~QPlatformTextureList();
@ -76,16 +81,18 @@ public:
bool isEmpty() const { return count() == 0; }
GLuint textureId(int index) const;
QRect geometry(int index) const;
bool stacksOnTop(int index) const;
QWidget *widget(int index);
Flags flags(int index) const;
void lock(bool on);
bool isLocked() const;
void appendTexture(GLuint textureId, const QRect &geometry, bool stacksOnTop = false);
void appendTexture(QWidget *widget, GLuint textureId, const QRect &geometry, Flags flags = 0);
void clear();
Q_SIGNALS:
void locked(bool);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QPlatformTextureList::Flags)
#endif
class Q_GUI_EXPORT QPlatformBackingStore

View File

@ -151,7 +151,7 @@ void QEGLCompositor::render(QEGLPlatformWindow *window)
const bool translucent = window->window()->requestedFormat().alphaBufferSize() > 0;
blend.set(translucent);
m_blitter->blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft);
} else if (!textures->stacksOnTop(i)) {
} 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);
@ -159,7 +159,7 @@ void QEGLCompositor::render(QEGLPlatformWindow *window)
}
for (int i = 0; i < textures->count(); ++i) {
if (textures->stacksOnTop(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);

View File

@ -152,7 +152,7 @@ void QEGLPlatformBackingStore::flush(QWindow *window, const QRegion &region, con
screen->compositingContext()->makeCurrent(dstWin->window());
updateTexture();
m_textures->clear();
m_textures->appendTexture(m_bsTexture, window->geometry());
m_textures->appendTexture(Q_NULLPTR, m_bsTexture, window->geometry());
composite(screen->compositingContext(), dstWin);
}
@ -176,10 +176,10 @@ void QEGLPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &r
m_textures->clear();
for (int i = 0; i < textures->count(); ++i)
m_textures->appendTexture(textures->textureId(i), textures->geometry(i), textures->stacksOnTop(i));
m_textures->appendTexture(textures->widget(i), textures->textureId(i), textures->geometry(i), textures->flags(i));
updateTexture();
m_textures->appendTexture(m_bsTexture, window->geometry());
m_textures->appendTexture(Q_NULLPTR, m_bsTexture, window->geometry());
textures->lock(true);
m_lockedWidgetTextures = textures;

View File

@ -963,9 +963,12 @@ void QWidgetBackingStore::sync(QWidget *exposedWidget, const QRegion &exposedReg
static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatformTextureList *widgetTextures)
{
QWidgetPrivate *wd = QWidgetPrivate::get(widget);
if (wd->renderToTexture)
widgetTextures->appendTexture(wd->textureId(), QRect(widget->mapTo(tlw, QPoint()), widget->size()),
widget->testAttribute(Qt::WA_AlwaysStackOnTop));
if (wd->renderToTexture) {
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);
}
for (int i = 0; i < wd->children.size(); ++i) {
QWidget *w = qobject_cast<QWidget *>(wd->children.at(i));
@ -1156,28 +1159,20 @@ void QWidgetBackingStore::doSync()
}
#ifndef QT_NO_OPENGL
// There is something other dirty than the renderToTexture widgets.
// Now it is time to include the renderToTexture ones among the others.
if (widgetTextures && widgetTextures->count()) {
for (int i = 0; i < widgetTextures->count(); ++i) {
const QRect rect = widgetTextures->geometry(i); // mapped to the tlw already
dirty += rect;
toClean += rect;
QWidget *w = widgetTextures->widget(i);
if (dirtyRenderToTextureWidgets.contains(w)) {
const QRect rect = widgetTextures->geometry(i); // mapped to the tlw already
dirty += rect;
toClean += rect;
}
}
}
#endif
// The dirtyRenderToTextureWidgets list is useless here, so just reset. As
// unintuitive as it is, we need to send paint events to renderToTexture
// widgets always when something (any widget) needs to be updated, even if
// the renderToTexture widget itself is clean, i.e. there was no update()
// call for it. This is because changing any widget will cause a flush and
// so a potentially blocking buffer swap for the window, and skipping paints
// for the renderToTexture widgets would make it impossible to have smoothly
// animated content in them.
for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i)
resetWidget(dirtyRenderToTextureWidgets.at(i));
dirtyRenderToTextureWidgets.clear();
#endif
#ifndef QT_NO_GRAPHICSVIEW
if (tlw->d_func()->extra->proxyWidget) {

View File

@ -34,6 +34,11 @@
#include <QtWidgets/QOpenGLWidget>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QPainter>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsRectItem>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QPushButton>
#include <QtTest/QtTest>
#include <QSignalSpy>
@ -49,6 +54,7 @@ private slots:
void painter();
void reparentToAlreadyCreated();
void reparentToNotYetCreated();
void asViewport();
};
void tst_QOpenGLWidget::create()
@ -253,6 +259,52 @@ void tst_QOpenGLWidget::reparentToNotYetCreated()
QVERIFY(image.pixel(20, 10) == qRgb(0, 0, 255));
}
class CountingGraphicsView : public QGraphicsView
{
public:
CountingGraphicsView(): m_count(0) { }
int paintCount() const { return m_count; }
void resetPaintCount() { m_count = 0; }
protected:
void drawForeground(QPainter *, const QRectF &) Q_DECL_OVERRIDE;
int m_count;
};
void CountingGraphicsView::drawForeground(QPainter *, const QRectF &)
{
++m_count;
}
void tst_QOpenGLWidget::asViewport()
{
// Have a QGraphicsView with a QOpenGLWidget as its viewport.
QGraphicsScene scene;
scene.addItem(new QGraphicsRectItem(10, 10, 100, 100));
CountingGraphicsView *view = new CountingGraphicsView;
view->setScene(&scene);
view->setViewport(new QOpenGLWidget);
QWidget widget;
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(view);
QPushButton *btn = new QPushButton("Test");
layout->addWidget(btn);
widget.setLayout(layout);
widget.show();
QTest::qWaitForWindowExposed(&widget);
QVERIFY(view->paintCount() > 0);
view->resetPaintCount();
// And now trigger a repaint on the push button. We must not
// receive paint events for the graphics view. If we do, that's a
// side effect of QOpenGLWidget's special behavior and handling in
// the widget stack.
btn->update();
qApp->processEvents();
QVERIFY(view->paintCount() == 0);
}
QTEST_MAIN(tst_QOpenGLWidget)
#include "tst_qopenglwidget.moc"