Fix opaque texture-based widgets not being always shown.
Whenever a regular QWidget contains a child render-to-texture widget (like a QOpenGLWidget) that is opaque (attribute Qt::WA_OpaquePaintEvent is set) and completely covers the parent geometry, the child widget would not be shown. This happens because QWidgetBackingStore::doSync contains a check to see if an opaque child completely covers its parent, in which case it does not draw the parent, and only draws the child. This is an issue if the widget is actually a texture-based one, because for it to be seen on screen, the parent widget has to be redrawn with a proper blending mask, so that the rtt widget gets properly composed into the place where the mask is. The fix consists in keeping the parent widget being marked dirty, in case it has an opaque texture-based child that completely covers it. This will force a redraw of the parent widget with a proper blending mask. Change-Id: If1feec04b86bff2c49158b8d72f175cec252dea1 Task-number: QTBUG-52123 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
This commit is contained in:
parent
886086f5d3
commit
00061b968d
@ -1211,10 +1211,20 @@ void QWidgetBackingStore::doSync()
|
||||
// We know for sure that the widget isn't overlapped if 'isMoved' is true.
|
||||
if (!wd->isMoved)
|
||||
wd->subtractOpaqueSiblings(wd->dirty, &hasDirtySiblingsAbove);
|
||||
|
||||
// Make a copy of the widget's dirty region, to restore it in case there is an opaque
|
||||
// render-to-texture child that completely covers the widget, because otherwise the
|
||||
// render-to-texture child won't be visible, due to its parent widget not being redrawn
|
||||
// with a proper blending mask.
|
||||
const QRegion dirtyBeforeSubtractedOpaqueChildren = wd->dirty;
|
||||
|
||||
// Scrolled and moved widgets must draw all children.
|
||||
if (!wd->isScrolled && !wd->isMoved)
|
||||
wd->subtractOpaqueChildren(wd->dirty, w->rect());
|
||||
|
||||
if (wd->dirty.isEmpty() && wd->textureChildSeen)
|
||||
wd->dirty = dirtyBeforeSubtractedOpaqueChildren;
|
||||
|
||||
if (wd->dirty.isEmpty()) {
|
||||
resetWidget(w);
|
||||
continue;
|
||||
|
@ -34,11 +34,14 @@
|
||||
#include <QtWidgets/QOpenGLWidget>
|
||||
#include <QtGui/QOpenGLFunctions>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
#include <QtWidgets/QGraphicsView>
|
||||
#include <QtWidgets/QGraphicsScene>
|
||||
#include <QtWidgets/QGraphicsRectItem>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtTest/QtTest>
|
||||
#include <QSignalSpy>
|
||||
|
||||
@ -59,6 +62,7 @@ private slots:
|
||||
void fboRedirect();
|
||||
void showHide();
|
||||
void nativeWindow();
|
||||
void stackWidgetOpaqueChildIsVisible();
|
||||
};
|
||||
|
||||
void tst_QOpenGLWidget::create()
|
||||
@ -425,6 +429,135 @@ void tst_QOpenGLWidget::nativeWindow()
|
||||
QVERIFY(image.pixel(30, 40) == qRgb(0, 255, 0));
|
||||
}
|
||||
|
||||
static inline QString msgRgbMismatch(unsigned actual, unsigned expected)
|
||||
{
|
||||
return QString::asprintf("Color mismatch, %#010x != %#010x", actual, expected);
|
||||
}
|
||||
|
||||
static QPixmap grabWidgetWithoutRepaint(const QWidget *widget, QRect clipArea)
|
||||
{
|
||||
const QWidget *targetWidget = widget;
|
||||
#ifdef Q_OS_WIN
|
||||
// OpenGL content is not properly grabbed on Windows when passing a top level widget window,
|
||||
// because GDI functions can't grab OpenGL layer content.
|
||||
// Instead the whole screen should be captured, with an adjusted clip area, which contains
|
||||
// the final composited content.
|
||||
QDesktopWidget *desktopWidget = QApplication::desktop();
|
||||
const QWidget *mainScreenWidget = desktopWidget->screen();
|
||||
targetWidget = mainScreenWidget;
|
||||
clipArea = QRect(widget->mapToGlobal(clipArea.topLeft()),
|
||||
widget->mapToGlobal(clipArea.bottomRight()));
|
||||
#endif
|
||||
|
||||
const QWindow *window = targetWidget->window()->windowHandle();
|
||||
Q_ASSERT(window);
|
||||
WId windowId = window->winId();
|
||||
|
||||
QScreen *screen = window->screen();
|
||||
Q_ASSERT(screen);
|
||||
|
||||
const QSize size = clipArea.size();
|
||||
const QPixmap result = screen->grabWindow(windowId,
|
||||
clipArea.x(),
|
||||
clipArea.y(),
|
||||
size.width(),
|
||||
size.height());
|
||||
return result;
|
||||
}
|
||||
|
||||
#define VERIFY_COLOR(child, region, color) verifyColor(child, region, color, __LINE__)
|
||||
|
||||
bool verifyColor(const QWidget *widget, const QRect &clipArea, const QColor &color, int callerLine)
|
||||
{
|
||||
for (int t = 0; t < 6; t++) {
|
||||
const QPixmap pixmap = grabWidgetWithoutRepaint(widget, clipArea);
|
||||
if (!QTest::qCompare(pixmap.size(),
|
||||
clipArea.size(),
|
||||
"pixmap.size()",
|
||||
"rect.size()",
|
||||
__FILE__,
|
||||
callerLine))
|
||||
return false;
|
||||
|
||||
|
||||
const QImage image = pixmap.toImage();
|
||||
QPixmap expectedPixmap(pixmap); /* ensure equal formats */
|
||||
expectedPixmap.detach();
|
||||
expectedPixmap.fill(color);
|
||||
|
||||
uint alphaCorrection = image.format() == QImage::Format_RGB32 ? 0xff000000 : 0;
|
||||
uint firstPixel = image.pixel(0,0) | alphaCorrection;
|
||||
|
||||
// Retry a couple of times. Some window managers have transparency animation, or are
|
||||
// just slow to render.
|
||||
if (t < 5) {
|
||||
if (firstPixel == QColor(color).rgb()
|
||||
&& image == expectedPixmap.toImage())
|
||||
return true;
|
||||
else
|
||||
QTest::qWait(200);
|
||||
} else {
|
||||
if (!QTest::qVerify(firstPixel == QColor(color).rgb(),
|
||||
"firstPixel == QColor(color).rgb()",
|
||||
qPrintable(msgRgbMismatch(firstPixel, QColor(color).rgb())),
|
||||
__FILE__, callerLine)) {
|
||||
return false;
|
||||
}
|
||||
if (!QTest::qVerify(image == expectedPixmap.toImage(),
|
||||
"image == expectedPixmap.toImage()",
|
||||
"grabbed pixmap differs from expected pixmap",
|
||||
__FILE__, callerLine)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void tst_QOpenGLWidget::stackWidgetOpaqueChildIsVisible()
|
||||
{
|
||||
#ifdef Q_OS_OSX
|
||||
QSKIP("QScreen::grabWindow() doesn't work properly on OSX HighDPI screen: QTBUG-46803");
|
||||
return;
|
||||
#endif
|
||||
|
||||
QStackedWidget stack;
|
||||
|
||||
QWidget* emptyWidget = new QWidget(&stack);
|
||||
stack.addWidget(emptyWidget);
|
||||
|
||||
// Create an opaque red QOpenGLWidget.
|
||||
const int dimensionSize = 400;
|
||||
ClearWidget* clearWidget = new ClearWidget(&stack, dimensionSize, dimensionSize);
|
||||
clearWidget->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
stack.addWidget(clearWidget);
|
||||
|
||||
// Show initial QWidget.
|
||||
stack.setCurrentIndex(0);
|
||||
stack.resize(dimensionSize, dimensionSize);
|
||||
stack.show();
|
||||
QTest::qWaitForWindowExposed(&stack);
|
||||
QTest::qWaitForWindowActive(&stack);
|
||||
|
||||
// Switch to the QOpenGLWidget.
|
||||
stack.setCurrentIndex(1);
|
||||
QTRY_COMPARE(clearWidget->m_paintCalled, true);
|
||||
|
||||
// Resize the tested region to be half size in the middle, because some OSes make the widget
|
||||
// have rounded corners (e.g. OSX), and the grabbed window pixmap will not coincide perfectly
|
||||
// with what was actually painted.
|
||||
QRect clipArea = stack.rect();
|
||||
clipArea.setSize(clipArea.size() / 2);
|
||||
const int translationOffsetToMiddle = dimensionSize / 4;
|
||||
clipArea.translate(translationOffsetToMiddle, translationOffsetToMiddle);
|
||||
|
||||
// Verify that the QOpenGLWidget was actually painted AND displayed.
|
||||
const QColor red(255, 0, 0, 255);
|
||||
VERIFY_COLOR(&stack, clipArea, red);
|
||||
#undef VERIFY_COLOR
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QOpenGLWidget)
|
||||
|
||||
#include "tst_qopenglwidget.moc"
|
||||
|
Loading…
Reference in New Issue
Block a user