diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index c4317e5f0f..6e469f5b6d 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -1494,10 +1494,25 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame const bool needsPresent = !flags.testFlag(QRhi::SkipPresent); if (needsPresent) { - auto drawable = swapChainD->d->curDrawable; - [swapChainD->cbWrapper.d->cb addScheduledHandler:^(id) { - [drawable present]; - }]; + // beginFrame-endFrame without a render pass inbetween means there is no + // drawable, handle this gracefully because presentDrawable does not like + // null arguments. + if (id drawable = swapChainD->d->curDrawable) { + // QTBUG-103415: while the docs suggest the following two approaches are + // equivalent, there is a difference in case a frame is recorded earlier than + // (i.e. not in response to) the next CVDisplayLink callback. Therefore, stick + // with presentDrawable, which gives results identical to OpenGL, and all other + // platforms, i.e. throttles to vsync as expected, meaning constant 15-17 ms with + // a 60 Hz screen, no jumps with smaller intervals, regardless of when the frame + // is submitted by the app) +#if 1 + [swapChainD->cbWrapper.d->cb presentDrawable: drawable]; +#else + [swapChainD->cbWrapper.d->cb addScheduledHandler:^(id) { + [drawable present]; + }]; +#endif + } } // Must not hold on to the drawable, regardless of needsPresent diff --git a/tests/manual/rhi/hellominimalcrossgfxtriangle/window.cpp b/tests/manual/rhi/hellominimalcrossgfxtriangle/window.cpp index 153bd2c37f..2a3475538d 100644 --- a/tests/manual/rhi/hellominimalcrossgfxtriangle/window.cpp +++ b/tests/manual/rhi/hellominimalcrossgfxtriangle/window.cpp @@ -3,6 +3,7 @@ #include "window.h" #include +#include Window::Window(QRhi::Implementation graphicsApi) : m_graphicsApi(graphicsApi) @@ -196,7 +197,20 @@ void Window::render() m_rhi->endFrame(m_sc.get()); + // Always request the next frame via requestUpdate(). On some platforms this is backed + // by a platform-specific solution, e.g. CVDisplayLink on macOS, which is potentially + // more efficient than a timer, queued metacalls, etc. + // + // However, the rendering behavior is identical no matter how the next round of + // rendering is triggered: the rendering thread is throttled to the presentation rate + // (either in beginFrame() or endFrame()) so the triangle should rotate at the exact + // same speed no matter which approach is taken here. + +#if 1 requestUpdate(); +#else + QTimer::singleShot(0, this, [this] { render(); }); +#endif } void Window::customInit()