From 0616e14de0bf867bc2730b2b07005fbcbb234bb4 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 10 Sep 2019 14:09:41 +0200 Subject: [PATCH] rhi: Better handling of device loss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting with D3D11. The other backends will follow later. Change-Id: I4f165c9f1743df0fb00bdce1e898917575bf5f6e Reviewed-by: Christian Strømme --- src/gui/rhi/qrhi.cpp | 43 +++++++++++++++++++++++++++++++++++- src/gui/rhi/qrhi_p.h | 2 ++ src/gui/rhi/qrhi_p_p.h | 1 + src/gui/rhi/qrhid3d11.cpp | 21 ++++++++++++++++-- src/gui/rhi/qrhid3d11_p_p.h | 2 ++ src/gui/rhi/qrhigles2.cpp | 5 +++++ src/gui/rhi/qrhigles2_p_p.h | 1 + src/gui/rhi/qrhimetal.mm | 5 +++++ src/gui/rhi/qrhimetal_p_p.h | 1 + src/gui/rhi/qrhinull.cpp | 5 +++++ src/gui/rhi/qrhinull_p_p.h | 1 + src/gui/rhi/qrhivulkan.cpp | 5 +++++ src/gui/rhi/qrhivulkan_p_p.h | 1 + 13 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 5ad433cf23..fbdc90bd1b 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -455,7 +455,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \value FrameOpDeviceLost The graphics device was lost. This can be recoverable by attempting to repeat the operation (such as, beginFrame()) - and releasing and reinitializing all objects backed by native graphics + after releasing and reinitializing all objects backed by native graphics resources. */ @@ -5045,6 +5045,47 @@ void QRhi::releaseCachedResources() d->releaseCachedResources(); } +/*! + \return true if the graphics device was lost. + + The loss of the device is typically detected in beginFrame(), endFrame() or + QRhiSwapChain::buildOrResize(), depending on the backend and the underlying + native APIs. The most common is endFrame() because that is where presenting + happens. With some backends QRhiSwapChain::buildOrResize() can also fail + due to a device loss. Therefore this function is provided as a generic way + to check if a device loss was detected by a previous operation. + + When the device is lost, no further operations should be done via the QRhi. + Rather, all QRhi resources should be released, followed by destroying the + QRhi. A new QRhi can then be attempted to be created. If successful, all + graphics resources must be reinitialized. If not, try again later, + repeatedly. + + While simple applications may decide to not care about device loss, + on the commonly used desktop platforms a device loss can happen + due to a variety of reasons, including physically disconnecting the + graphics adapter, disabling the device or driver, uninstalling or upgrading + the graphics driver, or due to errors that lead to a graphics device reset. + Some of these can happen under perfectly normal circumstances as well, for + example the upgrade of the graphics driver to a newer version is a common + task that can happen at any time while a Qt application is running. Users + may very well expect applications to be able to survive this, even when the + application is actively using an API like OpenGL or Direct3D. + + Qt's own frameworks built on top of QRhi, such as, Qt Quick, can be + expected to handle and take appropriate measures when a device loss occurs. + If the data for graphics resources, such as textures and buffers, are still + available on the CPU side, such an event may not be noticeable on the + application level at all since graphics resources can seamlessly be + reinitialized then. However, applications and libraries working directly + with QRhi are expected to be prepared to check and handle device loss + situations themselves. + */ +bool QRhi::isDeviceLost() const +{ + return d->isDeviceLost(); +} + /*! \return a new graphics pipeline resource. diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index 928d1f8fa7..51e70af18e 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -1422,6 +1422,8 @@ public: void releaseCachedResources(); + bool isDeviceLost() const; + protected: QRhi(); diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h index b69757ae6d..7c0b000c33 100644 --- a/src/gui/rhi/qrhi_p_p.h +++ b/src/gui/rhi/qrhi_p_p.h @@ -158,6 +158,7 @@ public: virtual void sendVMemStatsToProfiler() = 0; virtual void makeThreadLocalNativeContextCurrent() = 0; virtual void releaseCachedResources() = 0; + virtual bool isDeviceLost() const = 0; bool isCompressedFormat(QRhiTexture::Format format) const; void compressedFormatInfo(QRhiTexture::Format format, const QSize &size, diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 119234035a..2350691dc0 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -267,6 +267,8 @@ bool QRhiD3D11::create(QRhi::Flags flags) if (FAILED(context->QueryInterface(IID_ID3DUserDefinedAnnotation, reinterpret_cast(&annotations)))) annotations = nullptr; + deviceLost = false; + nativeHandlesStruct.dev = dev; nativeHandlesStruct.context = context; @@ -487,6 +489,11 @@ void QRhiD3D11::releaseCachedResources() clearShaderCache(); } +bool QRhiD3D11::isDeviceLost() const +{ + return deviceLost; +} + QRhiRenderBuffer *QRhiD3D11::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) { @@ -1003,8 +1010,14 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame if (!flags.testFlag(QRhi::SkipPresent)) { const UINT presentFlags = 0; HRESULT hr = swapChainD->swapChain->Present(swapChainD->swapInterval, presentFlags); - if (FAILED(hr)) + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { + qWarning("Device loss detected in Present()"); + deviceLost = true; + return QRhi::FrameOpDeviceLost; + } else if (FAILED(hr)) { qWarning("Failed to present: %s", qPrintable(comErrorMessage(hr))); + return QRhi::FrameOpError; + } // move on to the next buffer swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QD3D11SwapChain::BUFFER_COUNT; @@ -3850,7 +3863,11 @@ bool QD3D11SwapChain::buildOrResize() const UINT count = useFlipDiscard ? BUFFER_COUNT : 1; HRESULT hr = swapChain->ResizeBuffers(count, UINT(pixelSize.width()), UINT(pixelSize.height()), colorFormat, swapChainFlags); - if (FAILED(hr)) { + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { + qWarning("Device loss detected in ResizeBuffers()"); + rhiD->deviceLost = true; + return false; + } else if (FAILED(hr)) { qWarning("Failed to resize D3D11 swapchain: %s", qPrintable(comErrorMessage(hr))); return false; } diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h index cc4e095d10..da7fe84b1a 100644 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -633,6 +633,7 @@ public: void sendVMemStatsToProfiler() override; void makeThreadLocalNativeContextCurrent() override; void releaseCachedResources() override; + bool isDeviceLost() const override; void enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD, int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc); @@ -658,6 +659,7 @@ public: IDXGIFactory1 *dxgiFactory = nullptr; bool hasDxgi2 = false; bool supportsFlipDiscardSwapchain = false; + bool deviceLost = false; QRhiD3D11NativeHandles nativeHandlesStruct; struct { diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 4d91ac45bd..d408490b34 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -772,6 +772,11 @@ void QRhiGles2::releaseCachedResources() m_shaderCache.clear(); } +bool QRhiGles2::isDeviceLost() const +{ + return false; +} + QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) { diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index 877eb88d27..3664e7162b 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -666,6 +666,7 @@ public: void sendVMemStatsToProfiler() override; void makeThreadLocalNativeContextCurrent() override; void releaseCachedResources() override; + bool isDeviceLost() const override; bool ensureContext(QSurface *surface = nullptr) const; void executeDeferredReleases(); diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 3bf95ad676..770786db98 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -596,6 +596,11 @@ void QRhiMetal::releaseCachedResources() d->shaderCache.clear(); } +bool QRhiMetal::isDeviceLost() const +{ + return false; +} + QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) { diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index 01b0bf4f56..633e9b8de4 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -418,6 +418,7 @@ public: void sendVMemStatsToProfiler() override; void makeThreadLocalNativeContextCurrent() override; void releaseCachedResources() override; + bool isDeviceLost() const override; void executeDeferredReleases(bool forced = false); void finishActiveReadbacks(bool forced = false); diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 29a3968bfc..b58d9f5c56 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -179,6 +179,11 @@ void QRhiNull::releaseCachedResources() // nothing to do here } +bool QRhiNull::isDeviceLost() const +{ + return false; +} + QRhiRenderBuffer *QRhiNull::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) { diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h index b43f830d5e..d6dbdbdc75 100644 --- a/src/gui/rhi/qrhinull_p_p.h +++ b/src/gui/rhi/qrhinull_p_p.h @@ -284,6 +284,7 @@ public: void sendVMemStatsToProfiler() override; void makeThreadLocalNativeContextCurrent() override; void releaseCachedResources() override; + bool isDeviceLost() const override; QRhiNullNativeHandles nativeHandlesStruct; QRhiSwapChain *currentSwapChain = nullptr; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index c4f298dafb..5dd4b12329 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -3750,6 +3750,11 @@ void QRhiVulkan::releaseCachedResources() // nothing to do here } +bool QRhiVulkan::isDeviceLost() const +{ + return false; +} + QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) { diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h index 23cc80b814..0f16c54779 100644 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -713,6 +713,7 @@ public: void sendVMemStatsToProfiler() override; void makeThreadLocalNativeContextCurrent() override; void releaseCachedResources() override; + bool isDeviceLost() const override; VkResult createDescriptorPool(VkDescriptorPool *pool); bool allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex);