Details
-
Bug
-
Resolution: Unresolved
-
P2: Important
-
None
-
6.3.0, 6.3.2
-
None
-
- x86_64 Qt Metal app (VSync ON)
- macOS Monterey or newer
- ARM Macs (M1, M2, etc.)
Description
Hi Qt,
We've experienced some GUI lag/frame drops when running x86_64 builds on ARM Macs (e.g. M1, M2) and also reproduced the same issue in basic Qt demo app using Metal.
====================================================================
To reproduce the issue
- Build Qt app using Metal for x86_64 with VSync ON
- Upgrade ARM Macs to Monterey or newer (we also found that the QSGRenderThread runs around 4 times longer on each update after upgrading to Monterey or newer)
- Run x86_64 builds on ARM Macs (Rosetta involved)
Root cause and explanation
CAMetalLayer.nextDrawable() blocks the QSGRenderThread for around 10 ms every update which retards the QSGRenderThread.
And it ends up with the Main Thread being ahead and posting new WMSyncEvent while the QSGRenderThread is still rendering and then wait for the next Metal drawable for the previous event, so the Main Thread will keep blocking itself waiting for the delayed syncing to finish every time, which lasts around 16 ms and made the Main Thread have very limited time/chance to process other events.
The issue looks like a combination of longer rendering time on Monterey or newer and Rosetta involvement ending up with asking too much from the CAMetalLayer and caused the waiting on the next Metal drawable.
Suggested fix
Skip the update request/WMSyncEvent if the QSGRenderThread is still busy (can check QSGRenderThread::sleeping == false) so the QSGRenderThread can get a chance to empty the event queue and catch up, otherwise the wait on the next Metal drawable will happen constantly as there are always new events to render.
Note: Turning VSync off in CAMetalLayer can also eliminate the wait on drawable but will cause screen tearing sometimes which is not ideal.
Backtraces
Main Thread:
__psynch_cvwait
QWaitConditionPrivate::wait(QDeadlineTimer)
QWaitConditionPrivate::wait(QDeadlineTimer)
QWaitCondition::wait(QMutex*, QDeadlineTimer)
QSGThreadedRenderLoop::polishAndSync(QSGThreadedRenderLoop::Window*, bool)
QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow*)
QQuickWindow::event(QEvent*)
serato::gui::MainQtView::event(QEvent*)
QCoreApplicationPrivate::setEventSpontaneous(QEvent*, bool)
QApplicationPrivate::notify_helper(QObject*, QEvent*)
QApplication::notify(QObject*, QEvent*)
QCoreApplication::notifyInternal2(QObject*, QEvent*)
QPlatformWindow::deliverUpdateRequest()
QCocoaWindow::deliverUpdateRequest()
QCocoaScreen::deliverUpdateRequests()
_dispatch_client_callout
_dispatch_continuation_pop
_dispatch_source_invoke
_dispatch_main_queue_drain
_dispatch_main_queue_callback_4CF
{}CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE{}
__CFRunLoopRun
CFRunLoopRunSpecific
RunCurrentEventLoopInMode
ReceiveNextEventCommon
_BlockUntilNextEventMatchingListInModeWithFilter
_DPSNextEvent
-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:]
-[NSApplication run]
RunEventLoop()
main
start
QSGRenderThread:
semaphore_timedwait_trap
_dispatch_semaphore_wait_slow
_dispatch_semaphore_wait_slow
-[CAMetalLayer nextDrawable]
CAMetalLayer_nextDrawable
QRhiMetal::beginPass(QRhiCommandBuffer*, QRhiRenderTarget*, QColor const&, QRhiDepthStencilClearValue const&, QRhiResourceUpdateBatch*, QFlags<QRhiCommandBuffer::BeginPassFlag>)
QRhiCommandBuffer::beginPass(QRhiRenderTarget*, QColor const&, QRhiDepthStencilClearValue const&, QRhiResourceUpdateBatch*, QFlags<QRhiCommandBuffer::BeginPassFlag>)
QSGBatchRenderer::Renderer::beginRenderPass(QSGBatchRenderer::Renderer::RenderPassContext*)
QSGBatchRenderer::Renderer::render()
bool std::{}1::{}cxx_atomic_load<bool>(std::{}1::{}cxx_atomic_base_impl<bool> const*, std::{_}_1::memory_order)
std::{}1::{}atomic_base<bool, false>::load(std::{_}_1::memory_order) const
bool QAtomicOps<bool>::loadRelaxed<bool>(std::__1::atomic<bool> const&)
QBasicAtomicInteger<bool>::loadRelaxed() const
QLoggingCategory::isDebugEnabled() const
QSGRenderer::renderScene()
QQuickWindowPrivate::renderSceneGraph(QSize const&, QSize const&)
bool std::{}1::{}cxx_atomic_load<bool>(std::{}1::{}cxx_atomic_base_impl<bool> const*, std::{_}_1::memory_order)
std::{}1::{}atomic_base<bool, false>::load(std::{_}_1::memory_order) const
bool QAtomicOps<bool>::loadRelaxed<bool>(std::__1::atomic<bool> const&)
QBasicAtomicInteger<bool>::loadRelaxed() const
QLoggingCategory::isDebugEnabled() const
QSGRenderThread::syncAndRender()
QSGRenderThread::run()
QThreadPrivate::start(void*)
_pthread_start
thread_start