rhi: d3d11: Add the device lost testing machinery

The device can be lost when physically removing the graphics adapter,
disabling the driver (Device Manager), upgrading/uninstalling the
graphics driver, and when it is reset due to an error.

Some of these can (and should) be tested manually, but the last
one has a convenient, programmatic way of triggering: by triggering
the timeout detection and recovery (TDR) of WDDM. A compute shader
with an infinite loop should trigger this after 2 seconds by default.

All tests in tests/manual/rhi can now be started with a --curse <count>
argument where <count> specifies the number of frames to render before
breaking the device. Qt Quick will get an environment variable with
similar semantics in a separate patch.

Change-Id: I4b6f8d977a15b5b89d686b3973965df6435810ae
Reviewed-by: Christian Strømme <christian.stromme@qt.io>
bb10
Laszlo Agocs 2019-09-07 19:23:09 +02:00
parent 08ad96404b
commit f567129bb5
6 changed files with 304 additions and 2 deletions

209
src/gui/rhi/cs_tdr.h Normal file
View File

@ -0,0 +1,209 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Gui module
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qglobal.h>
#ifdef Q_OS_WIN
#include <qt_windows.h>
#if 0
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
// Buffer Definitions:
//
// cbuffer ConstantBuffer
// {
//
// uint zero; // Offset: 0 Size: 4
//
// }
//
//
// Resource Bindings:
//
// Name Type Format Dim HLSL Bind Count
// ------------------------------ ---------- ------- ----------- -------------- ------
// uav UAV uint buf u0 1
// ConstantBuffer cbuffer NA NA cb0 1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// no Input
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// no Output
cs_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer CB0[1], immediateIndexed
dcl_uav_typed_buffer (uint,uint,uint,uint) u0
dcl_input vThreadID.x
dcl_thread_group 256, 1, 1
loop
breakc_nz cb0[0].x
store_uav_typed u0.xyzw, vThreadID.xxxx, cb0[0].xxxx
endloop
ret
// Approximately 5 instruction slots used
#endif
const BYTE g_killDeviceByTimingOut[] =
{
68, 88, 66, 67, 217, 62,
220, 38, 136, 51, 86, 245,
161, 96, 18, 35, 141, 17,
26, 13, 1, 0, 0, 0,
164, 2, 0, 0, 5, 0,
0, 0, 52, 0, 0, 0,
100, 1, 0, 0, 116, 1,
0, 0, 132, 1, 0, 0,
8, 2, 0, 0, 82, 68,
69, 70, 40, 1, 0, 0,
1, 0, 0, 0, 144, 0,
0, 0, 2, 0, 0, 0,
60, 0, 0, 0, 0, 5,
83, 67, 0, 1, 0, 0,
0, 1, 0, 0, 82, 68,
49, 49, 60, 0, 0, 0,
24, 0, 0, 0, 32, 0,
0, 0, 40, 0, 0, 0,
36, 0, 0, 0, 12, 0,
0, 0, 0, 0, 0, 0,
124, 0, 0, 0, 4, 0,
0, 0, 4, 0, 0, 0,
1, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0,
0, 0, 128, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 117, 97,
118, 0, 67, 111, 110, 115,
116, 97, 110, 116, 66, 117,
102, 102, 101, 114, 0, 171,
128, 0, 0, 0, 1, 0,
0, 0, 168, 0, 0, 0,
16, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
208, 0, 0, 0, 0, 0,
0, 0, 4, 0, 0, 0,
2, 0, 0, 0, 220, 0,
0, 0, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 122, 101,
114, 111, 0, 100, 119, 111,
114, 100, 0, 171, 0, 0,
19, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
213, 0, 0, 0, 77, 105,
99, 114, 111, 115, 111, 102,
116, 32, 40, 82, 41, 32,
72, 76, 83, 76, 32, 83,
104, 97, 100, 101, 114, 32,
67, 111, 109, 112, 105, 108,
101, 114, 32, 49, 48, 46,
49, 0, 73, 83, 71, 78,
8, 0, 0, 0, 0, 0,
0, 0, 8, 0, 0, 0,
79, 83, 71, 78, 8, 0,
0, 0, 0, 0, 0, 0,
8, 0, 0, 0, 83, 72,
69, 88, 124, 0, 0, 0,
80, 0, 5, 0, 31, 0,
0, 0, 106, 8, 0, 1,
89, 0, 0, 4, 70, 142,
32, 0, 0, 0, 0, 0,
1, 0, 0, 0, 156, 8,
0, 4, 0, 224, 17, 0,
0, 0, 0, 0, 68, 68,
0, 0, 95, 0, 0, 2,
18, 0, 2, 0, 155, 0,
0, 4, 0, 1, 0, 0,
1, 0, 0, 0, 1, 0,
0, 0, 48, 0, 0, 1,
3, 0, 4, 4, 10, 128,
32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 164, 0,
0, 7, 242, 224, 17, 0,
0, 0, 0, 0, 6, 0,
2, 0, 6, 128, 32, 0,
0, 0, 0, 0, 0, 0,
0, 0, 22, 0, 0, 1,
62, 0, 0, 1, 83, 84,
65, 84, 148, 0, 0, 0,
5, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 2, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
1, 0, 0, 0
};
#endif // Q_OS_WIN

View File

@ -36,6 +36,7 @@
#include "qrhid3d11_p_p.h"
#include "qshader_p.h"
#include "cs_tdr.h"
#include <QWindow>
#include <QOperatingSystemVersion>
#include <qmath.h>
@ -119,9 +120,14 @@ QT_BEGIN_NAMESPACE
*/
QRhiD3D11::QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice)
: ofr(this)
: ofr(this),
deviceCurse(this)
{
debugLayer = params->enableDebugLayer;
deviceCurse.framesToActivate = params->framesUntilKillingDeviceViaTdr;
deviceCurse.permanent = params->repeatDeviceKill;
importedDevice = importDevice != nullptr;
if (importedDevice) {
dev = reinterpret_cast<ID3D11Device *>(importDevice->dev);
@ -264,6 +270,9 @@ bool QRhiD3D11::create(QRhi::Flags flags)
nativeHandlesStruct.dev = dev;
nativeHandlesStruct.context = context;
if (deviceCurse.framesToActivate > 0)
deviceCurse.initResources();
return true;
}
@ -281,6 +290,8 @@ void QRhiD3D11::destroy()
clearShaderCache();
deviceCurse.releaseResources();
if (annotations) {
annotations->Release();
annotations = nullptr;
@ -1003,6 +1014,20 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
swapChainD->frameCount += 1;
contextState.currentSwapChain = nullptr;
if (deviceCurse.framesToActivate > 0) {
deviceCurse.framesLeft -= 1;
if (deviceCurse.framesLeft == 0) {
deviceCurse.framesLeft = deviceCurse.framesToActivate;
if (!deviceCurse.permanent)
deviceCurse.framesToActivate = -1;
deviceCurse.activate();
} else if (deviceCurse.framesLeft % 100 == 0) {
qDebug("Impending doom: %d frames left", deviceCurse.framesLeft);
}
}
return QRhi::FrameOpSuccess;
}
@ -3914,4 +3939,34 @@ bool QD3D11SwapChain::buildOrResize()
return true;
}
void QRhiD3D11::DeviceCurse::initResources()
{
framesLeft = framesToActivate;
HRESULT hr = q->dev->CreateComputeShader(g_killDeviceByTimingOut, sizeof(g_killDeviceByTimingOut), nullptr, &cs);
if (FAILED(hr)) {
qWarning("Failed to create compute shader: %s", qPrintable(comErrorMessage(hr)));
return;
}
}
void QRhiD3D11::DeviceCurse::releaseResources()
{
if (cs) {
cs->Release();
cs = nullptr;
}
}
void QRhiD3D11::DeviceCurse::activate()
{
if (!cs)
return;
qDebug("Activating Curse. Goodbye Cruel World.");
q->context->CSSetShader(cs, nullptr, 0);
q->context->Dispatch(256, 1, 1);
}
QT_END_NAMESPACE

View File

@ -58,6 +58,9 @@ QT_BEGIN_NAMESPACE
struct Q_GUI_EXPORT QRhiD3D11InitParams : public QRhiInitParams
{
bool enableDebugLayer = false;
int framesUntilKillingDeviceViaTdr = -1;
bool repeatDeviceKill = false;
};
struct Q_GUI_EXPORT QRhiD3D11NativeHandles : public QRhiNativeHandles

View File

@ -694,6 +694,19 @@ public:
QByteArray bytecode;
};
QHash<QRhiShaderStage, Shader> m_shaderCache;
struct DeviceCurse {
DeviceCurse(QRhiD3D11 *impl) : q(impl) { }
QRhiD3D11 *q;
int framesToActivate = -1;
bool permanent = false;
int framesLeft = 0;
ID3D11ComputeShader *cs = nullptr;
void initResources();
void releaseResources();
void activate();
} deviceCurse;
};
Q_DECLARE_TYPEINFO(QRhiD3D11::ActiveReadback, Q_MOVABLE_TYPE);

9
src/gui/rhi/tdr.hlsl Normal file
View File

@ -0,0 +1,9 @@
RWBuffer<uint> uav;
cbuffer ConstantBuffer { uint zero; }
[numthreads(256, 1, 1)]
void killDeviceByTimingOut(uint3 id: SV_DispatchThreadID)
{
while (zero == 0)
uav[id.x] = zero;
}

View File

@ -126,6 +126,7 @@ int sampleCount = 1;
QRhiSwapChain::Flags scFlags = 0;
QRhi::BeginFrameFlags beginFrameFlags = 0;
QRhi::EndFrameFlags endFrameFlags = 0;
int framesUntilTdr = -1;
class Window : public QWindow
{
@ -278,6 +279,10 @@ void Window::init()
if (graphicsApi == D3D11) {
QRhiD3D11InitParams params;
params.enableDebugLayer = true;
if (framesUntilTdr > 0) {
params.framesUntilKillingDeviceViaTdr = framesUntilTdr;
params.repeatDeviceKill = true;
}
m_r = QRhi::create(QRhi::D3D11, &params, rhiFlags);
}
#endif
@ -461,8 +466,13 @@ int main(int argc, char **argv)
// Testing cleanup both with QWindow::close() (hitting X or Alt-F4) and
// QCoreApplication::quit() (e.g. what a menu widget would do) is important.
// Use this parameter for the latter.
QCommandLineOption sdOption({ "s", "self-destruct" }, QLatin1String("Self destruct after 5 seconds"));
QCommandLineOption sdOption({ "s", "self-destruct" }, QLatin1String("Self-destruct after 5 seconds."));
cmdLineParser.addOption(sdOption);
// Attempt testing device lost situations on D3D at least.
QCommandLineOption tdrOption(QLatin1String("curse"), QLatin1String("Curse the graphics device. "
"(generate a device reset every <count> frames when on D3D11)"),
QLatin1String("count"));
cmdLineParser.addOption(tdrOption);
cmdLineParser.process(app);
if (cmdLineParser.isSet(nullOption))
@ -521,6 +531,9 @@ int main(int argc, char **argv)
}
#endif
if (cmdLineParser.isSet(tdrOption))
framesUntilTdr = cmdLineParser.value(tdrOption).toInt();
// Create and show the window.
Window w;
#if QT_CONFIG(vulkan)