Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qsgrenderloop_p.h"
#include "qsgthreadedrenderloop_p.h"
#include "qsgrhisupport_p.h"
#include <private/qquickanimatorcontroller_p.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QElapsedTimer>
#include <QtCore/QLibraryInfo>
#include <QtCore/private/qabstractanimation_p.h>
#include <QtGui/QOffscreenSurface>
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
#include <QPlatformSurfaceEvent>
#include <QtQml/private/qqmlglobal_p.h>
#include <QtQuick/QQuickWindow>
#include <QtQuick/private/qquickwindow_p.h>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qsgcontext_p.h>
#include <QtQuick/private/qsgrenderer_p.h>
#include <private/qquickprofiler_p.h>
#include <qtquick_tracepoints_p.h>
#include <private/qsgrhishadereffectnode_p.h>
#include <private/qsgdefaultrendercontext_p.h>
#ifdef Q_OS_WIN
#include <QtCore/qt_windows.h>
#endif
QT_BEGIN_NAMESPACE
extern bool qsg_useConsistentTiming();
#define ENABLE_DEFAULT_BACKEND
Q_TRACE_POINT(qtquick, QSG_renderWindow_entry)
Q_TRACE_POINT(qtquick, QSG_renderWindow_exit)
Q_TRACE_POINT(qtquick, QSG_polishItems_entry)
Q_TRACE_POINT(qtquick, QSG_polishItems_exit)
Q_TRACE_POINT(qtquick, QSG_sync_entry)
Q_TRACE_POINT(qtquick, QSG_sync_exit)
Q_TRACE_POINT(qtquick, QSG_render_entry)
Q_TRACE_POINT(qtquick, QSG_render_exit)
Q_TRACE_POINT(qtquick, QSG_swap_entry)
Q_TRACE_POINT(qtquick, QSG_swap_exit)
/*
- Uses one QRhi per window. (and so each window has its own QOpenGLContext or ID3D11Device(Context) etc.)
- Animations are advanced using the standard timer (no custom animation
driver is installed), so QML animations run as expected even when vsync
throttling is broken.
*/
DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP);
DEFINE_BOOL_CONFIG_OPTION(qmlForceThreadedRenderer, QML_FORCE_THREADED_RENDERER); // Might trigger graphics driver threading bugs, use at own risk
QSGRenderLoop *QSGRenderLoop::s_instance = nullptr;
QSGRenderLoop::~QSGRenderLoop()
{
}
void QSGRenderLoop::cleanup()
{
if (!s_instance)
return;
const auto windows = s_instance->windows();
for (QQuickWindow *w : windows) {
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(w);
if (wd->windowManager == s_instance) {
s_instance->windowDestroyed(w);
wd->windowManager = nullptr;
}
}
delete s_instance;
s_instance = nullptr;
}
QSurface::SurfaceType QSGRenderLoop::windowSurfaceType() const
{
#ifdef ENABLE_DEFAULT_BACKEND
return QSGRhiSupport::instance()->windowSurfaceType();
#else
return QSurface::RasterSurface;
#endif
}
void QSGRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
{
Q_ASSERT(job);
#ifdef ENABLE_DEFAULT_BACKEND
Q_ASSERT(window);
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
if (cd->rhi)
cd->rhi->makeThreadLocalNativeContextCurrent();
job->run();
#else
Q_UNUSED(window);
job->run();
#endif
delete job;
}
#ifdef ENABLE_DEFAULT_BACKEND
class QSGGuiThreadRenderLoop : public QSGRenderLoop
{
Q_OBJECT
public:
QSGGuiThreadRenderLoop();
~QSGGuiThreadRenderLoop();
void show(QQuickWindow *window) override;
void hide(QQuickWindow *window) override;
void windowDestroyed(QQuickWindow *window) override;
void renderWindow(QQuickWindow *window);
void exposureChanged(QQuickWindow *window) override;
QImage grab(QQuickWindow *window) override;
void maybeUpdate(QQuickWindow *window) override;
void update(QQuickWindow *window) override { maybeUpdate(window); } // identical for this implementation.
void handleUpdateRequest(QQuickWindow *) override;
void releaseResources(QQuickWindow *) override;
QAnimationDriver *animationDriver() const override { return nullptr; }
QSGContext *sceneGraphContext() const override;
QSGRenderContext *createRenderContext(QSGContext *) const override;
void releaseSwapchain(QQuickWindow *window);
void handleDeviceLoss();
void teardownGraphics();
bool eventFilter(QObject *watched, QEvent *event) override;
struct WindowData {
WindowData()
: updatePending(false),
rhiDeviceLost(false),
rhiDoomed(false)
{ }
QRhi *rhi = nullptr;
bool ownRhi = true;
QSGRenderContext *rc = nullptr;
QElapsedTimer timeBetweenRenders;
int sampleCount = 1;
bool updatePending : 1;
bool rhiDeviceLost : 1;
bool rhiDoomed : 1;
};
bool ensureRhi(QQuickWindow *window, WindowData &data);
QHash<QQuickWindow *, WindowData> m_windows;
QOffscreenSurface *offscreenSurface = nullptr;
QSGContext *sg;
mutable QSet<QSGRenderContext *> pendingRenderContexts;
bool m_inPolish = false;
bool swRastFallbackDueToSwapchainFailure = false;
};
#endif
QSGRenderLoop *QSGRenderLoop::instance()
{
if (!s_instance) {
QSGRhiSupport::checkEnvQSgInfo();
s_instance = QSGContext::createWindowManager();
#ifdef ENABLE_DEFAULT_BACKEND
if (!s_instance) {
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
QSGRenderLoopType loopType;
if (rhiSupport->rhiBackend() != QRhi::OpenGLES2) {
loopType = ThreadedRenderLoop;
} else {
if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
loopType = ThreadedRenderLoop;
else
loopType = BasicRenderLoop;
}
switch (rhiSupport->rhiBackend()) {
case QRhi::Null:
loopType = BasicRenderLoop;
break;
case QRhi::D3D11:
// The threaded loop's model may not be suitable for DXGI
// due to the possibility of having the main thread (with
// the Windows message pump) blocked while issuing a
// Present on the render thread. However, according to the
// docs this can be a problem for fullscreen swapchains
// only. So leave threaded enabled by default for now and
// revisit later if there are problems.
break;
default:
break;
}
// The environment variables can always override. This is good
// because in some situations it makes perfect sense to try out a
// render loop that is otherwise disabled by default.
if (qmlNoThreadedRenderer())
loopType = BasicRenderLoop;
else if (qmlForceThreadedRenderer())
loopType = ThreadedRenderLoop;
if (Q_UNLIKELY(qEnvironmentVariableIsSet("QSG_RENDER_LOOP"))) {
const QByteArray loopName = qgetenv("QSG_RENDER_LOOP");
if (loopName == "windows") {
qWarning("The 'windows' render loop is no longer supported. Using 'basic' instead.");
loopType = BasicRenderLoop;
} else if (loopName == "basic") {
loopType = BasicRenderLoop;
} else if (loopName == "threaded") {
loopType = ThreadedRenderLoop;
}
}
switch (loopType) {
#if QT_CONFIG(thread)
case ThreadedRenderLoop:
qCDebug(QSG_LOG_INFO, "threaded render loop");
s_instance = new QSGThreadedRenderLoop();
break;
#endif
default:
qCDebug(QSG_LOG_INFO, "basic render loop");
s_instance = new QSGGuiThreadRenderLoop();
break;
}
}
#endif
qAddPostRoutine(QSGRenderLoop::cleanup);
}
return s_instance;
}
void QSGRenderLoop::setInstance(QSGRenderLoop *instance)
{
Q_ASSERT(!s_instance);
s_instance = instance;
}
void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window)
{
// Must always be called on the gui thread.
// Guard for recursion; relevant due to the MessageBox() on Windows.
static QSet<QQuickWindow *> recurseGuard;
if (recurseGuard.contains(window))
return;
recurseGuard.insert(window);
QString translatedMessage;
QString untranslatedMessage;
QQuickWindowPrivate::rhiCreationFailureMessage(QSGRhiSupport::instance()->rhiBackendName(),
&translatedMessage,
&untranslatedMessage);
// If there is a slot connected to the error signal, emit it and leave it to
// the application to do something with the message. If nothing is connected,
// show a message on our own and terminate.
const bool signalEmitted =
QQuickWindowPrivate::get(window)->emitError(QQuickWindow::ContextNotAvailable,
translatedMessage);
#if defined(Q_OS_WIN)
if (!signalEmitted && !QLibraryInfo::isDebugBuild() && !GetConsoleWindow()) {
MessageBox(0, (LPCTSTR) translatedMessage.utf16(),
(LPCTSTR)(QCoreApplication::applicationName().utf16()),
MB_OK | MB_ICONERROR);
}
#endif // Q_OS_WIN
if (!signalEmitted)
qFatal("%s", qPrintable(untranslatedMessage));
recurseGuard.remove(window);
}
#ifdef ENABLE_DEFAULT_BACKEND
QSGGuiThreadRenderLoop::QSGGuiThreadRenderLoop()
{
if (qsg_useConsistentTiming()) {
QUnifiedTimer::instance(true)->setConsistentTiming(true);
qCDebug(QSG_LOG_INFO, "using fixed animation steps");
}
sg = QSGContext::createDefaultContext();
}
QSGGuiThreadRenderLoop::~QSGGuiThreadRenderLoop()
{
qDeleteAll(pendingRenderContexts);
delete sg;
}
void QSGGuiThreadRenderLoop::show(QQuickWindow *window)
{
if (!m_windows.contains(window))
m_windows.insert(window, {});
m_windows[window].timeBetweenRenders.start();
maybeUpdate(window);
}
void QSGGuiThreadRenderLoop::hide(QQuickWindow *window)
{
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
cd->fireAboutToStop();
auto it = m_windows.find(window);
if (it != m_windows.end())
it->updatePending = false;
}
void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window)
{
hide(window);
WindowData data = m_windows.value(window, {});
m_windows.remove(window);
QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
if (data.rhi) {
// Direct OpenGL calls in user code need a current context, like
// when rendering; ensure this (no-op when not running on GL).
// Also works when there is no handle() anymore.
data.rhi->makeThreadLocalNativeContextCurrent();
}
if (d->swapchain) {
if (window->handle()) {
// We get here when exiting via QCoreApplication::quit() instead of
// through QWindow::close().
releaseSwapchain(window);
} else {
qWarning("QSGGuiThreadRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
window, d->swapchain);
}
}
d->cleanupNodesOnShutdown();
#if QT_CONFIG(quick_shadereffect)
QSGRhiShaderEffectNode::resetMaterialTypeCache(window);
#endif
if (data.rc) {
data.rc->invalidate();
delete data.rc;
}
if (data.ownRhi)
QSGRhiSupport::instance()->destroyRhi(data.rhi, d->graphicsConfig);
d->rhi = nullptr;
d->animationController.reset();
if (m_windows.isEmpty()) {
delete offscreenSurface;
offscreenSurface = nullptr;
}
}
void QSGGuiThreadRenderLoop::teardownGraphics()
{
for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
if (it->rhi) {
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(it.key());
wd->cleanupNodesOnShutdown();
if (it->rc)
it->rc->invalidate();
releaseSwapchain(it.key());
if (it->ownRhi)
QSGRhiSupport::instance()->destroyRhi(it->rhi, {});
it->rhi = nullptr;
wd->rhi = nullptr;
}
}
}
void QSGGuiThreadRenderLoop::handleDeviceLoss()
{
qWarning("Graphics device lost, cleaning up scenegraph and releasing RHIs");
for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
if (!it->rhi || !it->rhi->isDeviceLost())
continue;
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(it.key());
wd->cleanupNodesOnShutdown();
if (it->rc)
it->rc->invalidate();
releaseSwapchain(it.key());
it->rhiDeviceLost = true;
if (it->ownRhi)
QSGRhiSupport::instance()->destroyRhi(it->rhi, {});
it->rhi = nullptr;
wd->rhi = nullptr;
}
}
void QSGGuiThreadRenderLoop::releaseSwapchain(QQuickWindow *window)
{
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
delete wd->rpDescForSwapchain;
wd->rpDescForSwapchain = nullptr;
delete wd->swapchain;
wd->swapchain = nullptr;
delete wd->depthStencilForSwapchain;
wd->depthStencilForSwapchain = nullptr;
wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
}
bool QSGGuiThreadRenderLoop::eventFilter(QObject *watched, QEvent *event)
{
switch (event->type()) {
case QEvent::PlatformSurface:
// this is the proper time to tear down the swapchain (while the native window and surface are still around)
if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
QQuickWindow *w = qobject_cast<QQuickWindow *>(watched);
if (w)
releaseSwapchain(w);
// keep this filter on the window - needed for uncommon but valid
// sequences of calls like window->destroy(); window->show();
}
break;
default:
break;
}
return QObject::eventFilter(watched, event);
}
bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data)
{
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
bool ok = data.rhi != nullptr;
if (!data.rhi) {
// This block below handles both the initial QRhi initialization and
// also the subsequent reinitialization attempts after a device lost
// (reset) situation.
if (data.rhiDoomed) // no repeated attempts if the initial attempt failed
return false;
if (!offscreenSurface)
offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
data.rhi = rhiResult.rhi;
data.ownRhi = rhiResult.own;
if (data.rhi) {
data.rhiDeviceLost = false;
ok = true;
// We need to guarantee that sceneGraphInitialized is
// emitted with a context current, if running with OpenGL.
data.rhi->makeThreadLocalNativeContextCurrent();
// The sample count cannot vary between windows as we use the same
// rendercontext for all of them. Decide it here and now.
data.sampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, data.rhi);
cd->rhi = data.rhi; // set this early in case something hooked up to rc initialized() accesses it
QSGDefaultRenderContext::InitParams rcParams;
rcParams.rhi = data.rhi;
rcParams.sampleCount = data.sampleCount;
rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
rcParams.maybeSurface = window;
cd->context->initialize(&rcParams);
} else {
if (!data.rhiDeviceLost) {
data.rhiDoomed = true;
handleContextCreationFailure(window);
}
// otherwise no error, just return false so that we will retry on a subsequent rendering attempt
}
}
if (data.rhi && !cd->swapchain) {
// if it's not the first window then the rhi is not yet stored to
// QQuickWindowPrivate, do it now
cd->rhi = data.rhi;
// Unlike the threaded render loop, we use the same rhi for all windows
// and so createRhi() is called only once. Certain initialization may
// need to be done on a per window basis still, so make sure it is done.
rhiSupport->prepareWindowForRhi(window);
QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
const QSurfaceFormat requestedFormat = window->requestedFormat();
// QQ is always premul alpha. Decide based on alphaBufferSize in
// requestedFormat(). (the platform plugin can override format() but
// what matters here is what the application wanted, hence using the
// requested one)
const bool alpha = requestedFormat.alphaBufferSize() > 0;
if (alpha)
flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
// Request NoVSync if swap interval was set to 0 (either by the app or
// by QSG_NO_VSYNC). What this means in practice is another question,
// but at least we tried.
if (requestedFormat.swapInterval() == 0) {
qCDebug(QSG_LOG_INFO, "Swap interval is 0, attempting to disable vsync when presenting.");
flags |= QRhiSwapChain::NoVSync;
}
cd->swapchain = data.rhi->newSwapChain();
static bool depthBufferEnabled = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
if (depthBufferEnabled) {
cd->depthStencilForSwapchain = data.rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
QSize(),
data.sampleCount,
QRhiRenderBuffer::UsedWithSwapChainOnly
| QSGRhiSupport::depthStencilBufferFlags());
cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
}
cd->swapchain->setWindow(window);
rhiSupport->applySwapChainFormat(cd->swapchain, window);
qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s",
data.sampleCount, alpha ? "yes" : "no");
cd->swapchain->setSampleCount(data.sampleCount);
cd->swapchain->setFlags(flags);
cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
window->installEventFilter(this);
}
if (!data.rc) {
QSGRenderContext *rc = cd->context;
pendingRenderContexts.remove(rc);
data.rc = rc;
if (!data.rc)
qWarning("No QSGRenderContext for window %p, this should not happen", window);
}
return ok;
}
void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
{
auto winDataIt = m_windows.find(window);
if (winDataIt == m_windows.end())
return;
WindowData &data(*winDataIt);
bool alsoSwap = data.updatePending;
data.updatePending = false;
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
if (!cd->isRenderable())
return;
if (!cd->updatesEnabled)
return;
if (!ensureRhi(window, data))
return;
cd->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
// 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();
// An update request could still be delivered right before we get an
// unexpose. With Vulkan on Windows for example attempting to render
// leads to failures at this stage since the surface size is already 0.
if (effectiveOutputSize.isEmpty())
return;
}
Q_TRACE_SCOPE(QSG_renderWindow);
QElapsedTimer renderTimer;
qint64 renderTime = 0, syncTime = 0, polishTime = 0;
const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
if (profileFrames)
renderTimer.start();
Q_TRACE(QSG_polishItems_entry);
Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishFrame);
m_inPolish = true;
cd->polishItems();
m_inPolish = false;
if (profileFrames)
polishTime = renderTimer.nsecsElapsed();
Q_TRACE(QSG_polishItems_exit);
Q_QUICK_SG_PROFILE_SWITCH(QQuickProfiler::SceneGraphPolishFrame,
QQuickProfiler::SceneGraphRenderLoopFrame,
QQuickProfiler::SceneGraphPolishPolish);
Q_TRACE(QSG_sync_entry);
emit window->afterAnimating();
// Begin the frame before syncing -> sync is where we may invoke
// updatePaintNode() on the items and they may want to do resource updates.
// Also relevant for applications that connect to the before/afterSynchronizing
// signals and want to do graphics stuff already there.
if (cd->swapchain) {
Q_ASSERT(!effectiveOutputSize.isEmpty());
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
const QSize previousOutputSize = cd->swapchain->currentPixelSize();
if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
if (cd->swapchainJustBecameRenderable)
qCDebug(QSG_LOG_RENDERLOOP, "just became exposed");
cd->hasActiveSwapchain = cd->swapchain->createOrResize();
if (!cd->hasActiveSwapchain) {
if (data.rhi->isDeviceLost()) {
handleDeviceLoss();
return;
} else if (previousOutputSize.isEmpty() && !swRastFallbackDueToSwapchainFailure && rhiSupport->attemptReinitWithSwRastUponFail()) {
qWarning("Failed to create swapchain."
" Retrying by requesting a software rasterizer, if applicable for the 3D API implementation.");
swRastFallbackDueToSwapchainFailure = true;
teardownGraphics();
return;
}
}
cd->swapchainJustBecameRenderable = false;
cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
if (cd->hasActiveSwapchain) {
// surface size atomicity: now that buildOrResize() succeeded,
// query the size that was used in there by the swapchain, and
// that is the size we will use while preparing the next frame.
effectiveOutputSize = cd->swapchain->currentPixelSize();
qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << effectiveOutputSize;
} else {
qWarning("Failed to build or resize swapchain");
}
}
emit window->beforeFrameBegin();
Q_ASSERT(data.rhi == cd->rhi);
QRhi::FrameOpResult frameResult = data.rhi->beginFrame(cd->swapchain);
if (frameResult != QRhi::FrameOpSuccess) {
if (frameResult == QRhi::FrameOpDeviceLost)
handleDeviceLoss();
else if (frameResult == QRhi::FrameOpError)
qWarning("Failed to start frame");
// out of date is not worth warning about - it may happen even during resizing on some platforms
emit window->afterFrameEnd();
return;
}
}
// Enable external OpenGL rendering connected to one of the
// QQuickWindow signals (beforeSynchronizing, beforeRendering,
// etc.) to function like it did on the direct OpenGL path,
// i.e. ensure there is a context current, just in case.
data.rhi->makeThreadLocalNativeContextCurrent();
cd->syncSceneGraph();
data.rc->endSync();
if (profileFrames)
syncTime = renderTimer.nsecsElapsed();
Q_TRACE(QSG_sync_exit);
Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
QQuickProfiler::SceneGraphRenderLoopSync);
Q_TRACE(QSG_render_entry);
cd->renderSceneGraph();
if (profileFrames)
renderTime = renderTimer.nsecsElapsed();
Q_TRACE(QSG_render_exit);
Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
QQuickProfiler::SceneGraphRenderLoopRender);
Q_TRACE(QSG_swap_entry);
const bool needsPresent = alsoSwap && window->isVisible();
double lastCompletedGpuTime = 0;
if (cd->swapchain) {
QRhi::EndFrameFlags flags;
if (!needsPresent)
flags |= QRhi::SkipPresent;
QRhi::FrameOpResult frameResult = data.rhi->endFrame(cd->swapchain, flags);
if (frameResult != QRhi::FrameOpSuccess) {
if (frameResult == QRhi::FrameOpDeviceLost)
handleDeviceLoss();
else if (frameResult == QRhi::FrameOpError)
qWarning("Failed to end frame");
} else {
lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
}
}
if (needsPresent)
cd->fireFrameSwapped();
emit window->afterFrameEnd();
qint64 swapTime = 0;
if (profileFrames)
swapTime = renderTimer.nsecsElapsed();
Q_TRACE(QSG_swap_exit);
Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
QQuickProfiler::SceneGraphRenderLoopSwap);
if (profileFrames) {
qCDebug(QSG_LOG_TIME_RENDERLOOP,
"[window %p][gui thread] syncAndRender: frame rendered in %dms, polish=%d, sync=%d, render=%d, swap=%d, perWindowFrameDelta=%d",
window,
int(swapTime / 1000000),
int(polishTime / 1000000),
int((syncTime - polishTime) / 1000000),
int((renderTime - syncTime) / 1000000),
int((swapTime - renderTime) / 1000000),
int(data.timeBetweenRenders.restart()));
if (!qFuzzyIsNull(lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] syncAndRender: last retrieved GPU frame time was %.4f ms",
window,
lastCompletedGpuTime * 1000.0);
}
}
// Might have been set during syncSceneGraph()
if (data.updatePending)
maybeUpdate(window);
}
void QSGGuiThreadRenderLoop::exposureChanged(QQuickWindow *window)
{
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
// This is tricker than used to be. We want to detect having an empty
// surface size (which may be the case even when window->size() is
// non-empty, on some platforms with some graphics APIs!) as well as the
// case when the window just became "newly exposed" (e.g. after a
// minimize-restore on Windows, or when switching between fully obscured -
// not fully obscured on macOS)
if (!window->isExposed() || (wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()))
wd->hasRenderableSwapchain = false;
if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
&& !wd->swapchain->surfacePixelSize().isEmpty())
{
wd->hasRenderableSwapchain = true;
wd->swapchainJustBecameRenderable = true;
}
auto winDataIt = m_windows.find(window);
if (winDataIt != m_windows.end()) {
if (window->isExposed() && (!winDataIt->rhi || !wd->hasActiveSwapchain || wd->hasRenderableSwapchain)) {
winDataIt->updatePending = true;
renderWindow(window);
}
}
}
QImage QSGGuiThreadRenderLoop::grab(QQuickWindow *window)
{
auto winDataIt = m_windows.find(window);
if (winDataIt == m_windows.end())
return QImage();
if (!ensureRhi(window, *winDataIt))
return QImage();
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
m_inPolish = true;
cd->polishItems();
m_inPolish = false;
// 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->rhi->makeThreadLocalNativeContextCurrent(); // for custom GL rendering before/during/after sync
cd->syncSceneGraph();
cd->renderSceneGraph();
QImage image = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(cd->rhi, cd->swapchain->currentFrameCommandBuffer());
cd->rhi->endFrame(cd->swapchain, QRhi::SkipPresent);
image.setDevicePixelRatio(window->effectiveDevicePixelRatio());
return image;
}
void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window)
{
auto winDataIt = m_windows.find(window);
if (winDataIt == m_windows.end())
return;
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
if (!cd->isRenderable())
return;
winDataIt->updatePending = true;
// An updatePolish() implementation may call update() to get the QQuickItem
// dirtied. That's fine but it also leads to calling this function.
// Requesting another update is a waste then since the updatePolish() call
// will be followed up with a round of sync and render.
if (m_inPolish)
return;
window->requestUpdate();
}
QSGContext *QSGGuiThreadRenderLoop::sceneGraphContext() const
{
return sg;
}
QSGRenderContext *QSGGuiThreadRenderLoop::createRenderContext(QSGContext *sg) const
{
QSGRenderContext *rc = sg->createRenderContext();
pendingRenderContexts.insert(rc);
return rc;
}
void QSGGuiThreadRenderLoop::releaseResources(QQuickWindow *w)
{
// No full invalidation of the rendercontext, just clear some caches.
QQuickWindowPrivate *d = QQuickWindowPrivate::get(w);
emit d->context->releaseCachedResourcesRequested();
if (d->renderer)
d->renderer->releaseCachedResources();
#if QT_CONFIG(quick_shadereffect)
QSGRhiShaderEffectNode::garbageCollectMaterialTypeCache(w);
#endif
}
void QSGGuiThreadRenderLoop::handleUpdateRequest(QQuickWindow *window)
{
renderWindow(window);
}
#endif // ENABLE_DEFAULT_BACKEND
QT_END_NAMESPACE
#include "qsgrenderloop.moc"
#include "moc_qsgrenderloop_p.cpp"