Do grabs out of order
...while extending the autotest to cover more complicated cases, such as grabbing again after show-hide and doing show-grab-hide-grab-show-grab. In fact some of these cases have not been working in Qt 5. Now the basic render loop is fixed up to support the all the combinations threaded does. Task-number: QTBUG-87399 Change-Id: Id01995bc3a2660b16cfb2f8bedc84becea0be1bb Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
parent
8103b8ce12
commit
c5ac1b8a06
|
@ -172,18 +172,20 @@ public:
|
|||
|
||||
struct WindowData {
|
||||
WindowData()
|
||||
: updatePending(false),
|
||||
grabOnly(false),
|
||||
: sampleCount(1),
|
||||
updatePending(false),
|
||||
rhiDeviceLost(false),
|
||||
rhiDoomed(false)
|
||||
{ }
|
||||
QElapsedTimer timeBetweenRenders;
|
||||
int sampleCount;
|
||||
bool updatePending : 1;
|
||||
bool grabOnly : 1;
|
||||
bool rhiDeviceLost : 1;
|
||||
bool rhiDoomed : 1;
|
||||
};
|
||||
|
||||
bool ensureRhi(QQuickWindow *window, WindowData &data);
|
||||
|
||||
QHash<QQuickWindow *, WindowData> m_windows;
|
||||
|
||||
QOffscreenSurface *offscreenSurface = nullptr;
|
||||
|
@ -191,7 +193,6 @@ public:
|
|||
QSGContext *sg;
|
||||
QSGRenderContext *rc;
|
||||
|
||||
QImage grabContent;
|
||||
bool m_inPolish = false;
|
||||
};
|
||||
#endif
|
||||
|
@ -447,22 +448,11 @@ bool QSGGuiThreadRenderLoop::eventFilter(QObject *watched, QEvent *event)
|
|||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
|
||||
bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data)
|
||||
{
|
||||
if (!m_windows.contains(window))
|
||||
return;
|
||||
|
||||
WindowData &data = const_cast<WindowData &>(m_windows[window]);
|
||||
bool alsoSwap = data.updatePending;
|
||||
data.updatePending = false;
|
||||
|
||||
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
|
||||
if (!cd->isRenderable())
|
||||
return;
|
||||
|
||||
bool current = false;
|
||||
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
|
||||
int rhiSampleCount = 1;
|
||||
bool current = false;
|
||||
|
||||
if (!rhi) {
|
||||
// This block below handles both the initial QRhi initialization and
|
||||
|
@ -470,7 +460,7 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
|
|||
// (reset) situation.
|
||||
|
||||
if (data.rhiDoomed) // no repeated attempts if the initial attempt failed
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (!offscreenSurface)
|
||||
offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
|
||||
|
@ -488,13 +478,13 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
|
|||
|
||||
// The sample count cannot vary between windows as we use the same
|
||||
// rendercontext for all of them. Decide it here and now.
|
||||
rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
|
||||
data.sampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
|
||||
|
||||
cd->rhi = rhi; // set this early in case something hooked up to rc initialized() accesses it
|
||||
|
||||
QSGDefaultRenderContext::InitParams rcParams;
|
||||
rcParams.rhi = rhi;
|
||||
rcParams.sampleCount = rhiSampleCount;
|
||||
rcParams.sampleCount = data.sampleCount;
|
||||
rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
|
||||
rcParams.maybeSurface = window;
|
||||
cd->context->initialize(&rcParams);
|
||||
|
@ -546,14 +536,14 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
|
|||
if (depthBufferEnabled) {
|
||||
cd->depthStencilForSwapchain = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
|
||||
QSize(),
|
||||
rhiSampleCount,
|
||||
data.sampleCount,
|
||||
QRhiRenderBuffer::UsedWithSwapChainOnly);
|
||||
cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
|
||||
}
|
||||
cd->swapchain->setWindow(window);
|
||||
qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s",
|
||||
rhiSampleCount, alpha ? "yes" : "no");
|
||||
cd->swapchain->setSampleCount(rhiSampleCount);
|
||||
data.sampleCount, alpha ? "yes" : "no");
|
||||
cd->swapchain->setSampleCount(data.sampleCount);
|
||||
cd->swapchain->setFlags(flags);
|
||||
cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
|
||||
cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
|
||||
|
@ -561,6 +551,25 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
|
|||
window->installEventFilter(this);
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
|
||||
{
|
||||
if (!m_windows.contains(window))
|
||||
return;
|
||||
|
||||
WindowData &data = const_cast<WindowData &>(m_windows[window]);
|
||||
bool alsoSwap = data.updatePending;
|
||||
data.updatePending = false;
|
||||
|
||||
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
|
||||
if (!cd->isRenderable())
|
||||
return;
|
||||
|
||||
if (!ensureRhi(window, data))
|
||||
return;
|
||||
|
||||
bool lastDirtyWindow = true;
|
||||
auto i = m_windows.constBegin();
|
||||
while (i != m_windows.constEnd()) {
|
||||
|
@ -571,16 +580,11 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
|
|||
i++;
|
||||
}
|
||||
|
||||
if (!current)
|
||||
cd->flushFrameSynchronousEvents();
|
||||
// Event delivery/processing triggered the window to be deleted or stop rendering.
|
||||
if (!m_windows.contains(window))
|
||||
return;
|
||||
|
||||
if (!data.grabOnly) {
|
||||
cd->flushFrameSynchronousEvents();
|
||||
// Event delivery/processing triggered the window to be deleted or stop rendering.
|
||||
if (!m_windows.contains(window))
|
||||
return;
|
||||
}
|
||||
|
||||
QSize effectiveOutputSize; // always prefer what the surface tells us, not the QWindow
|
||||
if (cd->swapchain) {
|
||||
effectiveOutputSize = cd->swapchain->surfacePixelSize();
|
||||
|
@ -682,13 +686,6 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
|
|||
QQuickProfiler::SceneGraphRenderLoopRender);
|
||||
Q_TRACE(QSG_swap_entry);
|
||||
|
||||
if (data.grabOnly) {
|
||||
if (cd->swapchain)
|
||||
grabContent = rhiSupport->grabAndBlockInCurrentFrame(rhi, cd->swapchain->currentFrameCommandBuffer());
|
||||
grabContent.setDevicePixelRatio(window->effectiveDevicePixelRatio());
|
||||
data.grabOnly = false;
|
||||
}
|
||||
|
||||
const bool needsPresent = alsoSwap && window->isVisible();
|
||||
if (cd->swapchain) {
|
||||
QRhi::EndFrameFlags flags;
|
||||
|
@ -766,13 +763,25 @@ QImage QSGGuiThreadRenderLoop::grab(QQuickWindow *window)
|
|||
if (!m_windows.contains(window))
|
||||
return QImage();
|
||||
|
||||
m_windows[window].grabOnly = true;
|
||||
if (!ensureRhi(window, m_windows[window]))
|
||||
return QImage();
|
||||
|
||||
renderWindow(window);
|
||||
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
|
||||
m_inPolish = true;
|
||||
cd->polishItems();
|
||||
m_inPolish = false;
|
||||
|
||||
QImage grabbed = grabContent;
|
||||
grabContent = QImage();
|
||||
return grabbed;
|
||||
// The assumption is that the swapchain is usable since on expose we do a
|
||||
// renderWindow() so one cannot get to grab() without having done at least
|
||||
// one on-screen frame.
|
||||
cd->rhi->beginFrame(cd->swapchain);
|
||||
cd->syncSceneGraph();
|
||||
cd->renderSceneGraph(window->size());
|
||||
QImage image = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, cd->swapchain->currentFrameCommandBuffer());
|
||||
cd->rhi->endFrame(cd->swapchain, QRhi::SkipPresent);
|
||||
|
||||
image.setDevicePixelRatio(window->effectiveDevicePixelRatio());
|
||||
return image;
|
||||
}
|
||||
|
||||
void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window)
|
||||
|
|
|
@ -303,8 +303,8 @@ public:
|
|||
bool event(QEvent *) override;
|
||||
void run() override;
|
||||
|
||||
void syncAndRender(QImage *grabImage = nullptr);
|
||||
void sync(bool inExpose, bool inGrab);
|
||||
void syncAndRender();
|
||||
void sync(bool inExpose);
|
||||
|
||||
void requestRepaint()
|
||||
{
|
||||
|
@ -443,8 +443,17 @@ bool QSGRenderThread::event(QEvent *e)
|
|||
mutex.lock();
|
||||
if (ce->window) {
|
||||
if (rhi) {
|
||||
rhi->makeThreadLocalNativeContextCurrent();
|
||||
syncAndRender(ce->image);
|
||||
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(ce->window);
|
||||
cd->rhi->makeThreadLocalNativeContextCurrent();
|
||||
// The assumption is that the swapchain is usable, because on
|
||||
// expose the thread starts up and renders a frame so one cannot
|
||||
// get here without having done at least one on-screen frame.
|
||||
cd->rhi->beginFrame(cd->swapchain);
|
||||
cd->syncSceneGraph();
|
||||
sgrc->endSync();
|
||||
cd->renderSceneGraph(ce->window->size());
|
||||
*ce->image = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, cd->swapchain->currentFrameCommandBuffer());
|
||||
cd->rhi->endFrame(cd->swapchain, QRhi::SkipPresent);
|
||||
}
|
||||
ce->image->setDevicePixelRatio(ce->window->effectiveDevicePixelRatio());
|
||||
}
|
||||
|
@ -559,11 +568,10 @@ void QSGRenderThread::invalidateGraphics(QQuickWindow *window, bool inDestructor
|
|||
Enters the mutex lock to make sure GUI is blocking and performs
|
||||
sync, then wakes GUI.
|
||||
*/
|
||||
void QSGRenderThread::sync(bool inExpose, bool inGrab)
|
||||
void QSGRenderThread::sync(bool inExpose)
|
||||
{
|
||||
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sync()");
|
||||
if (!inGrab)
|
||||
mutex.lock();
|
||||
mutex.lock();
|
||||
|
||||
Q_ASSERT_X(wm->m_lockedForSync, "QSGRenderThread::sync()", "sync triggered on bad terms as gui is not already locked...");
|
||||
|
||||
|
@ -612,7 +620,7 @@ void QSGRenderThread::sync(bool inExpose, bool inGrab)
|
|||
// the frame is rendered (submitted), so in that case waking happens later
|
||||
// in syncAndRender(). Otherwise, wake now and let the main thread go on
|
||||
// while we render.
|
||||
if (!inExpose && !inGrab) {
|
||||
if (!inExpose) {
|
||||
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync complete, waking Gui");
|
||||
waitCondition.wakeOne();
|
||||
mutex.unlock();
|
||||
|
@ -633,7 +641,7 @@ void QSGRenderThread::handleDeviceLoss()
|
|||
rhi = nullptr;
|
||||
}
|
||||
|
||||
void QSGRenderThread::syncAndRender(QImage *grabImage)
|
||||
void QSGRenderThread::syncAndRender()
|
||||
{
|
||||
const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
|
||||
QElapsedTimer threadTimer;
|
||||
|
@ -660,11 +668,9 @@ void QSGRenderThread::syncAndRender(QImage *grabImage)
|
|||
syncResultedInChanges = false;
|
||||
QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
|
||||
|
||||
const bool syncRequested = (pendingUpdate & SyncRequest) || grabImage;
|
||||
const bool syncRequested = (pendingUpdate & SyncRequest);
|
||||
const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
|
||||
const bool grabRequested = grabImage != nullptr;
|
||||
if (!grabRequested)
|
||||
pendingUpdate = 0;
|
||||
pendingUpdate = 0;
|
||||
|
||||
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
|
||||
// Begin the frame before syncing -> sync is where we may invoke
|
||||
|
@ -715,7 +721,7 @@ void QSGRenderThread::syncAndRender(QImage *grabImage)
|
|||
QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
|
||||
// Before returning we need to ensure the same wake up logic that
|
||||
// would have happened if beginFrame() had suceeded.
|
||||
if (syncRequested && !grabRequested) {
|
||||
if (syncRequested) {
|
||||
// Lock like sync() would do. Note that exposeRequested always includes syncRequested.
|
||||
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui");
|
||||
mutex.lock();
|
||||
|
@ -730,7 +736,7 @@ void QSGRenderThread::syncAndRender(QImage *grabImage)
|
|||
|
||||
if (syncRequested) {
|
||||
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- updatePending, doing sync");
|
||||
sync(exposeRequested, grabRequested);
|
||||
sync(exposeRequested);
|
||||
}
|
||||
#ifndef QSG_NO_RENDER_TIMING
|
||||
if (profileFrames)
|
||||
|
@ -759,11 +765,10 @@ void QSGRenderThread::syncAndRender(QImage *grabImage)
|
|||
// updatePaintNode() invoked from sync(). We are about to do a repaint
|
||||
// right now, so reset the flag. (bits other than RepaintRequest cannot
|
||||
// be set in pendingUpdate at this point)
|
||||
if (!grabRequested)
|
||||
pendingUpdate = 0;
|
||||
pendingUpdate = 0;
|
||||
|
||||
// Advance render thread animations (from the QQuickAnimator subclasses).
|
||||
if (animatorDriver->isRunning() && !grabRequested) {
|
||||
if (animatorDriver->isRunning()) {
|
||||
d->animationController->lock();
|
||||
animatorDriver->advance();
|
||||
d->animationController->unlock();
|
||||
|
@ -787,20 +792,8 @@ void QSGRenderThread::syncAndRender(QImage *grabImage)
|
|||
QQuickProfiler::SceneGraphRenderLoopRender);
|
||||
Q_TRACE(QSG_swap_entry);
|
||||
|
||||
// With the rhi grabs can only be done by adding a readback and then
|
||||
// blocking in a real frame. The legacy GL path never gets here with
|
||||
// grabs as it rather invokes sync/render directly without going
|
||||
// through syncAndRender().
|
||||
if (grabRequested) {
|
||||
Q_ASSERT(rhi && cd->swapchain);
|
||||
*grabImage = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, cd->swapchain->currentFrameCommandBuffer());
|
||||
}
|
||||
|
||||
if (cd->swapchain) {
|
||||
QRhi::EndFrameFlags flags;
|
||||
if (grabRequested)
|
||||
flags |= QRhi::SkipPresent;
|
||||
QRhi::FrameOpResult frameResult = rhi->endFrame(cd->swapchain, flags);
|
||||
QRhi::FrameOpResult frameResult = rhi->endFrame(cd->swapchain);
|
||||
if (frameResult != QRhi::FrameOpSuccess) {
|
||||
if (frameResult == QRhi::FrameOpDeviceLost)
|
||||
handleDeviceLoss();
|
||||
|
@ -810,9 +803,7 @@ void QSGRenderThread::syncAndRender(QImage *grabImage)
|
|||
QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!grabRequested)
|
||||
d->fireFrameSwapped();
|
||||
d->fireFrameSwapped();
|
||||
} else {
|
||||
Q_TRACE(QSG_render_exit);
|
||||
Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
|
||||
|
@ -1673,7 +1664,9 @@ QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
|
|||
|
||||
qCDebug(QSG_LOG_RENDERLOOP, "- polishing items");
|
||||
QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
|
||||
m_inPolish = true;
|
||||
d->polishItems();
|
||||
m_inPolish = false;
|
||||
|
||||
QImage result;
|
||||
w->thread->mutex.lock();
|
||||
|
|
|
@ -1528,6 +1528,34 @@ void tst_qquickwindow::grab()
|
|||
} else {
|
||||
QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000);
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
// Now hide() and regrab to exercise the case of a window that is
|
||||
// renderable and then becomes non-exposed (non-renderable). This is not
|
||||
// the same as the visible==false case which starts with a window that
|
||||
// never was renderable before grabbing.
|
||||
window.hide();
|
||||
QImage content = window.grabWindow();
|
||||
QCOMPARE(content.width(), int(window.width() * window.devicePixelRatio()));
|
||||
QCOMPARE(content.height(), int(window.height() * window.devicePixelRatio()));
|
||||
if (alpha) {
|
||||
QCOMPARE((uint) content.convertToFormat(QImage::Format_ARGB32_Premultiplied).pixel(0, 0), (uint) 0x00000000);
|
||||
} else {
|
||||
QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000);
|
||||
}
|
||||
|
||||
// now make it visible and exercise the main grab path again to see if it still works
|
||||
window.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
||||
content = window.grabWindow();
|
||||
QCOMPARE(content.width(), int(window.width() * window.devicePixelRatio()));
|
||||
QCOMPARE(content.height(), int(window.height() * window.devicePixelRatio()));
|
||||
if (alpha) {
|
||||
QCOMPARE((uint) content.convertToFormat(QImage::Format_ARGB32_Premultiplied).pixel(0, 0), (uint) 0x00000000);
|
||||
} else {
|
||||
QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tst_qquickwindow::multipleWindows()
|
||||
|
|
Loading…
Reference in New Issue