rhi: d3d11: Wait in beginFrame with a max latency of 2

Implements what's described in
https://learn.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains

By default SetMaximumFrameLatency is called with a value of 2, since we
do not want to force 1 on everyone as some application may have more GPU
heavy 2D/3D scenes. Use QT_D3D_MAX_FRAME_LATENCY to override. Setting to
0 disables the whole thing. This patch contains no C++ API (could be in
the backend-specific init params), because it is not clear if it is
reasonable to expect applications to control such details of the
presentation mechanism. This also allows cherry picking to earlier
branches.

The result is that, for example, the visible lag between the mouse
cursor and the dragged item will get reduced in Qt Quick scenes that
have a mouse draggable item in them. This comes at the expense of
potentially reducing CPU-GPU parallelism, but many scenes do not need
that kind of performance to begin with.

One big gain here is that with a MaximumFrameLatency of 2 the behavior
becomes closer, and basically identical, to what one gets when running
with OpenGL (where Qt has no control whatsoever over such presentation
details), and so the behavior becomes closer out of the box with Qt 6 to
what one got with Qt 5. This is true at least with NVIDIA graphics on
Windows 11; note that it may not apply to other vendors' GL
implementations.

The QRhi doc update just brings in what's been true for some time in Qt
6: as IDXGIFactory2 is required, the minimum required DXGI version is in
fact 1.3 and so D3D 11.2. (which is Windows 8.1 stuff so should be fine)

[ChangeLog][RHI] The D3D11 backend creates swapchains from now on with
DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT by default, with a
max frame latency of 2.

Task-number: QTBUG-127267
Change-Id: I74f68f7af41097b957b8e0bbdbae39f9302b1ad3
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
(cherry picked from commit 02bf4d06b2432d95df9a1f6c9ad072b953b06cda)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Laszlo Agocs 2024-07-18 12:07:41 +02:00 committed by Qt Cherry-pick Bot
parent 0a67a8c3d4
commit 9200606068
3 changed files with 59 additions and 12 deletions

View File

@ -72,10 +72,10 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
builds on QOpenGLContext, QOpenGLFunctions, and the related cross-platform
infrastructure of the Qt GUI module.
\li Direct3D 11.1 or newer, with Shader Model 5.0 or newer. When the D3D
runtime has no support for 11.1 features or Shader Model 5.0,
initialization using an accelerated graphics device will fail, but using
the
\li Direct3D 11.2 and newer (with DXGI 1.3 and newer), using Shader Model
5.0 or newer. When the D3D runtime has no support for 11.2 features or
Shader Model 5.0, initialization using an accelerated graphics device will
fail, but using the
\l{https://learn.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp}{software
adapter} is still an option.

View File

@ -222,9 +222,19 @@ bool QRhiD3D11::create(QRhi::Flags flags)
// there. (some features are not supported then, however)
useLegacySwapchainModel = qEnvironmentVariableIntValue("QT_D3D_NO_FLIP");
qCDebug(QRHI_LOG_INFO, "FLIP_* swapchain supported = true, ALLOW_TEARING supported = %s, use legacy (non-FLIP) model = %s",
if (!useLegacySwapchainModel) {
if (qEnvironmentVariableIsSet("QT_D3D_MAX_FRAME_LATENCY"))
maxFrameLatency = UINT(qMax(0, qEnvironmentVariableIntValue("QT_D3D_MAX_FRAME_LATENCY")));
} else {
maxFrameLatency = 0;
}
qCDebug(QRHI_LOG_INFO, "FLIP_* swapchain supported = true, ALLOW_TEARING supported = %s, use legacy (non-FLIP) model = %s, max frame latency = %u",
supportsAllowTearing ? "true" : "false",
useLegacySwapchainModel ? "true" : "false");
useLegacySwapchainModel ? "true" : "false",
maxFrameLatency);
if (maxFrameLatency == 0)
qCDebug(QRHI_LOG_INFO, "Disabling FRAME_LATENCY_WAITABLE_OBJECT usage");
if (!importedDeviceAndContext) {
IDXGIAdapter1 *adapter;
@ -1340,6 +1350,10 @@ QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
contextState.currentSwapChain = swapChainD;
const int currentFrameSlot = swapChainD->currentFrameSlot;
// if we have a waitable object, now is the time to wait on it
if (swapChainD->frameLatencyWaitableObject)
WaitForSingleObjectEx(swapChainD->frameLatencyWaitableObject, 1000, true);
swapChainD->cb.resetState();
swapChainD->rt.d.rtv[0] = swapChainD->sampleDesc.Count > 1 ?
@ -4949,6 +4963,11 @@ void QD3D11SwapChain::destroy()
dcompTarget = nullptr;
}
if (frameLatencyWaitableObject) {
CloseHandle(frameLatencyWaitableObject);
frameLatencyWaitableObject = nullptr;
}
QRHI_RES_RHI(QRhiD3D11);
if (rhiD) {
rhiD->unregisterResource(this);
@ -5132,6 +5151,17 @@ bool QD3D11SwapChain::createOrResize()
if (swapInterval == 0 && rhiD->supportsAllowTearing)
swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
// maxFrameLatency 0 means no waitable object usage.
// Ignore it also when NoVSync is on, and when using WARP.
const bool useFrameLatencyWaitableObject = rhiD->maxFrameLatency != 0
&& swapInterval != 0
&& rhiD->driverInfoStruct.deviceType != QRhiDriverInfo::CpuDevice;
if (useFrameLatencyWaitableObject) {
// the flag is not supported in real fullscreen on D3D11, but perhaps that's fine since we only do borderless
swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
}
if (!swapChain) {
sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount);
colorFormat = DEFAULT_FORMAT;
@ -5217,16 +5247,31 @@ bool QD3D11SwapChain::createOrResize()
if (SUCCEEDED(hr)) {
swapChain = sc1;
if (m_format != SDR) {
IDXGISwapChain3 *sc3 = nullptr;
if (SUCCEEDED(sc1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast<void **>(&sc3)))) {
IDXGISwapChain3 *sc3 = nullptr;
if (SUCCEEDED(sc1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast<void **>(&sc3)))) {
if (m_format != SDR) {
hr = sc3->SetColorSpace1(hdrColorSpace);
if (FAILED(hr))
qWarning("Failed to set color space on swapchain: %s",
qPrintable(QSystemError::windowsComString(hr)));
sc3->Release();
} else {
qPrintable(QSystemError::windowsComString(hr)));
}
if (useFrameLatencyWaitableObject) {
sc3->SetMaximumFrameLatency(rhiD->maxFrameLatency);
frameLatencyWaitableObject = sc3->GetFrameLatencyWaitableObject();
}
sc3->Release();
} else {
if (m_format != SDR)
qWarning("IDXGISwapChain3 not available, HDR swapchain will not work as expected");
if (useFrameLatencyWaitableObject) {
IDXGISwapChain2 *sc2 = nullptr;
if (SUCCEEDED(sc1->QueryInterface(__uuidof(IDXGISwapChain2), reinterpret_cast<void **>(&sc2)))) {
sc2->SetMaximumFrameLatency(rhiD->maxFrameLatency);
frameLatencyWaitableObject = sc2->GetFrameLatencyWaitableObject();
sc2->Release();
} else { // this cannot really happen since we require DXGIFactory2
qWarning("IDXGISwapChain2 not available, FrameLatencyWaitableObject cannot be used");
}
}
}
if (dcompVisual) {

View File

@ -625,6 +625,7 @@ struct QD3D11SwapChain : public QRhiSwapChain
IDCompositionVisual *dcompVisual = nullptr;
QD3D11SwapChainTimestamps timestamps;
int currentTimestampPairIndex = 0;
HANDLE frameLatencyWaitableObject = nullptr;
};
class QRhiD3D11 : public QRhiImplementation
@ -760,6 +761,7 @@ public:
QRhi::Flags rhiFlags;
bool debugLayer = false;
UINT maxFrameLatency = 2; // 1-3, use 2 to keep CPU-GPU parallelism while reducing lag compared to tripple buffering
bool importedDeviceAndContext = false;
ID3D11Device *dev = nullptr;
ID3D11DeviceContext1 *context = nullptr;