rhi: Better handling of device loss

Starting with D3D11. The other backends will follow later.

Change-Id: I4f165c9f1743df0fb00bdce1e898917575bf5f6e
Reviewed-by: Christian Strømme <christian.stromme@qt.io>
This commit is contained in:
Laszlo Agocs 2019-09-10 14:09:41 +02:00
parent d39d1a9e67
commit 0616e14de0
13 changed files with 90 additions and 3 deletions

View File

@ -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.

View File

@ -1422,6 +1422,8 @@ public:
void releaseCachedResources();
bool isDeviceLost() const;
protected:
QRhi();

View File

@ -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,

View File

@ -267,6 +267,8 @@ bool QRhiD3D11::create(QRhi::Flags flags)
if (FAILED(context->QueryInterface(IID_ID3DUserDefinedAnnotation, reinterpret_cast<void **>(&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;
}

View File

@ -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 {

View File

@ -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)
{

View File

@ -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();

View File

@ -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)
{

View File

@ -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);

View File

@ -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)
{

View File

@ -284,6 +284,7 @@ public:
void sendVMemStatsToProfiler() override;
void makeThreadLocalNativeContextCurrent() override;
void releaseCachedResources() override;
bool isDeviceLost() const override;
QRhiNullNativeHandles nativeHandlesStruct;
QRhiSwapChain *currentSwapChain = nullptr;

View File

@ -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)
{

View File

@ -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);